深入理解计算机系统------第八章fork函数运用

近期学到了深入理解计算机系统基础第八章异常控制流,接下来说一说在这一章C位出道的fork()函数吧。

fork函数

父进程通过调用fork函数来创建一个新的运行的子进程。

#include<sys/types.h>
#include<unistd.h>
pid_t fork(void);
										返回:子进程返回0,父进程返回子进程的PID,如果创建出错,返回-1

可以看出,fork函数是一个无参数返回一个整数型的函数。
新创建的子进程几乎完全跟父进程相同(几乎相同!不完全)。子进程得到与父进程用户级虚拟地址空间相同的(但是独立)一份副本,包括代码、数据段、堆、共享库、用户栈(想成就是继承了父进程的所有知识、能力,子承父业吧)。两者最大区别在于不同的PID(进程号)。
fork函数最大特点:一次调用,两次返回!!! 现在父进程调用一次,子进程还来一次(说了子进程能干父进程所有事情,当然也要返回啦)
我们根据返回的值判断当前程序是处于子进程还是父进程:子进程返回0,父进程返回子进程PID(总不为0).
fork系统调用注意

  1. fork系统调用之后父进程和子进程是交替执行,父子进程是处于不同空间中的。
  2. fork系统调用的一次调用存在两次返回,此时二个进程处于独立的空间,各自执行自己的参数。
    我们根据以下操作运行例子1:

fork0

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

在这里插入图片描述
可以看出,有两次输出;第一次到fork()==0的时候,是在父进程中执行,因为返回的不为0(子进程PID不为0),所有执行else后面语句;之后到子进程中(子进程跟父进程几乎一样),这时是在子进程中,所以fork返回0,执行child语句。

fork1

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

在这里插入图片描述
可以看出,使用fork()后,第一次是在父进程中执行,pid!=0(为子进程PID),所以执行–x和父进程PID(ppid)。
重点来了,之后到了子进程中:因为子进程里面内容(代码、数据)跟父进程一样,所有子进程里面的x还是=1(就是独立的副本),返回pid=0,执行++x和子进程的pid;可以看出子进程和父进程的ID是连着一起的,子进程PID就是父进程加1.

fork2

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

在这里插入图片描述
首先在父进程中输出L0,然后第一次调用fork,此时已经有一个子进程(1),然后父进程中输出L1,再次调用一个fork有了新的子进程(2),同时子进程(1)由于跟父进程代码一样,也有一个子进程(1_1),接着父进程输出Bye,回到子进程(2)输出一样的Bye;接着回到子进程(1),输出L1、Bye、Bye(跟父进程一样操作)。
在这里插入图片描述

fork3

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

在这里插入图片描述
在这里插入图片描述
可以看出,不管fork调用几次,都是先进行父进程里面的操作,再进行相应的子进程的程序,多次用fork函数,就是在相应的父进程(或者子进程)再次创建子进程(子子进程),然后一样依照顺序来执行程序。
先输出L0,调用fork1,有了子进程1;然后父进程中输出L1(子进程1也要输出,不过要等着),然后又一次fork2,生成子进程2(子进程1也要生成子进程1_1),输出L2;执行fork3(漏画了),父进程生成子进程3,子进程生成子进程2_1,子进程1生成子进程1_2,子进程1_1生成子进程1_1_1,然后最后都输出Bye。要注意,是按照顺序(一条线)执行完,不能随意穿插。

fork4

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

在这里插入图片描述

这个程序又有意思了,首先输出L0,然后父进程调用fork,有了一个子进程1,父进程中fork返回不为0,所以输出L1,接着父进程又调用fork,有了子进程2,同理输出L2,然后输出Bye;返回到子进程2中,从fork2开始执行,由于返回0,直接到最外层输出Bye;再回到子进程1,从fork1执行,返回0,直接到最外层输出Bye。
在这里插入图片描述

fork5

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

在这里插入图片描述
这个程序跟上一个就是一个等号的区别,进程图原理一样,输出L0后,父进程返回0,直接输出Bye;然后到了fork1生成的子进程1,调用fork2,生成子进程1_1,子进程1中的fork1也返回0(子进程1是子进程1_1的父进程),直接Bye;最后进入子进程1_1,输出L2,在输出Bye.
在这里插入图片描述

fork6

void cleanup(void) {
    printf("Cleaning up\n");
}
void fork6()
{
    atexit(cleanup);
    fork();
    exit(0);
}
PS:C 库函数 int atexit(void (*func)(void)) 当程序正常终止时,调用指定的函数 func。您可以在任何地方注册你的终止函数,但它会在程序终止的时候被调用。

在这里插入图片描述
这里开始没注意,把cleanup函数放在了main函数后面,出了错,放在前面就好了。
父进程退出的时候执行一次Cleanup,子进程退出时也执行一次。

fork7

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

在这里插入图片描述
这里,父进程并没有退出,所以导致程序一直在运行,成为僵尸(zombies.)进程,一直在后台运行,具体表现如下,无法在终端创建新程序,只有Ctrl+C或者Ctrl+Z退出此程序才可以进行新的操作。
在这里插入图片描述
fork8

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,但是之后执行死循环,子进程并没有退出,成为zombies,浪费空间。

fork9

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");
}
PS:使用wait(&status)函数等待子进程信号,我们可以通过分析status来得到子进程的状态。

在这里插入图片描述
emmmm,又打错"}"位置了。。。。。。

这个程序使用了wait函数,就是等一会儿在运行wait后面的内容,那人家子进程还在排队等着呢,父进程要停一会儿就让人家运行呗。于是“HP”之后直接到子进程里面,输出“HC”之后直接退出;插了队,完事儿了就得让之前的继续嘛,于是父进程开始未完的事业,输出“CT。。。”,最后输出Bye。

fork10

#define N 5
/* 
 * fork10 - Synchronizing with multiple children (wait)
 * Reaps children in arbitrary order
 * WIFEXITED and WEXITSTATUS to get info about terminated children
 */
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和状态值,之后再进行上一层的输出。

总结

linux创建一个新的进程是从复制开始的,在系统内核里首先是将父进程的进程控制块PCB进行拷贝,然后再根据自己的情况修改相应的参数,获取自己的进程号,再开始执行。我觉得整个过程重点就是理解子进程如何创建,在内核调用的几个重要的内核函数,以及子进程怎么返回开始执行的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值