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

					2013年12月26日
情報科学類 オペレーティングシステム II

                                       筑波大学 システム情報工学研究科 
                                       コンピュータサイエンス専攻, 電子・情報工学系
                                       新城 靖
                                       <yas@cs.tsukuba.ac.jp>

このページは、次の URL にあります。
http://www.coins.tsukuba.ac.jp/~yas/coins/os2-2013/2013-12-26
あるいは、次のページから手繰っていくこともできます。
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 を使う。 2013年12月現在、学類では次のコンピュータで 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 -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:
	leal	4(%esp), %ecx
	andl	$-16, %esp
	pushl	-4(%ecx)
	pushl	%ebp
	movl	%esp, %ebp
	pushl	%ecx
	subl	$20, %esp
	movl	$.LC0, 4(%esp)
	movl	$.LC1, (%esp)
	call	printf
	addl	$20, %esp
	popl	%ecx
	popl	%ebp
	leal	-4(%ecx), %esp
	ret
	.size	main, .-main
	.ident	"GCC: (GNU) 4.1.2 20080704 (Red Hat 4.1.2-48)"
	.section	.note.GNU-stack,"",@progbits

◆printf() のソース・プログラム

~yas/os2/glibc-2.5/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()システム・コール(ユーザ空間)

write.o の逆アセンブル結果より、主要部分抜粋。 
	push   %ebx		# ebxレジスタの内容をスタックへ退避
	mov    0x10(%esp),%edx	# 第3引数をレジスタ edx へ
	mov    0xc(%esp),%ecx	# 第2引数をレジスタ ecx へ
	mov    0x8(%esp),%ebx	# 第1引数をレジスタ ebx へ
	mov    $0x4,%eax	# システム・コールの番号 eax へ
	int    $0x80		# トラップ命令。カーネルへ制御を移す。
	pop    %ebx		# ebxレジスタの内容を回復
	cmp    $0xfffff001,%eax	# エラーが起きたら
	jae    __syscall_error	# __syscall_errorへジャンプ (errno等の保存)
	ret

■Linuxカーネルのソース

◆バージョン

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

http://kernel.org/ に保存されている。

展開の仕方

$ bzip2 -d < linux-X.Y.Z.tar.bz2 | tar xf - [←]
$ xz    -d < linux-X.Y.Z.tar.xz  | tar xf - [←]

2.6 以前には、2.4, 2.2, 2.0 等が広く使われた。 ~yas/os2/linux-3.12.6 に展開したものがある。

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

カーネル・ソースのトップレベルのディレクトリ
ディレクトリ 説明
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-3.12.6/arch-x86-include-generated-asm-syscalls_32.h (自動生成されたもの)

linux-3.12.6/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
...
 349:	#define __NR_kcmp 349
 350:	#define __NR_finit_module 350

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

linux-3.12.6/arch/x86/kernel/syscall_32.c
  12:	#define __SYSCALL_I386(nr, sym, compat) [nr] = sym,
  13:	
  14:	typedef asmlinkage void (*sys_call_ptr_t)(void);
  15:	
  16:	extern asmlinkage void sys_ni_syscall(void);
  17:	
  18:	__visible const sys_call_ptr_t sys_call_table[__NR_syscall_max+1] = {
  19:	        /*
  20:	         * Smells like a compiler bug -- it doesn't work
  21:	         * when the & below is removed.
  22:	         */
  23:	        [0 ... __NR_syscall_max] = &sys_ni_syscall,
  24:	#include <asm/syscalls_32.h>
  25:	};

linux-3.12.6/arch/x86/include/generated/asm/syscalls_32.h
   1:	__SYSCALL_I386(0, sys_restart_syscall, sys_restart_syscall)
   2:	__SYSCALL_I386(1, sys_exit, sys_exit)
   3:	__SYSCALL_I386(2, sys_fork, stub32_fork)
   4:	__SYSCALL_I386(3, sys_read, sys_read)
   5:	__SYSCALL_I386(4, sys_write, sys_write)
   6:	__SYSCALL_I386(5, sys_open, compat_sys_open)
   7:	__SYSCALL_I386(6, sys_close, sys_close)
   8:	__SYSCALL_I386(7, sys_waitpid, sys32_waitpid)
   9:	__SYSCALL_I386(8, sys_creat, sys_creat)
  10:	__SYSCALL_I386(9, sys_link, sys_link)
  11:	__SYSCALL_I386(10, sys_unlink, sys_unlink)
  12:	__SYSCALL_I386(11, sys_execve, stub32_execve)
  13:	__SYSCALL_I386(12, sys_chdir, sys_chdir)
...
 328:	__SYSCALL_I386(349, sys_kcmp, sys_kcmp)
 329:	__SYSCALL_I386(350, sys_finit_module, sys_finit_module)

◆sys_write()

linux-3.12.6/fs/read_write.c

 514:	SYSCALL_DEFINE3(write, unsigned int, fd, const char __user *, buf,
 515:	                size_t, count)
 516:	{
 517:	        struct fd f = fdget(fd);
 518:	        ssize_t ret = -EBADF;
 519:	
 520:	        if (f.file) {
 521:	                loff_t pos = file_pos_read(f.file);
 522:	                ret = vfs_write(f.file, buf, count, &pos);
 523:	                if (ret >= 0)
 524:	                        file_pos_write(f.file, pos);
 525:	                fdput(f);
 526:	        }
 527:	
 528:	        return ret;
 529:	}
この定義は、以下のように展開される。
asmlinkage long sys_write(unsigned int fd, const char __user *buf,
	   	size_t count)
{
	省略
}

◆エラー番号の例

linux-3.12.6/include/uapi/asm-generic/errno-base.h
...
   4:	#define EPERM            1      /* Operation not permitted */
   5:	#define ENOENT           2      /* No such file or directory */
   6:	#define ESRCH            3      /* No such process */
   7:	#define EINTR            4      /* Interrupted system call */
   8:	#define EIO              5      /* I/O error */
   9:	#define ENXIO            6      /* No such device or address */
  10:	#define E2BIG            7      /* Argument list too long */
  11:	#define ENOEXEC          8      /* Exec format error */
  12:	#define EBADF            9      /* Bad file number */
  13:	#define ECHILD          10      /* No child processes */
  14:	#define EAGAIN          11      /* Try again */
  15:	#define ENOMEM          12      /* Out of memory */
  16:	#define EACCES          13      /* Permission denied */
...

◆SYSCALL_DEFINEマクロ

linux-3.12.6/include/linux/syscalls.h

 170:	#define SYSCALL_DEFINE0(sname)                                  \
 171:	        SYSCALL_METADATA(_##sname, 0);                          \
 172:	        asmlinkage long sys_##sname(void)
 173:	
 174:	#define SYSCALL_DEFINE1(name, ...) SYSCALL_DEFINEx(1, _##name, __VA_ARGS__)
 175:	#define SYSCALL_DEFINE2(name, ...) SYSCALL_DEFINEx(2, _##name, __VA_ARGS__)
 176:	#define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__)
 177:	#define SYSCALL_DEFINE4(name, ...) SYSCALL_DEFINEx(4, _##name, __VA_ARGS__)
 178:	#define SYSCALL_DEFINE5(name, ...) SYSCALL_DEFINEx(5, _##name, __VA_ARGS__)
 179:	#define SYSCALL_DEFINE6(name, ...) SYSCALL_DEFINEx(6, _##name, __VA_ARGS__)
 180:	
 181:	#define SYSCALL_DEFINEx(x, sname, ...)                          \
...
 183:	        __SYSCALL_DEFINEx(x, sname, __VA_ARGS__)
...
 186:	#define __SYSCALL_DEFINEx(x, name, ...)                                 \
 187:	        asmlinkage long sys##name(__MAP(x,__SC_DECL,__VA_ARGS__));      \
...
  99:	#define __SC_DECL(t, a) t a
SYSCALL_DEFINEn マクロにより、システム・コールを処理する関 数が定義される。

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

main()、printf()、・・・、write()、トラップ、・・・、sys_write()

図? hello world プログラムでのシステム・コールの実行

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

◆strace コマンド

システム・コールの動きを調べるには、strace コマンド(Linux) が便利。
$ strace ./hello [←]
execve("./hello", ["./hello"], [/* 38 vars */]) = 0
brk(0)                                  = 0x83a3000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY)      = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=169180, ...}) = 0
mmap2(NULL, 169180, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7f67000
close(3)                                = 0
open("/lib/libc.so.6", O_RDONLY)        = 3
read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\340_\222\0004\0\0\0"..., 512) = 512
fstat64(3, {st_mode=S_IFREG|0755, st_size=1689640, ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7f66000
mmap2(0x910000, 1410500, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x910000
mmap2(0xa63000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x152) = 0xa63000
mmap2(0xa66000, 9668, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xa66000
close(3)                                = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7f65000
set_thread_area({entry_number:-1 -> 6, base_addr:0xb7f656c0, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0
mprotect(0xa63000, 8192, PROT_READ)     = 0
mprotect(0x83d000, 4096, PROT_READ)     = 0
munmap(0xb7f67000, 169180)              = 0
fstat64(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 1), ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7f90000
write(1, "hello, world!\n", 14hello, world!
)         = 14
exit_group(14)                          = ?
$ []

◆ltrace コマンド

ltrace コマンド(Linux) を使うと、ライブラリの呼び出しを調べることができ る。
$ ./hello  [←]
hello, world!
$ ltrace ./hello [←]
__libc_start_main(0x80483a4, 1, 0xbf8d3764, 0x80483f0, 0x80483e0 
printf("hello, %s!\n", "world"hello, world!
)                  = 14
+++ exited (status 14) +++
$ []

◆gdb デバッガ

$ gdb hello [←]
省略
(gdb) b main[←]        # ブレークポイントの設定
Breakpoint 1 at 0x80483b2
(gdb) r[←]             # 実行
Starting program: 省略/hello 
Breakpoint 1, 0x080483b2 in main ()
(gdb) b write[←]       # ブレークポイントの設定
Breakpoint 2 at 0x9d2b80
(gdb) c[←]             # 実行継続
Continuing.
Breakpoint 2, 0x009d2b80 in write () from /lib/libc.so.6
(gdb) bt[←]            # バックトレース
#0  0x009d2b80 in write () from /lib/libc.so.6
省略
#6  0x0094c271 in vfprintf () from /lib/libc.so.6
#7  0x00955e83 in printf () from /lib/libc.so.6
#8  0x080483c9 in main ()
(gdb) c[←]             # 実行継続
Continuing.
hello, world!
Program exited with code 016.
(gdb) 

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

■プロセスとは

「オペレーティングシステム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 24099 24098  15   0  88428  2800 wait   Ss   pts/1      0:00 -bash
0  1013 24754 24099  17   0   7836  2092 finish T    pts/1      0:00 nm /usr/l
0  1013 24755 24099  15   0  65804   896 finish T    pts/1      0:00 lv
0  1013 24798 24099  17   0  63480   796 -      R+   pts/1      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()

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

pid
   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)
文字列
UID(user ID, user identifier)
16ビット-32ビットの整数

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

◆プロセスの属性

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

◆idコマンド

idコマンドを使うと、そのプロセス(の親プロセスであるシェルから継承した) UIDとGIDが調べられる。
$ id [←]
uid=1013(yas) gid=510(prof) groups=20(games),510(prof),1020(c-admin),1065(c-spec),1150(tebiki)
$ []

◆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-3.12.6/include/linux/sched.h

1023:	struct task_struct {
1024:	        volatile long state;    /* -1 unrunnable, 0 runnable, >0 stopped */
...
1099:	        int exit_state;
1100:	        int exit_code, exit_signal;
...
1131:	        struct task_struct __rcu *real_parent; /* real parent process */
1132:	        struct task_struct __rcu *parent; /* recipient of SIGCHLD, wait4() reports */
...
1136:	        struct list_head children;      /* list of my children */
1137:	        struct list_head sibling;       /* linkage in my parent's children list */
1138:	        struct task_struct *group_leader;       /* threadgroup leader */
...
1149:	        struct pid_link pids[PIDTYPE_MAX];
...
1182:	        const struct cred __rcu *cred;  /* effective (overridable) subjective task
...
1184:	        char comm[TASK_COMM_LEN]; /* executable name excluding path
1185:	                                     - access with [gs]et_task_comm (which lock
1186:	                                       it with task_lock())
1187:	                                     - initialized normally by setup_new_exec */
...
1414:	};

◆state

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

 134:	#define TASK_RUNNING            0
 135:	#define TASK_INTERRUPTIBLE      1
 136:	#define TASK_UNINTERRUPTIBLE    2
 137:	#define __TASK_STOPPED          4
 138:	#define __TASK_TRACED           8
 139:	/* in tsk->exit_state */
 140:	#define EXIT_ZOMBIE             16
 141:	#define EXIT_DEAD               32
 142:	/* in tsk->state again */
 143:	#define TASK_DEAD               64
 144:	#define TASK_WAKEKILL           128
 145:	#define TASK_WAKING             256
 146:	#define TASK_PARKED             512
 147:	#define TASK_STATE_MAX          1024
 148:	
 149:	#define TASK_STATE_TO_CHAR_STR "RSDTtZXxKWP"
一般的な状態 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に戻って開いている番号を探して使う。)

◆struct pidとstruct upid

APIとしてのプロセス識別子を、カーネルの中で表現するための構造体。

◆pids[]

pids[] は、プロセス識別子を保持するための配列。いくつかの種類が あるが、getpid(),getppid(),fork() に関連しているものは、0 番目 の pids[PIDTYPE_PID]

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

linux-3.12.6/include/linux/pid.h

   6:	enum pid_type
   7:	{
   8:	        PIDTYPE_PID,
...
  12:	};

  50:	struct upid {
...
  52:	        int nr;
...
  55:	};

  57:	struct pid
  58:	{
...
  62:	        struct hlist_head tasks[PIDTYPE_MAX];
...
  65:	};

  69:	struct pid_link
  70:	{
...
  72:	        struct pid *pid;
  73:	};

◆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 *real_cred

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

task_struct構造体、cred構造体、group_info構造体

図? uid, gid, groups の保持方法

◆struct cred

task_struct に保存すべき内容のうち、uid、gid、groups 等を抜き出したもの。
linux-3.12.6/include/linux/cred.h

  29:	#define NGROUPS_SMALL           32
...
  32:	struct group_info {
...
  34:	        int             ngroups;
...
  36:	        kgid_t          small_block[NGROUPS_SMALL];
...
  38:	};

 102:	struct cred {
...
 111:	        kuid_t          uid;            /* real UID of the task */
 112:	        kgid_t          gid;            /* real GID of the task */
...
 137:	        struct group_info *group_info;  /* supplementary groups for euid/fsgid */
...
 139:	};

linux-3.12.6/include/linux/uidgid.h
  22:	typedef struct {
  23:	        uid_t val;
  24:	} kuid_t;
...
  27:	typedef struct {
  28:	        gid_t val;
  29:	} kgid_t;

linux-3.12.6/include/linux/types.h
  31:	typedef __kernel_uid32_t        uid_t;
  32:	typedef __kernel_gid32_t        gid_t;

linux-3.12.6/include/uapi/asm-generic/posix_types.h
  48:	typedef unsigned int    __kernel_uid32_t;
  49:	typedef unsigned int    __kernel_gid32_t;

kuid_t は、uid をカーネル内で保持するための型。32 ビットのuid_t (unsigned int) を保持するための構造体として定義されている。 (システム・コールの結果を返す所で、名前空間によるマッピングが行われることがある。)

◆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-3.12.6/kernel/sys.c
 810:	SYSCALL_DEFINE0(getpid)
 811:	{
 812:	        return task_tgid_vnr(current);
 813:	}

linux-3.12.6/include/linux/sched.h
1498:	static inline pid_t task_tgid_vnr(struct task_struct *tsk)
1499:	{
1500:	        return pid_vnr(task_tgid(tsk));
1501:	}

1436:	static inline struct pid *task_tgid(struct task_struct *task)
1437:	{
1438:	        return task->group_leader->pids[PIDTYPE_PID].pid;
1439:	}
group_leader は、ユーザレベルで同じプロセスに属する、カーネル・レベルの スレッドの先頭のものを指す。group_leader は、どのカーネル・レベルのスレッ ドから調べても、同じ struct task_struct を指している。
linux-3.12.6/kernel/pid.c
 509:	pid_t pid_vnr(struct pid *pid)
 510:	{
 511:	        return pid_nr_ns(pid, 省略);
 512:	}

 495:	pid_t pid_nr_ns(struct pid *pid, 省略)
 496:	{
 497:	        struct upid *upid;
 498:	        pid_t nr = 0;
 501:	                upid = &pid->numbers[省略];
...
 503:	                        nr = upid->nr;
...
 505:	        return nr;
 506:	}

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

linux-3.12.6/kernel/sys.c
 827:	SYSCALL_DEFINE0(getppid)
 828:	{
 829:	        int pid;
...
 832:	        pid = task_tgid_vnr(rcu_dereference(current->real_parent));
...
 835:	        return pid;
 836:	}
currentのreal_parentを引数にして task_tgid_vnr() という関数を呼ぶ。 以降、getpid() と同じ。

rcu_dereference() の dereference は、ポインタが参照している先を取り出す ことを意味する。rcu は、read-copy update の省略形。マルチプロセッサで、 ポインタが変更される可能性がある時でも、ロックなしで読み込むことができ る。意味を考える時には、最初は rcu_dereference() を無視してよい。

■idutils

大きなソースコードを読むには、grepやgrep -r よりも idutils パッケージが便利。
mkid {filenames}
make id。ソースコードを解析して索引ファイル (./ID という名前) を作る。一度だけやれば十分。
lid id
list id。引数で与えられた id を含むファイルを表示する。
lid -R grep id、または、gid id
grep 形式での検索。
実行例
$ pwd [←]
/home/prof/yas/os2/linux-3.12.6
$ ls -l ID [←]
-rw-r--r--  1 yas  prof  55798174 12  2 16:17 ID
$ lid chdir [←]
chdir          arch/powerpc/include/asm/systbl.h fs/open.c scripts/kconfig/confdata.c 
tools/lguest/lguest.c tools/vm/slabinfo.c arch/parisc/hpux/sys_hpux.c arch/um/drivers/cow_user.c
tools/perf/util/run-command.c arch/ia64/kernel/fsys.S arch/parisc/kernel/syscall_table.S
$ []

$ gid chdir [←]
arch/powerpc/include/asm/systbl.h:18:SYSCALL_SPU(chdir)
fs/open.c:380:SYSCALL_DEFINE1(chdir, const char __user *, filename)
scripts/kconfig/confdata.c:842: if (chdir("include/config"))
scripts/kconfig/confdata.c:936: if (chdir("../.."))
tools/lguest/lguest.c:2026:             if (chdir("/") != 0)
tools/lguest/lguest.c:2027:                     err(1, "chdir(\"/\") failed");
tools/vm/slabinfo.c:1143:       if (chdir("/sys/kernel/slab") && chdir("/sys/slab"))
tools/vm/slabinfo.c:1167:                       if (chdir(de->d_name))
tools/vm/slabinfo.c:1222:                       chdir("..");
arch/parisc/hpux/sys_hpux.c:493:        "chdir",                 
arch/um/drivers/cow_user.c:158:         if (chdir(from)) {
arch/um/drivers/cow_user.c:188: if (chdir(save_cwd)) {
tools/perf/util/run-command.c:100:              if (cmd->dir && chdir(cmd->dir))
arch/ia64/kernel/fsys.S:609:    data8 0                         // chdir
arch/parisc/kernel/syscall_table.S:70:  ENTRY_SAME(chdir)
$ []
$ gid gid_t |egrep typedef [←]
include/linux/types.h:41:typedef __kernel_gid32_t       gid_t;
include/linux/uidgid.h:47:typedef gid_t kgid_t;
$ []

■課題の出し方

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

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

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

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

chdir() システム・コールのマニュアルには、次のような記述がある。
CHDIR(2)                   Linux Programmer's Manual                  CHDIR(2)

NAME
       chdir, fchdir - change working directory

SYNOPSIS
       #include <unistd.h>

       int chdir(const char *path);
...
DESCRIPTION
       chdir() changes the current working directory of the calling process to
       the directory specified in path.
...
RETURN VALUE
       On success, zero is returned.  On error, -1 is returned, and  errno  is
       set appropriately.
...
このシステム・コールを処理する関数がカーネルの中でどのように定義されて いるか、その概略(引数と結果の宣言)を示しなさい。関数の内容は空でよい。 マクロを利用しても利用しなくてもどちらでもよい。
引数と結果の宣言
{
   /*内容*/
}

★問題(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) getuid()システム・コール

getuid() システム・コールを実装の概略を、今日の授業の範囲内で答えなさい。 利用する重要な変数、マクロ、構造体を列挙しなさい。そして、どのようにポ インタをたどっていくかを示しなさい。概略を記述するためには、簡易的なC 言語、日本語、または、英語を使いなさい。

なお、実際の getuid() システム・コールの実装は、名前空間の導入により複 雑になっており、今日の授業の範囲を超えている。この課題では、実際のコー ドではなく、この授業の範囲内で答えなさい。(実際のコードをそのまま回答 しても、得点を与えない。)


Last updated: 2014/01/31 17:18:57
Yasushi Shinjo / <yas@cs.tsukuba.ac.jp>