Csapp之fork()函数

fork()定义:

pid_t fork( void);
(pid_t 是一个宏定义,其实质是int 被定义在#include<sys/types.h>中)
返回值: 若成功调用一次则返回两个值,子进程返回0,父进程返回子进程ID;否则,出错返回-1
一个现有进程可以调用fork函数创建一个新进程。由fork创建的新进程被称为子进程(child process)。fork函数被调用一次但返回两次。两次返回的唯一区别是子进程中返回0值而父进程中返回子进程ID。 子进程是父进程的副本,它将获得父进程数据空间、堆、栈等资源的副本。注意,子进程持有的是上述存储空间的“副本”,这意味着父子进程间不共享这些存储空间。
UNIX将复制父进程的地址空间内容给子进程,因此,子进程有了独立的地址空间。在不同的UNIX (Like)系统下,我们无法确定fork之后是子进程先运行还是父进程先运行,这依赖于系统的实现。所以在移植代码的时候我们不应该对此作出任何的假设。
例子:

void fork0() 
{
    if (fork() == 0) {
	printf("Hello from child\n");
    }
    else {
	printf("Hello from parent\n");
    }
}

ice@ice-virtual-machine:~/chap8$ ./forks
Hello from parent
ice@ice-virtual-machine:~/chap8$ Hello from child

看结果有两次输出,第一次到if时,由于是父进程,故执行else,进入子进程,fork()==0,故执行if语句

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);
}

./forks 1
Parent has x = 0
Bye from process 2405 with x = 0
ice@ice-virtual-machine:~/chap8$ Child has x = 2
Bye from process 2406 with x= 2

这个程序与fork0原理相同,第一次执行父进程,pid!=0所以执行else语句,接下来在执行子进程时,由于子进程是父进程的独立的副本,x=1,所以再执行if语句;同时也可以看出子进程与父进程的pid是连着的,子进程的pid是父进程的+1。

void fork2()
{
    printf("L0\n");
    fork();
    printf("L1\n");    
    fork();
    printf("Bye\n");
}

./forks 2
L0
L1
Bye
ice@ice-virtual-machine:~/chap8$ Bye
L1
Bye
Bye

上述程序:首先父进程进行输出L0,然后运行第一个fork(),产生一个子进程1,然后父进程输出L1,接着在第二个fork时又产生一个子进程2,同时在子进程1中也产生了一个子进程(1,2),然后父进程输出Bye,接着在父进程的子进程2中输出Bye,然后回到子进程1中,子进程1输出L1,接着跟父进程类似,输出Bye,Bye。

void fork3()
{
    printf("L0\n");
    fork();
    printf("L1\n");    
    fork();
    printf("L2\n");    
    fork();
    printf("Bye\n");
}

./forks 3
L0
L1
L2
Bye
ice@ice-virtual-machine:~/chap8$ Bye
L2
Bye
L1
L2
Bye
Bye
Bye
L2
Bye
Bye
在这里插入图片描述

void fork4()
{
    printf("L0\n");
    if (fork() != 0) {
	printf("L1\n");    
	if (fork() != 0) {
	    printf("L2\n");
	}
    }
    printf("Bye\n");
}

./forks 4
Lo
L1
Bye
ice@ice-virtual-machine:~/chap8$ Bye
Bye

void fork5()
{
    printf("L0\n");
    if (fork() == 0) {
	printf("L1\n");    
	if (fork() == 0) {
	    printf("L2\n");
	}
    }
    printf("Bye\n");
}

./forks 5
L0
Bye
ice@ice-virtual-machine:~/chap8$ L1
Bye
L2

void cleanup(void) {
    printf("Cleaning up\n");
}
void fork6()
{
    atexit(cleanup);
    fork();
    exit(0);
}

./forks 6
Cleaning up
ice@ice-virtual-machine:~/chap8$ Cleaning up

这里注意一下:int atexit(void (*func)(void)) 当程序正常终止时,调用指定的函数 func。您可以在任何地方注册你的终止函数,但它会在程序终止的时候被调用。
父进程退出的时候执行一次Cleanup,子进程退出时也执行一次。
在这里插入图片描述

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 */
    }
}

./forks 7
Running Parent, PID = 2049
Terminating Child , PID = 2050
这里,父进程并没有退出,所以导致程序一直在运行,成为僵尸(zombies.)进程,一直在后台运行,具体表现如下,无法在终端创建新程序,只有Ctrl+C或者Ctrl+Z退出此程序才可以进行新的操作。
./forks 7
Running Parent, PID = 2049
Terminating Child , PID = 2050
^C
ice@ice-virtual-machine:~/chap8$

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);
    }
}

和前面的一样,父进程返回非0,所以输出PPID;然后进入子进程,输出PID,但是之后进入死循环,子进程并没有退出,成为僵尸进程。
ice@ice-virtual-machine:~/chap8$ ./fords 8
Terminating Parent ,PID = 2059
ice@ice-virtual-machine:~/chap8$ Running Child, PID = 2060
ps
PID TTY TIME CMD
1806 pts/0 00:00:00 bash
2060 pts/0 00:00:14 forks
2061 pts/0 00:00:00 ps

void fork9()
{
    int child_status;

    if (fork() == 0) {
	printf("HC: hello from child\n");
        exit(0);
    } else {
	printf("HP: hello from parent\n");
	wait(&child_status);
	printf("CT: child has terminated\n");
    }
    printf("Bye\n");
}

注意一下:pid_t wait(int *status)
进程一旦调用了wait,就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经退出,如果让它找到了这样一个已经变成僵尸的子进程,wait就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。
上述程序,父进程需在调用wait()时等待子进程结束,再执行下面的操作。
ice@ice-virtual-machine:~/chap8$ ./forks 9
HP:hello from parent
HC: hello from child
CT: child has terminated
Bye

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);
    }
}

这个程序一直for循环,让每个子进程有了自己的状态值,相应的父进程一直在wait着子进程,当最深的子进程推出时,输出pid和状态值,之后再进行上一层的输出。
ice@ice-virtual-machine:~/chap8$ ./forks 10
Child 2096 terminated with exit status 103
Child 2097 terminated with exit status 104
Child 2095 terminated with exit status 102
Child 2094 terminated with exit status 101
Child 2093 terminated with exit status 100

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);
    }
}

这里注意:pid_t waitpid(pid_t pid,int *status,int options);

返回:如果成功,则为子进程的PID,如果WNOHANG,则为0,如果其他错误,则为-1

以上程序在父进程中等待子进程退出,并输出他们的pid。
ice@ice-virtual-machine:~/chap8$ ./forks 11
Child 2103 terminated with exit status 104
Child 2102 terminated with exit status 103
Child 2101 terminated with exit status 102
Child 2100 terminated with exit status 101
Child 2099 terminated with exit status 100

下面我们总结一下:
1.fork函数调用一次,返回两次。
2.fork 使子进程得到返回值0的理由:一个子进程只会有一个父进程,所以子进程可以通过调用getpid函数得到父进程的ID。
3.进程ID 0 总是有内核交换进程使用,所以一个子进程的进程ID不可能为0。
4.fork 之后进程的动作:子进程和父进程继续执行 fork 调用之后的指令。
5.父进程和子进程谁先被执行,完全由进程调度器决定,谁先调度,是不确定的。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值