システムコールとLinuxカーネルのソース

					2010年12月07日
情報科学類 オペレーティングシステム II

                                       筑波大学 システム情報工学研究科 
                                       コンピュータサイエンス専攻, 電子・情報工学系
                                       新城 靖
                                       <yas@is.tsukuba.ac.jp>

このページは、次の URL にあります。
http://www.coins.tsukuba.ac.jp/~yas/coins/os2-2010/2010-12-07
あるいは、次のページから手繰っていくこともできます。
http://www.coins.tsukuba.ac.jp/~yas/
http://www.cs.tsukuba.ac.jp/~yas/

■連絡事項

Softlab 研究室紹介

■今日の大事な話

◆参考書

Robert Love: "Linux Kernel Development", Addison-Wesley Professional (2010). ISBN-13: 9780672329463

Claudia Salzberg Rodriguez, Gordon Fischer, and Steven Smolski: "The Linux Kernel Primer: A Top-Down Approach for x86 and PowerPC Architectures", Prentice Hall (2005). ISBN-10: 0131181637

Daniel P. Bovet, Marco Cesati 著, 高橋 浩和 (監訳), 杉田 由美子, 清水 正明 , 高杉 昌督 , 平松 雅巳 , 安井 隆宏(訳) 詳解 Linuxカーネル 第3版 オライリー・ジャパン (2007). ISBN-13: 978-4873113135

Jonathan Corbet, Alessandro Rubini, Greg Kroah-Hartman (著), 山崎 康宏 , 山崎 邦子 , 長原 宏治 , 長原 陽子(訳): "Linuxデバイスドライバ", オライリージャパン (2005). ISBN-13: 978-4873112534

■オペレーティングシステムII

◆この授業の狙い

◆この授業を受ける意義

将来カーネルをいじる人もいじらない人にも意義がある。

◆メタレベルのプログラム

インタプリタ言語を考える。例:Ruby。
$ ls -l hello.rb  [←]
-rw-r--r-- 1 yas prof 26 Dec  7 06:35 hello.rb
$ cat hello.rb  [←]
printf("hello, world!\n")
$ ruby hello.rb  [←]
hello, world!
$ []
Ruby 言語のインタプリタ自身は、C 言語で記述されている。

rubyのCのソース(メタ)、コンパイル&リンク、実行形式、プロセス、hello.rbソース(プログラム)

図? メタレベルのプログラミング

プログラム「hello.rb」の動きを変更する方法
プログラミング
hello.rb を書き換える
メタレベルのプログラミング
ruby インタプリタのプログラムを書き換える
「hello.rb」は、ruby インタプリタから見ると、入力「データ」の1つ。

◆メタレベルのプログラムとしてのOS

$ ls -l hello.c [←]
-rw-r--r-- 1 yas prof 44 Dec  7 06:25 hello.c
$ cat hello.c [←]
main()
{
        printf("hello, %s!\n","world");
}
$ cc hello.c -o hello [←]
$ ./hello  [←]
hello, world!
$ []

プロセス、カーネル(メタ)、ハードウェア(メタメタ)

図? メタレベルのプログラムとしてのOSカーネル

プログラム「hello.c」の動きを変更する方法
プログラミング
hello.c を書き換える
メタレベルのプログラミング
OSカーネルのプログラムを書き換える
「hello(実行形式)」は、OSカーネルから見ると、入力「データ」に相当する。

OSカーネル自身は、普通は、ハードウェアによるインタプリタ(CPU、入出力)で 実行される。インタフェースは、機械語命令や入出力機器への操作。

OSカーネルへのインタフェースは、システム・コール。 == OSカーネルは、システム・コールのインタプリタ。

◆シラバス

◆Linux

教材としては、Linux を使う。 2010年12月現在、学類では次のコンピュータで Linux が使える。 サーバには、ssh でログインして使う。

■システム・コールとライブラリ

システム・コールとライブラリは、プログラマにとってのオペレーティング・ システム。共通点は、両方とも、C言語からは、関数呼出しの形で使えること。 2つを合わせて、API (Application Programming Interface) とも言う。

◆OSの構造

図? 3層、複数コンポーネント

図? OSの構造

プログラム・コードの場所

◆システム・コールのライブラリの違い

システム・コール(system call) ライブラリ(library)

システムコールの例:

ライブラリの例:

システムコールとライブラリの見分け方(Unix編)

同じUnix系OSでも、細かい所でシステム・コールとライブラリが入れ替わって いることもある。

システム・コールは、トラップ命令(trap instruction)を含む。 その部分は、アセンブリ言語。

ライブラリ関数は、大部分はC言語で書かれている。 printf() のソース・プログラムがある。

C言語で記述したプログラムは、文法さえあっていれば コンパイルはできる。 ライブラリ関数やシステムコールがないと動作しない。

◆リンク

ライブラリやシステム・コールは、コンパイルされて オブジェクト・コードの形で保存されている。 実行形式を作る時に取り出され、 アプリケーション・プログラマから作成した オブジェクト・コード(main()関数など)と結合(リンク)される。

静的リンク(static link)
プログラム実行する前(実行形式を作成する時点)で、ライブラリ関数の呼出し をすべて機械語の手続き呼び出し命令に変換する。データへの参照を固定番地 に変換する。
動的リンク(dynamic link)
プログラム実行中に必要に応じてリンクを行う。

動的リンクを行う時には、共有ライブラリ(shared library) を使い、メモリの節約をする。

Microsoft Windows では、システムコールとライブラリの区別が希薄。 Win32 API。3000以上。 Unix のシステムコールは、300程度。

◆hello.cとgcc -S

main()
{
	printf("hello, %s!\n","world");
}
$ ls hello*   [←]
hello.c
$ make hello    [←]
cc     hello.c   -o hello
hello.c: In function 'main':
hello.c:3: warning: incompatible implicit declaration of built-in function 'printf'
$ ./hello  [←]
hello, world!
$ gcc -S hello.c [←]
hello.c: In function 'main':
hello.c:3: warning: incompatible implicit declaration of built-in function 'printf'
$ ls hello* [←]
hello  hello.c  hello.s
$ []
	.file	"hello.c"
	.section	.rodata
.LC0:
	.string	"world"
.LC1:
	.string	"hello, %s!\n"
	.text
.globl main
	.type	main, @function
main:
	leal	4(%esp), %ecx
	andl	$-16, %esp
	pushl	-4(%ecx)
	pushl	%ebp
	movl	%esp, %ebp
	pushl	%ecx
	subl	$20, %esp
	movl	$.LC0, 4(%esp)
	movl	$.LC1, (%esp)
	call	printf
	addl	$20, %esp
	popl	%ecx
	popl	%ebp
	leal	-4(%ecx), %esp
	ret
	.size	main, .-main
	.ident	"GCC: (GNU) 4.1.2 20080704 (Red Hat 4.1.2-48)"
	.section	.note.GNU-stack,"",@progbits

◆printf() のソース・プログラム

~yas/os2/glibc-2.5/stdio-common/printf.c

<省略>
  28:	int
  29:	__printf (const char *format, ...)
  30:	{
  31:	  va_list arg;
  32:	  int done;
  33:	
  34:	  va_start (arg, format);
  35:	  done = vfprintf (stdout, format, arg);
  36:	  va_end (arg);
  37:	
  38:	  return done;
  39:	}
<省略>
  42:	ldbl_strong_alias (__printf, printf);
PRINTF(3)                  Linux Programmer's Manual                 PRINTF(3)

NAME
       printf,   fprintf,  sprintf,  snprintf,  vprintf,  vfprintf,  vsprintf,
       vsnprintf - formatted output conversion
SYNOPSIS
...
       int printf(const char *format, ...);
       int fprintf(FILE *stream, const char *format, ...);
...
       int vfprintf(FILE *stream, const char *format, va_list ap);
...
DESCRIPTION
...
       The functions vprintf(), vfprintf(), vsprintf(), vsnprintf() are equiv-
       alent  to  the  functions  printf(),  fprintf(), sprintf(), snprintf(),
       respectively, except that they are called with a va_list instead  of  a
       variable  number  of  arguments. These functions do not call the va_end
       macro. Consequently, the value of ap is undefined after the  call.  The
       application should call va_end(ap) itself afterwards.
...

◆write()システム・コールのマニュアル

WRITE(2)                   Linux Programmer's Manual                  WRITE(2)

NAME
       write - write to a file descriptor

SYNOPSIS
       #include <unistd.h>

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

DESCRIPTION
       write()  writes  up  to  count bytes to the file referenced by the file
       descriptor fd from the buffer starting at buf.  POSIX requires  that  a
       read()  which  can  be  proved  to  occur  after a write() has returned
       returns the new data.  Note that not all file systems  are  POSIX  con-
       forming.

RETURN VALUE
       On  success,  the  number of bytes written are returned (zero indicates
       nothing was written).  On error, -1  is  returned,  and  errno  is  set
       appropriately.   If  count  is zero and the file descriptor refers to a
       regular file, 0 may be returned, or an error could be detected.  For  a
       special file, the results are not portable.

ERRORS
       EAGAIN Non-blocking  I/O  has  been  selected  using O_NONBLOCK and the
              write would block.

       EBADF  fd is not a valid file descriptor or is not open for writing.
...

◆write()システム・コール(ユーザ空間)

write.o の逆アセンブル結果より、主要部分抜粋。 
	push   %ebx		# ebxレジスタの内容をスタックへ退避
	mov    0x10(%esp),%edx	# 第3引数をレジスタ edx へ
	mov    0xc(%esp),%ecx	# 第2引数をレジスタ ecx へ
	mov    0x8(%esp),%ebx	# 第1引数をレジスタ ebx へ
	mov    $0x4,%eax	# システム・コールの番号 eax へ
	int    $0x80		# トラップ命令。カーネルへ制御を移す。
	pop    %ebx		# ebxレジスタの内容を回復
	cmp    $0xfffff001,%eax	# エラーが起きたら
	jae    __syscall_error	# __syscall_errorへジャンプ (errno等の保存)
	ret

■Linuxカーネルのソース

◆バージョン

4つの数からなる。例 「メジャー・バージョン」. 「マイナー・バージョン」. 「マイナー・バージョン」. 「リビジョン」. 「安定バージョン」

http://kernel.org/ に保存されている。

展開の仕方

$ bzip2 -d < linux-X.Y.Z.tar.bz2 | tar xf - [←]

~yas/os2/linux-2.6.36/ に展開したものがある。

◆カーネルのディレクトリ構成

カーネル・ソースのトップレベルのディレクトリ
ディレクトリ 説明
Documentation ドキュメント
arch アーキテクチャ依存
block ブロック入出力層
crypto 暗号化
drivers デバイス・ドライバ
firmware ファームウェア
fs VFS(Virtual File System)とファイル・システム
include カーネル用のヘッダ・ファイル
init 起動と初期化(initialization)
ipc プロセス間通信(interprocess communication)
kernel カーネルの中心部分
lib ヘルパ
mm メモリ管理(memory management)
net ネットワーク
samples サンプルコード
scripts カーネルのコンパイルに必要なスクリプト
security Lnux Security Module
sound サウンド・サブシステム
tools カーネル開発用のツール
usr 起動直後にユーザ空間で実行されるプログラム(initramfs)
virt 仮想化インフラ

■システム・コール

◆システムコールの番号

システム・コールの種類は、番号(整数)で区別される。 番号は、アーキテクチャ(CPU)に依存している。

linux-2.6.36/arch/x86/include/asm/unistd_32.h

...
   8:	#define __NR_restart_syscall      0
   9:	#define __NR_exit                 1
  10:	#define __NR_fork                 2
  11:	#define __NR_read                 3
  12:	#define __NR_write                4
  13:	#define __NR_open                 5
  14:	#define __NR_close                6
  15:	#define __NR_waitpid              7
  16:	#define __NR_creat                8
  17:	#define __NR_link                 9
  18:	#define __NR_unlink              10
  19:	#define __NR_execve              11
  20:	#define __NR_chdir               12
...
 346:	#define __NR_fanotify_init      338
 347:	#define __NR_fanotify_mark      339
 348:	#define __NR_prlimit64          340
...
 352:	#define NR_syscalls 341
...

◆システム・コールの表 sys_call_table

linux-2.6.36/arch/x86/kernel
   1:	ENTRY(sys_call_table)
   2:	        .long sys_restart_syscall       /* 0 - old "setup()" system call, used for restarting */
   3:	        .long sys_exit
   4:	        .long ptregs_fork
   5:	        .long sys_read
   6:	        .long sys_write
   7:	        .long sys_open          /* 5 */
   8:	        .long sys_close
   9:	        .long sys_waitpid
  10:	        .long sys_creat
  11:	        .long sys_link
  12:	        .long sys_unlink        /* 10 */
...
 340:	        .long sys_fanotify_init
 341:	        .long sys_fanotify_mark
 342:	        .long sys_prlimit64             /* 340 */

◆sys_write()

linux-2.6.36/fs/read_write.c
 408:	SYSCALL_DEFINE3(write, unsigned int, fd, const char __user *, buf,
 409:	                size_t, count)
 410:	{
 411:	        struct file *file;
 412:	        ssize_t ret = -EBADF;
 413:	        int fput_needed;
 414:	
 415:	        file = fget_light(fd, &fput_needed);
 416:	        if (file) {
 417:	                loff_t pos = file_pos_read(file);
 418:	                ret = vfs_write(file, buf, count, &pos);
 419:	                file_pos_write(file, pos);
 420:	                fput_light(file, fput_needed);
 421:	        }
 422:	
 423:	        return ret;
 424:	}
この定義は、以下のように展開される。
asmlinkage long sys_write(unsigned int fd, const char __user *buf,
	   	size_t count)
{
	省略
}

◆エラー番号の例

include/asm-generic/errno-base.h
...
   4:	#define EPERM            1      /* Operation not permitted */
   5:	#define ENOENT           2      /* No such file or directory */
   6:	#define ESRCH            3      /* No such process */
   7:	#define EINTR            4      /* Interrupted system call */
   8:	#define EIO              5      /* I/O error */
   9:	#define ENXIO            6      /* No such device or address */
  10:	#define E2BIG            7      /* Argument list too long */
  11:	#define ENOEXEC          8      /* Exec format error */
  12:	#define EBADF            9      /* Bad file number */
  13:	#define ECHILD          10      /* No child processes */
...

◆SYSCALL_DEFINEマクロ

include/linux/syscalls.h
 188:	#define SYSCALL_DEFINE0(name)      asmlinkage long sys_##name(void)
...
 191:	#define SYSCALL_DEFINE1(name, ...) SYSCALL_DEFINEx(1, _##name, __VA_ARGS__)
 192:	#define SYSCALL_DEFINE2(name, ...) SYSCALL_DEFINEx(2, _##name, __VA_ARGS__)
 193:	#define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__)
 194:	#define SYSCALL_DEFINE4(name, ...) SYSCALL_DEFINEx(4, _##name, __VA_ARGS__)
 195:	#define SYSCALL_DEFINE5(name, ...) SYSCALL_DEFINEx(5, _##name, __VA_ARGS__)
 196:	#define SYSCALL_DEFINE6(name, ...) SYSCALL_DEFINEx(6, _##name, __VA_ARGS__)
...
 223:	#define SYSCALL_DEFINEx(x, sname, ...)                          \
 224:	        __SYSCALL_DEFINEx(x, sname, __VA_ARGS__)
...
 245:	#define __SYSCALL_DEFINEx(x, name, ...)                                 \
 246:	        asmlinkage long sys##name(__SC_DECL##x(__VA_ARGS__))

◆システム・コールの実行のまとめ

main()、printf()、・・・、write()、トラップ、・・・、sys_write()

図? hello world プログラムでのシステム・コールの実行

■システム・コールやライブラリを観察するためのコマンド

◆strace コマンド

システム・コールの動きを調べるには、strace コマンド(Linux) が便利。
$ strace ./hello [←]
execve("./hello", ["./hello"], [/* 38 vars */]) = 0
brk(0)                                  = 0x83a3000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY)      = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=169180, ...}) = 0
mmap2(NULL, 169180, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7f67000
close(3)                                = 0
open("/lib/libc.so.6", O_RDONLY)        = 3
read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\340_\222\0004\0\0\0"..., 512) = 512
fstat64(3, {st_mode=S_IFREG|0755, st_size=1689640, ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7f66000
mmap2(0x910000, 1410500, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x910000
mmap2(0xa63000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x152) = 0xa63000
mmap2(0xa66000, 9668, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xa66000
close(3)                                = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7f65000
set_thread_area({entry_number:-1 -> 6, base_addr:0xb7f656c0, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0
mprotect(0xa63000, 8192, PROT_READ)     = 0
mprotect(0x83d000, 4096, PROT_READ)     = 0
munmap(0xb7f67000, 169180)              = 0
fstat64(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 1), ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7f90000
write(1, "hello, world!\n", 14hello, world!
)         = 14
exit_group(14)                          = ?
$ []

◆ltrace コマンド

ltrace コマンド(Linux) を使うと、ライブラリの呼び出しを調べることができ る。
$ ./hello  [←]
hello, world!
$ ltrace ./hello [←]
__libc_start_main(0x80483a4, 1, 0xbf8d3764, 0x80483f0, 0x80483e0 
printf("hello, %s!\n", "world"hello, world!
)                  = 14
+++ exited (status 14) +++
$ []

◆gdb デバッガ

$ gdb hello [←]
省略
(gdb) b main[←]        # ブレークポイントの設定
Breakpoint 1 at 0x80483b2
(gdb) r[←]             # 実行
Starting program: 省略/hello 
Breakpoint 1, 0x080483b2 in main ()
(gdb) b write[←]       # ブレークポイントの設定
Breakpoint 2 at 0x9d2b80
(gdb) c[←]             # 実行継続
Continuing.
Breakpoint 2, 0x009d2b80 in write () from /lib/libc.so.6
(gdb) bt[←]            # バックトレース
#0  0x009d2b80 in write () from /lib/libc.so.6
省略
#6  0x0094c271 in vfprintf () from /lib/libc.so.6
#7  0x00955e83 in printf () from /lib/libc.so.6
#8  0x080483c9 in main ()
(gdb) c[←]             # 実行継続
Continuing.
hello, world!
Program exited with code 016.
(gdb) 

◆その他のプロセスを調べるコマンド

■クイズ1 システムコールとLinuxカーネルのソース

★問題(101) システム・コールとライブラリ関数の共通点

C言語でプログラムを作成する場合、システム・コールとライブラリ関数の共通 点を述べなさい。

★問題(102) システム・コールとライブラリ関数の相違点

システム・コールとライブラリ関数の相違点を述べなさい。

★問題(103) close()システムコールの引数と結果

close() システム・コールのマニュアルには、次のような
CLOSE(2)                   Linux Programmer's Manual                  CLOSE(2)
NAME
       close - close a file descriptor
SYNOPSIS
       #include <unistd.h>
       int close(int fd);
DESCRIPTION
       close()  closes  a  file descriptor, so that it no longer refers to any
       file and may be reused.  Any record locks (see fcntl(2))  held  on  the
       file  it  was  associated  with,  and owned by the process, are removed
       (regardless of the file descriptor that was used to obtain the lock).
...
このシステム・コールを処理する関数がカーネルの中でどのように定義されて いるか、その概略(引数と結果の宣言)を示しなさい。関数の内容は空でよい。 マクロを利用しても利用しなくてもどちらでもよい。
引数と結果の宣言
{
   /*内容*/
}

Last updated: 2010/12/21 16:46:37
Yasushi Shinjo / <yas@is.tsukuba.ac.jp>