2016年01月08日 情報科学類 オペレーティングシステム II 筑波大学 システム情報系 新城 靖 <yas@cs.tsukuba.ac.jp>
このページは、次の URL にあります。
http://www.coins.tsukuba.ac.jp/~yas/coins/os2-2015/2016-01-08
あるいは、次のページから手繰っていくこともできます。
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 言語で記述されている。
図? メタレベルのプログラミング
$ 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カーネル
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 -m32 -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 -m32 -S hello.c
」
のように、「-S
」オプションをつけると、
通常は削除されるアセンブリ言語の出力が
残される。
「-m32
」は、32ビットのコードを出力させるためのオプション。
.file "hello.c" .section .rodata .LC0: .string "world" .LC1: .string "hello, %s!\n" .text .globl main .type main, @function main: pushl %ebp movl %esp, %ebp andl $-16, %esp subl $16, %esp movl $.LC0, 4(%esp) movl $.LC1, (%esp) call printf leave ret .size main, .-main .ident "GCC: (GNU) 4.4.7 20120313 (Red Hat 4.4.7-4)" .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. ...
write: ... 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 へ call *_dl_sysinfo # 変数 _dl_sysinfo の内容の関数を呼び出す pop %ebx # ebxレジスタの内容を回復 cmp $0xfffff001,%eax # エラーが起きたら jae __syscall_error # __syscall_errorへジャンプ (errno等の保存) ret # 呼び出した関数へリターン
linux-4.3.3/arch/x86/entry/vdso/vdso32/sysenter.S 31: __kernel_vsyscall: 33: push %ecx 35: push %edx 37: push %ebp 39: movl %esp,%ebp 40: sysenter ... 46: int $0x80 47: /* 16: System call normal return point is here! */ 48: VDSO32_SYSENTER_RETURN: /* Symbol used by sysenter.c via vdso32-syms.h */ 49: pop %ebp 51: pop %edx 53: pop %ecx 55: ret
http://kernel.org/
に保存されている。
展開の仕方
$ tar xf linux-X.Y.Z.tar.xz
$ tar xf linux-X.Y.Z.tar.bz2
tar コマンドが圧縮形式を認識できない時の展開の仕方。
$ xz -d < linux-X.Y.Z.tar.xz | tar xf -
$ bzip2 -d < linux-X.Y.Z.tar.bz2 | tar xf -
2.6 以前には、2.4, 2.2, 2.0 等が広く使われた。 ~yas/os2/linux-4.3.3 に展開したものがある。
ディレクトリ | 説明 |
---|---|
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-4.3.3/arch/x86/include/generated/uapi/asm/unistd_32.h (自動生成されたもの)
linux-4.3.3/arch/x86/include/generated/uapi/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 ... 374: #define __NR_userfaultfd 374 375: #define __NR_membarrier 375
linux-4.3.3/arch/x86/entry/syscall_32.c 20: #define __SYSCALL_I386(nr, sym, compat) [nr] = SYM(sym, compat), 21: 22: typedef asmlinkage void (*sys_call_ptr_t)(void); 23: 24: extern asmlinkage void sys_ni_syscall(void); 25: 26: __visible const sys_call_ptr_t ia32_sys_call_table[__NR_syscall_compat_max+1] = { 27: /* 28: * Smells like a compiler bug -- it doesn't work 29: * when the & below is removed. 30: */ 31: [0 ... __NR_syscall_compat_max] = &sys_ni_syscall, 32: #include <asm/syscalls_32.h> 33: }; linux-4.3.3/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, sys_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, sys_execve, stub32_execve) 13: __SYSCALL_I386(12, sys_chdir, sys_chdir) ... 353: __SYSCALL_I386(374, sys_userfaultfd, sys_userfaultfd) 354: __SYSCALL_I386(375, sys_membarrier, sys_membarrier)
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 ); // 関数と思って呼ぶ }
linux-4.3.3/fs/read_write.c 577: SYSCALL_DEFINE3(write, unsigned int, fd, const char __user *, buf, 578: size_t, count) 579: { 580: struct fd f = fdget_pos(fd); 581: ssize_t ret = -EBADF; 582: 583: if (f.file) { 584: loff_t pos = file_pos_read(f.file); 585: ret = vfs_write(f.file, buf, count, &pos); 586: if (ret >= 0) 587: file_pos_write(f.file, pos); 588: fdput_pos(f); 589: } 590: 591: return ret; 592: }この定義は、以下のように展開される。
asmlinkage long sys_write(unsigned int fd, const char __user *buf, size_t count) { 省略 }
E
で始
まる名前(マクロ)が付けられている。
定義は、
<errno.h> (include/uapi/asm-generic/errno-base.h) 等に含まれてい
る。
sys_xxx()
は、成功すると
正の値を返す。
エラーが発生すると、カーネル内では、エラー
番号を負の値にしたものを返す。
errno
を見ると分かる。
(マルチスレッドのプログラムでは、スレッド固有データ)
x86-32 では、__syscall_error という手続きが
レジスタにあるエラー番号を グローバル変数 errno
に
セットする。
write()システム・コール(ユーザ空間)
参照。
linux-4.3.3/include/uapi/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 */ 14: #define EAGAIN 11 /* Try again */ 15: #define ENOMEM 12 /* Out of memory */ 16: #define EACCES 13 /* Permission denied */ 17: #define EFAULT 14 /* Bad address */ ...
linux-4.3.3/include/linux/syscalls.h 178: #define SYSCALL_DEFINE0(sname) \ 179: SYSCALL_METADATA(_##sname, 0); \ 180: asmlinkage long sys_##sname(void) 181: 182: #define SYSCALL_DEFINE1(name, ...) SYSCALL_DEFINEx(1, _##name, __VA_ARGS__) 183: #define SYSCALL_DEFINE2(name, ...) SYSCALL_DEFINEx(2, _##name, __VA_ARGS__) 184: #define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__) 185: #define SYSCALL_DEFINE4(name, ...) SYSCALL_DEFINEx(4, _##name, __VA_ARGS__) 186: #define SYSCALL_DEFINE5(name, ...) SYSCALL_DEFINEx(5, _##name, __VA_ARGS__) 187: #define SYSCALL_DEFINE6(name, ...) SYSCALL_DEFINEx(6, _##name, __VA_ARGS__) 188: 189: #define SYSCALL_DEFINEx(x, sname, ...) \ 190: SYSCALL_METADATA(sname, x, __VA_ARGS__) \ ... 194: #define __SYSCALL_DEFINEx(x, name, ...) \ 195: asmlinkage long sys##name(__MAP(x,__SC_DECL,__VA_ARGS__)) \ ... 101: #define __SC_DECL(t, a) t aSYSCALL_DEFINEn マクロにより、システム・コールを処理する関 数が定義される。
sys_
」 というプレフィックスが付けられる。
##
」 は、Cプリプロセッサの命令で、文字列の結合を意味する。
図? hello world プログラムでのシステム・コールの実行
$ ./hello
hello, world!
$ strace ./hello
execve("./hello", ["./hello"], [/* 41 vars */]) = 0
brk(0) = 0x1858000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fab706f4000
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=61827, ...}) = 0
mmap(NULL, 61827, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fab706e4000
close(3) = 0
open("/lib64/libc.so.6", O_RDONLY) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0p\356\301O9\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=1926760, ...}) = 0
mmap(0x394fc00000, 3750152, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x394fc00000
mprotect(0x394fd8a000, 2097152, PROT_NONE) = 0
mmap(0x394ff8a000, 20480, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x18a000) = 0x394ff8a000
mmap(0x394ff8f000, 18696, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x394ff8f000
close(3) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fab706e3000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fab706e2000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fab706e1000
arch_prctl(ARCH_SET_FS, 0x7fab706e2700) = 0
mprotect(0x394ff8a000, 16384, PROT_READ) = 0
mprotect(0x394fa1f000, 4096, PROT_READ) = 0
munmap(0x7fab706e4000, 61827) = 0
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 3), ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fab706f3000
write(1, "hello, world!\n", 14hello, world!
) = 14
exit_group(14) = ?
$
$ ./hello
hello, world!
$ ltrace ./hello
(0, 0, 716032, -1, 0x1f25bc2) = 0x394fa21160
__libc_start_main(0x4004c4, 1, 0x7fff87ef0a78, 0x4004f0, 0x4004e0
printf("hello, %s!\n", "world"hello, world!
) = 14
+++ exited (status 14) +++
$
$ gdb hello
省略
Reading symbols from /home/prof/yas/coins/syspro-2010/cc/hello...(no debugging symbols found)...done.
(gdb) b main
# ブレークポイントの設定
Breakpoint 1 at 0x4004c8
(gdb) r
# 実行
Starting program: /home/prof/yas/coins/syspro-2010/cc/hello
Breakpoint 1, 0x00000000004004c8 in main ()
(gdb) b write
# ブレークポイントの設定
Breakpoint 2 at 0x394f816ee0: file ../sysdeps/unix/syscall-template.S, line 82. (2 locations)
(gdb) c
# 実行継続
Continuing.
Breakpoint 2, write () at ../sysdeps/unix/syscall-template.S:82
82 T_PSEUDO (SYSCALL_SYMBOL, SYSCALL_NAME, SYSCALL_NARGS)
(gdb) bt
# バックトレース
#0 write () at ../sysdeps/unix/syscall-template.S:82
#1 0x000000394fc71ad3 in _IO_new_file_write (f=0x394ff8e7a0, data=, n=14) at fileops.c:1268
#2 0x000000394fc73085 in new_do_write (fp=0x394ff8e7a0, data=0x7ffff7ffc000 "hello, world!\n", to_do=14) at fileops.c:522
#3 _IO_new_do_write (fp=0x394ff8e7a0, data=0x7ffff7ffc000 "hello, world!\n", to_do=14) at fileops.c:495
#4 0x000000394fc7174d in _IO_new_file_xsputn (f=0x394ff8e7a0, data=0x4005e7, n=2) at fileops.c:1350
#5 0x000000394fc4461f in _IO_vfprintf_internal (s=, format=, ap=) at vfprintf.c:1672
#6 0x000000394fc4f18a in __printf (format=) at printf.c:35
#7 0x00000000004004dc in main ()
(gdb) c
# 実行継続
Continuing.
hello, world!
Program exited with code 016.
(gdb)
状態 | 説明 |
---|---|
新規(New) | プロセスが作られつつある。 |
実行待ち(Ready) | CPUがあれば実行できるが CPU がないので実行されていない。CPUが割り当てられるのを待っている。 |
実行中(Running) | CPUが実際に割り当てられ、実行されている。 |
待機中(Waiting、Blocked) | プロセスが、I/Oの完了やシグナルの受信といった事象(event)が 起きてるのを待っている。 |
終了(Terminated) | プロセスが実行を終えた。 |
図? プロセスの5状態
スレッドとは、1つのプロセス(のアドレス空間)の内部にふくまれている論 理的な並列処理の単位。
表示 | 説明 |
STAT | State。状態。 |
PID | Process ID。プロセス1つ1つに重複ないように(unique)割り当てた番号。 |
PPID | Parent PID。親プロセスのPID。 |
UID | User ID。プロセスを生成した利用者の識別子。 |
$ ps l
F UID PID PPID PRI NI VSZ RSS WCHAN STAT TTY TIME COMMAND
0 1013 19935 19934 20 0 106452 1808 wait Ss pts/0 0:00 -bash
0 1013 19984 19935 20 0 6258068 98480 futex_ Sl pts/0 0:01 /usr/bin/ja
0 1013 20067 19935 20 0 153232 5232 poll_s S pts/0 0:00 xterm -clas
0 1013 20072 20067 20 0 106440 1720 n_tty_ Ss+ pts/1 0:00 bash
0 1013 20157 19935 20 0 108132 980 - R+ pts/0 0:00 ps l
$
/proc/PID
; cat /proc/PID/status
1: /* 2: fork-pid.c -- fork() して画面に pid を表示するプログラム 3: ~yas/syspro/proc/fork-pid.c 4: Created on: 2010/12/13 21:19:17 5: */ 6: 7: #include <sys/types.h> /* getpid(), getppid() */ 8: #include <unistd.h> /* getpid(), getppid() */ 9: #include <stdio.h> 10: 11: main() 12: { 13: pid_t pid; 14: fork(); 15: pid = getpid(); 16: printf("pid=%d\n", pid ); 17: }
$ make fork-pid
cc fork-pid.c -o fork-pid
$ ./fork-pid
pid=1005
pid=1006
$ ./fork-pid
pid=1011
pid=1012
$
図? fork()によるプロセス生成と getpid()
1: /* 2: proc-pid-ppid.c -- 画面に pid と ppid を表示するプログラム 3: ~yas/syspro/proc/proc-pid-ppid.c 4: Created on: 2010/12/13 21:00:48 5: */ 6: 7: #include <sys/types.h> /* getpid(), getppid() */ 8: #include <unistd.h> /* getpid(), getppid() */ 9: #include <stdio.h> 10: 11: main() 12: { 13: pid_t pid, ppid; 14: pid = getpid(); 15: ppid = getppid(); 16: printf("pid=%d, ppid=%d\n", pid, ppid ); 17: }
$ make proc-pid-ppid
cc proc-pid-ppid.c -o proc-pid-ppid
$ echo $$
10771
$ ./proc-pid-ppid
pid=10873, ppid=10771
$ ./proc-pid-ppid
pid=10874, ppid=10771
$ ./proc-pid-ppid
pid=10875, ppid=10771
$
1: /* 2: fork-hello.c -- 画面に文字列を表示するプログラム 3: ~yas/syspro/proc/fork-hello.c 4: Start: 2001/05/13 23:19:01 5: */ 6: 7: #include <stdio.h> 8: 9: main() 10: { 11: fork(); 12: fork(); 13: fork(); 14: printf("hello\n"); 15: }
$ make fork-hello
cc fork-hello.c -o fork-hello
$ ./fork-hello
hello
hello
hello
hello
$ hello
hello
hello
hello
図? fork()システム・コールによるプロセスのコピー
Unixでは、全てのファイルやプロセスは、あるユーザの所有物である。 ファイルとプロセスには、UID が付加されている。
1人のユーザが複数のグループに属することができる。
$ id
uid=1013(yas) gid=510(prof) groups=510(prof),20(games),1020(c-admin),1065(c-spec),1150(tebiki) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
$
1: 2: /* 3: id-simple.c -- a simple id command 4: Created on: 2009/12/07 22:16:23 5: */ 6: 7: #include <unistd.h> /* getuid(), getgid(), getgroups() */ 8: #include <sys/types.h> /* getuid(), getgid(), getgroups() */ 9: #include <stdio.h> /* printf() */ 10: 11: #define MAXNGROUPS 100 12: 13: main( int argc, char *argv[], char *envp[] ) 14: { 15: uid_t uid ; 16: gid_t gid ; 17: gid_t groups[MAXNGROUPS]; 18: int len, i; 19: uid = getuid(); 20: gid = getgid(); 21: len = getgroups(MAXNGROUPS,&groups[0]); 22: printf("uid=%d gid=%d groups=", uid, gid ); 23: for( i=0; i<len; i++ ) 24: printf("%d,", groups[i]); 25: printf("\n"); 26: }
$ cc id-simple.c -o id-simple
$ ./id-simple
uid=1013 gid=510 groups=20,510,1020,1065,1150,
$
Linux の特殊事情
linux-4.3.3/include/linux/sched.h 1378: struct task_struct { 1379: volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */ ... 1450: int exit_state; 1451: int exit_code, exit_signal; .. 1490: struct task_struct __rcu *real_parent; /* real parent process */ 1491: struct task_struct __rcu *parent; /* recipient of SIGCHLD, wait4() reports */ ... 1495: struct list_head children; /* list of my children */ 1496: struct list_head sibling; /* linkage in my parent's children list */ 1497: struct task_struct *group_leader; /* threadgroup leader */ ... 1508: struct pid_link pids[PIDTYPE_MAX]; ... 1540: const struct cred __rcu *cred; /* effective (overridable) subjective task 1541: * credentials (COW) */ 1542: char comm[TASK_COMM_LEN]; /* executable name excluding path 1543: - access with [gs]et_task_comm (which lock 1544: it with task_lock()) 1545: - initialized normally by setup_new_exec */ ... 1823: };
STAT
の部分に現れる。
一般的に、プロセスは、
3つの状態を持つ。
Linux のプロセスの状態はもう少し多い。主に task_struct 構造体の stateと
いうフィールドでプロセスの状態を表ている。(補助的に task_struct の
exit_state も使う)。
linux-4.3.3/include/linux/sched.h 207: #define TASK_RUNNING 0 208: #define TASK_INTERRUPTIBLE 1 209: #define TASK_UNINTERRUPTIBLE 2 210: #define __TASK_STOPPED 4 211: #define __TASK_TRACED 8 212: /* in tsk->exit_state */ 213: #define EXIT_DEAD 16 214: #define EXIT_ZOMBIE 32 215: #define EXIT_TRACE (EXIT_ZOMBIE | EXIT_DEAD) 216: /* in tsk->state again */ 217: #define TASK_DEAD 64 218: #define TASK_WAKEKILL 128 219: #define TASK_WAKING 256 220: #define TASK_PARKED 512 221: #define TASK_NOLOAD 1024 222: #define TASK_STATE_MAX 2048 223: 224: #define TASK_STATE_TO_CHAR_STR "RSDTtXZxKWPN"
一般的な状態 | Linuxの状態 | ps表示 | 説明 |
---|---|---|---|
実行待ち(Ready) | TASK_RUNNING | R | 実行可能。CPU が割り当てられていれば実行中。 |
実行中(Running) | TASK_RUNNING | ||
待機中(Waiting、Blocked) | TASK_INTERRUPTIBLE | S | キーボードや他のプロセスからの入力を待っている。 |
TASK_UNINTERRUPTIBLE | D | ディスク入出力などの完了を待っている。割り込み不可。 | |
__TASK_STOPPED, __TASK_TRACED | T | 一時的に停止しているか、デバッグの対象になっている。 | |
終了(Terminated) | TASK_DEAD | Z | 既に終了していて、終了処理の完了を待ってる。 |
linux-4.3.3/include/linux/types.h 21: typedef __kernel_pid_t pid_t; linux-4.3.3/include/uapi/asm-generic/posix_types.h 27: typedef int __kernel_pid_t;
pids[]
は、プロセス識別子を保持するための配列。いくつかの種類が
あるが、getpid(),getppid(),fork()
に関連しているものは、0 番目
の pids[PIDTYPE_PID]
。
pids[PIDTYPE_PID] は、pid_link 型で、内部にstruct pid を持つ。struct pid の中には、struct upid があり、その中には getpid() 等で用いる pid を 保持するフィールド nr がある。
linux-4.3.3/include/linux/pid.h 69: struct pid_link 70: { ... 72: struct pid *pid; 73: }; 57: struct pid 58: { ... 64: struct upid numbers[1]; 65: }; 50: struct upid { ... 52: int nr; ... 55: }; 6: enum pid_type 7: { 8: PIDTYPE_PID, ... 12: };
図? プロセスの木構造
図? uid, gid, groups の保持方法
linux-4.3.3/include/linux/cred.h 29: #define NGROUPS_SMALL 32 32: struct group_info { ... 34: int ngroups; ... 36: kgid_t small_block[NGROUPS_SMALL]; ... 38: }; 118: struct cred { ... 127: kuid_t uid; /* real UID of the task */ 128: kgid_t gid; /* real GID of the task */ ... 154: struct group_info *group_info; /* supplementary groups for euid/fsgid */ ... 156: }; linux-4.3.3/include/linux/uidgid.h 20: typedef struct { 21: uid_t val; 22: } kuid_t; 25: typedef struct { 26: gid_t val; 27: } kgid_t; linux-4.3.3/include/linux/types.h 31: typedef __kernel_uid32_t uid_t; 32: typedef __kernel_gid32_t gid_t; linux-4.3.3/include/uapi/asm-generic/posix_types.h 48: typedef unsigned int __kernel_uid32_t; 49: typedef unsigned int __kernel_gid32_t;kuid_t は、uid をカーネル内で保持するための型。32 ビットのuid_t (unsigned int) を保持するための構造体として定義されている。 (システム・コールの結果を返す所で、名前空間によるマッピングが行われることがある。)
直感的には、次のような大域変数があると思ってよい(実際には、CPUごとに異なる値を持つ)。
struct task_struct *current;
図? current変数によるtask_structの参照
linux-4.3.3/kernel/sys.c 830: SYSCALL_DEFINE0(getpid) 831: { 832: return task_tgid_vnr(current); 833: } linux-4.3.3/include/linux/sched.h 1936: static inline pid_t task_tgid_vnr(struct task_struct *tsk) 1937: { 1938: return pid_vnr(task_tgid(tsk)); 1939: } 1874: static inline struct pid *task_tgid(struct task_struct *task) 1875: { 1876: return task->group_leader->pids[PIDTYPE_PID].pid; 1877: }
linux-4.3.3/kernel/pid.c 514: pid_t pid_vnr(struct pid *pid) 515: { 516: return pid_nr_ns(pid, 省略); 517: } 500: pid_t pid_nr_ns(struct pid *pid, 省略) 501: { 502: struct upid *upid; 503: pid_t nr = 0; ... 506: upid = &pid->numbers[省略]; ... 508: nr = upid->nr; 510: return nr; 511: }
linux-4.3.3/kernel/sys.c 847: SYSCALL_DEFINE0(getppid) 848: { 849: int pid; ... 852: pid = task_tgid_vnr(rcu_dereference(current->real_parent)); ... 855: return pid; 856: }currentのreal_parentを引数にして task_tgid_vnr() という関数を呼ぶ。 以降、getpid() と同じ。
rcu_dereference() の dereference は、ポインタが参照している先を取り出す ことを意味する。rcu は、read-copy update の省略形。マルチプロセッサで、 ポインタが変更される可能性がある時でも、ロックなしで読み込むことができ る。意味を考える時には、最初は rcu_dereference() を無視してよい。
make gtags
で GNU global 用の索引が作られる。
$ pwd
/home/prof/yas/os2/linux-4.3.3
$ ls -l G*
-rw-r--r--. 1 yas prof 5611520 12月 28 11:40 2015 GPATH
-rw-r--r--. 1 yas prof 307437568 12月 28 11:40 2015 GRTAGS
-rw-r--r--. 1 yas prof 104161280 12月 28 11:40 2015 GTAGS
$ global task_struct
include/linux/sched.h
$ global -x task_struct
task_struct 1378 include/linux/sched.h struct task_struct {
$ global -rx task_struct | wc
3230 26939 315621
$ global -rx task_struct | head -3
task_struct 118 arch/x86/entry/vsyscall/vsyscall_64.c struct task_struct *tsk;
task_struct 8 arch/x86/include/asm/current.h struct task_struct;
task_struct 10 arch/x86/include/asm/current.h DECLARE_PER_CPU(struct task_struct *, current_task);
$ global getpid
$ global -s getpid
Documentation/accounting/getdelays.c
Documentation/connector/ucon.c
Documentation/timers/hpet_example.c
kernel/sys.c
scripts/kconfig/confdata.c
$ global -sx getpid
getpid 175 Documentation/accounting/getdelays.c rc = send_cmd(sd, GENL_ID_CTRL, getpid(), CTRL_CMD_GETFAMILY,
getpid 382 Documentation/accounting/getdelays.c mypid = getpid();
getpid 72 Documentation/connector/ucon.c nlh->nlmsg_pid = getpid();
getpid 250 Documentation/timers/hpet_example.c if ((fcntl(fd, F_SETOWN, getpid()) == 1) ||
getpid 830 kernel/sys.c SYSCALL_DEFINE0(getpid)
getpid 773 scripts/kconfig/confdata.c sprintf(tmpname, "%s.tmpconfig.%d", dirname, (int)getpid());
$
2016年1月14日(木) 5-6時限
情報科学類 オペレーティングシステム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. ...このシステム・コールを処理する関数がカーネルの中でどのように定義されて いるか、その概略(引数と結果の宣言)を示しなさい。関数の内容は空でよい。 マクロを利用しても利用しなくてもどちらでもよい。
引数と結果の宣言 { /*内容*/ }
1: #include <stdio.h> 2: #include <unistd.h> 3: 4: main() { 5: pid_t pid, ppid; 6: fork(); 7: pid = getpid(); 8: ppid = getppid(); 9: printf("hello: (pid=%d,ppid=%d)\n",pid, ppid); 10: }以下の空欄(空欄A、空欄B、空欄C、空欄D)を埋めて、起こり得る結果を 1つ作りなさい。
$ echo $$
1001
$ ./fork-printf
hello: (pid=空欄A,ppid=空欄B)
hello: (pid=空欄C,ppid=空欄D)
$
ただし、PID としては、1001,1002,1003,1004 の中から選びなさい。
なお、答えは1通りではない。
上のプログラムをコンパイルして実行した結果を参考にして、回答してもよい。 ただし、PID としては、実行結果のものをそのまま使うのではなく、指定され たものを使いなさい。
なお、実際の getuid() システム・コールの実装は、名前空間の導入により複 雑になっており、今日の授業の範囲を超えている。この課題では、実際のコー ドではなく、この授業の範囲内で答えなさい。(実際のコードをそのまま回答 しても、得点を与えない。)