2012年12月04日 情報科学類 オペレーティングシステム II 筑波大学 システム情報工学研究科 コンピュータサイエンス専攻, 電子・情報工学系 新城 靖 <yas@cs.tsukuba.ac.jp>
このページは、次の URL にあります。
http://www.coins.tsukuba.ac.jp/~yas/coins/os2-2012/2012-12-04
あるいは、次のページから手繰っていくこともできます。
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 -
2.6 以前には、2.4, 2.2, 2.0 等が広く使われた。 ~yas/os2/linux-3.6.8 に展開したものがある。
ディレクトリ | 説明 |
---|---|
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-3.6.8/arch/x86/include/generated/asm/unistd_32.h (自動生成されたもの)
... 4: #define __NR_restart_syscall 0 5: #define __NR_exit 1 6: #define __NR_fork 2 7: #define __NR_read 3 8: #define __NR_write 4 9: #define __NR_open 5 10: #define __NR_close 6 11: #define __NR_waitpid 7 12: #define __NR_creat 8 13: #define __NR_link 9 14: #define __NR_unlink 10 15: #define __NR_execve 11 16: #define __NR_chdir 12 ... 347: #define __NR_process_vm_readv 347 348: #define __NR_process_vm_writev 348 349: #define __NR_kcmp 349 ...
12: #define __SYSCALL_I386(nr, sym, compat) [nr] = sym, 13: 14: typedef asmlinkage void (*sys_call_ptr_t)(void); 15: 16: extern asmlinkage void sys_ni_syscall(void); 17: 18: const sys_call_ptr_t sys_call_table[__NR_syscall_max+1] = { 19: /* 20: * Smells like a compiler bug -- it doesn't work 21: * when the & below is removed. 22: */ 23: [0 ... __NR_syscall_max] = &sys_ni_syscall, 24: #include <asm/syscalls_32.h> 25: }; linux-3.6.8/arch/x86/./include/generated/asm/syscalls_32.h 1: __SYSCALL_I386(0, sys_restart_syscall, sys_restart_syscall) 2: __SYSCALL_I386(1, sys_exit, sys_exit) 3: __SYSCALL_I386(2, ptregs_fork, stub32_fork) 4: __SYSCALL_I386(3, sys_read, sys_read) 5: __SYSCALL_I386(4, sys_write, sys_write) 6: __SYSCALL_I386(5, sys_open, compat_sys_open) 7: __SYSCALL_I386(6, sys_close, sys_close) 8: __SYSCALL_I386(7, sys_waitpid, sys32_waitpid) 9: __SYSCALL_I386(8, sys_creat, sys_creat) 10: __SYSCALL_I386(9, sys_link, sys_link) 11: __SYSCALL_I386(10, sys_unlink, sys_unlink) 12: __SYSCALL_I386(11, ptregs_execve, stub32_execve) 13: __SYSCALL_I386(12, sys_chdir, sys_chdir) ...
sys_
で始まる。
ユーザ空間のシステム・コールと紛れないようにするため。
例: ユーザの write() システム・コールは、カーネル内の
sys_write() という関数で処理される。
sys_call_ptr_t sys_call_table[__NR_syscall_max+1]; ... { long n = システム・コールの番号; long f = sys_call_table[n]; (*f)( 引数0, 引数1, 引数2 ); // 関数と思って呼ぶ }
479: SYSCALL_DEFINE3(write, unsigned int, fd, const char __user *, buf, 480: size_t, count) 481: { 482: struct file *file; 483: ssize_t ret = -EBADF; 484: int fput_needed; 485: 486: file = fget_light(fd, &fput_needed); 487: if (file) { 488: loff_t pos = file_pos_read(file); 489: ret = vfs_write(file, buf, count, &pos); 490: file_pos_write(file, pos); 491: fput_light(file, fput_needed); 492: } 493: 494: return ret; 495: }この定義は、以下のように展開される。
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 */ ...
192: #define SYSCALL_DEFINE0(name) asmlinkage long sys_##name(void) ... 195: #define SYSCALL_DEFINE1(name, ...) SYSCALL_DEFINEx(1, _##name, __VA_ARGS__) 196: #define SYSCALL_DEFINE2(name, ...) SYSCALL_DEFINEx(2, _##name, __VA_ARGS__) 197: #define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__) 198: #define SYSCALL_DEFINE4(name, ...) SYSCALL_DEFINEx(4, _##name, __VA_ARGS__) 199: #define SYSCALL_DEFINE5(name, ...) SYSCALL_DEFINEx(5, _##name, __VA_ARGS__) 200: #define SYSCALL_DEFINE6(name, ...) SYSCALL_DEFINEx(6, _##name, __VA_ARGS__) ... 227: #define SYSCALL_DEFINEx(x, sname, ...) \ 228: __SYSCALL_DEFINEx(x, sname, __VA_ARGS__) ... 249: #define __SYSCALL_DEFINEx(x, name, ...) \ 250: asmlinkage long sys##name(__SC_DECL##x(__VA_ARGS__)) ... 80: #define __SC_DECL1(t1, a1) t1 a1 81: #define __SC_DECL2(t2, a2, ...) t2 a2, __SC_DECL1(__VA_ARGS__) 82: #define __SC_DECL3(t3, a3, ...) t3 a3, __SC_DECL2(__VA_ARGS__) 83: #define __SC_DECL4(t4, a4, ...) t4 a4, __SC_DECL3(__VA_ARGS__) 84: #define __SC_DECL5(t5, a5, ...) t5 a5, __SC_DECL4(__VA_ARGS__) 85: #define __SC_DECL6(t6, a6, ...) t6 a6, __SC_DECL5(__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)
情報科学類 オペレーティングシステムII 課題<n> 学籍番号 ________ 名前________ 提出日________
CHDIR(2) Linux Programmer's Manual CHDIR(2) NAME chdir, fchdir - change working directory SYNOPSIS #include <unistd.h> int chdir(const char *path); ... DESCRIPTION chdir() changes the current working directory of the calling process to the directory specified in path. ... RETURN VALUE On success, zero is returned. On error, -1 is returned, and errno is set appropriately. ...このシステム・コールを処理する関数がカーネルの中でどのように定義されて いるか、その概略(引数と結果の宣言)を示しなさい。関数の内容は空でよい。 マクロを利用しても利用しなくてもどちらでもよい。
引数と結果の宣言 { /*内容*/ }