linux中对fork()的初步理解

2 篇文章 0 订阅
2 篇文章 0 订阅
  1. fork()初识
  2. 初识fork代码
  3. 缓冲区独立
  4. 回收子进程

一,fork()初识

介绍

fork系统调用用于创建一个新的进程,称为子进程,它与进程(系统调用fork的进程)同时运行,此进程称为父进程。创建新的进程后,然后把父进程的所有值都复制到新的子进程中,只有少数值与原来的进程的值不同。相当于克隆了一个父进程。两个进程执行fork()系统调用之后的下一条指令。

fork()返回值

fork()会返回一个整数值,下面是fork()返回的不同值。

fork()<0   //返回值小于0时,表示创建子进程失败。
fork()==0  //返回值等于0时,表示当前进程为子进程。
fork()>0   //返回值大于0时,表示当前进程为父进程,且返回的值为其子进程的进程ID。
fork函数说明

由fork函数创建的新进程被称为子进程,注意fork函数被调用一次,但返回两次。一次是子进程中返回值0,一次是父进程中返回的子进程ID。

子进程是父进程的副本,它获得父进程的数据空间,堆,栈等资源的副本。但注意,子进程所有的是这些储存空间的副本,因此,与父进程不共享这些储存空间。Unix将复制父进程的地址空间的内容给子进程,因此,子进程有了独立的地址空间。

getpid() 和 getppid()

getpid(): 用于获取当前进程的进程ID。
getppid(): 用于获取当前进程的父进程ID。

二,初识fork代码

代码一
int main()
{
	pid_t pid;
	int x = 1;
	
	pid = fork();
	if (pid == 0){	/*child*/
		printf("child: x=%d\n",++x);
		exit(0);
	}
	/*parent*/
	printf("parent: x=%d\n",--x);
	exit(0);
}

运行结果:

parent:x=0;
chlid:x=2;

如何看待这段代码,我想用进程结构图可以很容易看明白:
在这里插入图片描述
每一个节点代表该进程所执行的一个动作。
首先,入口函数main为第一个节点,该进程遇到“pid=fork();”创建了一个进程,为第二个节点,因为创建了一个子进程,因此有了一个分支。当pid>0时,当前为父进程,运行printf(–x),为下一个节点,然后退出。当pid=0时,当前为子进程,执行printf(++x),为一个节点,然后执行exit(0)结束子进程,因此不再执行下面的printf程序。

当然执行的结果也可能是如下:

chlid:x=2;
parent:x=0;

这是因为父进程与子进程两者处于独立的空间,两者运行互不干扰,以至于是先执行父进程还是子进程都是随机的,是由操作系统的调度器决定的。

由于两个进程的地址空间是独立的,所以在此代码中的X也是独立的,因此child:x=2而不是等于1。

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

执行结果:

L0
Bye
L1
Bye
L2
Bye

同样再次利用进程结构图帮助我们分析:
在这里插入图片描述
最下面这一条进程路线为主进程,其余的分支都为子进程。

通过进程结构图我们可以发现 printf(“Bye\n”)这一条语句执行了三次,因为父进程将代码,数据等资源全部复制给了子进程,所以每一个进程都会执行这条语句,且每一个进程独立运行。

进程之间的前后顺序也是随机的,所以每一次运行程序的结果都可能不一样。

三,缓冲区独立

调用fork后,子进程获得父进程数据段、堆和栈的副本。
缓冲区是在堆上的,所以子进程也获得了父进程的缓冲区。

由于我在缓冲区上踩了坑,所以拿出来讲一下。

缓冲区在父进程和子进程之间也是独立的

以下例子:

void main(){
printf("aa");//aa被放入缓冲区中
if(fork()==0){//子进程有和父进程相同的缓冲区,此时aa在缓冲区中未被输出。
	printf("bb\n");//bb进入缓冲区,连同aa一起被输出
	exit(0);
}
printf("cc\n");
exit(0);
}

输出结果:

aacc
aabb

在fork创建子进程之前,aa在缓冲区中未被释放,因此子进程拷贝了同样的缓冲区,所在输出“bb”的时,bb也加入缓存区,当遇见“\n”时,缓冲区清空,同时输出“aabb”。

可以通过fflush(stdout)释放缓冲区。

疑惑:
第一个printf(“”aa“”);到底有没有被输出? 即“aacc”中的aa是在fork()前输出还是在fork()后输出?

例子:

void main(){
printf("aa");
if(fork()==0){
	printf("bb\n");
	exit(0);
}
sleep(2);//让子进程先执行
printf("cc\n");
exit(0);
}

如果“aa”在fork()前输出,那么输出结果应该为:
aaaabb
cc
如果“aa”是在输出“cc”时一起被输出,那么结果应该为:
aabb
aacc

最终实际输出为:
在这里插入图片描述

眼见为实:
所以第一个“printf(“aa”)”为被输出,而是随着后面的cc一起被输出。

四,回收子进程

当一个进程由于某种原因终止时,内核并不是立刻把他从系统中清除。相反,进程被保持在一种已终止的状态中,直到被他的父进程回收。当父进程回收已终止的子进程时,内核将子进程的退出状态传递给父进程,然后抛弃已终止的进程,该进程就不在了。一个终止了但还未被回收的进程称为僵死进程

但是,即使僵死进程没有运行,它们仍然消耗系统的内存资源。

因此,一个进程可以调用waitpid函数来等待它的子进程终止或停止,以便父进程回收,从而没有僵死进程。

#include<sys/types.h>
#include<sys/wait.h>

pid_t waitpid(pid_t pid,int  *statusp,int options);
//返回:如果成功,则为子进程的PID,如果WNOHANG,则为0,如果其他错误,则为-1.

默认情况下(当options=0时),waitpid挂起调用进程的执行,直到它的等待集合(wait set)中的一个子进程终止。

等待集合的成员是有参数pid来确定的:

如果pid>0,等待集合就是一个单独的子进程,它的进程ID等于pid。
如果pid=-1,等待集合就是由父进程所有的子进程组成的。

wait函数

wait函数是waitpid函数的简单版本:

#include<sys/types.h>
#include<sys/wait.h>

pid_t wait(int *statusp);
//返回:如果成功,则为子进程的PID,如果出错,则为-1.

调用wait(&statusp)等价于调用waitpid(-1,&statusp,0)。

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

首先创建了N个子进程,通过调用wait函数来得到关于导致返回的子进程的状态信息wpid,再通过WIFEXITED(child_status)来验证子进程是否是正常的终止退出状态。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值