TLPI 第24 章 练习:Process Creation 笔记和练习博客总目录见开始读TLPI。24-1在一个程序执行以下一系列 fork() 调用之后会产生多少个新进程假设所有调用都成功fork();fork();fork();总共会有8个进程新进程有7个。如下图初始进程 P0 ├─ 第一次fork后分裂 │ P0父 │ └─ P1子1 │ ├─ 第二次fork各自分裂 │ │ P0 → P2 │ │ P1 → P3 │ │ ├─ 第三次fork全部再分裂 │ │ │ P0 → P4 │ │ │ P2 → P5 │ │ │ P1 → P6 │ │ │ P3 → P7 │ │ │ │ │ │ 全部8个进程P0 P1 P2 P3 P4 P5 P6 P724-2编写一个程序来演示在 vfork() 之后子进程可以关闭一个文件描述符例如描述符 0而不会影响父进程中的相应文件描述符。原书提供的答案如procexec/vfork_fd_test.c#define_BSD_SOURCE/* To get vfork() declaration from unistd.h in case _XOPEN_SOURCE 700 */#includetlpi_hdr.hintmain(intargc,char*argv[]){switch(fork()){case-1:errExit(vfork);case0:if(close(STDOUT_FILENO)-1)errMsg(close - child);_exit(EXIT_SUCCESS);default:break;}/* Now parent closes STDOUT_FILENO twice: only the second close should fail, indicating that the close(STDOUT_FILENO) by the child did not affect the parent. */if(close(STDOUT_FILENO)-1)errMsg(first close);if(close(STDOUT_FILENO)-1)errMsg(second close);exit(EXIT_SUCCESS);}我修改了close之后的提示这样可以明确是first还是second close。他的解题思路挺巧妙就是如果第一次close失败说明影响了否则就没影响。运行如下$ ./vfork_fd_test ERROR[EBADF Badfiledescriptor]second close我的代码如下代码模板抄自fork(2) #includesignal.h#includestdint.h#includestdio.h#includestdlib.h#includeunistd.hintmain(void){pid_tpid;if(signal(SIGCHLD,SIG_IGN)SIG_ERR){perror(signal);exit(EXIT_FAILURE);}pidvfork();switch(pid){case-1:perror(fork);exit(EXIT_FAILURE);case0:puts(Child exiting.);close(STDOUT_FILENO);exit(EXIT_SUCCESS);default:printf(Child is PID %jd\n,(intmax_t)pid);puts(Parent exiting.);exit(EXIT_SUCCESS);}}运行如下$ ./ex24-2 child - Child exiting. parent - Child is PID16238parent - Parent exiting.我的思路是子进程关闭标准输出后看父进程输出信息是否能成功。不要被题目误导如果把vfork换成fork行为是一样的。但输出顺序是不同的因为vfork保证了子进程先执行。以下是vfork换成fork后的输出$ ./ex24-2 parent - Child is PID16229parent - Parent exiting. child - Child exiting.24-3假设我们可以修改程序源代码如何在某一特定时刻获取进程的核心转储core dump同时让进程继续执行由signal(7)和core(5)可知生成core dump的方式有两种killabort代码如下#includesignal.h#includestdint.h#includestdio.h#includestdlib.h#includeunistd.h#defineTESTSIGSIGABRTintmain(void){pid_tpid;if(signal(SIGCHLD,SIG_IGN)SIG_ERR){perror(signal);exit(EXIT_FAILURE);}pidfork();switch(pid){case-1:perror(fork);exit(EXIT_FAILURE);case0:puts(child - Child exiting.);raise(TESTSIG);puts(child - Never get here.);_exit(0);default:printf(parent - Child is PID %jd\n,(intmax_t)pid);for(;;)pause();puts(parent - Never get here.);}}执行$ ./ex24-3 parent - Child is PID16416child - Child exiting. ^C验证$ coredumpctl list TIME PIDUIDGID SIG COREFILE EXE SIZE Tue2026-07-14 01:52:22 UTC1641610001000SIGABRT present /home/vagrant/tlpi-book/ex/ex24-322.5K其实这道题很简单的但还是花了近1小时原因是发现vfork和fork行为不一。这道题只能用fork而非vfork因为vfork:vfork() differs from fork(2) in that the calling thread is suspended until the child terminates (ei‐ther normally, by calling _exit(2), or abnormally, after delivery of a fatal signal), or it makes acall to execve(2). Until that point, the child shares all memory with its parent, including thestack.24-4.在其他 UNIX 系统实现环境中运行清单 24-5 所示程序fork_whos_on_first.c进行测试以此探究各类系统在调用fork()创建子进程后是如何调度父进程与子进程的。fork_whos_on_first.c在目录procexec下。代码如下#includesys/wait.h#includetlpi_hdr.hintmain(intargc,char*argv[]){intnumChildren,j;pid_tchildPid;if(argc1strcmp(argv[1],--help)0)usageErr(%s [num-children]\n,argv[0]);numChildren(argc1)?getInt(argv[1],GN_GT_0,num-children):1;setbuf(stdout,NULL);/* Make stdout unbuffered */for(j0;jnumChildren;j){switch(childPidfork()){case-1:errExit(fork);case0:printf(%d child\n,j);_exit(EXIT_SUCCESS);default:printf(%d parent\n,j);wait(NULL);/* Wait for child to terminate */break;}}exit(EXIT_SUCCESS);}运行如下parent总是优先于child调度$ ./fork_whos_on_first50parent0child1parent1child2parent2child3parent3child4parent4child以上是在OEL 9.7上的行为。其他的UNIX环境可使用FreeBSD。参见VirtualBox上安装FreeBSD在FreeBSD上编译TLPI示例代码在FreeBSD下的行为是一样的$uname-aFreeBSD freebsd14.4-RELEASE FreeBSD14.4-RELEASE releng/14.4-n273675-a456f852d145 GENERIC amd64 tlpifreebsd:~/tlpi-dist/procexec $ ./fork_whos_on_first50parent0child1parent1child2parent2child3parent3child4parent4child24-5假设在清单 24-6 的程序中子进程也需要等待父进程完成某些操作。为了强制执行这一点需要对程序进行哪些修改清单 24-6即procexec/fork_sig_sync.c。代码如下/* Listing 24-6 */#includesignal.h#includecurr_time.h/* Declaration of currTime() */#includetlpi_hdr.h#defineSYNC_SIGSIGUSR1/* Synchronization signal */staticvoid/* Signal handler - does nothing but return */handler(intsig){}intmain(intargc,char*argv[]){pid_tchildPid;sigset_tblockMask,origMask,emptyMask;structsigactionsa;setbuf(stdout,NULL);/* Disable buffering of stdout */sigemptyset(blockMask);sigaddset(blockMask,SYNC_SIG);/* Block signal */if(sigprocmask(SIG_BLOCK,blockMask,origMask)-1)errExit(sigprocmask);sigemptyset(sa.sa_mask);sa.sa_flagsSA_RESTART;sa.sa_handlerhandler;if(sigaction(SYNC_SIG,sa,NULL)-1)errExit(sigaction);switch(childPidfork()){case-1:errExit(fork);case0:/* Child *//* Child does some required action here... */printf([%s %ld] Child started - doing some work\n,currTime(%T),(long)getpid());sleep(2);/* Simulate time spent doing some work *//* And then signals parent that its done */printf([%s %ld] Child about to signal parent\n,currTime(%T),(long)getpid());if(kill(getppid(),SYNC_SIG)-1)errExit(kill);/* Now child can do other things... */_exit(EXIT_SUCCESS);default:/* Parent *//* Parent may do some work here, and then waits for child to complete the required action */printf([%s %ld] Parent about to wait for signal\n,currTime(%T),(long)getpid());sigemptyset(emptyMask);if(sigsuspend(emptyMask)-1errno!EINTR)errExit(sigsuspend);printf([%s %ld] Parent got signal\n,currTime(%T),(long)getpid());/* If required, return signal mask to its original state */if(sigprocmask(SIG_SETMASK,origMask,NULL)-1)errExit(sigprocmask);/* Parent carries on to do other things... */exit(EXIT_SUCCESS);}}此程序为父子进程设置了一个联络信号SYNC_SIG子进程做一些工作后发送联络信号而父进程则等待这个信号。运行如下$ ./fork_sig_sync[02:39:5816571]Parent about towaitforsignal[02:39:5816572]Child started - doing some work[02:40:0016572]Child about to signal parent[02:40:0016571]Parent got signal如果父进程也需要做一些工作则将之前的同步机制再使用一次即可即子进程等待父进程的同步信号而父进程在完成任务后向子进程发送同步信号。代码如下#includesignal.h#includecurr_time.h/* Declaration of currTime() */#includetlpi_hdr.h#defineSYNC_SIGSIGUSR1/* Synchronization signal */staticvoid/* Signal handler - does nothing but return */handler(intsig){}intmain(intargc,char*argv[]){pid_tchildPid;sigset_tblockMask,origMask,emptyMask;structsigactionsa;setbuf(stdout,NULL);/* Disable buffering of stdout */sigemptyset(blockMask);sigaddset(blockMask,SYNC_SIG);/* Block signal */if(sigprocmask(SIG_BLOCK,blockMask,origMask)-1)errExit(sigprocmask);sigemptyset(sa.sa_mask);sa.sa_flagsSA_RESTART;sa.sa_handlerhandler;if(sigaction(SYNC_SIG,sa,NULL)-1)errExit(sigaction);switch(childPidfork()){case-1:errExit(fork);case0:/* Child *//* Child does some required action here... */printf([%s %ld] Child started - doing some work\n,currTime(%T),(long)getpid());sleep(2);/* Simulate time spent doing some work *//* And then signals parent that its done */printf([%s %ld] Child about to signal parent\n,currTime(%T),(long)getpid());if(kill(getppid(),SYNC_SIG)-1)errExit(kill);/* Now child can do other things... */if(sigsuspend(emptyMask)-1errno!EINTR)errExit(sigsuspend);printf([%s %ld] Child got signal\n,currTime(%T),(long)getpid());_exit(EXIT_SUCCESS);default:/* Parent *//* Parent may do some work here, and then waits for child to complete the required action */printf([%s %ld] Parent about to wait for signal\n,currTime(%T),(long)getpid());sigemptyset(emptyMask);if(sigsuspend(emptyMask)-1errno!EINTR)errExit(sigsuspend);printf([%s %ld] Parent got signal\n,currTime(%T),(long)getpid());/* If required, return signal mask to its original state */if(sigprocmask(SIG_SETMASK,origMask,NULL)-1)errExit(sigprocmask);/* Parent carries on to do other things... */printf([%s %ld] Parent started - doing some work\n,currTime(%T),(long)getpid());sleep(2);/* Simulate time spent doing some work */printf([%s %ld] Child about to signal parent\n,currTime(%T),(long)getpid());if(kill(childPid,SYNC_SIG)-1)errExit(kill);exit(EXIT_SUCCESS);}}