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/
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
$ 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 言語で記述されている。
図? メタレベルのプログラミング
プログラム「hello.rb」の動きを変更する方法
$ 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」の動きを変更する方法OSカーネル自身は、普通は、ハードウェアによるインタプリタ(CPU、入出力)で 実行される。インタフェースは、機械語命令や入出力機器への操作。
OSカーネルへのインタフェースは、システム・コール。 == OSカーネルは、システム・コールのインタプリタ。
図? OSの構造
プログラム・コードの場所システムコールの例:
システムコールとライブラリの見分け方(Unix編)
同じUnix系OSでも、細かい所でシステム・コールとライブラリが入れ替わって いることもある。
システム・コールは、トラップ命令(trap instruction)を含む。 その部分は、アセンブリ言語。
ライブラリ関数は、大部分はC言語で書かれている。 printf() のソース・プログラムがある。
C言語で記述したプログラムは、文法さえあっていれば コンパイルはできる。 ライブラリ関数やシステムコールがないと動作しない。
動的リンクを行う時には、共有ライブラリ(shared library) を使い、メモリの節約をする。
Microsoft Windows では、システムコールとライブラリの区別が希薄。 Win32 API。3000以上。 Unix のシステムコールは、300程度。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
$
gcc -S file.c
」で、通常は削除されるアセンブリ言語の出力が
残される。
.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
<省略> 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(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. ...
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
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 | 仮想化インフラ |
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 ...
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() システム・コールは、カーネル内の
sys_write() という関数で処理される。
long sys_call_table[NR_syscalls]; ... { long n = システム・コールの番号 long f = sys_call_table[n]; (*f)( 引数0, 引数1, 引数2 ); // 関数と思って呼ぶ }
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) { 省略 }
E
で始
まる名前(マクロ)が付けられている。
定義は、
<errno.h> (include/asm-generic/errno-base.h) 等に含まれてい
る。
sys_xxx()
は、成功すると
正の値を返す。
エラーが発生すると、カーネル内では、エラー
番号を負の値にしたものを返す。
errno
を見ると分かる。
(マルチスレッドのプログラムでは、スレッド固有データ)
x86-32 では、__syscall_error という手続きが
レジスタにあるエラー番号を グローバル変数 errno
に
セットする。
write()システム・コール(ユーザ空間)
参照。
... 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 */ ...
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__))
sys_
」 というプレフィックスが付けられる。
「##
」 は、Cプリプロセッサの命令で、文字列の結合を意味する。
図? hello world プログラムでのシステム・コールの実行
$ 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) = ?
$
$ ./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 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)
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). ...このシステム・コールを処理する関数がカーネルの中でどのように定義されて いるか、その概略(引数と結果の宣言)を示しなさい。関数の内容は空でよい。 マクロを利用しても利用しなくてもどちらでもよい。
引数と結果の宣言 { /*内容*/ }