システムコール、プロセスとtask_struct構造体

					2018年01月11日
情報科学類 オペレーティングシステム II

                                       筑波大学 システム情報系 
                                       新城 靖
                                       <yas@cs.tsukuba.ac.jp>

このページは、次の URL にあります。
http://www.coins.tsukuba.ac.jp/~yas/coins/os2-2017/2018-01-11
あるいは、次のページから手繰っていくこともできます。
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

■オペレーティングシステムII

◆この授業の狙い

◆この授業を受ける意義

将来カーネルをいじる人もいじらない人にも意義がある。

◆メタレベルのプログラム

インタプリタ言語を考える。例:Ruby。
$ 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 言語で記述されている。

rubyのCのソース(メタ)、コンパイル&リンク、実行形式、プロセス、hello.rbソース(プログラム)
図? メタレベルのプログラミング

プログラム「hello.rb」の動きを変更する方法
プログラミング
hello.rb を書き換える
メタレベルのプログラミング
ruby インタプリタのプログラムを書き換える
「hello.rb」は、ruby インタプリタから見ると、入力「データ」の1つ。

◆メタレベルのプログラムとしてのOS

$ 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」の動きを変更する方法
プログラミング
hello.c を書き換える
メタレベルのプログラミング
OSカーネルのプログラムを書き換える
「hello(実行形式)」は、OSカーネルから見ると、入力「データ」に相当する。

OSカーネル自身は、普通は、ハードウェアによるインタプリタ(CPU、入出力)で 実行される。インタフェースは、機械語命令や入出力機器への操作。

OSカーネルへのインタフェースは、システム・コール。 == OSカーネルは、システム・コールのインタプリタ。

◆シラバス

◆Linux

教材としては、Linux を使う。 2018年1月現在、学類では次のコンピュータで Linux が使える。 サーバには、ssh でログインして使う。

■システム・コールとライブラリ

システム・コールとライブラリは、プログラマにとってのオペレーティング・ システム。共通点は、両方とも、C言語からは、関数呼出しの形で使えること。 2つを合わせて、API (Application Programming Interface) とも言う。

◆OSの構造

図? 3層、複数コンポーネント
図? OSの構造

プログラム・コードの場所

◆システム・コールのライブラリの違い

システム・コール(system call) ライブラリ(library)

システムコールの例:

ライブラリの例:

システムコールとライブラリの見分け方(Unix編)

同じUnix系OSでも、細かい所でシステム・コールとライブラリが入れ替わって いることもある。

システム・コールは、トラップ命令(trap instruction)を含む。 その部分は、アセンブリ言語。

ライブラリ関数は、大部分はC言語で書かれている。 printf() のソース・プログラムがある。

C言語で記述したプログラムは、文法さえあっていれば コンパイルはできる。 ライブラリ関数やシステムコールがないと動作しない。

◆リンク

ライブラリやシステム・コールは、コンパイルされて オブジェクト・コードの形で保存されている。 実行形式を作る時に取り出され、 アプリケーション・プログラマから作成した オブジェクト・コード(main()関数など)と結合(リンク)される。

静的リンク(static link)
プログラム実行する前(実行形式を作成する時点)で、ライブラリ関数の呼出し をすべて機械語の手続き呼び出し命令に変換する。データへの参照を固定番地 に変換する。
動的リンク(dynamic link)
プログラム実行中に必要に応じてリンクを行う。

動的リンクを行う時には、共有ライブラリ(shared library) を使い、メモリの節約をする。

Microsoft Windows では、システムコールとライブラリの区別が希薄。 Win32 API。3000以上。 Unix のシステムコールは、300程度。

◆hello.cとgcc -S

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
$ []
	.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

◆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()システム・コールのマニュアル

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()システム・コール(ユーザ空間)

x86 アーキテクチャ(32ビット)の write.o 逆アセンブル結果より、主要部分抜 粋。 
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.14.12/arch/x86/entry/vdso/vdso32/system_call.S

  15:	__kernel_vsyscall:
...
  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
参考

■Linuxカーネルのソース

◆バージョン

3.0 (2011年20周年) 以降。3つの数からなる。 2.6 系列は、4つの数からなる。例 「メジャー・バージョン」. 「マイナー・バージョン」. 「リビジョン」. 「パッチ番号」

2.6 以前には、2.4, 2.2, 2.0 等が広く使われた。

◆kernel.org

Linux のカーネルのソース・コードは、 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-4.14.12 に展開したものがある。

◆カーネルのディレクトリ構成

カーネル・ソースのトップレベルのディレクトリ
ディレクトリ 説明
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 仮想化インフラ

■システム・コール

◆システムコールの番号

システム・コールの種類は、番号(整数)で区別される。 番号は、アーキテクチャ(CPU)に依存している。

(自動生成されたもの)

linux-4.14.12/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
...
 382:	#define __NR_pkey_free 382
 383:	#define __NR_statx 383
 384:	#define __NR_arch_prctl 384
 385:	

◆システム・コールの表 sys_call_table

linux-4.14.12/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);
  26:	extern const sys_call_ptr_t sys_call_table[];
...
  29:	#define ia32_sys_call_table sys_call_table
  30:	#define __NR_syscall_compat_max __NR_syscall_max
...

linux-4.14.12/arch/x86/entry/syscall_32.c
  14:	#define __SYSCALL_I386(nr, sym, qual) [nr] = sym,
...
  18:	__visible const sys_call_ptr_t ia32_sys_call_table[__NR_syscall_compat_max+1] = {
  23:	        [0 ... __NR_syscall_compat_max] = &sys_ni_syscall,
  24:	#include <asm/syscalls_32.h>
  25:	};

linux-4.14.12/arch/x86/include/generated/asm/syscalls_32.h
   1:	__SYSCALL_I386(0, sys_restart_syscall, )
   2:	__SYSCALL_I386(1, sys_exit, )
   4:	__SYSCALL_I386(2, sys_fork, )
   8:	__SYSCALL_I386(3, sys_read, )
   9:	__SYSCALL_I386(4, sys_write, )
  11:	__SYSCALL_I386(5, sys_open, )
  15:	__SYSCALL_I386(6, sys_close, )
  17:	__SYSCALL_I386(7, sys_waitpid, )
  21:	__SYSCALL_I386(8, sys_creat, )
  22:	__SYSCALL_I386(9, sys_link, )
  23:	__SYSCALL_I386(10, sys_unlink, )
  25:	__SYSCALL_I386(11, sys_execve, )
  29:	__SYSCALL_I386(12, sys_chdir, )
...
 889:	__SYSCALL_I386(382, sys_pkey_free, )
 890:	__SYSCALL_I386(383, sys_statx, )
 892:	__SYSCALL_I386(384, sys_arch_prctl, )

◆sys_write()

linux-4.14.12/fs/read_write.c

 581:	SYSCALL_DEFINE3(write, unsigned int, fd, const char __user *, buf,
 582:	                size_t, count)
 583:	{
 584:	        struct fd f = fdget_pos(fd);
 585:	        ssize_t ret = -EBADF;
 586:	
 587:	        if (f.file) {
 588:	                loff_t pos = file_pos_read(f.file);
 589:	                ret = vfs_write(f.file, buf, count, &pos);
 590:	                if (ret >= 0)
 591:	                        file_pos_write(f.file, pos);
 592:	                fdput_pos(f);
 593:	        }
 594:	
 595:	        return ret;
 596:	}
この定義は、以下のように展開される。
asmlinkage long sys_write(unsigned int fd, const char __user *buf,
	   	size_t count)
{
	省略
}

◆エラー番号の例

linux-4.14.12/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 */
...

◆SYSCALL_DEFINEマクロ

linux-4.14.12/include/linux/syscalls.h

 192:	#define SYSCALL_DEFINE0(sname)                                  \
 193:	        SYSCALL_METADATA(_##sname, 0);                          \
 194:	        asmlinkage long sys_##sname(void)
 195:	
 196:	#define SYSCALL_DEFINE1(name, ...) SYSCALL_DEFINEx(1, _##name, __VA_ARGS__)
 197:	#define SYSCALL_DEFINE2(name, ...) SYSCALL_DEFINEx(2, _##name, __VA_ARGS__)
 198:	#define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__)
 199:	#define SYSCALL_DEFINE4(name, ...) SYSCALL_DEFINEx(4, _##name, __VA_ARGS__)
 200:	#define SYSCALL_DEFINE5(name, ...) SYSCALL_DEFINEx(5, _##name, __VA_ARGS__)
 201:	#define SYSCALL_DEFINE6(name, ...) SYSCALL_DEFINEx(6, _##name, __VA_ARGS__)

 205:	#define SYSCALL_DEFINEx(x, sname, ...)                          \
 206:	        SYSCALL_METADATA(sname, x, __VA_ARGS__)                 \
 207:	        __SYSCALL_DEFINEx(x, sname, __VA_ARGS__)

 210:	#define __SYSCALL_DEFINEx(x, name, ...)                                 \
 211:	        asmlinkage long sys##name(__MAP(x,__SC_DECL,__VA_ARGS__))       \
...
 102:	#define __SC_DECL(t, a) t a
SYSCALL_DEFINEn マクロにより、システム・コールを処理する関 数が定義される。

◆システム・コールの実行のまとめ

main()、printf()、・・・、write()、systnter トラップ、・・・、sys_write()
図? hello world プログラムでのシステム・コールの実行

■システム・コールやライブラリを観察するためのコマンド

◆strace コマンド

システム・コールの動きを調べるには、strace コマンド(Linux) が便利。
$ ./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)                          = ?
$ []

◆ltrace コマンド

ltrace コマンド(Linux) を使うと、ライブラリの呼び出しを調べることができ る。
$ ./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 デバッガ

$ 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) []

◆その他のプロセスを調べるコマンド

■プロセスとは

「オペレーティングシステムI」の復習

◆プロセスの3つの状態

基本は3つ。新規と終了を入れたら5つ。
プロセスの基本的な状態
状態 説明
新規(New) プロセスが作られつつある。
実行待ち(Ready) CPUがあれば実行できるが CPU がないので実行されていない。CPUが割り当てられるのを待っている。
実行中(Running) CPUが実際に割り当てられ、実行されている。
待機中(Waiting、Blocked) プロセスが、I/Oの完了やシグナルの受信といった事象(event)が 起きてるのを待っている。
終了(Terminated) プロセスが実行を終えた。

New、Ready、Running、Waiting、Terminated
図? プロセスの5状態

◆プロセスとスレッド

スレッドとは、1つのプロセス(のアドレス空間)の内部にふくまれている論 理的な並列処理の単位。

シングルスレッドのプログラム
1度に1つの手続き(Cの関数)しか動かない。
マルチスレッドのプログラム
1度にスレッドの数だけの手続きが論理的には同時に動く。 (同期でブロックされているものも含む)
スレッドはプロセスの機能のうち、保護の機能を抜いて高速化したもの。歴史 的には、SMP (Symmetric multiprocessor) での効率的な並列処理のために導入 された。同一プロセスに属するスレッドは、アドレス空間やメモリを共有する。 生成、消滅、スレッド間通信が高速に実現される。

■利用者から見たプロセスの見え方

次の3つのレベルで見える

◆Unixにおけるプロセスに関するシステム・コール(一部)

◆ps lコマンド

ps l の結果のうち、次の属性に注目。IDは、identifierの意味。
表示 説明
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
$ []

◆その他のコマンド

◆fork() と pid を調べるプログラム

   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()してprintf()するプログラム
図? 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
$ []

◆fork()によるプロセスの木

   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() を 3 回するプログラム
図? fork()システム・コールによるプロセスのコピー

◆ユーザ

ユーザ(user, 利用者)とは、Unixの外では、個人(人間)。 Unixの内部では、次のどれかで表現する。
ユーザ名(user name)
文字列
UID(user ID, user identifier)
16ビット-32ビットの整数

Unixでは、全てのファイルやプロセスは、あるユーザの所有物である。 ファイルとプロセスには、UID が付加されている。

◆グループ

グループ(group)とは、Unixの外の世界では、計算機を使う人間の集合。 Unixの内部では、次のどれかで表現する。
グループ名(group name)
文字列
GID(group ID, group identifier)
16ビット-32ビットの整数

1人のユーザが複数のグループに属することができる。

◆プロセスの属性

プロセスは、UID (1個) と GID を複数個持つ。

◆idコマンド

idコマンドを使うと、そのプロセス(の親プロセスであるシェルから継承した) UIDとGIDが調べられる。
$ 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
$ []

◆id-simple.c

id コマンドは、内部的にはgetuid(), getgid(), getgroups() システム・コー ルを使っている。以下は、id コマンドの簡易版である。
   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,
$ []

◆UID、GIDとアクセス制御

1年生の「コンピュータリテラシ」の試料も、参考になる。

■Linux task構造体

利用者レベルの視点では、プロセスは、メモリに読み込まれてCPUが割り当てら れれば実行可能な「プログラム」である。メタレベルでは、「データ」になる。 Linux のプロセスは、task_struct 構造体というデータ構造で表現されている。

Linux の特殊事情

◆task_struct構造体

linux-4.14.12/include/linux/sched.h

 519:	struct task_struct {
...
 528:	        volatile long                   state;
...
 615:	        int                             exit_state;
 616:	        int                             exit_code;
...
 673:	        /* Real parent process: */
 674:	        struct task_struct __rcu        *real_parent;
 675:	
 676:	        /* Recipient of SIGCHLD, wait4() reports: */
 677:	        struct task_struct __rcu        *parent;
...
 679:	        /*
 680:	         * Children/sibling form the list of natural children:
 681:	         */
 682:	        struct list_head                children;
 683:	        struct list_head                sibling;
 684:	        struct task_struct              *group_leader;
...
 695:	        /* PID/PID hash table linkage. */
 696:	        struct pid_link                 pids[PIDTYPE_MAX];
...
 750:	        /* Effective (overridable) subjective task credentials (COW): */
 751:	        const struct cred __rcu         *cred;
...
 753:	        /*
 754:	         * executable name, excluding path.
 755:	         *
 756:	         * - normally initialized setup_new_exec()
 757:	         * - access it with [gs]et_task_comm()
 758:	         * - lock it with task_lock()
 759:	         */
 760:	        char                            comm[TASK_COMM_LEN];
...
1116:	};

◆state

プロセスの状態。psコマンドで STAT の部分に現れる。 一般的に、プロセスは、 3つの状態を持つ。 Linux のプロセスの状態はもう少し多い。主に task_struct 構造体の stateと いうフィールドでプロセスの状態を表ている。(補助的に task_struct の exit_state も使う)。
linux-4.14.12/include/linux/sched.h

  68:	/* Used in tsk->state: */
  69:	#define TASK_RUNNING                    0x0000
  70:	#define TASK_INTERRUPTIBLE              0x0001
  71:	#define TASK_UNINTERRUPTIBLE            0x0002
  72:	#define __TASK_STOPPED                  0x0004
  73:	#define __TASK_TRACED                   0x0008
  74:	/* Used in tsk->exit_state: */
  75:	#define EXIT_DEAD                       0x0010
  76:	#define EXIT_ZOMBIE                     0x0020
  77:	#define EXIT_TRACE                      (EXIT_ZOMBIE | EXIT_DEAD)
  78:	/* Used in tsk->state again: */
  79:	#define TASK_PARKED                     0x0040
  80:	#define TASK_DEAD                       0x0080
  81:	#define TASK_WAKEKILL                   0x0100
  82:	#define TASK_WAKING                     0x0200
  83:	#define TASK_NOLOAD                     0x0400
  84:	#define TASK_NEW                        0x0800
  85:	#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既に終了していて、終了処理の完了を待ってる。

◆pid_t型

pid_t 型は、システム・コールのレベルで(APIとして) プロセス識別子を表す 型。int型を typedef で名前を付けている。1から32,768までで明いている部分 を再利用する。(32,768を超えたら、1に戻って開いている番号を探して使う。)
linux-4.14.12/include/linux/types.h
  22:	typedef __kernel_pid_t          pid_t;

linux-4.14.12/include/uapi/asm-generic/posix_types.h
  28:	typedef int             __kernel_pid_t;

◆struct pid_link、struct pidとstruct upid

APIとしてのプロセス識別子を、カーネルの中で表現するための構造体。 pids[] は、プロセス識別子を保持するための配列。いくつかの種類が あるが、getpid(),getppid(),fork() に関連しているものは、0 番目 の pids[PIDTYPE_PID]

pids[PIDTYPE_PID] は、pid_link 型で、内部にstruct pid を持つ。struct pid の中には、struct upid があり、その中には getpid() 等で用いる pid を 保持するフィールド nr がある。

linux-4.14.12/include/linux/pid.h
  72:	struct pid_link
  73:	{
...
  75:	        struct pid *pid;
  76:	};

  60:	struct pid
  61:	{
...
  67:	        struct upid numbers[1];
  68:	};

  53:	struct upid {
...
  55:	        int nr;
...
  58:	};

   7:	enum pid_type
   8:	{
   9:	        PIDTYPE_PID,
...
  15:	};

◆char comm[TASK_COMM_LEN]

command名。最大16文字。

◆プロセスの木構造

real_parent, children, sibling というフィールドでプロセスの木構造が作られる。

task_struct、parent、child、sibling
図? プロセスの木構造

◆struct task_struct *real_parent

親プロセス(fork() システム・コールでコピー元のプロセス。)のtask_struct構造体へのポインタ。 親が先に死んだ場合には、initプロセス(PID==1の最初に作られるプロセス)に養子に出される。

◆struct task_struct *parent

ptrace で、トレースしているプロセス(デバッガ等)。

◆struct list_head children

子プロセスのリストの先頭。

◆struct list_head sibling

兄弟プロセス(つまり、同じ親プロセスの子プロセス)のリスト。

◆struct cred *cred

credentials。資格。ファイルをアクセスする時等にプロセスがどのような権限 を持っているかを表す。

task_struct構造体、cred構造体、group_info構造体
図? uid, gid, groups の保持方法

◆struct cred

task_struct に保存すべき内容のうち、uid、gid、groups 等を抜き出したもの。
linux-4.14.12/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 */
...
 147:	        struct group_info *group_info;  /* supplementary groups for euid/fsgid */
...
 149:	} __randomize_layout;

  30:	struct group_info {
  31:	        atomic_t        usage;
  32:	        int             ngroups;
  33:	        kgid_t          gid[0];
  34:	} __randomize_layout;

linux-4.14.12/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-4.14.12/include/linux/types.h
  32:	typedef __kernel_uid32_t        uid_t;
  33:	typedef __kernel_gid32_t        gid_t;

linux-4.14.12/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) を保持するための構造体として定義されている。 (システム・コールの結果を返す所で、名前空間によるマッピングが行われることがある。)

◆uidとgid

uidは、ユーザを識別する番号、gidは、グループを識別する番号を保持する。 ファイルを開く時等のアクセス権のチェックに使われる。

◆group_info

追加のgid (getgroups()) を保持する。

■current

変数 current は、カーネル中で現在実行中のプロセスの task_struct 構造体 を保持する。さまざまなシステム・コールで、current を参照 する。

直感的には、次のような大域変数があると思ってよい(実際には、CPUごとに異なる値を持つ)。

struct task_struct *current; 

current→task_struct
図? current変数によるtask_structの参照

◆getpid()システム・コール

linux-4.14.12/kernel/sys.c
 838:	SYSCALL_DEFINE0(getpid)
 839:	{
 840:	        return task_tgid_vnr(current);
 841:	}

linux-4.14.12/include/linux/sched.h
1218:	static inline pid_t task_tgid_vnr(struct task_struct *tsk)
1219:	{
1220:	        return __task_pid_nr_ns(tsk, __PIDTYPE_TGID, NULL);
1221:	}

linux-4.14.12/kernel/pid.c
 521:	pid_t __task_pid_nr_ns(struct task_struct *task, enum pid_type type,
 522:	                        struct pid_namespace *ns)
 523:	{
 524:	        pid_t nr = 0;
...
 530:	                if (type != PIDTYPE_PID) {
 531:	                        if (type == __PIDTYPE_TGID)
 532:	                                type = PIDTYPE_PID;
 533:	                        task = task->group_leader;
 534:	                }
 535:	                nr = pid_nr_ns(rcu_dereference(task->pids[type].pid), ns);
...
 539:	        return nr;
 540:	}

 501:	pid_t pid_nr_ns(struct pid *pid, 省略)
 502:	{
 503:	        struct upid *upid;
 504:	        pid_t nr = 0;
...
 507:	                upid = &pid->numbers[省略];
...
 509:	                        nr = upid->nr;
...
 511:	        return nr;
 512:	}

group_leader は、ユーザレベルで同じプロセスに属する、カーネル・レベルの スレッドの先頭の task_struct を指す。group_leader は、どのカーネル・レ ベルのスレッドから調べても、同じ struct task_struct を指している。1つ のプロセスにスレッドが1つしかなければ、自分自身の task_struct を指して いる。

◆getppid()システム・コール

linux-4.14.12/kernel/sys.c
 855:	SYSCALL_DEFINE0(getppid)
 856:	{
 857:	        int pid;
...
 860:	        pid = task_tgid_vnr(rcu_dereference(current->real_parent));
...
 863:	        return pid;
 864:	}
currentのreal_parentを引数にして task_tgid_vnr() という関数を呼ぶ。 以降、getpid() と同じ。

◆fork()システム・コール

fork() システム・コールの実装では、task_struct をコピーし、uid, gid を引き継ぐ。
linux-4.14.12/kernel/fork.c
2115:	SYSCALL_DEFINE0(fork)
2116:	{
...
2118:	        return _do_fork(SIGCHLD, 0, 0, NULL, NULL, 0);
...
2123:	}

2019:	long _do_fork(省略)
2025:	{
2026:	        struct task_struct *p;
2028:	        long nr;
...
2048:	        p = copy_process(clone_flags, stack_start, stack_size,
2049:	                         child_tidptr, NULL, trace, tls, NUMA_NO_NODE);
...
2061:	                pid = get_task_pid(p, PIDTYPE_PID);
2062:	                nr = pid_vnr(pid);
...
2088:	        return nr;
2089:	}

1538:	static __latent_entropy struct task_struct *copy_process(省略)
1547:	{
1548:	        int retval;
1549:	        struct task_struct *p;
...
1594:	        p = dup_task_struct(current, node);
...
1627:	        retval = copy_creds(p, clone_flags);
...
1643:	        INIT_LIST_HEAD(&p->children);
1644:	        INIT_LIST_HEAD(&p->sibling);
...
1770:	                pid = alloc_pid(p->nsproxy->pid_ns_for_children);
...
1806:	        p->pid = pid_nr(pid);
...
1816:	                p->group_leader = p;
1817:	                p->tgid = p->pid;
...
1850:	                p->real_parent = current;
...
1904:	                        list_add_tail(&p->sibling, &p->real_parent->children);
...
1935:	        return p;
...
1988:	}

 512:	static struct task_struct *dup_task_struct(struct task_struct *orig, int node)
 513:	{
 514:	        struct task_struct *tsk;
...
 521:	        tsk = alloc_task_struct_node(node);
...
 531:	        err = arch_dup_task_struct(tsk, orig);
...
 588:	        return tsk;
...
 595:	}

 497:	int __weak arch_dup_task_struct(struct task_struct *dst,
 498:	                                               struct task_struct *src)
 499:	{
 500:	        *dst = *src;
 501:	        return 0;
 502:	}
注意: Linux では、単純な fork() 以外に、PID 等をコピーして引き継げるよ うなシステム・コール clone() がある。

■GNU global

大きなソースコードを読むには、grepやgrep -r よりも GNU GLOBAL が便利。
gtags
ソースコードを解析して索引ファイル (GTAGS, GRTAGS, GPATH という名前) を作る。一度だけやれば十分。 Linux では、make gtags で GNU global 用の索引が作られる。
global id
引数で与えられた id を「定義している」ファイルの名前を表示する。
global -x id
引数で与えられた id を「定義している」ファイルの名前と行を表示する。
global -s id
global -sx id
引数で与えられた、「定義」以外のid のファイルの名前(-xでその行)を 表示する。 Linux で、SYSCALL_DEFINE0() 等の中のシステム・コール名を探す のに使える。
global -r id
global -rx id
引数で与えられた id を利用しているファイルの名前(-xでその行)を表示 する。
実行例
$ pwd [←]
/home/prof/yas/os2/linux-4.14.12
$ ls -l G* [←]
-rw-r--r--. 1 yas prof   7151616  1月  8 16:50 2018 GPATH
-rw-r--r--. 1 yas prof 371687424  1月  8 16:50 2018 GRTAGS
-rw-r--r--. 1 yas prof 127131648  1月  8 16:50 2018 GTAGS
$ global task_struct [←]
include/linux/sched.h
$ global -x task_struct [←]
task_struct       519 include/linux/sched.h struct task_struct {
$ global -rx task_struct | head -5 [←]
task_struct       129 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       289 arch/x86/include/asm/elf.h struct task_struct;
$ global -rx task_struct | wc [←]
   3511   29230  345422
$ global getpid [←]
$ global -s getpid [←]
kernel/sys.c
samples/bpf/bpf_load.c
samples/bpf/cgroup_helpers.c
samples/bpf/test_current_task_under_cgroup_user.c
samples/connector/ucon.c
samples/timers/hpet_example.c
scripts/kconfig/confdata.c
$ global -sx getpid [←]
getpid            838 kernel/sys.c     SYSCALL_DEFINE0(getpid)
getpid            760 samples/bpf/bpf_load.c            if (nh->nlmsg_pid != getpid()) {
getpid            762 samples/bpf/bpf_load.c                           nh->nlmsg_pid, getpid());
getpid             88 samples/bpf/cgroup_helpers.c      pid_t pid = getpid();
getpid             21 samples/bpf/test_current_task_under_cgroup_user.c        pid_t remote_pid, local_pid = getpid();
getpid             72 samples/connector/ucon.c  nlh->nlmsg_pid = getpid();
getpid            251 samples/timers/hpet_example.c     if ((fcntl(fd, F_SETOWN, getpid()) == 1) ||
getpid            776 scripts/kconfig/confdata.c                sprintf(tmpname, "%s.tmpconfig.%d", dirname, (int)getpid());
$ []

◆Emacs gtags-mode

Emacs の gtags-mode を使うと、Emacs の中で、Gnu Global の機能を使って定 義に移動できる。 ~/.emacs 等に次のような設定を加える。(/usr/local3/coins/ のパス名は、 coins のみ有効。) これで、C 言語のファイルについて gtags-mode が有効になる。
(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 元に戻る
参考 Emacs 以外に vim 等でも使える。

■毎週のレポートの出し方

■課題1 システムコール、プロセスとtask_struct構造体

★問題(101) システム・コールとライブラリ関数の相違点

システム・コールとライブラリ関数の相違点を述べなさい。

★問題(102) access()システムコールの引数と結果

access() システム・コールのマニュアルには、次のような記述がある。
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.
...
このシステム・コールを処理する関数がカーネルの中でどのように定義されて いるか、その概略(引数と結果の宣言)を示しなさい。関数の内容は空でよい。 マクロを利用しても利用しなくてもどちらでもよい。
引数と結果の宣言
{
   /*内容*/
}

★問題(103) Ready状態のプロセス

オペレーティング・システムでは、「一般に」プロセスは、 実行待ち(Ready)、実行中(Running)、 待機中(Waiting、Blocked)という3つの 状態を持つ。Linux において、プロセスが Ready 状態であることを 示すために、task struct のフィールド state に、何という値を設定しているか。

★問題(104) pidとppid

次のプログラムをシェルから実行したとする。
   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 としては、実行結果のものをそのまま使うのではなく、指定され たものを使いなさい。

★問題(105) fork()システム・コール

fork() システムコールの実装について、本日の授業の範囲内で、次の項目を答 えなさい。
Last updated: 2018/01/11 17:46:03
Yasushi Shinjo / <yas@cs.tsukuba.ac.jp>