TECHNOLOGY LINUX KERNEL

【第3回】元東大教員から学ぶLinuxカーネル「アイソレーションとシステムコール」

本記事の信頼性

  • リアルタイムシステムの研究歴12年.
  • 東大教員の時に,英語でOSの授業.
  • 2012年9月~2013年8月にアメリカのノースカロライナ大学チャペルヒル校コンピュータサイエンス学部2021年の世界大学学術ランキングで20位)で客員研究員として勤務.C言語でリアルタイムLinuxの研究開発
  • プログラミング歴15年以上,習得している言語: C/C++Solidity/Vyper,Java,Python,Ruby,HTML/CSS/JS/PHP,MATLAB,Assembler (x64,ARM).
  • 東大教員の時に,C++言語で開発した「LLVMコンパイラの拡張」,C言語で開発した独自のリアルタイムOS「Mcube Kernel」GitHubにオープンソースとして公開

こういった私から学べます.

前回を読んでいない方はこちらからどうぞ.

第2回Linuxカーネル
【第2回】元東大教員から学ぶLinuxカーネル「開発ツールとカーネルプログラミング vs. ユーザプログラミング」

こういった私から学べます. 前回を読んでいない方はこちらからどうぞ. Linuxカーネルの記事一覧はこちらからどうぞ. 今回は,Linuxカーネルの開発ツールとカーネル vs. ユーザプログラミングを ...

続きを見る

Linuxカーネルの記事一覧はこちらからどうぞ.

Linuxカーネル
元東大教員から学ぶLinuxカーネル

こういった私から学べます. Linuxカーネルとは,C言語で開発されたオープンソースのOSです. Linuxカーネルは主に以下のコンピュータで広く利用されています. スーパーコンピュータ サーバ An ...

続きを見る

今回のテーマは,「アイソレーションとシステムコール」です.

アイソレーションとシステムコールの仕組みを理解するために,まずカーネルコードの読み方を紹介します.

カーネルコードの読み方

Linuxカーネルのソースコード(カーネルコード)は合計2,800万行以上と膨大で,全てを読み解くことは困難です.

そこで,自分が求める部分のカーネルコードを発見して読み解くためのトップダウン法とボトムアップ法,カーネルコードをナビゲートするための方法を解説します.

トップダウン法

トップダウン法で,カーネルコードを読み解く方法を紹介します.

例えば,ext4ファイルシステムの場合は以下になります.

  1. OSのファイルシステムに関する一般的な理解
  2. Linuxカーネルのファイルシステムの理解(後の回で紹介)
  3. ext4のドキュメントディスクレイアウトのチェック
  4. ext4カーネルコードの読解
    • モジュール毎(例:ディレクトリ,ファイル,ブロック管理)
    • システムコールから開始(例:どのようにwriteシステムコールが実装されているのか?)
  5. LWNを検索して最新の変更点を確認(例:ext4の暗号化サポート

ボトムアップ法

ボトムアップ法では,関数トレーサーを利用します.

  • ftrace:関数トレーサーのフレームワーク
  • perf(perf tools):ftraceのフロントエンドのLinuxカーネルの性能解析ツール
  • trace-cmd:ftraceを操作するツール

以下に,trace-cmdでlsコマンドを実行した場合のvfs_read関数を追跡結果を示します.

trace-cmdは,ftraceによるLinuxカーネルの関数呼び出しのグラフを追跡し,呼び出された関数とタイムスタンプを表示していることがわかります.

trace-cmdのGUIツール「KernelShark」を知りたいあなたはこちらからどうぞ.

C言語 KernelShark
【C言語】Linuxカーネルの関数呼び出しをトレースするKernelSharkの使い方

こういった悩みにお答えします. こういった私から学べます. 目次1 KernelShark2 Linuxカーネルの関数呼び出しをトレースするKernelSharkの使い方3 KernelSharkの解 ...

続きを見る

カーネルコードをナビゲートする方法

カーネルコードをナビゲートするためには,cscopeを使います.

以下にcscopeを利用してvi(vimはviの高機能版)でカーネルコードをナビゲートする例を示します.

※vimのナビゲート方法の詳細は,「Browsing programs with tags」を参照して下さい.

アイソレーションとシステムコール

カーネルコードの読み方がわかったところで,今回のテーマ「アイソレーションとシステムコール」を解説していきます.

第1回でOSデザインは以下の4つの役割があることを述べました.

・利便性や移植性の向上するためにハードウェアの抽象化(ユーザがハードウェアの機能を関数呼び出しで(暗黙的に)利用できること)

・複数のアプリケーション間のハードウェアの多重化(どのアプリケーションがどのハードウェアを利用しているかを関数呼び出しで(暗黙的に)利用できること)

・バグを含むアプリケーションのアイソレーション(分離して独立で実行すること)

・アプリケーション間でのデータ等の共有の許可

https://hiroyukichishiro.com/linux-kernel-vol1/#Linux

今回のテーマは,以下の2つです.

  • ユーザアプリケーションをカーネルから分離するには?
  • ユーザアプリケーションからカーネルに安全にアクセスするには?

それでは,アイソレーションとシステムコールを説明します.

アイソレーションとは

OSにおけるアイソレーション(プロテクション)とは,特定の処理(プロセス等)を他の処理から保護する仕組みです.

アイソレーションによる保護する状況は主に以下の3つになります.

  • プロセスXがプロセスYを破壊したり,(CPU,メモリ,FD,資源の枯渇等)をスパイしたりすることから保護
  • (カーネルによる分離を防止するために)プロセスがOS自体を破壊することから保護
  • (例:悪いプロセスがハードウェアやカーネルを騙そうとする可能性がある)バグや悪意に直面した場合に保護

また,OSにおける分離機構は以下の4つになります.

  • ユーザ・カーネルモードのフラグ(x86-64はリングプロテクション,ARM64(AARCH64)は例外レベル)
  • アドレス空間(後の回で紹介)
  • タイムスライス(後の回で紹介)
  • システムコールのインターフェース

x86-64のアイソレーション

x86-64のアイソレーションを紹介します.

x86-64のリングプロテクションはring 0~ring 3まで,以下のように設定されています.

  • ring 0:OS
  • ring 1,ring 2:デバイスドライバ等(あまり利用されない)
  • ring 3:アプリ

つまり,ring 0は最高の特権レベル,ring 3は最低の特権レベルとなります.

しかし,x86-64のリングプロテクションの登場後に,ring 0のOSよりアイソレーションレベルが高いハイパーバイザーシステムマネジメントモードが登場しました.

そこで,ring 0よりアイソレーションレベルが高いring -1をハイパーバイザー,ring -2をシステムマネジメントモードに利用します.

※x86-64のリングプロテクションにはring -1,ring -2は存在せず,説明上の概念になります.

ARM64のアイソレーション

ARM64のアイソレーションを紹介します.

ARM64は例外レベル(EL:Exception Level)で特権レベルを設定し,x86-64のリングプロテクションと同様にEL0~EL3です.

しかし,x86-64のリングプロテクションとは異なり,EL0が最低の特権レベル,EL3が最高の特権レベルになります.

また,EL0がアプリ,EL1がOSを動作する実装になっているため,EL2をハイパーバイザー,EL3をTrustZoneのセキュアモニターに利用します.

※TrustZoneのセキュアモニターはx86-64のシステムマネジメントモードのようなものです.

ARM64は実際の例外レベルを利用できるので,x86-64のリングプロテクションより概念や実装が理解しやすいです.

x86-64とARM64のハードウェアアイソレーションは下表になります.

x86-64のハードウェア
アイソレーション
ARM64のハードウェア
アイソレーション
用途
ring 3EL0アプリ
ring 0EL1OS
ring -1EL2ハイパーバイザー
ring -2EL3システムマネジメントモード(x86-64)
TrustZoneのセキュアモニター(ARM64)

システムコールとは

システムコールは,ユーザ空間のアプリケーションがカーネル空間に入り,OSのサービスやハードウェアへのアクセスなどの特権的な操作を要求するための唯一無二の方法です.

システムコールにより,x86-64だとring 3からring 0,ARM64だとEL0からEL1に特権レベルを切り替えることができます.

これにより,特権レベルでしかアクセスできないディスプレイのようなI/Oポートへアクセスすることができます.

システムコールは以下の機能を提供します.

  • ハードウェアとユーザ空間プロセスの間のレイヤ
  • ユーザ空間のための抽象的なハードウェアインタフェース
  • システムのセキュリティと安定性を確保
システムコール

上図にシステムコールの呼び出しと復帰の流れを示します.

プログラムはユーザモードで実行し,システムコールの呼び出しでカーネルモードに切り替わります.

そして,システムコールから復帰した時にユーザモードに戻ります.

システムコールは,以下のアーキテクチャ固有のシステムコール専用命令のラッパー関数として実装されます.

また,システムコールから戻る命令は以下になります.

システムコールの例

システムコールの例を以下に紹介します.

  • プロセス管理やスケジューリング
    • fork:子プロセスを生成します.
    • exit:呼び出し元のプロセスを終了させます.
    • execve:プログラムを実行します.
    • nice:プロセスの優先度を変更します.
    • getpriority:プログラムのスケジューリングの優先度を取得します.
    • setpriority:プログラムのスケジューリングの優先度を設定します.
  • メモリ管理
    • brk:データセグメントのサイズを変更します.
    • mmap:ファイルやデバイスをメモリにマップします.
    • munmap:ファイルやデバイスをメモリにアンマップします.
  • ファイルシステム
    • open:ファイルのオープン,作成を行います.
    • read:ファイルディスクリプタから読み込みます.
    • write:ファイルディスクリプタに書き込みます.
    • lseek:ファイルの読み書きオフセットの位置を変えます.
    • stat:ファイルの状態を取得します.
  • プロセス間通信
    • pipe:パイプを生成します.
    • shmget:System V 共有メモリーセグメントを割り当てられます.
  • 時間管理
  • その他
    • getuid:ユーザIDを取得します.
    • setuid:ユーザIDを設定します.
    • connect:ソケットの接続を行います.

システムコールの実装

システムコールは,システムコールの関数ポインタの配列(syscall table)で管理されています.

x86-64の場合はlinux/arch/x86/entry/syscalls/syscall_64.tbl,ARM64(AARCH64)の場合はlinux/include/uapi/asm-generic/unistd.hにあります.

x86-64はアーキテクチャ固有のシステムコールID,ARM64は他のアーキテクチャと共通のシステムコールIDになります.

システムコール一覧はこちらがわかりやすいです.

例えば,システムコールIDは以下のような違いがあります.

  • readシステムコールのID:x86-64の場合は0,ARM64の場合は3
  • writeシステムコールのID:x86-64の場合は1,ARM64の場合は64

また,システムコールの配列はsys_call_tableという変数で定義され,名前はx86-64やARM64を含む全アーキテクチャ共有です.

sys_call_tableの実態は以下にあります.

  • x86-64:linux/arch/x86/entry/syscall_64.c
  • ARM64:linux/arch/arm/kernel/sys.c

linux/fs/read_write.cにあるreadシステムコールの実装は以下になります.

23行目のSYSCALL_DEFINE3マクロ(3つのパラメータを持つシステムコールを定義するためのマクロ)でreadシステムコールの呼び出しをカーネル側で受け取り,4行目のksys_read関数で実際の読み込み処理をします.

invoke system call from user space

上図にユーザ空間からのシステムコールの呼び出しを示します.

システムコールが直接呼び出されることはほとんどありません.

それらのほとんどはC言語のライブラリ(glibc等)によりラッパー関数として呼び出されます.

syscall関数を利用してアプリケーションからのシステムコールの呼び出し

syscall関数を利用してアプリケーションからのシステムコールを呼び出します.

syscall関数は,システムコールを起動する小さなライブラリ関数です.

numberで指定されたアセンブリ言語インターフェースのシステムコールを,指定された引数を設定して実行します.

syscall関数が役に立つのは,C言語のライブラリにラッパー関数が存在しないシステムコールを呼び出したい場合です.

例えば,sched_setattr/sched_getattrシステムコールが挙げられます.

sched_setattr/sched_getattrシステムコールを利用するリアルタイムスケジューリングRMとEDFの実装を知りたいあなたはこちらからどうぞ.

C言語 Linux RMとEDF
【C言語】LinuxにおけるリアルタイムスケジューリングRMとEDFの実装

こういった悩みにお答えします. こういった私から学べます. 目次1 C言語でLinuxにおけるリアルタイムスケジューリングRMとEDFの実装2 sched_attr構造体とsched_setattr/ ...

続きを見る

x86-64のアプリケーションからのwriteシステムコールの呼び出し

x86-64のアプリケーションからのwriteシステムコールの呼び出しは以下のコードになります.

12行目で呼び出しているsyscall関数の引数は以下の意味になります.

  • 第1引数の1:x86-64のシステムコールの1番(writeシステムコール
  • 第2引数の1:writeシステムコールの第1引数fd(1は標準出力)
  • 第3引数のstr:writeシステムコールの第2引数buf(出力する文字列)
  • 第4引数の14:writeシステムコールの第3引数count(出力する文字列"Hello World!\n"のバイト数)

syscall.cのアセンブリ言語(x86-64)は以下の手順で作成して,catコマンドでstack.sの中身を表示します.

30行目でsyscall関数を呼び出していることがわかります.

実行結果は以下になります.

syscall関数でwriteシステムコールを直接呼び出すことで「Hello World!」を表示していることがわかります.

※stdio.hをインクルードしてprintf関数のような標準ライブラリ関数を呼び出さずに文字列を表示していることに着目して下さい.

ARM64のアプリケーションからのwriteシステムコールの呼び出し

ARM64のアプリケーションからのwriteシステムコールの呼び出しは以下になります.

writeシステムコールのIDはx86-64では1ですが,ARM64では64であることに注意して下さい.

つまり,syscall関数の第1引数が1から64に変更します.

実行結果は以下になります.同様です.

gettimeofdayシステムコールの実装と利用例

gettimeofdayシステムコールは,時刻を取得します.

gettimeofdayシステムコールの使い方は以下になります.

実行結果は以下になります.

Linuxのカーネルのgettimeofdayシステムコールの実装はlinux/kernel/time/time.cにあります.

ここで,SYSCALL_DEFINE2は,2つのパラメータを持つシステムコールを定義するためのマクロです.

ユーザ空間とカーネル空間のメモリ転送

ユーザ空間とカーネル空間のメモリ転送を解説します.

アイソレーションにより,ユーザ空間のアプリケーションはカーネル空間のメモリにアクセスできません.

また,カーネル空間ではユーザ空間へのポインタに盲目的に従わないことが大事です.

なぜかというと,間違ったユーザアドレスにアクセスすると,カーネルがクラッシュすることがあります.

そこで,上記のcopy_from_user/copy_to_user関数を利用してカーネル空間からユーザ空間のメモリにアクセスします.

提供されたユーザ空間のメモリが正当でない場合,不正アクセスエラーを発生します.

ユーザ空間のメモリが存在しない場合(スワップアウトされた場合),カーネルはスワップインするまでスリープし,スワップイン後にユーザ空間のメモリにアクセスします.

新しいシステムコールの実装方法

新しいシステムコールの実装方法を知りたいあなたはこちらの記事を読みましょう!

C言語 Linuxカーネル ビルド
【C言語】Linuxカーネルのビルド方法と新しいシステムコールの実装方法

こういった悩みにお答えします. こういった私から学べます. 目次1 Linuxカーネルのビルド方法1.1 GRUBを編集して起動するLinuxカーネルを指定1.2 Linuxカーネルのビルド環境の構築 ...

続きを見る

システムコールの性能向上

システムコールの性能は多くのアプリケーションで重要です.

それでは,システムコールの性能向上のためのハードウェアとソフトウェアの方法を紹介していきます.

ハードウェア:Intelのsysenter/sysexit命令,AMDのsyscall/sysret命令

x86-64では,「int 0x80」(ソフトウェア割り込みを発生させるint命令の引数0x80)をシステムコールに利用していました.(ソフトウェア割り込みから戻る時はiret命令を利用します.)

1998年にIntelのPentium IIがsysenter/sysexit命令を実装したことで,Linuxカーネルはsysenter/sysexit命令に置き換えられました.(AMDはsyscall/sysret命令です.)

Intelのsysenter/sysexit命令は,int 0x80と比較してソフトウェア割り込みによるオーバヘッドを削減します.

ソフトウェア:vDSO(virtual dynamically linked shared object)

vDSO(virtual dynamically linked shared object)は,カーネル空間の関数をユーザ空間のアプリケーションにエクスポートすることで性能向上するカーネル機構です.

vDSOは,システムコールインターフェースを使って同じカーネル空間ルーチンを呼び出す場合に利用できます.

ユーザモードからカーネルモードへのモード切り替えによる性能低下を招くことなく,アプリケーションからプロセス内のカーネル空間ルーチンを呼び出せるようになります.

つまり,コンテストスイッチが不要になり,そのオーバヘッドが0になります.

vDSOの主な利用例は,gettimeofdayシステムコールです.

ソフトウェア:例外を少なくする(Exception-Lessな)システムコール

例外を少なくする(Exception-Lessな)システムコールを紹介します.

FlexSCは,FlexSCは2010年に国際会議OSDIで発表された例外を少なくするシステムコールの柔軟なシステムコールのスケジューリングです.

FlexSCの論文とGitHub(論文の著者ではない実装)は以下になります.

vDSOとは異なり,FlexSCは現在Linuxカーネルのメインラインにマージされていないので注意して下さい.

FlexSCによる性能向上は以下になります(アプリケーションを変更せずに実現).

  • Apacheの性能を最大116%向上
  • MySQLの性能を最大40%向上
  • BINDの性能を最大105%向上

他には,nginxのようなWebサーバ(イベントドリブンサーバ)で利用される「Exception-Less System Calls for Event-Driven Servers」の論文もあります.

ソフトウェア:終了を少なくする(Exit-Lessな)システムコール

終了を少なくする(Exit-Lessな)システムコールに関する論文は以下になります.

これらの論文を読んで,終了を少なくするシステムコールと例外を少なくするシステムコールの違いを学びましょう!

まとめ

今回はアイソレーションとシステムコールを紹介しました.

アイソレーションによりユーザアプリケーションをカーネルから分離し,システムコールによりユーザアプリケーションからカーネルに安全にアクセスすることがわかりました.

システムコールを深く理解したいあなたは,以下の記事を読みましょう!

最後まで読んで頂きありがとうございました.

LinuxカーネルはC言語で書かれています.

私にC言語の無料相談をしたいあなたは,公式LINE「ChishiroのC言語」の友だち追加をお願い致します.

私のキャパシティもあり,一定数に達したら終了しますので,今すぐ追加しましょう!

友だち追加

独学が難しいあなたは,元東大教員がおすすめするC言語を学べるオンラインプログラミングスクール5社で自分に合うスクールを見つけましょう.後悔はさせません!

次回はこちらからどうぞ.

第4回 Linuxカーネル
【第4回】元東大教員から学ぶLinuxカーネル「カーネルのデータ構造」

こういった私から学べます. 前回を読んでいない方はこちらからどうぞ. Linuxカーネルの記事一覧はこちらからどうぞ. 今回のテーマは,「カーネルのデータ構造」です. 以下のデータ構造を理解しているこ ...

続きを見る

-TECHNOLOGY, LINUX KERNEL
-, , , , , , ,