Linux(2.6.11)において,int 0x80
命令によってシステムコールが呼ばれる流れを追ってみた
この記事では,以下の本を参考に,実際にコードを見ていく記事.
参考文献: Linuxカーネル2.6解読室

- 作者: 高橋浩和,小田逸郎,山幡為佐久
- 出版社/メーカー: ソフトバンククリエイティブ
- 発売日: 2006/11/18
- メディア: 単行本
- 購入: 14人 クリック: 197回
- この商品を含むブログ (118件) を見る
そもそもシステムコールとは
ウィキペディアにはこのように書いてある.
オペレーティングシステム (OS)(より明確に言えばOSのカーネル)の機能を呼び出すために使用される機構のこと。 実際のプログラミングにおいては、OSの機能は関数 (API) 呼び出しによって実現されるので、OSの備える関数 (API) のことを指すこともある。
例えば,プログラムから見て,外界に繋がる操作をする際は,システムコールを使用する. システムコールでは,デバイスを扱う処理などをOSが引き受けることで,より安全に,確実に処理を行えるようになる利点がある.
Linux 2.6におけるシステムコールの機構
i386 / x86_64 アーキテクチャでは,システムコールを実行する方法として,4つの方法を提供している.そのうち,Linuxで使用しているのは,以下のチェックが入った二つの方法を使用している.
- [x] ソフトウェア割り込み(int n命令)
- [ ] コールゲート
- [ ] タスクゲート
- [x] sysenter命令を使用
システムコールは,基本的にはglibcライブラリが,定められた手順によって実際に発行されるため,プログラマはそのAPIがライブラリルーチンなのか,システムコールなのかを知る必要はない. 例えばopenシステムコールでは,一旦は,glibcのライブラリを呼び出し,その中の処理でシステムコールを発行している.
システムコールはそれぞれに番号が振られているが,カーネル内では,この番号に応じた関数が定義されている.内部的にシステムコールの実態のなる関数は配列に定義されており, 前述のシステムコールの番号というのは,この配列のインデックスとなっている.
Linuxカーネル2.6/i386アーキテクチャでは,約270のシステムコールが定義されている.
#ifndef _ASM_I386_UNISTD_H_ #define _ASM_I386_UNISTD_H_ /* * This file contains the system call numbers. */ #define __NR_restart_syscall 0 #define __NR_exit 1 #define __NR_fork 2 #define __NR_read 3 #define __NR_write 4 #define __NR_open 5 #define __NR_close 6 #define __NR_waitpid 7 // 省略.... #define __NR_sys_kexec_load 283 #define __NR_waitid 284 /* #define __NR_sys_setaltroot 285 */ #define __NR_add_key 286 #define __NR_request_key 287 #define __NR_keyctl 288
ちなみに,Linux2.6時点でのx86_64のシステムコールはここにある.
C言語においては,システムコールの呼び出しとの変換は,glibc
が行なっており,呼び出し方法としては,int 0x80/iret
命令か,sysente/sysexit
命令が用いられる.
システムコールを呼び出す際は,特定のレジスタにそれぞれ対応した値を格納したのちに,int 0x80
命令なりsysenter
命令を実行することで,処理が行われる.
int 0x80
を使ったシステムコール
32bit(i386)の場合
eax
に呼び出すシステムコールの番号ebx
に0番目の引数ecx
に1番目の引数- 以降,
edx
,esi
,edi
,ebp
と続いていく.
64bit(x86_64)の場合
EAX
:呼び出すシステムコールの番号RDI
:0番目の引数RSI
:1番目の引数- 以降,
RDX
,R10
,R8
,R9
と続いていく
例えば,x86_64 Linux
でwriteシステムコール
を使いたい場合は,
mov eax,0x1 # 1番のシステムコールを呼びたい. mov edi,0x1 # 0番目の引数 movabs rsi,0x601001 # 1番目の引数 mov edx,0x1 # 2番目の引数 syscall # まだ出てきていないが,x86_64においてシステムコールを呼び出すもの.
のようにすると呼び出すことができる.
では実際に処理を見ていく.
通常のC言語を用いたプログラミング(インラインアセンブラなどを用いない)では,先ほども書いたように,glibc
が実際のシステムコールの呼び出しを行う.
ライブラリが,レジスタに引数を設定したのち,int 0x80
命令を実行する.すると,CPUは特権モードに移り,system_call
から実行が始まる.
https://elixir.bootlin.com/linux/v2.6.11/source/arch/i386/kernel/entry.S#L241
ENTRY(system_call) # 1 pushl %eax # save orig_eax # 2 SAVE_ALL # 3 GET_THREAD_INFO(%ebp) # 4 # system call tracing in operation testb $(_TIF_SYSCALL_TRACE|_TIF_SYSCALL_AUDIT),TI_flags(%ebp) jnz syscall_trace_entry cmpl $(nr_syscalls), %eax jae syscall_badsys # 6 syscall_call: call *sys_call_table(,%eax,4) # 7 movl %eax,EAX(%esp) # store the return value syscall_exit: cli # make sure we don't miss an interrupt # setting need_resched or sigpending # between sampling and the iret movl TI_flags(%ebp), %ecx testw $_TIF_ALLWORK_MASK, %cx # current->work jne syscall_exit_work restore_all: RESTORE_ALL # perform work that needs to be done immediately before resumption ALIGN
- 1: カーネルのスタックに切り替わる.スタックには,
SS
,ESP
,EFLAGS
,CS
,EIP
レジスタが積まれている. - 2: システムコール番号をpush
- 3:
SAVE_ALL
でプロセスを実行していた時のレジスタの状態をpush
https://elixir.bootlin.com/linux/v2.6.11/source/arch/i386/kernel/entry.S#L84
#define SAVE_ALL \ cld; \ pushl %es; \ pushl %ds; \ pushl %eax; \ pushl %ebp; \ pushl %edi; \ pushl %esi; \ pushl %edx; \ pushl %ecx; \ pushl %ebx; \ movl $(__USER_DS), %edx; \ movl %edx, %ds; \ movl %edx, %es;
- 4: 実行中プロセスの
struct thread_info
をebp
に格納. - 5: 通知系(省略)
- 6: システムコールが不正なものかどうかをチェックしている(https://elixir.bootlin.com/linux/v2.6.11/source/arch/i386/kernel/entry.S#L52)
- ここで
#define nr_syscalls ((syscall_table_size)/4)
と比較をしている
- ここで
cmpl $(nr_syscalls), %eax jae syscall_badsys
- 7: システムコール関数を実際に呼び出しているところ.
- ここで先ほど指定されていた番号のインデックスを辿り,関数を実行している.
https://elixir.bootlin.com/linux/v2.6.11/source/arch/i386/kernel/entry.S#L575
ENTRY(sys_call_table) .long sys_restart_syscall /* 0 - old "setup()" system call, used for restarting */ .long sys_exit .long sys_fork .long sys_read .long sys_write .long sys_open /* 5 */ # 省略 .long sys_ni_syscall /* reserved for kexec */ .long sys_waitid .long sys_ni_syscall /* 285 */ /* available */ .long sys_add_key .long sys_request_key .long sys_keyctl
上で例にあげたwriteシステムコール
の実装を探してみると.
https://elixir.bootlin.com/linux/v2.6.11/source/arch/i386/kernel/entry.S#L580
↓
https://elixir.bootlin.com/linux/v2.6.11/source/fs/read_write.c#L330
システムコールは,カーネルの中に定義された関数であるということがわかった.
これ以降の処理は,ユーザーモードに戻る準備をしている.
sysenter
を使ったシステムコール
sysenter, 32bit(i386)の場合
eax
に呼び出すシステムコールの番号ebx
に0番目の引数ecx
に1番目の引数- 以降,
edx
,esi
,edi
と続いていく. ebp
,5番目の引数へのポインタで,ユーザープロセスのスタックを指す.
最後だけ少し違う.
今回は省略.
次回以降
この記事では,i386(32bit)
のLinux 2.6.11のコードをみるに止まった.今後は,最新版Linuxのx86_64
の処理を追ってみたいと思う.