プロセスとtask_struct構造体

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

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

このページは、次の URL にあります。
http://www.coins.tsukuba.ac.jp/~yas/coins/os2-2012/2012-12-11
あるいは、次のページから手繰っていくこともできます。
http://www.coins.tsukuba.ac.jp/~yas/
http://www.cs.tsukuba.ac.jp/~yas/

■連絡事項

Softlab 研究室紹介

■今日の大事な話

■補足

システム・コールの処理では、特権命令を必要とする。ライブラリ関数では、 普通は特権命令を使えない(必要な時にはシステム・コールを利用する)。

■プロセスとは

「オペレーティングシステム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.6.8/include/linux/sched.h

1234:	struct task_struct {
1235:	        volatile long state;    /* -1 unrunnable, 0 runnable, >0 stopped */
...
1307:	        int exit_state;
1308:	        int exit_code, exit_signal;
...
1337:	        struct task_struct __rcu *real_parent; /* real parent process */
1338:	        struct task_struct __rcu *parent; /* recipient of SIGCHLD, wait4() reports */
...
1342:	        struct list_head children;      /* list of my children */
1343:	        struct list_head sibling;       /* linkage in my parent's children list */
1344:	        struct task_struct *group_leader;       /* threadgroup leader */
...
1355:	        struct pid_link pids[PIDTYPE_MAX];
...
1379:	        const struct cred __rcu *cred;  /* effective (overridable) subjective task
1380:	                                         * credentials (COW) */
1381:	        char comm[TASK_COMM_LEN]; /* executable name excluding path
1382:	                                     - access with [gs]et_task_comm (which lock
1383:	                                       it with task_lock())
1384:	                                     - initialized normally by setup_new_exec */
...
1592:	};

◆state

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

 184:	#define TASK_RUNNING            0
 185:	#define TASK_INTERRUPTIBLE      1
 186:	#define TASK_UNINTERRUPTIBLE    2
 187:	#define __TASK_STOPPED          4
 188:	#define __TASK_TRACED           8
 189:	/* in tsk->exit_state */
 190:	#define EXIT_ZOMBIE             16
 191:	#define EXIT_DEAD               32
 192:	/* in tsk->state again */
 193:	#define TASK_DEAD               64
 194:	#define TASK_WAKEKILL           128
 195:	#define TASK_WAKING             256
 196:	#define TASK_STATE_MAX          512
一般的な状態 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.6.8/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.6.8/include/linux/cred.h

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

 117:	struct cred {
...
 126:	        kuid_t          uid;            /* real UID of the task */
 127:	        kgid_t          gid;            /* real GID of the task */
...
 151:	        struct group_info *group_info;  /* supplementary groups for euid/fsgid */
...
 153:	};

include/linux/uidgid.h:
47:typedef gid_t kgid_t;

include/linux/types.h:
41:typedef __kernel_gid32_t       gid_t;

include/asm-generic/posix_types.h:49:
typedef unsigned int       __kernel_gid32_t
kuid_t は、uid をカーネル内で保持するための型。uid_t と同じ32 ビットの 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.6.8/kernel/timer.c

1421:	SYSCALL_DEFINE0(getpid)
1422:	{
1423:	        return task_tgid_vnr(current);
1424:	}

linux-3.6.8/include/linux/sched.h
1695:	static inline pid_t task_tgid_vnr(struct task_struct *tsk)
1696:	{
1697:	        return pid_vnr(task_tgid(tsk));
1698:	}

1633:	static inline struct pid *task_tgid(struct task_struct *task)
1634:	{
1635:	        return task->group_leader->pids[PIDTYPE_PID].pid;
1636:	}

group_leader は、ユーザレベルで同じプロセスに属する、カーネル・レベルの スレッドの先頭のものを指す。group_leader は、どのカーネル・レベルのスレッ ドから調べても、同じ struct task_struct を指している。
linux-3.6.8/kernel/pid.c

 483:	pid_t pid_vnr(struct pid *pid)
 484:	{
 485:	        return pid_nr_ns(pid, 省略);
 486:	}

 470:	pid_t pid_nr_ns(struct pid *pid, 省略)
 471:	{
 472:	        struct upid *upid;
 473:	        pid_t nr = 0;
..
 476:	                upid = &pid->numbers[省略];
...
 478:	                        nr = upid->nr;
...
 480:	        return nr;
 481:	}

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

linux-3.6.8/kernel/timer.c

1432:	SYSCALL_DEFINE0(getppid)
1433:	{
1434:	        int pid;
...
1437:	        pid = task_tgid_vnr(rcu_dereference(current->real_parent));
...
1440:	        return pid;
1441:	}

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

■課題2 プロセスとtask_struct構造体

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

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

★問題(202) 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 としては、実行結果のものをそのまま使うのではなく、指定され たものを使いなさい。

★問題(203) getuid()システム・コール

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

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


Last updated: 2012/12/10 12:39:12
Yasushi Shinjo / <yas@cs.tsukuba.ac.jp>