2023年01月06日 情報科学類 オペレーティングシステム II 筑波大学 システム情報系 新城 靖 <yas@cs.tsukuba.ac.jp>
このページは、次の URL にあります。
http://www.coins.tsukuba.ac.jp/~yas/coins/os2-2022/2023-01-06
あるいは、次のページから手繰っていくこともできます。
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言語のライブラリ関数は、大部分はC言語で書かれている。 printf() のソース・プログラムがある。
C言語で記述したプログラムは、文法さえあっていれば コンパイルはできる。 ライブラリ関数やシステムコールがないと動作しない。
Microsoft Windows では、システムコールとライブラリの区別が希薄。 Win32 API。3000以上。 Unix のシステムコールは、300程度。
動的リンクを行う時には、共有ライブラリ(shared library) を使い、メモリの節約をする。
#include <stdio.h> int main() { printf("hello, %s!\n","world"); }
$ ls
hello.c
$ make hello
cc hello.c -o hello
$ ls
hello hello.c
$ ./hello
hello, world!
$ cc -S hello.c
$ ls
hello hello.c hello.s
$
gcc -S hello.c
」のように、「-S
」オプションを
つけると、通常は削除されるアセンブリ言語の出力が残される。
-m32
」もつける。
.file "hello.c" .text .section .rodata .LC0: .string "world" .LC1: .string "hello, %s!\n" .text .globl main .type main, @function main: .LFB0: .cfi_startproc pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp .cfi_def_cfa_register 6 leaq .LC0(%rip), %rsi leaq .LC1(%rip), %rdi movl $0, %eax call printf@PLT movl $0, %eax popq %rbp .cfi_def_cfa 7, 8 ret .cfi_endproc .LFE0: .size main, .-main .ident "GCC: (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0" .section .note.GNU-stack,"",@progbitsx86 (32ビット)
.file "hello.c" .section .rodata .LC0: .string "world" .LC1: .string "Hello, %s!\n" .text .globl main .type main, @function main: .LFB0: .cfi_startproc pushl %ebp .cfi_def_cfa_offset 8 .cfi_offset 5, -8 movl %esp, %ebp .cfi_def_cfa_register 5 andl $-16, %esp subl $16, %esp movl $.LC0, 4(%esp) movl $.LC1, (%esp) call printf leave .cfi_restore 5 .cfi_def_cfa 4, 4 ret .cfi_endproc .LFE0: .size main, .-main .ident "GCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-44)" .section .note.GNU-stack,"",@progbits
$ rpm -qa |grep glibc-debuginfo
glibc-debuginfo-2.17-325.el7_9.i686
glibc-debuginfo-common-2.17-325.el7_9.x86_64
glibc-debuginfo-2.17-325.el7_9.x86_64
$ rpm -ql glibc-debuginfo-2.17-325.el7_9.x86_64| grep '/printf\.c'
/usr/src/debug/glibc-2.17-c758a686/stdio-common/printf.c
$ cat -n /usr/src/debug/glibc-2.17-c758a686/stdio-common/printf.c | sed 1,26d
27 int
28 __printf (const char *format, ...)
29 {
30 va_list arg;
31 int done;
32
33 va_start (arg, format);
34 done = vfprintf (stdout, format, arg);
35 va_end (arg);
36
37 return done;
38 }
39
40 #undef _IO_printf
41 ldbl_strong_alias (__printf, printf);
42 /* This is for libg++. */
43 ldbl_strong_alias (__printf, _IO_printf);
$
~yas/os2/glibc-2.12/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(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: # 引数が edi, esi, edx レジスタにセットされている ... mov $0x1,%eax # システム・コールの番号を eax レジスタへ syscall # カーネル空間へ制御を移動 cmp $0xfffffffffffff001,%rax # エラーが起きたら jae __syscall_error # __syscall_errorへジャンプ (errno等の保存) retq # 呼び出した関数へリターン
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 # 呼び出した関数へリターン
$ gdb arch/x86/entry/vdso/vdso32/system_call.o
...
(gdb) x/12i __kernel_vsyscall
0x0 <__kernel_vsyscall>: push %ecx
0x1 <__kernel_vsyscall+1>: push %edx
0x2 <__kernel_vsyscall+2>: push %ebp
0x3 <__kernel_vsyscall+3>: nop
0x4 <__kernel_vsyscall+4>: nop
0x5 <__kernel_vsyscall+5>: nop
0x6 <__kernel_vsyscall+6>: nop
0x7 <__kernel_vsyscall+7>: int $0x80
0x9 <__kernel_vsyscall+9>: pop %ebp
0xa <__kernel_vsyscall+10>: pop %edx
0xb <__kernel_vsyscall+11>: pop %ecx
0xc <__kernel_vsyscall+12>: ret
(gdb) quit
$
linux-6.1.2/arch/x86/entry/vdso/vdso32/system_call.S 42: pushl %ecx 45: pushl %edx 48: pushl %ebp ... 52: #define SYSENTER_SEQUENCE "movl %esp, %ebp; sysenter" ... 60: ALTERNATIVE "", SYSENTER_SEQUENCE, X86_FEATURE_SEP ... 64: int $0x80 72: popl %ebp 75: popl %edx 78: popl %ecx 81: ret
https://postd.cc/the-definitive-guide-to-linux-system-calls/, Linuxシステムコール徹底ガイド, PackageCloud, 2016
https://blog.packagecloud.io/eng/2016/04/05/the-definitive-guide-to-linux-system-calls/, The Definitive Guide to Linux System Calls, PackageCloud, 2016.
2.6 以前には、2.4, 2.2, 2.0 等が広く使われた。
https://www.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 -
~yas/os2/linux-6.1.2 に展開したものがある。
ディレクトリ | 説明 |
---|---|
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 | Linux Security Module |
sound | サウンド・サブシステム |
tools | カーネル開発用のツール |
usr | 起動直後にユーザ空間で実行されるプログラム(initramfs) |
virt | 仮想化インフラ |
(自動生成されたもの)
linux-6.1.2/arch/x86/include/generated/uapi/asm/unistd_64.h 1: #ifndef _UAPI_ASM_UNISTD_64_H 2: #define _UAPI_ASM_UNISTD_64_H 3: 4: #define __NR_read 0 5: #define __NR_write 1 6: #define __NR_open 2 7: #define __NR_close 3 8: #define __NR_stat 4 9: #define __NR_fstat 5 10: #define __NR_lstat 6 11: #define __NR_poll 7 12: #define __NR_lseek 8 13: #define __NR_mmap 9 14: #define __NR_mprotect 10 15: #define __NR_munmap 11 16: #define __NR_brk 12 ... 363: #define __NR_process_mrelease 448 364: #define __NR_futex_waitv 449 365: #define __NR_set_mempolicy_home_node 450 ... 368: #define __NR_syscalls 451
linux-6.1.2/arch/x86/include/generated/uapi/asm/unistd_32.h 1: #ifndef _UAPI_ASM_UNISTD_32_H 2: #define _UAPI_ASM_UNISTD_32_H 3: 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 ... 437: #define __NR_landlock_create_ruleset 444 438: #define __NR_landlock_add_rule 445 439: #define __NR_landlock_restrict_self 446 440: #define __NR_memfd_secret 447 441: #define __NR_process_mrelease 448 442: #define __NR_futex_waitv 449 443: #define __NR_set_mempolicy_home_node 450 ... 446: #define __NR_syscalls 451
linux-6.1.2/arch/x86/include/asm/syscall.h 19: typedef long (*sys_call_ptr_t)(const struct pt_regs *); 20: extern const sys_call_ptr_t sys_call_table[]; linux-6.1.2/arch/x86/entry/syscall_64.c 14: #define __SYSCALL(nr, sym) __x64_##sym, 15: 16: asmlinkage const sys_call_ptr_t sys_call_table[] = { 17: #include <asm/syscalls_64.h> 18: }; linux-6.1.2/arch/x86/um/sys_call_table_64.c ... 29: const sys_call_ptr_t sys_call_table[] ____cacheline_aligned = { 30: #include <asm/syscalls_64.h> 31: }; linux-6.1.2/arch/x86/include/generated/asm/syscalls_64.h 1: __SYSCALL(0, sys_read) 2: __SYSCALL(1, sys_write) 3: __SYSCALL(2, sys_open) 4: __SYSCALL(3, sys_close) 5: __SYSCALL(4, sys_newstat) 6: __SYSCALL(5, sys_newfstat) 7: __SYSCALL(6, sys_newlstat) 8: __SYSCALL(7, sys_poll) 9: __SYSCALL(8, sys_lseek) 10: __SYSCALL(9, sys_mmap) 11: __SYSCALL(10, sys_mprotect) 12: __SYSCALL(11, sys_munmap) 13: __SYSCALL(12, sys_brk) ... 447: __SYSCALL(446, sys_landlock_restrict_self) 448: __SYSCALL(447, sys_memfd_secret) 449: __SYSCALL(448, sys_process_mrelease) 450: __SYSCALL(449, sys_futex_waitv) 451: __SYSCALL(450, sys_set_mempolicy_home_node)
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-6.1.2/arch/x86/include/asm/syscall.h 19: typedef long (*sys_call_ptr_t)(const struct pt_regs *); 20: extern const sys_call_ptr_t sys_call_table[]; ... 23: #define ia32_sys_call_table sys_call_table linux-6.1.2/arch/x86/entry/syscall_32.c 16: #define __SYSCALL(nr, sym) extern long __ia32_##sym(const struct pt_regs *); 17: 18: #include <asm/syscalls_32.h> 19: #undef __SYSCALL 20: 21: #define __SYSCALL(nr, sym) __ia32_##sym, 22: 23: __visible const sys_call_ptr_t ia32_sys_call_table[] = { 24: #include <asm/syscalls_32.h> 25: }; linux-6.1.2/arch/x86/include/generated/asm/syscalls_32.h 1: __SYSCALL(0, sys_restart_syscall) 2: __SYSCALL(1, sys_exit) 3: __SYSCALL(2, sys_fork) 4: __SYSCALL(3, sys_read) 5: __SYSCALL(4, sys_write) 6: __SYSCALL_WITH_COMPAT(5, sys_open, compat_sys_open) 7: __SYSCALL(6, sys_close) 8: __SYSCALL(7, sys_waitpid) 9: __SYSCALL(8, sys_creat) 10: __SYSCALL(9, sys_link) 11: __SYSCALL(10, sys_unlink) 12: __SYSCALL_WITH_COMPAT(11, sys_execve, compat_sys_execve) 13: __SYSCALL(12, sys_chdir) ... 447: __SYSCALL(446, sys_landlock_restrict_self) 448: __SYSCALL(447, sys_memfd_secret) 449: __SYSCALL(448, sys_process_mrelease) 450: __SYSCALL(449, sys_futex_waitv) 451: __SYSCALL(450, sys_set_mempolicy_home_node)
linux-6.1.2/fs/read_write.c 626: ssize_t ksys_write(unsigned int fd, const char __user *buf, size_t count) 627: { 628: struct fd f = fdget_pos(fd); 629: ssize_t ret = -EBADF; 630: 631: if (f.file) { 632: loff_t pos, *ppos = file_ppos(f.file); 633: if (ppos) { 634: pos = *ppos; 635: ppos = &pos; 636: } 637: ret = vfs_write(f.file, buf, count, ppos); 638: if (ret >= 0 && ppos) 639: f.file->f_pos = pos; 640: fdput_pos(f); 641: } 642: 643: return ret; 644: } 645: 646: SYSCALL_DEFINE3(write, unsigned int, fd, const char __user *, buf, 647: size_t, count) 648: { 649: return ksys_write(fd, buf, count); 650: }この定義は、以下のように展開される(__x64_sys_write, __ia32_sys_write)。
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-64 では、__syscall_error という手続きが
レジスタにあるエラー番号を グローバル変数 errno
に
セットする。
write()システム・コール(ユーザ空間)
参照。
linux-6.1.2/include/uapi/asm-generic/errno-base.h 5: #define EPERM 1 /* Operation not permitted */ 6: #define ENOENT 2 /* No such file or directory */ 7: #define ESRCH 3 /* No such process */ 8: #define EINTR 4 /* Interrupted system call */ 9: #define EIO 5 /* I/O error */ 10: #define ENXIO 6 /* No such device or address */ 11: #define E2BIG 7 /* Argument list too long */ 12: #define ENOEXEC 8 /* Exec format error */ 13: #define EBADF 9 /* Bad file number */ 14: #define ECHILD 10 /* No child processes */ 15: #define EAGAIN 11 /* Try again */ 16: #define ENOMEM 12 /* Out of memory */ 17: #define EACCES 13 /* Permission denied */ 18: #define EFAULT 14 /* Bad address */ ...
linux-6.1.2/include/linux/syscalls.h 210: #define SYSCALL_DEFINE0(sname) \ ... 214: asmlinkage long sys_##sname(void) 217: #define SYSCALL_DEFINE1(name, ...) SYSCALL_DEFINEx(1, _##name, __VA_ARGS__) 218: #define SYSCALL_DEFINE2(name, ...) SYSCALL_DEFINEx(2, _##name, __VA_ARGS__) 219: #define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__) 220: #define SYSCALL_DEFINE4(name, ...) SYSCALL_DEFINEx(4, _##name, __VA_ARGS__) 221: #define SYSCALL_DEFINE5(name, ...) SYSCALL_DEFINEx(5, _##name, __VA_ARGS__) 222: #define SYSCALL_DEFINE6(name, ...) SYSCALL_DEFINEx(6, _##name, __VA_ARGS__) 226: #define SYSCALL_DEFINEx(x, sname, ...) \ ... 228: __SYSCALL_DEFINEx(x, sname, __VA_ARGS__) 238: #define __SYSCALL_DEFINEx(x, name, ...) \ ... 247: asmlinkage long __se_sys##name(__MAP(x,__SC_LONG,__VA_ARGS__)) \ 248: { \ 249: long ret = __do_sys##name(__MAP(x,__SC_CAST,__VA_ARGS__));\ ... 252: return ret; \ 253: } \ ... 255: static inline long __do_sys##name(__MAP(x,__SC_DECL,__VA_ARGS__))SYSCALL_DEFINEn マクロにより、システム・コールを処理する関 数が定義される。
sys_
」 というプレフィックスが付けられる。
##
」 は、Cプリプロセッサの命令で、文字列の結合を意味する。
__se_sys##name()
と
__do_sys_##name()
を定義し、
前者から後者を呼び出している。
インライン展開されて、実際のコードには残らない。
se は sign-extends 32-bit ints to longs の意味。
図? hello world プログラムでのシステム・コールの実行
$ ./hello
hello, world!
$ strace ./hello
execve("./hello", ["./hello"], 0x7ffe3d1a5250 /* 29 vars */) = 0
brk(NULL) = 0x556e21190000
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=99328, ...}) = 0
mmap(NULL, 99328, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f526501d000
close(3) = 0
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\240\35\2\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=2030928, ...}) = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f526501b000
mmap(NULL, 4131552, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f5264a1c000
mprotect(0x7f5264c03000, 2097152, PROT_NONE) = 0
mmap(0x7f5264e03000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1e7000) = 0x7f5264e03000
mmap(0x7f5264e09000, 15072, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f5264e09000
close(3) = 0
arch_prctl(ARCH_SET_FS, 0x7f526501c4c0) = 0
mprotect(0x7f5264e03000, 16384, PROT_READ) = 0
mprotect(0x556e20a50000, 4096, PROT_READ) = 0
mprotect(0x7f5265036000, 4096, PROT_READ) = 0
munmap(0x7f526501d000, 99328) = 0
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0
brk(NULL) = 0x556e21190000
brk(0x556e211b1000) = 0x556e211b1000
write(1, "hello, world!\n", 14hello, world!
) = 14
exit_group(0) = ?
+++ exited with 0 +++
$
$ ltrace ./hello
printf("hello, %s!\n", "world"hello, world!
) = 14
+++ exited (status 0) +++
$
$ gdb hello
<省略>
(gdb) b write
# ブレークポイントの設定
Function "write" not defined.
Make breakpoint pending on future shared library load? (y or [n]) y
Breakpoint 1 (write) pending.
(gdb) r
# 実行(run)
Starting program: /home/prof/yas/.../hello
Breakpoint 1, __GI___libc_write (fd=1, buf=0x555555756260, nbytes=14)
at ../sysdeps/unix/sysv/linux/write.c:27
27 ../sysdeps/unix/sysv/linux/write.c: No such file or directory.
(gdb) bt
# バックトレース
#0 __GI___libc_write (fd=1, buf=0x555555756260, nbytes=14)
at ../sysdeps/unix/sysv/linux/write.c:27
#1 0x00007ffff7a6d15d in _IO_new_file_write (
f=0x7ffff7dce760 <_IO_2_1_stdout_>, data=0x555555756260, n=14)
at fileops.c:1203
#2 0x00007ffff7a6ef01 in new_do_write (to_do=14,
data=0x555555756260 "hello, world!\n", fp=0x7ffff7dce760 <_IO_2_1_stdout_>)
at fileops.c:457
#3 _IO_new_do_write (fp=0x7ffff7dce760 <_IO_2_1_stdout_>,
data=0x555555756260 "hello, world!\n", to_do=14) at fileops.c:433
#4 0x00007ffff7a6d98d in _IO_new_file_xsputn (
f=0x7ffff7dce760 <_IO_2_1_stdout_>, data=, n=2)
at fileops.c:1266
#5 0x00007ffff7a3d97a in _IO_vfprintf_internal (
s=0x7ffff7dce760 <_IO_2_1_stdout_>, format=0x5555555546fa "hello, %s!\n",
ap=ap@entry=0x7fffffffe670) at vfprintf.c:1674
#6 0x00007ffff7a46ee6 in __printf (format=) at printf.c:33
#7 0x0000555555554666 in main ()
(gdb) c
# 実行継続(continue)
Continuing.
hello, world!
[Inferior 1 (process 13764) exited normally]
(gdb) q
# 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
/proc
の下に、カーネル内のデータを取り出すための疑似的なファイ
ルが存在する。特に、/proc/PID
の下には、プロセス識別子
がPID
のプロセスの情報が現れる。詳しくは、man procを見
なさい。
$ echo $$
23069
$ ls /proc/$$
attr cpuset fd maps numa_maps schedstat status
auxv cwd io mem oom_adj smaps task
cmdline environ limits mounts oom_score stat wchan
coredump_filter exe loginuid mountstats root statm
$ head -11 /proc/$$/status
Name: bash
State: S (sleeping)
SleepAVG: 98%
Tgid: 23069
Pid: 23069
PPid: 23068
TracerPid: 0
Uid: 1013 1013 1013 1013
Gid: 510 510 510 510
FDSize: 256
Groups: 20 510 1020 1065 1150
$
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),1180(c-gakusei)
$
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,1180,
$
Linux の特殊事情
linux-6.1.2/include/linux/sched.h 737: struct task_struct { ... 745: unsigned int __state; ... 876: int exit_state; 877: int exit_code; ... 967: pid_t pid; 968: pid_t tgid; ... 981: struct task_struct __rcu *real_parent; ... 984: struct task_struct __rcu *parent; ... 989: struct list_head children; 990: struct list_head sibling; 991: struct task_struct *group_leader; ... 1003: struct pid *thread_pid; 1004: struct hlist_node pid_links[PIDTYPE_MAX]; ... 1064: const struct cred __rcu *cred; ... 1078: char comm[TASK_COMM_LEN]; ... 1546: };
STAT
の部分に現れる。
一般的に、プロセスは、
3つの状態を持つ。
Linux のプロセスの状態はもう少し多い。主に task_struct 構造体の stateと
いうフィールドでプロセスの状態を表ている。(補助的に task_struct の
exit_state も使う)。
linux-6.1.2/include/linux/sched.h 84: /* Used in tsk->state: */ 85: #define TASK_RUNNING 0x00000000 86: #define TASK_INTERRUPTIBLE 0x00000001 87: #define TASK_UNINTERRUPTIBLE 0x00000002 88: #define __TASK_STOPPED 0x00000004 89: #define __TASK_TRACED 0x00000008 90: /* Used in tsk->exit_state: */ 91: #define EXIT_DEAD 0x00000010 92: #define EXIT_ZOMBIE 0x00000020 93: #define EXIT_TRACE (EXIT_ZOMBIE | EXIT_DEAD) 94: /* Used in tsk->state again: */ 95: #define TASK_PARKED 0x00000040 96: #define TASK_DEAD 0x00000080 97: #define TASK_WAKEKILL 0x00000100 98: #define TASK_WAKING 0x00000200 99: #define TASK_NOLOAD 0x00000400 100: #define TASK_NEW 0x00000800 101: #define TASK_RTLOCK_WAIT 0x00001000 102: #define TASK_FREEZABLE 0x00002000 103: #define __TASK_FREEZABLE_UNSAFE (0x00004000 * IS_ENABLED(CONFIG_LOCKDEP)) 104: #define TASK_FROZEN 0x00008000 105: #define TASK_STATE_MAX 0x00010000
一般的な状態 | 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 では、/proc/sys/kernel/pid_max で設定できる。 64 ビットのシステムでは、最大、2^22 になっている(man 5 proc参照)。 FreeBSD や macOS のように、10 進数で表記した時に 5桁以内になるように、 99999 や 99998 になっているシステムもある。
32,768を超えたら、1に戻って使われていない番号を探すという操作は単純に実装すると重たい。 増える一方で済むなら、その方が速い。 PID の桁数が増えたら動作しなくなるプログラムもあると思われる。
linux-6.1.2/include/linux/types.h 22: typedef __kernel_pid_t pid_t; linux-6.1.2/include/uapi/asm-generic/posix_types.h 28: typedef int __kernel_pid_t;
linux-6.1.2/include/linux/pid.h 59: struct pid 60: { ... 70: struct upid numbers[1]; 71: }; 54: struct upid { 55: int nr; ... 57: };
linux-6.1.2/include/linux/pid.h 9: enum pid_type 10: { 11: PIDTYPE_PID, 12: PIDTYPE_TGID, 13: PIDTYPE_PGID, 14: PIDTYPE_SID, 15: PIDTYPE_MAX, 16: };シングルスレッドなら、PIDTYPE_PID 型の PID と PIDTYPE_TGID 型の PID は同じ番号になる。 カーネルレベルのスレッドを生成すると、その PIDTYPE_PID 型の PID は変化するが、PIDTYPE_TGID 型の PID は変化しない。 getpid() で使われるのは、PIDTYPE_TGID 型の PID。 group_leader は、この意味のスレッド・グループの先頭のスレッドを指す。
PGID は、Process Group ID。これは、killpg() システム・コール等で、プロ
セスのグループを使ったシグナルの送信に使われる。この機能は、シェルのジョ
ブコントロールの実装に使われる。たとえば、^Z でパイプでつながったプロセ
ス全部を一時的に止める時に使う。
PGID は、ps j
コマンドで表示される。
SID は、Session ID。端末を開くと新しいセッションが始まる。
SID は、ps j
コマンドで表示される。
図? プロセスの木構造
図? uid, gid, groups の保持方法
linux-6.1.2/include/linux/cred.h 110: struct cred { .. 119: kuid_t uid; /* real UID of the task */ 120: kgid_t gid; /* real GID of the task */ ... 147: struct group_info *group_info; /* supplementary groups for euid/fsgid */ ... 153: } __randomize_layout; 25: struct group_info { ... 27: int ngroups; 28: kgid_t gid[]; 29: } __randomize_layout; linux-6.1.2/include/linux/uidgid.h 21: typedef struct { 22: uid_t val; 23: } kuid_t; ... 26: typedef struct { 27: gid_t val; 28: } kgid_t; linux-6.1.2/include/linux/types.h 32: typedef __kernel_uid32_t uid_t; 33: typedef __kernel_gid32_t gid_t; linux-6.1.2/include/uapi/asm-generic/posix_types.h 49: typedef unsigned int __kernel_uid32_t; 50: typedef unsigned int __kernel_gid32_t;kuid_t は、uid をカーネル内で保持するための型。32 ビットのuid_t (unsigned int) を保持するための構造体として定義されている。 (システム・コールの結果を返す所で、名前空間によるマッピングが行われることがある。)
直感的には、次のような大域変数があると思ってよい(実際には、CPUごとに異なる値を持つ)。
struct task_struct *current;
図? current変数によるtask_structの参照
linux-6.1.2/kernel/sys.c 968: SYSCALL_DEFINE0(getuid) 969: { 970: /* Only we change this so SMP safe */ 971: return from_kuid_munged(current_user_ns(), current_uid()); 972: } linux-6.1.2/include/linux/cred.h 381: #define current_uid() (current_cred_xxx(uid)) 382: #define current_gid() (current_cred_xxx(gid)) 376: #define current_cred_xxx(xxx) \ 377: ({ \ 378: current_cred()->xxx; \ 379: }) 298: #define current_cred() \ 299: rcu_dereference_protected(current->cred, 1) 393: extern struct user_namespace init_user_ns; 394: #ifdef CONFIG_USER_NS 395: #define current_user_ns() (current_cred_xxx(user_ns)) 396: #else 397: static inline struct user_namespace *current_user_ns(void) 398: { 399: return &init_user_ns; 400: } 401: #endif linux-6.1.2/include/linux/uidgid.h 163: static inline uid_t from_kuid_munged(struct user_namespace *to, kuid_t kuid) 164: { 165: uid_t uid = from_kuid(to, kuid); 166: if (uid == (uid_t)-1) 167: uid = overflowuid; 168: return uid; 169: } 121: #ifdef CONFIG_USER_NS ... 141: #else ... 153: static inline uid_t from_kuid(struct user_namespace *to, kuid_t kuid) 154: { 155: return __kuid_val(kuid); 156: } ... 189: #endif /* CONFIG_USER_NS */ ... 34: static inline uid_t __kuid_val(kuid_t uid) 35: { 36: return uid.val; 37: }
SYSCALL_DEFINE0(getuid) { kuid_t kuid; uid_t uid; kuid = current->cred->uid; uid = kuid.val; return uid; }
linux-6.1.2/kernel/fork.c 2756: SYSCALL_DEFINE0(fork) 2757: { ... 2763: return kernel_clone(&args); ... 2768: } 2635: pid_t kernel_clone(省略) 2636: { ... 2639: struct pid *pid; 2640: struct task_struct *p; ... 2676: p = copy_process(省略); ... 2688: pid = get_task_pid(p, PIDTYPE_PID); 2689: nr = pid_vnr(pid); ... 2719: return nr; 2720: } 1991: static __latent_entropy struct task_struct *copy_process(省略) 1996: { 1997: int pidfd = -1, retval; 1998: struct task_struct *p; ... 2087: p = dup_task_struct(current, node); ... 2140: INIT_LIST_HEAD(&p->children); 2141: INIT_LIST_HEAD(&p->sibling); ... 2272: pid = alloc_pid(p->nsproxy->pid_ns_for_children, args->set_tid, 2273: args->set_tid_size); ... 2329: p->pid = pid_nr(pid); ... 2334: p->group_leader = p; 2335: p->tgid = p->pid; ... 2401: p->real_parent = current; ... 2459: list_add_tail(&p->sibling, &p->real_parent->children); ... 2498: return p; ... 2562: } 962: static struct task_struct *dup_task_struct(struct task_struct *orig, int node) 963: { 964: struct task_struct *tsk; ... 969: tsk = alloc_task_struct_node(node); ... 973: err = arch_dup_task_struct(tsk, orig); ... 1053: return tsk; ... 1061: } 947: int __weak arch_dup_task_struct(struct task_struct *dst, 948: struct task_struct *src) 949: { 950: *dst = *src; 951: return 0; 952: }
make gtags
で GNU global 用の索引が作られる。
$ pwd
/home/prof/yas/os2/linux-6.1.2
$ ls -l G*
-rw-r--r-- 1 yas prof 10461184 1月 2 17:41 GPATH
-rw-r--r-- 1 yas prof 497926144 1月 2 17:41 GRTAGS
-rw-r--r-- 1 yas prof 783851520 1月 2 17:41 GTAGS
$ global task_struct
fs/proc/internal.h
include/linux/sched.h
tools/testing/selftests/bpf/progs/strobemeta.h
tools/testing/selftests/bpf/progs/test_core_reloc_kernel.c
tools/testing/selftests/bpf/progs/test_core_retro.c
$ global -rx task_struct | head -5
task_struct 126 arch/x86/entry/vdso/vma.c int vdso_join_timens(struct task_struct *task, struct time_namespace *ns)
task_struct 123 arch/x86/entry/vsyscall/vsyscall_64.c struct task_struct *tsk;
task_struct 9 arch/x86/include/asm/current.h struct task_struct;
task_struct 11 arch/x86/include/asm/current.h DECLARE_PER_CPU(struct task_struct *, current_task);
task_struct 13 arch/x86/include/asm/current.h static __always_inline struct task_struct *get_current(void)
$ global -rx task_struct | wc
4320 36333 429616
$ global getuid
$ global -s getuid
kernel/sys.c
tools/power/acpi/tools/pfrut/pfrut.c
...
tools/thermal/tmon/tmon.c
$ global -sx getuid | grep SYSCALL
getuid 968 kernel/sys.c SYSCALL_DEFINE0(getuid)
$
(autoload 'gtags-mode "/usr/local3/coins/linux/share/gtags/gtags.el" "" t) (setq gtags-suggested-key-mapping t) (add-hook 'c-mode-common-hook (lambda () (gtags-mode 1)) )gtags-mode では、次のようなキーが使えるようになる。
キー | 関数 | 説明 |
---|---|---|
M-. | gtags-find-tag | 定義に移動する (global -x) |
M-* | gtags-pop-stack | 元に戻る |
C-c r | gtags-find-rtag | 呼びだし元を探す (global -rx) |
C-c s | gtags-find-symbol | シンボルを探す (global -sx) |
ボタン | 関数 | 説明 |
---|---|---|
<mouse-2> | gtags-find-tag-by-event | 定義に移動する |
<mouse-3> | gtags-pop-stack | 元に戻る |
2023年1月11日(水) 5-6時限
情報科学類 オペレーティングシステムII 課題<n> 学籍番号 ________ 名前________ 提出日________
図? 授業内容、課題、世の中で求められていること、自分の興味の関係
MKDIR(2) Linux Programmer's Manual MKDIR(2) NAME mkdir, mkdirat - create a directory SYNOPSIS #include <sys/stat.h> #include <sys/types.h> int mkdir(const char *pathname, mode_t mode); ... DESCRIPTION mkdir() attempts to create a directory named pathname. The argument mode specifies the mode for the new directory (see inode(7)). It is modified by the process's umask in the usual way: in the absence of a default ACL, the mode of the created directory is (mode & ~umask & 0777). Whether other mode bits are honored for the created directory depends on the operating system. For Linux, see NOTES below. ... RETURN VALUE mkdir() and mkdirat() return zero on success, or -1 if an error occurred (in which case, errno is set appropriately). ...このシステム・コールを処理する関数がカーネルの中でどのように定義されて いるか、その概略(引数と結果の宣言)を示しなさい。関数の内容は空で解答しなさい。 マクロを利用しても利用しなくてもどちらでもよい。
引数と結果の宣言 { /*内容*/ }
なお、実際の getgid() システム・コールの実装は、名前空間の導入により複 雑になっており、今日の授業の範囲を超えている。この課題では、実際のコー ドではなく、この授業の範囲内で答えなさい。(実際のコードをそのまま回答 しても、得点を与えない。)