システムコールとは何なのか

# なぜシステムコールについて気にするのか

システムコールとはOSが提供する機能をアプリケーションが利用する仕組みのことですが、システムコールについて知ることはアプリケーションの働きを理解する上で重要です。

なぜならアプリケーションの動作の中で、重要なもののほぼ全てはシステムコールを利用して実現されているからです。
例えば、ネットワークを利用した通信、ファイルへの入出力、新しいプロセスの生成、プロセス間通信、コンテナの生成などは、システムコールを使用することで実現されています。
逆にシステムコールを使わずにアプリケーションが出来ることと言えば、CPU上での計算とメモリへの入出力くらいでしょう。


## この記事で解説する内容
この記事で解説する内容は、システムコールの一般的な性質や仕組みについてです。
そもそもシステムコールとは何なのか、どのような仕組みで実現されているのかというところを解説します。
さらには、システムコールをライブラリを介さず直接使用したり、システムコールの実装をカーネル内にまで踏み込んで調べる方法も紹介します。

内容は以下のようになります。

- [システムコールの目的と役割](https://qiita.com/sxarp/items/aff43dd83b0da69b92ce#%E3%82%B7%E3%82%B9%E3%83%86%E3%83%A0%E3%82%B3%E3%83%BC%E3%83%AB%E3%81%AE%E7%9B%AE%E7%9A%84%E3%81%A8%E5%BD%B9%E5%89%B2)
- [アプリケーションがシステムコールを呼び出す仕組み](https://qiita.com/sxarp/items/aff43dd83b0da69b92ce#%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%81%8C%E3%82%B7%E3%82%B9%E3%83%86%E3%83%A0%E3%82%B3%E3%83%BC%E3%83%AB%E3%82%92%E5%91%BC%E3%81%B3%E5%87%BA%E3%81%99%E4%BB%95%E7%B5%84%E3%81%BF)
- [システムコールの実現方法と内部実装](https://qiita.com/sxarp/items/aff43dd83b0da69b92ce#%E3%82%B7%E3%82%B9%E3%83%86%E3%83%A0%E3%82%B3%E3%83%BC%E3%83%AB%E3%81%AE%E5%AE%9F%E7%8F%BE%E6%96%B9%E6%B3%95%E3%81%A8%E5%86%85%E9%83%A8%E5%AE%9F%E8%A3%85)

#  システムコールの目的と役割
[Linux Kernel Development](https://www.amazon.com/Linux-Kernel-Development-Developers-Library-ebook-dp-B003V4ATI0/dp/B003V4ATI0/ref=mt_kindle)などによるとシステムコールという仕組みを導入するメリットとして以下の2つがあります。

1. ハードウェアを操作するシンプルなインターフェイスの提供
2. アプリケーションが安全かつセキュアにOSのリソースを利用できる

## 1. ハードウェアを操作するシンプルなインターフェイスの提供
システムコールはアプリケーションに対して、ハードウェアを操作するための抽象化されたシンプルなインターフェイスを提供しています。これによってアプリケーションのコードは、背後にあるハードウェアの詳細に関して意識する必要がなくなります。
例えばシステムコール[`write`](http://man7.org/linux/man-pages/man2/write.2.html)をバイト列を何らかの対象に書き込むための共通化されたインターフェイスです。
書き込み対象として指定できる、つまりファイルとして扱えるものは非常に多岐にわたっており、例えば、様々な種類のファイルシステムはもちろん、プロセス間通信に使われるパイプ、ネットワーク通信に使われるソケット、モニターなどの各種出力デバイスなどが含まれます。
LinuxやUnixの基本哲学に[`すべてのものはファイルである`](https://en.wikipedia.org/wiki/Everything_is_a_file)というものがありますが、`write`を含め入出力に使われる各種のシステムコールはこれを具現しているものと言えるでしょう。

## 2. アプリケーションが安全かつセキュアにOSのリソースを利用できる
システムコールはアプリケーションとOSの管理するリソースとの間を仲介しており、アプリケーションがリソースを間違った方法で利用することを防いだり、セキュリティ上問題のあるような利用を防ぐ役割をもっています。これによってアプリケーションはリソースの利用を安全かつセキュアに行うことができます。

リソースの安全な利用の例としては、例えばプロセスによるメモリ領域の確保([`mmap`](http://man7.org/linux/man-pages/man2/mmap.2.html)を使用した)があるでしょう。メモリというハードウェアリソースは、他のプロセスやOSと共有しており、過った方法で利用すると他のプロセスやOSを破壊してしまう恐れがあります。プロセスがメモリ領域を確保したいときにこのシステムコールを介することで、OSが各プロセスへのメモリ領域の割り当てを安全な方法で行うことができます。
また、リソースのセキュアな利用の例としては、権限情報に基づくファイルへのアクセス制御などがあるでしょう。

# アプリケーションがシステムコールを呼び出す仕組み
アプリケーションはシステムコールを呼び出すときに何をやっているのでしょうか。普通の関数呼び出しと何が違うのでしょうか。ここでは、アプリケーションがシステムコールを呼び出す仕組みについて解説していきます。

アプリケーションがシステムコールを呼び出す際に行う手順は、以下の3ステップです。

1. 実行するシステムコールを指定する番号をレジスタ(CPUに内蔵された極小メモリ)にセットする
2. システムコールに渡す引数をレジスタにセットする
3. システムコールを発動するインストラクション/命令文を実行する

以下では、画面(標準出力)へ文字を出力するコードを例にして、上記の手順について詳しく解説していきます。

## 文字出力を例にしたサンプル
システムコール[`write`](http://man7.org/linux/man-pages/man2/write.2.html)は標準出力やファイルへの書き出しに使われますが、このシステムコールを呼び出すサンプルコードを使って、アプリケーションがシステムコールを呼び出す手順を見ていきましょう。

まず、サンプルコードを動かすのに使う[gccのimage](https://hub.docker.com/_/gcc/)をコンテナとして立ち上げます。

```sh
$ docker container run -it --rm gcc /bin/bash
```

立ち上がったコンテナ内に以下のようなアセンブリコードが書かれたファイル`hi.s`を作っていきます。コードの解説は後の方でやります。

```
# cat <<EOF > hi.s
.intel_syntax noprefix
.global main

main:
    push rbp
    mov rbp, rsp
    push 0xA216948
    mov rax, 1
    mov rdi, 1
    mov rsi, rsp
    mov rdx, 4
    syscall
    mov rsp, rbp
    pop rbp
    ret
EOF
```

コンパイルして実行してみると以下のように文字が画面に出力されます。

```
# gcc -o hi.o hi.s; ./hi.o
Hi!
```

システムコールの呼び出し方はCPUの仕様/アーキテクチャごとに微妙に異なっているのですが、上記のコードは[x86-64](https://ja.wikipedia.org/wiki/X64)という最もポピュラーなアーキテクチャでの`write`システムコールの呼び出しの例となっています。

上の紹介したサンプルコードについて解説する前に、まずはx86-64でのシステムコールの呼び出し方について説明していきます。なお他のアーキテクチャでもやってることはそんなに変わりません。

## x86-64でのシステムコールの呼び出し方

x86-64でのシステムコールの呼び出しは以下の3ステップで行われます。

1. `rax`レジスタにsystem call numberをセットする
2. システムコールに対して渡したい引数(もしあれば)をレジスタにセットする
3. `syscall`のインストラクション/命令を呼び出す

### ステップ1
まず、ステップ1ですが、ここでは`rax`というレジスタ(=CPUに内蔵された16~64bitサイズほどのメモリで、CPUから極めて高速にアクセス出来る)に、system call numberという、呼び出すシステムコールを指定する番号を格納します。この番号によってカーネルがどのシステムコールを実行したら良いのか識別する事ができます。

system call numberと対応するシステムコールは例えば以下のように指定されています。

| system call number  | システムコール名 | 内容 |
| ------------------ | ------------- |------------- |
| 0  | read  |読み込み  |
| 1  | write  |書き出し |
| 2  | open  |ファイルを開く |
| 57  | fork  |新しいプロセスを立ち上げる |

上記の例以外も含む網羅的な表は[こちら](https://github.com/torvalds/linux/blob/cd6c84d8f0cdc911df435bb075ba22ce3c605b07/arch/x86/entry/syscalls/syscall_64.tbl)で確認できます。

サンプルコード内だと、このステップに対応する部分は以下です。

```Nasm
    mov rax, 1
```

### ステップ2
次にステップ2ですが、ここではシステムコールに渡す引数をレジスタに格納していきます。
渡せる引数は6個までで、第1引数から順番にレジスタ`rdi`、`rsi`、`rdx`、`r10`、`r9`、`r8`を使用することになっています。

システムコール`write`の場合、渡す引数とレジスタは以下のように指定されています。

|レジスタ名| rdi| rsi | rdx |
|----------|------------- | ------------- |------------- |
|引数| ファイルディスクリプタ | 書き出すバイト列の先頭アドレス | 書き出すバイト列のサイズ  |

他のシステムコールでどのような引数を使えば良いかは[ここ](https://blog.rchapman.org/posts/Linux_System_Call_Table_for_x86_64/)で確認できます。

ちなみにファイルディスクリプタ(`rdi`レジスタにセットする)とは何なのかというと、これは各プロセスに紐付いた、プロセスからの入出力を制御するポート番号のようなものです。ファイルディスクリプタはデフォルトでは0~2の3つの値が使え、それぞれ 0 : 標準入力、1 : 標準出力、2 : 標準エラーに対応します。
ファイルやソケットを開くと、新しいファイルディスクリプタがさらに続けて3、4、5という様に割り当てられていき、それらに対して書き出し`write`や読み込み`read`などのシステムコールを実行する(システムコールに引数として渡す)ことができます。

サンプルコード内だと、このステップに対応する部分は以下です。

```Nasm
    mov rdi, 1
    mov rsi, rsp
    mov rdx, 4
```

### ステップ3
ステップ3では`syscall`というインストラクション/命令文を発行します。
これによって、CPUはアプリケーションのコードを実行するのを中断し、カーネルのコードを実行するモードに切り替わった上で、カーネル内にあるシステムコールを実行するコード(システムコールハンドラー)へとジャンプします。
`syscall`インストラクションを抜けたときには、戻り値が`rax`レジスタにセットされていて、必要に応じて参照できます。
この部分については記事後半の[システムコールの実現方法と内部実装]の中で詳しく解説します。

サンプルコード内だと、このステップに対応する部分は以下です。

```Nasm
    syscall
```

## サンプルコードの解説
サンプルコードの意味が分かるようにコメントを付けてみました。ポイントは`syscall`と書かれた行とその直前です。

```Nasm
.intel_syntax noprefix # コードの書式
.global main # 実行開始地点のラベル

main:
# 前処理
    push rbp
    mov rbp, rsp

# 文字列'Hi!'をスタックに格納する
    push 0xA216948

# ステップ1 system call number で'1'(=writeに対応)を指定する
    mov rax, 1

# ステップ2 writeのシステムコールに渡す引数をレジスタにセットする
    mov rdi, 1  # 書き出し対象のファイルディスクリプタで1(標準出力)をセットする
    mov rsi, rsp  # スタックの先頭アドレス(文字`H`が入っている)をセットする
    mov rdx, 4  # 出力する文字列のサイズをセットする

# ステップ3 システムコールを発行する
    syscall

# 後処理
    mov rsp, rbp
    pop rbp
    ret
```

ちなみに上記のコードの中のステップ2で文字列の先頭アドレスを渡している部分について少し補足すると

```Nasm
    mov rsi, rsp  # スタックの先頭アドレス(文字`H`が入っている)をセットする
```
文字列はスタック上に格納されていますが、`rsp`というレジスタはスタックの先頭アドレスを指している特殊なレジスタです。なので`rsp`の値を`rsi`にセットすることで、文字列の先頭アドレスをシステムコールに渡しているということになります。

# [補足] システムコールの提供方法について
通常システムコールはC言語のライブラリとして提供されています。システムコールを利用する際は基本はこのラッパーライブラリを使えばよく、アセンブリコードを直接書いていく必要はありません。

例えば、`write(2)`は[以下のようなシグネチャを持ったCの関数として定義されています。](http://man7.org/linux/man-pages/man2/write.2.html)

```c
ssize_t write(int fd, const void *buf, size_t count);
```

一方で、Cのライブラリを使いたくない場合は、上の例でやったように自前でアセンブリコードとしてシステムコールの呼び出しを実装する必要があります。
例えばGo言語はそのようなアプローチをとっています。

[golang/sys/unix/asm_linux_amd64.s](https://github.com/golang/sys/blob/c4afb3effaa53fd9a06ca61262dc7ce8df4c081b/unix/asm_linux_amd64.s#L22-L35)

```Nasm
TEXT ·SyscallNoError(SB),NOSPLIT,$0-48
	CALL	runtime·entersyscall(SB)
	MOVQ	a1+8(FP), DI
	MOVQ	a2+16(FP), SI
	MOVQ	a3+24(FP), DX
	MOVQ	$0, R10
	MOVQ	$0, R8
	MOVQ	$0, R9
	MOVQ	trap+0(FP), AX	// syscall entry
	SYSCALL
	MOVQ	AX, r1+32(FP)
	MOVQ	DX, r2+40(FP)
	CALL	runtime·exitsyscall(SB)
	RET
```

上記のコードについて補足すると、`AX`のような見覚えのないレジスタは`rax`レジスタなどの[16bit部分を指す別名になります。](https://en.wikibooks.org/wiki/X86_Assembly/X86_Architecture)

# [補足] CPUが機械語を実行していく仕組み
システムコールが発行されると、CPUは現在実行中のコードから、カーネル内のコードへと**ジャンプ**します。このような、実行するコードを途中でジャンプさせる機能はCPU上でどのようにして実現されているのでしょうか。これを理解するには、そもそもCPUがどのようにコード(機械語)を実行しているのかを理解する必要があります。

CPU上での機械語の実行は以下のステップを繰り返すことで行われます。

1. メモリから一つの命令文/インストラクションを読み出す
2. インストラクションを実行し、レジスタの値の更新などを行う
3. 現在実行中のインストラクションのアドレスが格納されているレジスタの値を更新し、次のインストラクションの読み出しに備える

インストラクションとは、CPUが一つの命令として解釈できるバイト列の事で、アセンブリコードの一行のコードとほぼ一対一対応します。例えば上で例として挙げたアセンブリコードだと、機械語とインストラクションの対応は以下のようになります。

```
# objdump -d -M intel ./hi.o | grep syscall -B 4
 66c:	48 c7 c0 01 00 00 00 	mov    rax,0x1
 673:	48 c7 c7 01 00 00 00 	mov    rdi,0x1
 67a:	48 89 e6             	mov    rsi,rsp
 67d:	48 c7 c2 20 00 00 00 	mov    rdx,0x20
 684:	0f 05                	syscall
```

また、現在実行中のインストラクションのアドレスが格納されているレジスタは、プログラムカウンターレジスタやインストラクションポインターレジスタと呼ばれ、一つのインストラクションを実行すると加算され直後のインストラクションのアドレスの値に更新されます。これによってコードが逐次的に実行されていきます。

CPUが実行するコードを途中でジャンプさせる機能ですが、これはプログラムカウンターレジスタを直接書き換えるようなインストラクション(例えば[`jmp`](https://www.felixcloutier.com/x86/jmp))を発行することで行なえます。これによって実現できるものとしては、システムコールでのカーネル内へのコードのジャンプだけではなく、さらに一般には条件分岐やループ処理、関数呼び出しなどが含まれます。

# システムコールの実現方法と内部実装

システムコールの大まかな処理の流れは以下のようになります。

1. システムコールを呼び出すインストラクションを実行し、カーネル内のコードにジャンプする
2. カーネル内でsystem call numberで指定されたシステムコールの実装を呼び出す
3. カーネル内でシステムコールの呼び出し元へ復帰するインストラクションを実行し、元のコードに復帰する

ステップ1とステップ3は、カーネル内へのジャンプとカーネル内からの復帰を行っていますが、これはx86-64の場合は専用のインストラクション、`syscall`と`sysretq`によって実現されてます。
言い換えると、この部分はハードウェア的に実装されている、つまりx86-64の仕様を満たすように組まれたCPU内の論理回路によって実装されているとも言えるでしょう。

ステップ2ですが、この処理はカーネル内のシステムコールハンドラーというコードで行われます。
システムコールハンドラー内でsystem call numberに基づいた各システムコールの実装へのディスパッチが行われます。

もう少し詳細に、システムコールの処理を分解すると以下のようになります。

1. `syscall`インストラクションを実行し、システムコールハンドラーのアドレスが入ったレジスタの値をプログラムカウンターレジスタに読み込む
2. システムコールハンドラーが処理を受け付ける
3. システムコールハンドラー内でsystem call numberに基づいて各システムコールの実装へとディスパッチする
4. `sysretq`インストラクションによってユーザープロセス上の元のコードに復帰する
5. システムコールハンドラーのアドレスを起動時にレジスタに登録する

図にするとこんな感じです。

![syscall (4).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/132253/c86cbfda-41fb-8836-ccb0-71c985d5a599.png)


以下で(1)~(5)の各要素について解説していきます。

## (1) syscallインストラクションの実行
`syscall`のインストラクションをCPUが実行すると、大まかには以下のようなことが行われます。

1. CPUの現在の実行モードを表す`FLAGS`レジスタの値を`R11`レジスタに退避させる
2. `FLAGS`レジスタの値を`IA32_FMASK MSR`レジスタの値でマスクし、CPUがカーネルのコードを実行できるモードへと切り替わる
3. プログラムカウンターレジスタ`RIP`の値を`RCX`レジスタに退避させる
4. プログラムカウンターレジスタ`RIP`に、システムコールハンドラーのアドレスを`IA32_LSTAR MSR`レジスタから読み込み、システムコールハンドラーへジャンプする

**1.** [FLAGSレジスタはCPUの現在のCPUの状態/実行モードを表すレジスタ](https://en.wikipedia.org/wiki/FLAGS_register)で、例えばCPUが[プロテクションリング](https://ja.wikipedia.org/wiki/%E3%83%AA%E3%83%B3%E3%82%B0%E3%83%97%E3%83%AD%E3%83%86%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3)上のどこにいるかなどを表しています。システムコールからユーザープロセスに戻るときに元の状態に復元したいので、R11レジスタに現在の値を退避させます。

**2.** `FLAGS`レジスタの値を`IA32_FMASK MSR`レジスタの値でマスクすることによってCPUのモードが切り替わり、Privilege Level 0、つまりカーネルのコードを実行可能なモードへと遷移します。

Privilege Levelは[FLAGSレジスタの12~13bitで表現されています](https://en.wikipedia.org/wiki/FLAGS_register)が、これらを`00`(Privilege Level 0)とするために以下のようなオペレーションが`syscall`呼び出し時に行われます。

[x86/syscall](https://www.felixcloutier.com/x86/syscall)

```
RFLAGS ← RFLAGS AND NOT(IA32_FMASK);
```

マスクの役割(`NOT`を取ってることに注意)を果たす`IA32_FMASK`レジスタへの値のセットはカーネル内だと以下のコードで行われています。

[linux/arch/x86/kernel/cpu/common.c](https://github.com/torvalds/linux/blob/9c7db5004280767566e91a33445bf93aa479ef02/arch/x86/kernel/cpu/common.c#L1570-#L1574)

```c
	/* Flags to clear on syscall */
	wrmsrl(MSR_SYSCALL_MASK,
	       X86_EFLAGS_TF|X86_EFLAGS_DF|X86_EFLAGS_IF|
	       X86_EFLAGS_IOPL|X86_EFLAGS_AC|X86_EFLAGS_NT);
```

上記の`X86_EFLAGS_IOPL`は12\~13bitを`00`にする役割のマスクですが実際以下のように、12\~13bitのみが1になるような値として定義されていることが分かります。

[linux/arch/x86/include/uapi/asm/processor-flags.h](https://github.com/torvalds/linux/blob/6f0d349d922ba44e4348a17a78ea51b7135965b1/arch/x86/include/uapi/asm/processor-flags.h#L31-L32)

```c
#define X86_EFLAGS_IOPL_BIT	12 /* I/O Privilege Level (2 bits) */
#define X86_EFLAGS_IOPL		(_AC(3,UL) << X86_EFLAGS_IOPL_BIT)
```

**3.** プログラムカウンターレジスタ`RIP`の値を`RCX`レジスタに退避させます。これをしないと、システムコールハンドラーからユーザープロセスへ復帰するときに、もといたインストラクションの場所(=プログラムカウンターの値)が分からなくなってしまいます。

**4.** プログラムカウンターレジスタ`RIP`へ`IA32_LSTAR MSR`にセットされたシステムコールハンドラーのアドレスを読み込み、システムコールハンドラーへとジャンプします。`IA32_LSTAR MSR`へのハンドラーのアドレスの読み込みがどのように行われているかは(5)で紹介します。


その他`syscall`が呼ばれたときの詳しいCPUの挙動に関しては[ここを参照](https://www.felixcloutier.com/x86/syscall)してください。

## (2) システムコールハンドラー
システムコールハンドラーとは、`syscall`インストラクションが呼ばれた後に実行されるカーネル内のコードで、この中でシステムコールの処理が行われます。
ここでは、システムコールハンドラーの前処理の部分、引数としてレジスタに渡された値を構造体に詰めて後続の処理に渡す部分を紹介します。

まず、システムコールハンドラーの入り口/エントリポイントは以下のコードになります。

[linux/arch/x86/entry/entry_64.S](https://github.com/torvalds/linux/blob/cd6c84d8f0cdc911df435bb075ba22ce3c605b07/arch/x86/entry/entry_64.S#L145-L148)

```Nasm
ENTRY(entry_SYSCALL_64)
	UNWIND_HINT_EMPTY
	/*
	 * Interrupts are off on entry.
```

この`entry_SYSCALL_64`の中では様々な処理が行われるのですが、その中の一つとして、以下のようにレジスタに引数として渡された値フィールドに持つような構造体をスタック上に構築しています。

[linux/arch/x86/entry/entry_64.S](https://github.com/torvalds/linux/blob/cd6c84d8f0cdc911df435bb075ba22ce3c605b07/arch/x86/entry/entry_64.S#L159-L168)

```Nasm
	/* Construct struct pt_regs on stack */
	pushq	$__USER_DS				/* pt_regs->ss */
	pushq	PER_CPU_VAR(cpu_tss_rw + TSS_sp2)	/* pt_regs->sp */
	pushq	%r11					/* pt_regs->flags */
	pushq	$__USER_CS				/* pt_regs->cs */
	pushq	%rcx					/* pt_regs->ip */
GLOBAL(entry_SYSCALL_64_after_hwframe)
	pushq	%rax					/* pt_regs->orig_ax */

	PUSH_AND_CLEAR_REGS rax=$-ENOSYS
```

上記のコードでは、スタック上の構造体`pt_regs`に、`rcx`や`r11`レジスタ(`syscall`インストラクション呼び出し時に退避用使われていた)や`rax`レジスタの値を代入しています。またシステムコールの引数として使われている`rdi`や`rsi`などの値の構造体への代入は最後の[マクロ`PUSH_AND_CLEAR_REGS`](https://github.com/torvalds/linux/blob/bfeffd155283772bbe78c6a05dec7c0128ee500c/arch/x86/entry/calling.h#L100-L145)の中で定義されています。


上で作った構造体とsystem call numberを、システムコールの処理を行う関数`do_syscall_64`に以下で渡しています。

[linux/arch/x86/entry/entry_64.S#L173-L175](https://github.com/torvalds/linux/blob/cd6c84d8f0cdc911df435bb075ba22ce3c605b07/arch/x86/entry/entry_64.S#L173-L175)

```Nasm
	movq	%rax, %rdi
	movq	%rsp, %rsi
	call	do_syscall_64		/* returns with IRQs disabled */
```

なお、`do_syscall_64`への引数の渡し方ですがレジスタを使用して行っています。x86-64で関数に引数を渡すときの[ルール](https://wiki.osdev.org/System_V_ABI)として、第1引数から順番に、`rdi`、 `rsi`、 `rdx`、 `rcx`、 `r8`、 `r9`のレジスタを使うことになってます。
なので以下のようなシグネチャを持った関数`do_syscall_64`に対して引数を渡したい場合、


[linux/arch/x86/entry/common.c](https://github.com/torvalds/linux/blob/7e9890a3500d95c01511a4c45b7e7192dfa47ae2/arch/x86/entry/common.c#L283)

```c
__visible void do_syscall_64(unsigned long nr, struct pt_regs *regs)
```

2つのレジスタ`rdi`と`rsi`に渡したい値をセットすることで行なえます。

- `movq	%rax, %rdi`で、第1引数に対応する`rdi`レジスタに対して、`rax`にセットされたsystem call numberを渡す
- `movq	%rsp, %rsi`で、第2引数に対応する`rsi`レジスタに対して、スタックの先頭アドレス(つまりスタックに積まれた構造体の先頭アドレス)を指す`rsp`レジスタの値を渡す

## (3) 各システムコールへディスパッチ
`do_syscall_64`内で各システムコールの実装の呼び出しが行われます。具体的には、各システムコールを実装する関数が入った配列`sys_call_table`対して、要素をsystem call numberで指定することで、対応するシステムコールの実装へとディスパッチが行われます。
また、各システムコールの実装は`SYSCALL_DEFINE*`マクロで定義されていて、これを目印に見つけることができます。

以下、該当するカーネルのコードを見ていきましょう。

`do_syscall_64`関数の引数として、第1引数`nr`にsystem call numberが、第2引数`regs`にシステムコール呼び出し時のレジスタの値で構成された構造体が渡されています。

[linux/arch/x86/entry/common.c](https://github.com/torvalds/linux/blob/7e9890a3500d95c01511a4c45b7e7192dfa47ae2/arch/x86/entry/common.c#L283-L288)

```c
__visible void do_syscall_64(unsigned long nr, struct pt_regs *regs)
{
	struct thread_info *ti;

	enter_from_user_mode();
	local_irq_enable();
```

`sys_call_table`は[ここ](https://github.com/torvalds/linux/blob/7e9890a3500d95c01511a4c45b7e7192dfa47ae2/arch/x86/entry/syscall_64.c#L18)で定義されている、各システムコールの処理を実装した関数の配列ですが、その配列の要素をsystem call number`nr`で指定して、構造体`regs`を渡すことで、各システムコールの処理へとディスパッチしています。

[linux/arch/x86/entry/common.c](https://github.com/torvalds/linux/blob/7e9890a3500d95c01511a4c45b7e7192dfa47ae2/arch/x86/entry/common.c#L299-L302)

```c
	if (likely(nr < NR_syscalls)) {
		nr = array_index_nospec(nr, NR_syscalls);
		regs->ax = sys_call_table[nr](regs);
	}
```

`regs->ax = sys_call_table[nr](regs);`がディスパッチを行っている部分です。また、関数の戻り値が`AX`レジスタ(=raxレジスタ)に対応する構造体のフィールドにセットされていますが、この値が`syscall`インストラクションを抜けた後に、戻り値として実際に`rax`レジスタにセットされることになります。

`sys_call_table`配列の要素となる、実際にシステムコールを処理する関数ですが、例えば`write`の処理は以下で実装されています。

[linux/fs/read_write.c](https://github.com/torvalds/linux/blob/6f0d349d922ba44e4348a17a78ea51b7135965b1/fs/read_write.c#L607-L611)

```c
SYSCALL_DEFINE3(write, unsigned int, fd, const char __user *, buf,
		size_t, count)
{
	return ksys_write(fd, buf, count);
}
```

一般に、各システムコールの実装は、[`SYSCALL_DEFINE*`マクロ](https://github.com/torvalds/linux/blob/dd5001e21a991b731d659857cd07acc7a13e6789/include/linux/syscalls.h#L215-L220)で定義されているので、これを目印に各実装を見つけることができます。

`sys_call_table`と`SYSCALL_DEFINE*`マクロに関する詳細に関しては、[こちらの記事](https://lwn.net/Articles/604287/)が参考になります。

## (4) sysretqインストラクションで復帰
システムコールハンドラーから、ユーザープロセス内の元のコードへの復帰は`sysretq`インストラクションによって行われます。`sysetq`インストラクションで行われる処理はほぼ`syscall`の逆の操作です。

- `RFLAGS`レジスタに対して、`R11`レジスタ(`RFLAGS`の元の値を退避させていた)の値を読み込むことで元の値に戻し、ユーザープロセスを実行するCPUのモード(Privilege level 3)へ復帰
-  プログラムカウンターレジスタ`RIP`に対して、`RCX`レジスタ(`RIP`の元の値を退避させていた)の値を読み込むことで元の値に戻し、`syscall`を呼び出した元のインストラクションの地点に復帰する 

この他にも色々な処理が行われているのですが、詳細は[こちら](https://www.felixcloutier.com/x86/sysret)を参照してください。

カーネル内で`sysretq`を呼び出すコードですが、まずシステムコールハンドラーで最後に到達するコードは以下で、

[linux/arch/x86/entry/entry_64.S](https://github.com/torvalds/linux/blob/cd6c84d8f0cdc911df435bb075ba22ce3c605b07/arch/x86/entry/entry_64.S#L273-L276)

```Nasm
	popq	%rdi
	popq	%rsp
	USERGS_SYSRET64
END(entry_SYSCALL_64)
```

この中の`USERGS_SYSRET64`にて`sysretq`が呼び出されています。

[linux/arch/x86/include/asm/irqflags.h](https://github.com/torvalds/linux/blob/7e9890a3500d95c01511a4c45b7e7192dfa47ae2/arch/x86/include/asm/irqflags.h#L147-L149)

```asm
#define USERGS_SYSRET64				\
	swapgs;					\
	sysretq;
```

## (5) 起動時にシステムコールハンドラーのアドレスをレジスタへ読み込む
`syscall`インストラクションが呼ばれたときに、プログラムカウンターレジスタ`RIP`に対して、システムコールハンドラーのアドレスを`IA32_LSTAR MSR`レジスタから読み込むことで、システムコールハンドラーへのジャンプが行われるのでした。では、`IA32_LSTAR MSR`レジスタはどのようにして、システムコールハンドラーのアドレスを知っているのでしょうか。
システムコールハンドラーのアドレスは、CPUの初期化時に`IA32_LSTAR MSR`レジスタへ読み込まれます。
以下ではこの初期化処理に対応するカーネルのコードを紹介していきます。

CPUの初期化は以下で行われていますが、

[linux/arch/x86/kernel/cpu/common.c](https://github.com/torvalds/linux/blob/9c7db5004280767566e91a33445bf93aa479ef02/arch/x86/kernel/cpu/common.c#L1684-L1693)

```c
/*
 * cpu_init() initializes state that is per-CPU. Some data is already
 * initialized (naturally) in the bootstrap process, such as the GDT
 * and IDT. We reload them nevertheless, this function acts as a
 * 'CPU state barrier', nothing should get across.
 */
#ifdef CONFIG_X86_64

void cpu_init(void)
{
```

この中で呼ばれている[`syscall_init();`](https://github.com/torvalds/linux/blob/9c7db5004280767566e91a33445bf93aa479ef02/arch/x86/kernel/cpu/common.c#L1736)によってアドレスのセットが行われます。

[linux/arch/x86/kernel/cpu/common.c](https://github.com/torvalds/linux/blob/9c7db5004280767566e91a33445bf93aa479ef02/arch/x86/kernel/cpu/common.c#L1547-L1550)

```c
void syscall_init(void)
{
	wrmsr(MSR_STAR, 0, (__USER32_CS << 16) | __KERNEL_CS);
	wrmsrl(MSR_LSTAR, (unsigned long)entry_SYSCALL_64);
```

上のコードの中の

```c
wrmsrl(MSR_LSTAR, (unsigned long)entry_SYSCALL_64);
```
によって、システムコールハンドラーのアドレスが`IA32_LSTAR MSR`レジスタ対して書き込まれています。

定数`MSR_LSTAR`と`entry_SYSCALL_64`ですが、定数`MSR_LSTAR`は書き込みターゲットのレジスタとして`IA32_LSTAR MSR`レジスタを指定しています。

[linux/arch/x86/include/asm/msr-index.h](https://github.com/torvalds/linux/blob/0ef0fd351550130129bbdb77362488befd7b69d2/arch/x86/include/asm/msr-index.h#L17)

```c
#define MSR_LSTAR		0xc0000082 /* long mode SYSCALL target */
```

`entry_SYSCALL_64`は、[ここ](https://github.com/torvalds/linux/blob/6f0d349d922ba44e4348a17a78ea51b7135965b1/arch/x86/include/asm/proto.h#L12)でプロトタイプ宣言されている値で、[システムコールハンドラーのエントリポイント](https://github.com/torvalds/linux/blob/cd6c84d8f0cdc911df435bb075ba22ce3c605b07/arch/x86/entry/entry_64.S#L145-L147)のアドレスを指しています。

# [補足] システムコールと割り込みの関係
システムコールについて調べると、割り込み(インターラプト)という概念によく遭遇すると思います。
例えばカーネル内だと以下のコードのコメントで`Interrupts are off`と書かれていますが、`interrupts`(割り込み)とは一体なんなのでしょうか。システムコールとどんな関わりがあるのでしょうか。

[linux/arch/x86/entry/entry_64.S](https://github.com/torvalds/linux/blob/cd6c84d8f0cdc911df435bb075ba22ce3c605b07/arch/x86/entry/entry_64.S#L145-L151)

```Nasm
ENTRY(entry_SYSCALL_64)
	UNWIND_HINT_EMPTY
	/*
	 * Interrupts are off on entry.
	 * We do not frame this tiny irq-off block with TRACE_IRQS_OFF/ON,
	 * it is too small to ever cause noticeable irq latency.
	 */
```



まず割り込みとシステムコールの関係ですが、システムコールは割り込みの一種とみなせます。
割り込みが発生すると、CPUは現在実行中のコード(ユーザープロセス内/カーネル内問わず)を中断し、後で再開可能なように実行状態を退避させた上でカーネル内の特定のコード(=インターラプトハンドラー)にジャンプします。これまで見てきたシステムコールとやってることは大体同じです。
割り込みの発生のさせかたには2種類あり、ソフトウェア起因のものをソフトウェア割り込み、ハードウェア起因のものをハードウェア割り込みと言います。

ソフトウェア割り込みの例

- システムコールの呼び出し
- 例外発生時の処理

ハードウェア割り込みの例

- キーボード入力への応答
- ネットワークカードにパケットが到達したときの処理

割り込みの仕組みのおかげで、「何かが起きたときにCPUを特定のコードに問答無用でジャンプさせて処理させる」という機能を実装することができ、これによって例えばハードウェアからの入力に対して迅速に応答することが可能になります。

なお、x86-64ではシステムコール用に専用のインストラクション(`syscall`と`sysretq`)が用意されていますが、一世代前のx86-32や他のアーキテクチャではシステムコールはインターラプトの仕組みの中で実現されています。
例えばx86-32でのシステムコールの実装は[`int`インストラクション](https://www.felixcloutier.com/x86/intn:into:int3:int1)にベクタ`0x80`を指定した[`int 0x80`](https://en.wikibooks.org/wiki/X86_Assembly/Interfacing_with_Linux#int_0x80)というインストラクションを呼び出すことで行われていました。

## 割り込み/irqをオフにするとはどういうことなのか
システムコールハンドラー内は一部は割り込みがオフ、つまり`irq`がオフの状態で実行されます。
`irq`とはインターラプトリクエストのことで、これをCPUが受け取ることで割り込みが発生します。
一般に、インターラプトハンドラーが割り込みを処理している時には、`irq`の一部はまたは全てを無効化することがあります。これによって割り込みの処理をしている最中に割り込みが発生するという状況が起きないようにできます。
`irq`がオフになっている状態だと割り込みを受け付けられないので、例えばキーボードの入力に応答することができません。なので、`irq`がオフの状態で実行するコードは、処理の時間がかからないように充分短い必要があります。

割り込みに関してさらに詳細が知りたい人は[こちらのページ](https://wiki.osdev.org/Interrupts)が参考になります。

# 次のステップに進むには
システムコールやカーネルについてさらに進んで調べていくための資料やキーワードを紹介します。
この記事を書くのに参考にした文献の紹介も兼ねています。

## システムコールにはどのような種類があるのか
- [man-pages section 2](http://man7.org/linux/man-pages/dir_section_2.html)

## x86-64やアセンブリコードについて
インストラクションについて調べる時は[こちら](https://www.felixcloutier.com/x86/index.html)を参考にしました。
x86-64のアセンブリコードの入門としては[低レイヤを知りたい人のためのCコンパイラ作成入門](https://www.sigbus.info/compilerbook)もオススメです。

## Linux Kernelについて
[Linux Kernel Development](https://www.amazon.com/Linux-Kernel-Development-Developers-Library-ebook-dp-B003V4ATI0/dp/B003V4ATI0/ref=mt_kindle)は非常に評価が高く、カーネルの内部実装の解説本としてはおそらく最良でしょう。
内容的には決して簡単ではない(少なくとも自分にとっては)のですが、語り口がたまに面白く、説明の仕方も非常に丁寧なのでオススメです。
カーネルに興味がない人にも、カーネル内の並行プログラミンのテクニックを解説した10章は色々と参考になるかもしれません。
またLKDの内容をまとめた[このノート](https://notes.shichao.io/lkd/)もオススメです。

## カーネルのソースコードの読み方
実はこの記事を書くにあたって初めてカーネルのコードをちゃんと読みました。
感想としては、もちろんコードに書かれていることの全てを理解できるわけではないのですが、処理の大体の流れを追っていく分にはそこまで難しくない印象です。コメントが丁寧に書かれている部分も結構ありますので。
読み方としては、[github](https://github.com/torvalds/linux)でキーワード検索(`/`キーを押して)したり、[こちら](https://elixir.bootlin.com/linux/latest/source)の定義元ジャンプ機能を使ったりして読んでいました。
自分もカーネルに関しては素人なので、もっと良い読み方があるかも知れませんが 、、、

## OSの仕組みや働きについて
OSにまつわる基本的に重要な概念として、プロセス、仮想メモリ、マルチタスキングは抑えておいて良いと思います。オススメのキーワードは以下です。

- プロセス
  - プログラムカウンター
  - スタックポインタ
  - プロセス制御ブロック

- 仮想メモリ
  - 仮想アドレス
  - 物理アドレス
  - メモリ管理ユニット
  - ページング

- マルチタスキング
  - プロセスの状態遷移
  - コンテキストスイッチ
  - プロセススケジューリング


## その他参考文献
- [Anatomy of a system call, part 1](https://lwn.net/Articles/604287/)
- [Anatomy of a system call, part 2](https://lwn.net/Articles/604515/)
- https://wiki.osdev.org/Interrupts
- [Linux man pages: section 2](http://man7.org/linux/man-pages/dir_section_2.html)