2020年01月10日
情報科学類 オペレーティングシステム II
                                       筑波大学 システム情報系 
                                       新城 靖
                                       <yas@cs.tsukuba.ac.jp>
このページは、次の URL にあります。
	http://www.coins.tsukuba.ac.jp/~yas/coins/os2-2019/2020-01-10
あるいは、次のページから手繰っていくこともできます。
	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-5.4.7/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 等が広く使われた。
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 - 
~yas/os2/linux-5.4.7 に展開したものがある。
| ディレクトリ | 説明 | 
|---|---|
| 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-5.4.7/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 ... 425: #define __NR_fsmount 432 426: #define __NR_fspick 433 427: #define __NR_pidfd_open 434 428: #define __NR_clone3 435
linux-5.4.7/arch/x86/include/asm/syscall.h
  23:	typedef asmlinkage long (*sys_call_ptr_t)(unsigned long, unsigned long,
  24:	                                          unsigned long, unsigned long,
  25:	                                          unsigned long, unsigned long);
...
  27:	extern const sys_call_ptr_t sys_call_table[];
...
  30:	#define ia32_sys_call_table sys_call_table
  31:	#define __NR_syscall_compat_max __NR_syscall_max
  32:	#define IA32_NR_syscalls NR_syscalls
linux-5.4.7/arch/x86/entry/syscall_32.c
  25:	#define __SYSCALL_I386(nr, sym, qual) [nr] = sym,
  26:	
  27:	__visible const sys_call_ptr_t ia32_sys_call_table[__NR_syscall_compat_max+1] = {
  28:	        /*
  29:	         * Smells like a compiler bug -- it doesn't work
  30:	         * when the & below is removed.
  31:	         */
  32:	        [0 ... __NR_syscall_compat_max] = &sys_ni_syscall,
  33:	#include <asm/syscalls_32.h>
  34:	};
linux-5.4.7/arch/x86/include/generated/asm/syscalls_32.h
   1:	#ifdef CONFIG_X86_32
   2:	__SYSCALL_I386(0, sys_restart_syscall, )
   3:	#else
   4:	__SYSCALL_I386(0, __ia32_sys_restart_syscall, )
   5:	#endif
..
   7:	__SYSCALL_I386(1, sys_exit, )
  12:	__SYSCALL_I386(2, sys_fork, )
  17:	__SYSCALL_I386(3, sys_read, )
  22:	__SYSCALL_I386(4, sys_write, )
  27:	__SYSCALL_I386(5, sys_open, )
  32:	__SYSCALL_I386(6, sys_close, )
  37:	__SYSCALL_I386(7, sys_waitpid, )
  42:	__SYSCALL_I386(8, sys_creat, )
...
2022:	__SYSCALL_I386(433, sys_fspick, )
2027:	__SYSCALL_I386(434, sys_pidfd_open, )
2032:	__SYSCALL_I386(435, sys_clone3, )
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-5.4.7/fs/read_write.c
 620:	SYSCALL_DEFINE3(write, unsigned int, fd, const char __user *, buf,
 621:	                size_t, count)
 622:	{
 623:	        return ksys_write(fd, buf, count);
 624:	}
 600:	ssize_t ksys_write(unsigned int fd, const char __user *buf, size_t count)
 601:	{
 602:	        struct fd f = fdget_pos(fd);
 603:	        ssize_t ret = -EBADF;
 604:	
 605:	        if (f.file) {
 606:	                loff_t pos, *ppos = file_ppos(f.file);
 607:	                if (ppos) {
 608:	                        pos = *ppos;
 609:	                        ppos = &pos;
 610:	                }
 611:	                ret = vfs_write(f.file, buf, count, ppos);
 612:	                if (ret >= 0 && ppos)
 613:	                        f.file->f_pos = pos;
 614:	                fdput_pos(f);
 615:	        }
 616:	
 617:	        return ret;
 618:	}
この定義は、以下のように展開される。
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-5.4.7/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-5.4.7/include/linux/syscalls.h
 207:	#define SYSCALL_DEFINE0(sname)                                  \
 208:	        SYSCALL_METADATA(_##sname, 0);                          \
 209:	        asmlinkage long sys_##sname(void);                      \
 210:	        ALLOW_ERROR_INJECTION(sys_##sname, ERRNO);              \
 211:	        asmlinkage long sys_##sname(void)
...
 214:	#define SYSCALL_DEFINE1(name, ...) SYSCALL_DEFINEx(1, _##name, __VA_ARGS__)
 215:	#define SYSCALL_DEFINE2(name, ...) SYSCALL_DEFINEx(2, _##name, __VA_ARGS__)
 216:	#define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__)
 217:	#define SYSCALL_DEFINE4(name, ...) SYSCALL_DEFINEx(4, _##name, __VA_ARGS__)
 218:	#define SYSCALL_DEFINE5(name, ...) SYSCALL_DEFINEx(5, _##name, __VA_ARGS__)
 219:	#define SYSCALL_DEFINE6(name, ...) SYSCALL_DEFINEx(6, _##name, __VA_ARGS__)
...
 223:	#define SYSCALL_DEFINEx(x, sname, ...)                          \
 224:	        SYSCALL_METADATA(sname, x, __VA_ARGS__)                 \
 225:	        __SYSCALL_DEFINEx(x, sname, __VA_ARGS__)
 235:	#define __SYSCALL_DEFINEx(x, name, ...)                                 \
...
 239:	        asmlinkage long sys##name(__MAP(x,__SC_DECL,__VA_ARGS__))       \
 240:	                __attribute__((alias(__stringify(__se_sys##name))));    \
...
 244:	        asmlinkage long __se_sys##name(__MAP(x,__SC_LONG,__VA_ARGS__))  \
 245:	        {                                                               \
 246:	                long ret = __do_sys##name(__MAP(x,__SC_CAST,__VA_ARGS__));\
...
 250:	        }                                                               \
...
 252:	        static inline long __do_sys##name(__MAP(x,__SC_DECL,__VA_ARGS__))
SYSCALL_DEFINEn マクロにより、システム・コールを処理する関
数が定義される。
sys_」 というプレフィックスが付けられる。
##」 は、Cプリプロセッサの命令で、文字列の結合を意味する。
__se_sys##name() と
__do_sys_##name() を定義し、
前者から後者を呼び出している。
sys##name()は__se_sys##name()の別名(alias)。
)
図? hello world プログラムでのシステム・コールの実行
$ ./hello 
Hello, world!
$ strace ./hello 
execve("./hello", ["./hello"], [/* 31 vars */]) = 0
strace: [ Process PID=30009 runs in 32 bit mode. ]
brk(NULL)                               = 0x999c000
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xf77cf000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=93696, ...}) = 0
mmap2(NULL, 93696, PROT_READ, MAP_PRIVATE, 3, 0) = 0xf77b8000
close(3)                                = 0
open("/lib/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\1\1\1\3\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\0\244\1\0004\0\0\0"..., 512) = 512
fstat64(3, {st_mode=S_IFREG|0755, st_size=2107464, ...}) = 0
mmap2(NULL, 1878556, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xf75ed000
mprotect(0xf77b1000, 4096, PROT_NONE)   = 0
mmap2(0xf77b2000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1c4000) = 0xf77b2000
mmap2(0xf77b5000, 10780, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xf77b5000
close(3)                                = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xf75ec000
set_thread_area({entry_number:-1, base_addr:0xf75ec700, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0 (entry_number:12)
mprotect(0xf77b2000, 8192, PROT_READ)   = 0
mprotect(0x8049000, 4096, PROT_READ)    = 0
stat("/etc/sysconfig/64bit_strstr_via_64bit_strstr_sse2_unaligned", 0xffb114ac) = -1 ENOENT (No such file or directory)
mprotect(0xf77f3000, 4096, PROT_READ)   = 0
munmap(0xf77b8000, 93696)               = 0
fstat64(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 2), ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xf77ce000
write(1, "Hello, world!\n", 14Hello, world!
)         = 14
exit_group(14)                          = ?
+++ exited with 14 +++
$ 
$ ./hello 
hello, world!
$ ltrace ./hello 
__libc_start_main(0x804840d, 1, 0xffaeae34, 0x8048430 
puts("Hello, 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 write
			# ブレークポイントの設定
Function "write" not defined.
Make breakpoint pending on future shared library load? (y or [n]) y
				# まだ読み込まれていない動的リンクライブラリにブレークポイントを設定する
Breakpoint 1 (write) pending.
(gdb) r				# 実行
Starting program: /home/prof/yas/coins/syspro-2010/cc/hello
Breakpoint 1, 0xf7ee18f0 in write () from /lib/libc.so.6
Missing separate debuginfos, use: debuginfo-install glibc-2.17-292.el7.i686
(gdb) bt
			# バックトレース
#0  0xf7ee18f0 in write () from /lib/libc.so.6
#1  0xf7e68971 in _IO_new_file_write () from /lib/libc.so.6
#2  0xf7e6a45a in __GI__IO_do_write () from /lib/libc.so.6
#3  0xf7e6a900 in __GI__IO_file_overflow () from /lib/libc.so.6
#4  0xf7e6b22c in __overflow () from /lib/libc.so.6
#5  0xf7e5ebcf in puts () from /lib/libc.so.6
#6  0x08048422 in main ()
(gdb) c
				# 実行継続
Continuing.
Hello, world!
[Inferior 1 (process 30217) exited with code 016]
(gdb) q
				# 終了
$ 
| 状態 | 説明 | 
|---|---|
| 新規(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) 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-5.4.7/include/linux/sched.h
 624:	struct task_struct {
...
 633:	        volatile long                   state;
...
 736:	        int                             exit_state;
 737:	        int                             exit_code;
...
 788:	        pid_t                           pid;
 789:	        pid_t                           tgid;
...
 802:	        struct task_struct __rcu        *real_parent;
...
 805:	        struct task_struct __rcu        *parent;
...
 810:	        struct list_head                children;
 811:	        struct list_head                sibling;
 812:	        struct task_struct              *group_leader;
...
 824:	        struct pid                      *thread_pid;
 825:	        struct hlist_node               pid_links[PIDTYPE_MAX];
...
 872:	        const struct cred __rcu         *ptracer_cred;
...
 892:	        char                            comm[TASK_COMM_LEN];
...
1286:	};
STAT の部分に現れる。
一般的に、プロセスは、
3つの状態を持つ。
Linux のプロセスの状態はもう少し多い。主に task_struct 構造体の stateと
いうフィールドでプロセスの状態を表ている。(補助的に task_struct の
exit_state も使う)。
linux-5.4.7/include/linux/sched.h 76: #define TASK_RUNNING 0x0000 77: #define TASK_INTERRUPTIBLE 0x0001 78: #define TASK_UNINTERRUPTIBLE 0x0002 79: #define __TASK_STOPPED 0x0004 80: #define __TASK_TRACED 0x0008 81: /* Used in tsk->exit_state: */ 82: #define EXIT_DEAD 0x0010 83: #define EXIT_ZOMBIE 0x0020 84: #define EXIT_TRACE (EXIT_ZOMBIE | EXIT_DEAD) 85: /* Used in tsk->state again: */ 86: #define TASK_PARKED 0x0040 87: #define TASK_DEAD 0x0080 88: #define TASK_WAKEKILL 0x0100 89: #define TASK_WAKING 0x0200 90: #define TASK_NOLOAD 0x0400 91: #define TASK_NEW 0x0800 92: #define TASK_STATE_MAX 0x1000
| 一般的な状態 | 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-5.4.7/include/linux/types.h 22: typedef __kernel_pid_t pid_t; linux-5.4.7/include/uapi/asm-generic/posix_types.h 28: typedef int __kernel_pid_t;
linux-5.4.7/include/linux/pid.h
  59:	struct pid
  60:	{
...
  68:	        struct upid numbers[1];
  69:	};
  54:	struct upid {
  55:	        int nr;
...
  57:	};
linux-5.4.7/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-5.4.7/include/linux/cred.h
 111:	struct cred {
...
 120:	        kuid_t          uid;            /* real UID of the task */
 121:	        kgid_t          gid;            /* real GID of the task */
...
 146:	        struct user_namespace *user_ns; /* user_ns the caps and keyrings are relative to. */
 147:	        struct group_info *group_info;  /* supplementary groups for euid/fsgid */
...
 153:	} __randomize_layout;
  25:	struct group_info {
  26:	        atomic_t        usage;
  27:	        int             ngroups;
  28:	        kgid_t          gid[0];
  29:	} __randomize_layout;
linux-5.4.7/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-5.4.7/include/linux/types.h
  32:	typedef __kernel_uid32_t        uid_t;
  33:	typedef __kernel_gid32_t        gid_t;
linux-5.4.7/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-5.4.7/kernel/sys.c
 919:	SYSCALL_DEFINE0(getuid)
 920:	{
 921:	        /* Only we change this so SMP safe */
 922:	        return from_kuid_munged(current_user_ns(), current_uid());
 923:	}
linux-5.4.7/include/linux/cred.h
 379:	#define current_uid()           (current_cred_xxx(uid))
 380:	#define current_gid()           (current_cred_xxx(gid))
 374:	#define current_cred_xxx(xxx)                   \
 375:	({                                              \
 376:	        current_cred()->xxx;                    \
 377:	})
 297:	#define current_cred() \
 298:	        rcu_dereference_protected(current->cred, 1)
 390:	extern struct user_namespace init_user_ns;
 391:	#ifdef CONFIG_USER_NS
 392:	#define current_user_ns()       (current_cred_xxx(user_ns))
 393:	#else
 394:	static inline struct user_namespace *current_user_ns(void)
 395:	{
 396:	        return &init_user_ns;
 397:	}
 398:	#endif
linux-5.4.7/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 */
linux-5.4.7/include/linux/uidgid.h
  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-5.4.7/kernel/fork.c
2447:	SYSCALL_DEFINE0(fork)
2448:	{
...
2454:	        return _do_fork(&args);
...
2459:	}
2329:	long _do_fork(省略)
2330:	{
...
2334:	        struct task_struct *p;
...
2336:	        long nr;
...
2356:	        p = copy_process(NULL, trace, NUMA_NO_NODE, args);
...
2368:	        pid = get_task_pid(p, PIDTYPE_PID);
2369:	        nr = pid_vnr(pid);
...
2392:	        return nr;
2393:	}
...
1760:	static __latent_entropy struct task_struct *copy_process(省略)
1765:	{
1766:	        int pidfd = -1, retval;
1767:	        struct task_struct *p;
...
1847:	        p = dup_task_struct(current, node);
...
1880:	        retval = copy_creds(p, clone_flags);
...
1896:	        INIT_LIST_HEAD(&p->children);
1897:	        INIT_LIST_HEAD(&p->sibling);
...
2025:	                pid = alloc_pid(p->nsproxy->pid_ns_for_children);
...
2081:	        p->pid = pid_nr(pid);
...
2091:	                p->group_leader = p;
2092:	                p->tgid = p->pid;
...
2136:	                p->real_parent = current;
...
2191:	                        list_add_tail(&p->sibling, &p->real_parent->children);
...
2224:	        return p;
...
2288:	}
 856:	static struct task_struct *dup_task_struct(struct task_struct *orig, int node)
 857:	{
 858:	        struct task_struct *tsk;
...
 865:	        tsk = alloc_task_struct_node(node);
...
 878:	        err = arch_dup_task_struct(tsk, orig);
...
 947:	        return tsk;
...
 954:	}
 841:	int __weak arch_dup_task_struct(struct task_struct *dst,
 842:	                                               struct task_struct *src)
 843:	{
 844:	        *dst = *src;
 845:	        return 0;
 846:	}
make gtags で GNU global 用の索引が作られる。
$ pwd 
/home/prof/yas/os2/linux-5.4.7
$ ls -l G* 
-rw-r--r-- 1 yas prof   7782400  1月  3 16:52 GPATH
-rw-r--r-- 1 yas prof 415358976  1月  3 16:52 GRTAGS
-rw-r--r-- 1 yas prof 335749120  1月  3 16:52 GTAGS
$ global task_struct 
fs/proc/internal.h
include/linux/sched.h
$ global -rx task_struct | head -5 
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)
task_struct       288 arch/x86/include/asm/elf.h struct task_struct;
$ global -rx task_struct | wc 
   3745   31360  369332
$ global getpid 
$ global -s getpid 
drivers/i3c/master.c
kernel/sys.c
samples/bpf/task_fd_query_user.c
samples/bpf/test_current_task_under_cgroup_user.c
samples/connector/ucon.c
samples/pidfd/pidfd-metadata.c
samples/timers/hpet_example.c
scripts/kconfig/confdata.c
$ global -sx getpid | grep SYSCALL 
getpid            891 kernel/sys.c     SYSCALL_DEFINE0(getpid)
$ 
(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 | 元に戻る | 
情報科学類 オペレーティングシステムII 課題<n> 学籍番号 ________ 名前________ 提出日________
ACCESS(2)                  Linux Programmer's Manual                 ACCESS(2)
NAME
       access - check real user's permissions for a file
SYNOPSIS
       #include <unistd.h>
       int access(const char *pathname, int mode);
DESCRIPTION
       access()  checks  whether the calling process can access the file path-
       name.  If pathname is a symbolic link, it is dereferenced.
...
RETURN VALUE
       On  success  (all requested permissions granted), zero is returned.  On
       error (at least one bit in mode asked for a permission that is  denied,
       or  some other error occurred), -1 is returned, and errno is set appro-
       priately.
...
このシステム・コールを処理する関数がカーネルの中でどのように定義されて
いるか、その概略(引数と結果の宣言)を示しなさい。関数の内容は空でよい。
マクロを利用しても利用しなくてもどちらでもよい。
引数と結果の宣言
{
   /*内容*/
}
なお、実際の getgid() システム・コールの実装は、名前空間の導入により複 雑になっており、今日の授業の範囲を超えている。この課題では、実際のコー ドではなく、この授業の範囲内で答えなさい。(実際のコードをそのまま回答 しても、得点を与えない。)