fork()指令
One call,Two return.返回值若为0,说明当前在子进程中;返回值大于0,说明在父进程中,返回值为其子进程的PID
getpid()
返回当前进程的ID
waitpid(pid_t pid,int *statusp,int options)
- pid:
pid>0时只等待进程ID为pid的子进程结束
pid=-1时等待其所有的子进程中的任何一个(只要一个)结束
- options:
options=0(默认情况)时,挂起父进程,等待其子进程结束。返回子进程编号。
options=WNOHANG时,父进程不挂起。如果一个子进程都没有结束的话,返回0;否则返回子进程编号。
- 如果调用函数的进程没有子进程,waitpid返回-1,errno设为ECHILD
wait(&status)
等价于waitpid(-1,&status,0)
WIFEXITED(status):
如果子进程是以exit或者return正常退出的,函数返回值就为true
WEXITSTATUS(status)
前提是WIFEXITED一定为true,此函数返回正常终止的子进程的退出状态,即exit的值
atexit()
在进程结束调用exit时,调用atexit()括号中的注册函数,注册几次就调用几次。并且它的调用顺序和登记顺序是相反的。与压栈顺序有关。
相关习题
- CSAPP书上部分
preparation:
1.http://csapp.cs.cmu.edu/public/code.html 下载.tar文件解压,包含了CSAPP书上的所有代码
2.运行sudo mv csapp.h /usr/include和sudo mv csapp.c /usr/include ,然后再usr/include中修改csapp.h在#endif 之前添加 #include"csapp.c"(我用的vi)
3.在gcc的时候最后加上-lpthread就可以
关于下列代码中用到的Fork和Wait在csapp.c中的定义:
/* $begin forkwrapper */
pid_t Fork(void)
{
pid_t pid;
if ((pid = fork()) < 0)
unix_error("Fork error");
return pid;
}
/* $end forkwrapper */
/* $begin wait */
pid_t Wait(int *status)
{
pid_t pid;
if ((pid = wait(status)) < 0)
unix_error("Wait error");
return pid;
}
/* $end wait */
*8.11这个程序会输出多少个"hello"输出行?

#include "csapp.h"
int main()
{
int i;
for(i=0;i<2;++i)
Fork();
printf("hello\n");
exit(0);
}
运行结果:
一共四行
进程图:
*8.12这个程序会输出多少个hello输出行?
#include "csapp.h"
void doit()
{
Fork();
Fork();
printf("hello\n");
return;
}
int main()
{
doit();
printf("hello\n");
exit(0);
}
总共8行hello
进程图:
*8.13下面程序的一种可能的输出是什么?
#include "csapp.h"
int main()
{
int x = 3;
if (Fork() != 0)
printf("x=%d\n", ++x);
printf("x=%d\n", --x);
exit(0);
}
运行结果:
可能的结果应该还有
和
*8.14下面这个程序会输出多少个"hello"输出行?
/* $begin forkprob5 */
#include "csapp.h"
void doit()
{
if (Fork() == 0) {
Fork();
printf("hello\n");
exit(0);
}
return;
}
int main()
{
doit();
printf("hello\n");
exit(0);
}
/* $end forkprob5 */
一共3个hello
进程图:
*8.15下面这个程序会输出多少个"hello"输出行?
/* $begin forkprob5 */
#include "csapp.h"
void doit()
{
if (Fork() == 0) {
Fork();
printf("hello\n");
return;
}
return;
}
int main()
{
doit();
printf("hello\n");
exit(0);
}
/* $end forkprob5 */
运行结果:
一共五个hello
进程图:
*8.16下面这个程序的输出是什么?
/* $begin forkprob7 */
#include "csapp.h"
int counter = 1;
int main()
{
if (fork() == 0) {
counter--;
exit(0);
}
else {
Wait(NULL);
printf("counter = %d\n", ++counter);
}
exit(0);
}
/* $end forkprob7 */
运行结果:
进程图:
**8.18考虑下面程序可能有的输出
/* $begin forkprob2 */
#include "csapp.h"
void end(void)
{
printf("2"); fflush(stdout);
}
int main()
{
if (Fork() == 0)
atexit(end);
if (Fork() == 0)
printf("0");fflush(stdout);
else
printf("1");fflush(stdout);
exit(0);
}
/* $end forkprob2 */
进程图:
可能输出就是拓扑排序,包括但不止:
112002、102120、100212
运行结果:
**8.19下面的函数会打印多少行输出?用一个n的函数给出答案。假设n>=1.
#include "csapp.h"
/* $begin forkprob8 */
void foo(int n)
{
int i;
for (i = 0; i < n; i++)
Fork();
printf("hello\n");
exit(0);
}
/* $end forkprob8 */
int main(int argc, char **argv)
{
if (argc < 2) {
printf("usage: %s <n>\n", argv[0]);
exit(0);
}
foo(atoi(argv[1]));
exit(0);
}
answer:2^n
解析:每个循环fork一次,一次fork有两个进程,每个进程打印一行hello,然后再进行fork。所以是一个指数的增长。
**8.21下面的程序的可能输出序列是什么?
#include "csapp.h"
/* $begin waitprob3 */
int main()
{
if (fork() == 0) {
printf("a");
exit(0);
}
else {
printf("b");
waitpid(-1, NULL, 0);
}
printf("c");
exit(0);
}
/* $end waitprob3 */
机器输出:
进程图:
一定是abc,除非中间加fflusn或者\n
因为当没有换行符的时候printf的输出其实并没有马上输出,而是放到了缓冲区中去。知道exit的时候进行输出,那么即使父进程先进行printf b的操作,b仍然在缓冲区中等待子进程结束打印完a后再继续运行知道父进程结束将bc输出。
到这里csapp书后关于fork和wait部分的题目写了一大半点点。顺着刚才的缓冲区和fork间的冲突我们来看看一些企业题目。
- 课外习题
1.以下程序将输出多少个*?
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main(void){
int i;
for(i=0;i<2;i++){
fork();
printf("*");
}
return 0;
}
answe:8
程序结果:
解释:
罪魁祸首就在于标准I/O并未在fork时就输出,而存在于缓冲区中。在fork时,子进程自动继承了父进程的缓冲区,导致比预期多输出两个*
再来看看另外一道好玩的题
2.假设下面这个程序编译后名字是main,请问这个程序执行后,系统总共会出现多少个main?
#include <stdio.h>
#include <stdlib.h>
#include <csapp.h>
int main(int argc,char* argv[])
{
Fork();
Fork()&&Fork()||Fork();
Fork();
printf("*\n");
sleep(5);
}
answer:产生了19个新的进程,一共有20个进程
进程图:
具体解析:
关键在于第二局逻辑关系符的语句
先抽象为A&&B||C
就有如下关系式子:
对应到fork两次返回的值0和大于0,即子进程为假,父进程为真
那么就有fork2为假时,fork2&&fork3一定为假,就不用判断fork3即不执行,直接做fork4.
fork2为真实,fork2&&fork3的真值不确定,一定会执行fork3.如果fork3为假则还要执行fork4来进行判别,如果为真,则跳过fork4
所以这里有第一行fork生成了一个新的进程,现在一共有两个进程。
第二行的3个fork生成了5个进程。所以有2 * 5=10个进程。
第三行的fork生成了两个进程。所以有10 *2=20个进程。
相应的总共就有20个进程,减去原有的一个进程,就有19个新进程。
- 一些额外的课堂代码
void fork0()
{
if (fork() == 0) {
printf("Hello from child\n");
}
else {
printf("Hello from parent\n");
}
}
机器运行结果:
1.
void fork1()
{
int x = 1;
pid_t pid = fork();
if (pid == 0) {
printf("Child has x = %d\n", ++x);
}
else {
printf("Parent has x = %d\n", --x);
}
printf("Bye from process %d with x = %d\n", getpid(), x);
}
机器运行结果
进程图:
2.
void fork2()
{
printf("L0\n");
fork();
printf("L1\n");
fork();
printf("Bye\n");
}
运行结果:
进程图:
3.
void fork3()
{
printf("L0\n");
pid_t pid;
pid=fork();
if(pid==0)printf("l1\n");
else printf("L1\n");
pid =fork();
if(pid==0) printf("l2\n");
else printf("L2\n");
pid=fork();
if(pid==0) printf("bye\n");
else printf("Bye\n");
}
真机运行结果:
进程图:
4.
void fork4()
{
printf("L0\n");
if (fork() != 0) {
printf("L1\n");
if (fork() != 0) {
printf("L2\n");
}
}
printf("Bye\n");
}
机器运行结果:
进程图:
5.
void fork5()
{
printf("L0\n");
if (fork() == 0) {
printf("L1\n");
if (fork() == 0) {
printf("L2\n");
}
}
printf("Bye\n");
}
机器运行结果:
进程图:
6.
void cleanup(void) {
printf("Cleaning up\n");
}
void fork6()
{
atexit(cleanup);
fork();
exit(0);
}
机器执行结果:
进程图:
7.
void fork7()
{
if (fork() == 0) {
/* Child */
printf("Terminating Child, PID = %d\n", getpid());
exit(0);
} else {
printf("Running Parent, PID = %d\n", getpid());
while (1)
; /* Infinite loop */
}
}
机器运行结果:
父进程进入死循环,机器执行完子进程以后子进程结束进入僵死defunct状态。
用kill-9命令杀死父进程,则僵死的子进程被init进程回收
具体运行:
8.
void fork8()
{
if (fork() == 0) {
/* Child */
printf("Running Child, PID = %d\n",
getpid());
while (1)
; /* Infinite loop */
} else {
printf("Terminating Parent, PID = %d\n",
getpid());
exit(0);
}
}
机器运行结果:
父进程结束,子进程尚在运行,则子进程变成孤儿进程,进程终止时被init回收。在这里,子进程进入死循环,必须用kill指令强制杀死。
9.
void fork9()
{
int child_status;
if (fork() == 0) {
printf("HC: hello from child\n");
//sleep(10);
exit(0);
} else {
//sleep(5);
printf("HP: hello from parent\n");
wait(&child_status);
printf("CT: child has terminated\n");
}
printf("Bye\n");
}
程序运行结果:
进程图:
为了更好的帮助了解wait函数,可以用注释的两个sleep函数来看一下wait的效果
10.
#define N 5
void fork10()
{
pid_t pid[N];
int i, child_status;
for (i = 0; i < N; i++)
if ((pid[i] = fork()) == 0) {
exit(100+i); /* Child */
}
for (i = 0; i < N; i++) {
/* Parent */
pid_t wpid = wait(&child_status);
if (WIFEXITED(child_status))
printf("Child %d terminated with exit status %d\n",
wpid, WEXITSTATUS(child_status));
else
printf("Child %d terminate abnormally\n", wpid);
}
}
程序运行结果:
进程图:
解释一下:首先fork的时候会把所有环境都复制一份,但是程序的第二个for循环其实可以看到发生在父进程中,父进程的pid数组保留了所有创建的子进程的ID号。然后for循环开始执行wait函数,随机找一个结束的子进程返回它的exit的值并回收该进程,然后打印出子进程。wpid时wait函数执行成功时返回的子进程的ID号,WEXITSTATUS(child_status)返回子进程exit的值。
11.
#define N 5
void fork11()
{
pid_t pid[N];
int i;
int child_status;
for (i = 0; i < N; i++)
if ((pid[i] = fork()) == 0)
exit(100+i); /* Child */
for (i = N-1; i >= 0; i--) {
pid_t wpid = waitpid(pid[i], &child_status, 0);
if (WIFEXITED(child_status))
printf("Child %d terminated with exit status %d\n",
wpid, WEXITSTATUS(child_status));
else
printf("Child %d terminate abnormally\n", wpid);
}
}
运行结果:
进程图:
解释一下:几乎和上面一个一样,只是这里调用的时waitpid函数并且pid给了指定的值。就是父进程记录的所有的子进程的ID号进行调用waitpid函数,等待ID号为pid[i]的进程结束,打印相关信息。