linux进程管理---fork与vfork的区别

进程管理及控制

fork函数
linux下创建进程主要是fork函数,linux下所有的进程都由进程init(PId=1)直接或间接创建

#include<unistd.h>
pid_t fork(void);

若执行成功,父进程中将返回子进程(新创建的进程)的PID,类型为pid_t,子进程将返回0,以区别父子进程若执行失败,则在父进程中返回-1,错误原因存储在errno中

fork函数调用成功后,将为子进程申请PCB(进程控制块)和用户内存空间。子进程将会复制父进程的几乎所有信息,在用户空间将复制父亲用户空间的所有数据(代码段、数据段、BSS、堆、栈),复制父亲进程内核空间PCB中的绝大多数信息。子进程从父进程继承下列属性:有效用户/组号、进程组号、环境变量、对文件的执行时关闭标志、信号处理方式设置、信号屏蔽集合、当前工作目录、根目录、文件模式掩码、文件大小限制、打开的文件描述符(共用同一个文件表项)

vfork函数
#include<unistd.h>
pid_t vfork(void);

若执行成功,父进程中将返回子进程(新创建的进程)的PID,类型为pid_t,子进程将返回0,以区别父子进程若执行失败,则在父进程中返回-1,错误原因存储在errno中

Fork与vfork的区别
1.执行顺序

使用fork创建一个子进程,哪个进程先执行取决于系统的调度。
而vfork创建一个子进程vfork保证子进程先运行,子进程调用exec或者exit之前父进程处于阻塞等待状态。
2.子进程的内存空间

fork 将父进程的内存数据copy到子进程中,相当于父进程的一个副本。

vfork函数创建一个子进程时,操作系统并不将父进程的地址空间完全复制到子进程,用vfork函数创建的子进程共享父进程的地址空间,
也就是说子进程完全运行在父进程的地址空间上。子进程对该地址空间中任何数据的修改同样为父进程所见。
vfork不会拷贝父进程的地址空间,这大大减小了系统开销。

使用vfork时要谨慎,最好不要允许子进程修改与父进程共享的全局变量和局部变量

一个是copy,一个是share。你 man vfork 一下,你可以看到,vfork是这样的工作的,1)保证子进程先执行。2)当子进程调用exit()或exec()后,父进程往下执行。那么,为什么要干出一个vfork这个玩意? 原因是这样的—— 起初只有fork,但是很多程序在fork一个子进程后就exec一个外部程序,于是fork需要copy父进程的数据这个动作就变得毫无意了,而且还很重,所以,搞出了个父子进程共享的vfork。所以,vfork本就是为了exec而生。为什么return会挂掉,exit()不会?从上面我们知道,结束子进程的调用是exit()而不是return,如果你在vfork中return了,那么,这就意味main()函数return了,注意因为函数栈父子进程共享,所以整个程序的栈就跪了。如果你在子进程中return,那么基本是下面的过程:

子进程的main() 函数 return了

而main()函数return后,通常会调用 exit()或相似的函数(如:exitgroup())
这时,父进程收到子进程exit(),开始从vfork返回,但是尼玛,老子的栈都被你干废掉了,你让我怎么执行?(注:栈会返回一个诡异一个栈地址,对于某些内核版本的实现,直接报“栈错误”就给跪了,然而,对于某些内核版本的实现,于是有可能会再次调用main(),于是进入了一个无限循环的结果,直到vfork 调用返回 error)

好了,现在再回到 return 和 exit,return会释放局部变量,并弹栈,回到上级函数执行。exit直接退掉。如果你用c++ 你就知道,return会调用局部对象的析构函数,exit不会。(注:exit不是系统调用,是glibc对系统调用 _exit()或_exitgroup()的封装)可见,子进程调用exit() 没有修改函数栈,所以,父进程得以顺利执行。

下面是具体实例

/************************************************************************
    > File Name: IO_vfork.c
    > 作者:YJK 
    > Mail: 745506980@qq.com 
    > Created Time: 2019年10月15日 星期二 15时45分10秒
 ***********************************************************************/
#include<stdio.h>
#include<fcntl.h>
#include<unistd.h>
#include<stdlib.h>
/*
 *使用vfork父子进程共享代码段与数据段  共享内存空间
 *	由于父子进程内存共享,当子进程刷新缓冲区时父进程的缓冲区同样被刷新
 * */
int main(int argc,char *argv[])
{
	pid_t pid=-1;
	int i=1;
	printf("hello linux");
	if((pid=vfork())==-1)
	{
		perror("vfork");
		exit(-1);
	}
	if(pid==0)
	{
		i++;
		printf("child i=%d\t",i);
	//	_exit(0);	//结束子进程但不刷新缓冲区	
		return 0;	//main函数栈空间被释放,父进程从vfork返回时,栈空间已不存在,栈错误	
	//	exit(0);	//结束进程,刷新缓冲区
	}
	else   //使用vfork创建的子进程 在子进程未结束之前父进程处于阻塞状态
	{
		printf("parent i=%d\t",i);
		printf("------------n");
		//exit(0);
		//_exit(0);//结束父进程但不刷新缓冲区
	}
	return 0;
}

在这里插入图片描述
错误原因:
在子进程中 return 0,main函数栈空间被释放,父进程从vfork返回时,栈空间已不存在,栈错误。

首先return与exit的区别
函数exit用于退出进程,在正式释放资源之前,将以反序方式执行有on_exit()函数和atexit()函数注册的清理函数,同时刷新流缓冲区;

void exit(int status)

如果执行成功没有返回值,并把参数status(用来标识退出状态)返回给父进程
如果失败返回-1 失败原因存放在errno中

Return

c语言关键字return与函数exit()在main函数中完成相同的操作,但两者有本质的区别

1.return退出当前函数,exit()函数退出当前进程,因此在main()函数里面return 0和exit()完成一样的功能,但是return 0之后会将main()函数的栈空间释放(弹栈),如果在main函数中使用vfork创建子进程,子进程执行return 0之后,子进程执行完毕,父进程由vfork返回处执行,但是此时main函数栈空间已不存在,便会出现栈错误。vfork 创建的子进程一定不能调用exit()函数(他的作用是调用父进程建立的出口程序并刷新父进程的stdio缓冲区)

2.return仅从子函数中返回,并不退出进程,调用exit()时要调用一段终止处理程序,然后关闭所有I/O流

在进程中exit()与_exit()的区别

linux man手册里明确说明vfork()之后,子进程只应该调用_exit()或者exec函数族,甚至调用exit()都是不正确的!

(1)_exit()执行后立即返回给内核,而exit()要先执行一些清除操作,然后将控制权交给内核,_exit不会处理标准IO缓冲区。
(2)调用_exit函数时,其会关闭进程所有的文件描述符,清理内存以及其他一些内核清理函数,但不会刷新流(stdin, stdout, stderr …). exit函数是在_exit函数之上的一个封装,其会调用_exit,并在调用之前先刷新流。

(3)exit()函数与_exit()函数最大区别就在于exit()函数在调用exit系统之前要检查文件的打开情况,把文件缓冲区的内容写回文件。由于Linux的标准函数库中,有一种被称作“缓冲I/O”的操作,其特征就是对应每一个打开的文件,在内存中都有一片缓冲区。每次读文件时,会连续的读出若干条记录,这样在下次读文件时就可以直接从内存的缓冲区读取;同样,每次写文件的时候也仅仅是写入内存的缓冲区,等满足了一定的条件(如达到了一定数量或遇到特定字符等,例如标准输出stdout为行缓存,在使用printf为stdout时,如果缓冲区写满或者遇到\n时才会刷新缓冲区将缓冲区的内容输出到屏幕上),再将缓冲区中的内容一次性写入文件。这种技术大大增加了文件读写的速度,但也给编程代来了一点儿麻烦。比如有一些数据,认为已经写入了文件,实际上因为没有满足特定的条件,它们还只是保存在缓冲区内,这时用_exit()函数直接将进程关闭,缓冲区的数据就会丢失。因此,要想保证数据的完整性,就一定要使用exit()函数

exit与_exit区别实例

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main(int argc,char *argv[])
{
	printf("hello linux\t");//stdout为行缓存当缓冲区写满或遇到\n时刷新缓冲区
	//exit(0);
	_exit(0);
	return 0;
}

由于_exit未刷新stdio的缓冲区,所以终端上并无输出
在这里插入图片描述
使用exit结束进程会刷新缓冲区
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值