プロセス、生成・終了・終了待ち

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

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

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

■今日の大事な話

◆CFS(Completely Fair Scheduler)

印刷資料は、先週配布済み。

■プロセスの生成

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

clone() は、Linux 独自のシステム・コール。プロセスが持つさまざまな資源 をコピーするか、コピーせずに、親子で「共有」するかを設定できる。

vfork() は、直後に execve() を行う時にコピーの一部をさぼるような fork()。copy-on-write が一般的な時代は、普通の fork() もコピーをさぼる ので、存在意義が薄くなった。

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

12月1日の資料参照

■Linux task構造体とfork

Linux のプロセスは、task_struct 構造体で表現されている。fork() システム・ コールを実装するには、基本的には、次のことを行えばよい。

◆likely()とunlikely()

`http://www.ibm.com/developerworks/jp/linux/library/l-gcc-hacks/',M. Tim Jones、Linux カーネルのなかに入り込む GCC、IBM Developer Works、2008年 11月 18日

◆sys_fork

arch/x86/kernel/process.c
 232: int sys_fork(struct pt_regs *regs)
 233: {
 234:         return do_fork(SIGCHLD, regs->sp, regs, 0, NULL, NULL);
 235: }

◆do_fork()

kernel/fork.c 
1339: long do_fork(unsigned long clone_flags,
1340:               unsigned long stack_start,
1341:               struct pt_regs *regs,
1342:               unsigned long stack_size,
1343:               int __user *parent_tidptr,
1344:               int __user *child_tidptr)
1345: {
1346:         struct task_struct *p;
...
1388:         p = copy_process(clone_flags, stack_start, regs, stack_size,
1389:                          child_tidptr, NULL, trace);
...
1394:         if (!IS_ERR(p)) {
1395:                 struct completion vfork;
...
1399:                 nr = task_pid_vnr(p);
...
1428:                         wake_up_new_task(p, clone_flags);
...
1440:         } else {
1441:                 nr = PTR_ERR(p);
1442:         }
1443:         return nr;
1444: }

 938: static struct task_struct *copy_process(unsigned long clone_flags,
 939:                                         unsigned long stack_start,
 940:                                         struct pt_regs *regs,
 941:                                         unsigned long stack_size,
 942:                                         int __user *child_tidptr,
 943:                                         struct pid *pid,
 944:                                         int trace)
 945: {
...
 972:         retval = -ENOMEM;
 973:         p = dup_task_struct(current);
 974:         if (!p)
 975:                 goto fork_out;
...
 985:         retval = -EAGAIN;
...
1015:         INIT_LIST_HEAD(&p->children);
1016:         INIT_LIST_HEAD(&p->sibling);
...
1089:         sched_fork(p, clone_flags);
..
1100:         if ((retval = copy_files(clone_flags, p)))
1101:                 goto bad_fork_cleanup_semundo;
1102:         if ((retval = copy_fs(clone_flags, p)))
1103:                 goto bad_fork_cleanup_files;
1104:         if ((retval = copy_sighand(clone_flags, p)))
1105:                 goto bad_fork_cleanup_fs;
1106:         if ((retval = copy_signal(clone_flags, p)))
1107:                 goto bad_fork_cleanup_sighand;
1108:         if ((retval = copy_mm(clone_flags, p)))
1109:                 goto bad_fork_cleanup_signal;
1110:         if ((retval = copy_namespaces(clone_flags, p)))
1111:                 goto bad_fork_cleanup_mm;
...
1118:         if (pid != &init_struct_pid) {
1119:                 retval = -ENOMEM;
1120:                 pid = alloc_pid(p->nsproxy->pid_ns);
...
1129:         }
1130: 
1131:         p->pid = pid_nr(pid);
1132:         p->tgid = p->pid;
...
1174:         p->exit_state = 0;
...
1212:                 p->real_parent = current;
...
1241:         if (likely(p->pid)) {
1242:                 list_add_tail(&p->sibling, &p->real_parent->children);
...
1259:         }
...
1267:         return p;
...
1310: fork_out:
1311:         return ERR_PTR(retval);
1312: }

 212: static struct task_struct *dup_task_struct(struct task_struct *orig)
 213: {
 214:         struct task_struct *tsk;
 216:         unsigned long *stackend;
...
 222:         tsk = alloc_task_struct();
...
 232:         err = arch_dup_task_struct(tsk, orig);
...
 257:         return tsk;
...
 263: }
...
 205: int __attribute__((weak)) arch_dup_task_struct(struct task_struct *dst,
 206:                                                struct task_struct *src)
 207: {
 208:         *dst = *src;
 209:         return 0;
 210: }

◆fork-pid-ppid.c

PID と PPID を調べるプログラム。
   1:	/*
   2:	        fork-pid-ppid.c -- 画面に pid と ppid を表示するプログラム
   3:	        ~yas/syspro/proc/fork-pid-ppid.c
   4:	        Created on: 2009/12/08 00:50:25
   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:	        fork();
  15:	        pid = getpid();
  16:	        ppid = getppid();
  17:	        printf("pid=%d, ppid=%d\n", pid, ppid );
  18:	}
  19:	
% cc fork-pid-ppid.c -o fork-pid-ppid [←]
% echo $$ [←]
11080
% ./fork-pid-ppid [←]
pid=11650, ppid=11080
pid=11651, ppid=11650
% ./fork-pid-ppid [←]
pid=11652, ppid=11080
pid=11653, ppid=11652
% ./fork-pid-ppid [←]
pid=11654, ppid=11080
pid=11655, ppid=11654
% ./fork-pid-ppid [←]
pid=11658, ppid=11657
pid=11657, ppid=11080
% []

fork-parent-child-scheduling-async

図? 親プロセスと子プロセスの実行(非同期)

◆waitシステム・コールの利用目的

◆exitシステム・コール

プロセスを終了させるシステム・コール。 生成されたプロセスは、exit() システム・コールで自発的に終了するか、 シグナルを受けて強制的に終了させられるかどちらか。

親プロセスに対して、exit status という小さな値(0..254)を返す事ができる。 シェルでは、直前のコマンドの exit status を $? という変数で調べることができる。

% tcsh [←]
[yas@viola01 proc]$ exit 300
exit
% echo $? [←]
44
% tcsh [←]
[yas@viola01 proc]$ exit 254
exit
% echo $? [←]
254
% []

Unix のコマンドの多くは、成功すると exit( 0 )し、失敗すると 0 以外を返す(C言語の true/false とは逆)。

% ls /bin/ls [←]
/bin/ls
% echo $? [←]
0
% ls /bin/nocommand [←]
ls: /bin/nocommand: No such file or directory
% echo $? [←]
2
% []
exit status は、シェルの if 文で使うことができる。

tcsh, csh の場合

% /bin/true [←]
% echo $? [←]
0
% /bin/false [←]
% echo $? [←]
1
% if ( { /bin/true } ) echo ok [←]
ok
% if ( { /bin/false } ) echo ok [←]
% []
sh, bash の場合
$ if /bin/true; then echo ok; fi
ok
$ if /bin/false; then echo ok; fi
$ 

◆fork-pid-ppid-wait.c

PID と PPID を調べるプログラム。wait 付き。
   1:	/*
   2:	        fork-pid-ppid-wait.c -- 画面に pid と ppid を表示するプログラム(wait付)
   3:	        ~yas/syspro/proc/fork-pid-ppid-wait.c
   4:	        Created on: 2009/12/20 16:04:41
   5:	*/
   6:	
   7:	#include <sys/types.h>  /* getpid(), getppid() */
   8:	#include <unistd.h>     /* getpid(), getppid() */
   9:	#include <sys/wait.h>   /* wait() */
  10:	#include <stdlib.h>     /* exit() */
  11:	#include <stdio.h>
  12:	
  13:	main()
  14:	{
  15:	    pid_t pid, ppid, child_pid;
  16:	    int status;
  17:	        child_pid = fork();
  18:	        if( child_pid > 0 )
  19:	        {
  20:	            /* parent process */
  21:	            if( wait( &status ) < 0 )
  22:	            {
  23:	                perror("wait");
  24:	            }
  25:	            printf("child exited with %d (%d)\n",
  26:	                   WEXITSTATUS(status),status );
  27:	        }
  28:	        else if( child_pid == 0 )
  29:	        {
  30:	            /* child process */
  31:	            /* fall through */
  32:	        }
  33:	        else
  34:	        {
  35:	            perror("fork");
  36:	            exit( 1 );
  37:	        }
  38:	        pid = getpid();
  39:	        ppid = getppid();
  40:	        printf("pid=%d, ppid=%d\n", pid, ppid );
  41:	        exit( 100 );
  42:	}
% cc fork-pid-ppid-wait.c -o fork-pid-ppid-wait [←]
% ./fork-pid-ppid-wait  [←]
pid=32423, ppid=32422
child exited with 100 (25600)
pid=32422, ppid=11080
% ./fork-pid-ppid-wait [←]
pid=32425, ppid=32424
child exited with 100 (25600)
pid=32424, ppid=11080
% ./fork-pid-ppid-wait [←]
pid=32427, ppid=32426
child exited with 100 (25600)
pid=32426, ppid=11080
% echo $? [←]
100
% []

fork-parent-child-scheduling-wait

図? 親プロセスと子プロセスの実行(waitシステム・コールによる同期付き)

◆sys/wait.h

wait.h には、wait() システム・コールで得られる status から子プロセスが 行った exit() システム・コールの結果を得るためのマクロ WEXITSTATUS() や その他のマクロの定義が含まれている。 /usr/include/sys/wait.h
# ifdef __USE_BSD
...
# else /* Don't use BSD.  */

#  define __WAIT_INT(status)    (status)
#  define __WAIT_STATUS         int *
#  define __WAIT_STATUS_DEFN    int *

# endif /* Use BSD.  */

# include <bits/waitstatus.h>

# define WEXITSTATUS(status)    __WEXITSTATUS(__WAIT_INT(status))
# define WTERMSIG(status)       __WTERMSIG(__WAIT_INT(status))
# define WSTOPSIG(status)       __WSTOPSIG(__WAIT_INT(status))
# define WIFEXITED(status)      __WIFEXITED(__WAIT_INT(status))
#endif  /* <stdlib.h> not included.  */
/usr/include/bits/waitstatus.h
#define __WEXITSTATUS(status)   (((status) & 0xff00) >> 8)
#define __WTERMSIG(status)      ((status) & 0x7f)
#define __WSTOPSIG(status)      __WEXITSTATUS(status)
#define __WIFEXITED(status)     (__WTERMSIG(status) == 0)

◆sys_exit()

kernel/exit.c
1032: SYSCALL_DEFINE1(exit, int, error_code)
1033: {
1034:         do_exit((error_code&0xff)<<8);
1035: }

◆do_exit()

kernel/exit.c
 888: NORET_TYPE void do_exit(long code)
 889: {
...
 953:         tsk->exit_code = code;
...
 956:         exit_mm(tsk);
...
 963:         exit_files(tsk);
 964:         exit_fs(tsk);
...
 984:         exit_notify(tsk, group_dead);
..
1012:         tsk->state = TASK_DEAD;
1013:         schedule();
1014:         BUG();
...
1018: }

 803: static void exit_notify(struct task_struct *tsk, int group_dead)
 804: {
...
 843:         if (signal >= 0)
 844:                 signal = do_notify_parent(tsk, signal);
 846:         tsk->exit_state = signal == DEATH_REAP ? EXIT_DEAD : EXIT_ZOMBIE;
 859:         if (signal == DEATH_REAP)
 860:                 release_task(tsk);
 861: }

◆waitシステム・コールの種類

pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options);
int waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options);
pid_t wait3(int *status, int options, struct rusage *rusage);
pid_t wait4(pid_t pid, int *status, int options, struct rusage *rusage);
最終的には、すべて wait4() を呼ぶ。

◆sys_wait4()

kernel/exit.c
1689: SYSCALL_DEFINE4(wait4, pid_t, upid, int __user *, stat_addr,
1690:                 int, options, struct rusage __user *, ru)
1691: {
1692:         struct wait_opts wo;
1693:         struct pid *pid = NULL;
1694:         enum pid_type type;
1695:         long ret;
1696: 
1697:         if (options & ~(WNOHANG|WUNTRACED|WCONTINUED|
1698:                         __WNOTHREAD|__WCLONE|__WALL))
1699:                 return -EINVAL;
1700: 
1701:         if (upid == -1)
1702:                 type = PIDTYPE_MAX;
1703:         else if (upid < 0) {
1704:                 type = PIDTYPE_PGID;
1705:                 pid = find_get_pid(-upid);
1706:         } else if (upid == 0) {
1707:                 type = PIDTYPE_PGID;
1708:                 pid = get_task_pid(current, PIDTYPE_PGID);
1709:         } else /* upid > 0 */ {
1710:                 type = PIDTYPE_PID;
1711:                 pid = find_get_pid(upid);
1712:         }
1713: 
1714:         wo.wo_type      = type;
1715:         wo.wo_pid       = pid;
1716:         wo.wo_flags     = options | WEXITED;
1717:         wo.wo_info      = NULL;
1718:         wo.wo_stat      = stat_addr;
1719:         wo.wo_rusage    = ru;
1720:         ret = do_wait(&wo);
...
1725:         return ret;
1726: }

◆do_wait()

kernel/exit.c
1563: static long do_wait(struct wait_opts *wo)
1564: {
1565:         DECLARE_WAITQUEUE(wait, current);
1566:         struct task_struct *tsk;
1567:         int retval;
...
1571:         add_wait_queue(&current->signal->wait_chldexit,&wait);
1572: repeat:
...
1584:         set_current_state(TASK_INTERRUPTIBLE);
1586:         tsk = current;
...
1588:                 retval = do_wait_thread(wo, tsk);
1589:                 if (retval)
1590:                         goto end;
...
1601: notask:
1602:         retval = wo->notask_error;
1603:         if (!retval && !(wo->wo_flags & WNOHANG)) {
1604:                 retval = -ERESTARTSYS;
1605:                 if (!signal_pending(current)) {
1606:                         schedule();
1607:                         goto repeat;
1608:                 }
1609:         }
1610: end:
1611:         __set_current_state(TASK_RUNNING);
1612:         remove_wait_queue(&current->signal->wait_chldexit,&wait);
...
1638:         return retval;
1639: }

◆do_wait_thread()

kernel/exit.c

1532: static int do_wait_thread(struct wait_opts *wo, struct task_struct *tsk)
1533: {
1534:         struct task_struct *p;
1536:         list_for_each_entry(p, &tsk->children, sibling) {
...
1541:                         int ret = wait_consider_task(wo, tsk, 0, p);
1542:                         if (ret)
1543:                                 return ret;
..
1545:         }
1547:         return 0;
1548: }

1473: static int wait_consider_task(struct wait_opts *wo, struct task_struct *parent,
1474:                                 int ptrace, struct task_struct *p)
1475: {
...
1508:         if (p->exit_state == EXIT_ZOMBIE && !delay_group_leader(p))
1509:                 return wait_task_zombie(wo, p);
...
1521: }

◆wait_task_zombie()

kernel/exit.c

1159: static int wait_task_zombie(struct wait_opts *wo, struct task_struct *p)
1160: {
...
1201:         if (likely(!traced) && likely(!task_detached(p))) {
...
1221:                 psig = p->real_parent->signal;
1222:                 sig = p->signal;
1223:                 psig->cutime =
1224:                         cputime_add(psig->cutime,
1225:                         cputime_add(p->utime,
1226:                         cputime_add(sig->utime,
1227:                                     sig->cutime)));
...
1255:         }
...
1263:         retval = wo->wo_rusage
1264:                 ? getrusage(p, RUSAGE_BOTH, wo->wo_rusage) : 0;
1265:         status = (p->signal->flags & SIGNAL_GROUP_EXIT)
1266:                 ? p->signal->group_exit_code : p->exit_code;
1267:         if (!retval && wo->wo_stat)
1268:                 retval = put_user(status, wo->wo_stat);
...
1314:         if (p != NULL)
1315:                 release_task(p);
1317:         return retval;
1318: }

■クイズ4 プロセス、生成・終了・終了待ち

★問題(401) task_structのコピー

fork() システム・コールの実装では、dup_task_struct() でtask_struct をコ ピーしている。しかし、task_structには、単に値をコピーするだけではすまな いフィールドがある。そのようなフィールドの例を上げなさい。そのフィール ドは、なぜコピーできないのか理由を簡単に説明しなさい。

★問題(402) exit statusの保存

プログラム fork-pid-ppid-waitで子 プロセスが exit( 100 ) している値を、task_struct のあるフィールドに保存 している。どのフィールドにどのように保存しているか示しなさい。

★問題(403) wait4()によるwait()の実装

wait4() が利用可能な時に、wait4() を呼び出すとでwait() システム・コール を実装しなさい。

★問題(404) wait()における exit status の取得

親プロセスか wait() システム・コールを実行した時に子プロセスが終了して いたとする。この時、子プロセスの exit status をどのようにして得ているか。

★問題(405) wait()によるブロック

親プロセスか wait() システム・コールを実行した時に子プロセスがまだ終了 していないことがある。この場合、親プロセスの状態(task_struct の state) は、何から何へ変化するか。

★問題(406) wait()によるブロックからの回復

wait() システム・コールで、親プロセスがブロックしていたとする。子プロセ スが終了すると、親プロセスの親プロセスの状態(task_struct の state) は、 何から何へ変化するか。
Last updated: 2010/01/25 17:48:57
Yasushi Shinjo / <yas@is.tsukuba.ac.jp>