Linux上的 fork 和 vfork 的区别

Linux 编程的 fork 和 vfork 的区别

fork函数:

一个进程可以调用fork函数创建一个新进程

#include<unistd.h>
pid_t fork();  //返回值:子进程返回0,父进程返回子进程ID,若出错返回-1

需要注意的是fork函数调用一次返回两次,子进程的返回值是0,父进程的返回值是子进程的PID,因此通常利用这个区别去设置判断语句令父子进程执行不同的操作。

现在的操作系统基本都采用了COW机制,因此不再是完全的拷贝父进程的所有内容了。将会对父进程区域进行一个共享,内核将其权限改为只读,如果父子进程中任何一个进程要修改该内容,则引发页错误并对其进行一个拷贝然后再写。
在这里插入图片描述

因为fork函数有两个返回值,还要注意的是一旦调用fork函数就会令其一个函数执行同样的code,因此这里会执行两次判断,父子进程判断的结果也会不同。运行结果如下:
在这里插入图片描述
可以看到父子进程都执行了一遍。 一般来说,fork之后的父子进程的执行顺序是不确定的,这个取决于调度算法。

用一个程序说明子进程对变量的改变不影响父进程中该变量的值:
在这里插入图片描述
查看运行结果:
在这里插入图片描述
直接将结果输出,则可以看到子进程对变量进行了修改,但是父进程中该变量的值并没有受到影响

还有一个有趣的现象,将标准输出进行输出时,只得到一个printf,但是输出到文件却有两个printf。这里的原因:write函数是不带缓冲的,标准IO库是带缓冲的,标准IO库中的缓冲会由换行符清洗。因此,再fork之前调用了一次printf,但是该数据仍在缓冲区,因此子进程会将该区域同样进程COW机制(过去则是直接拷贝),故父子进程都会有带有该内容的缓冲区。故第二个printf时会把将内容继续添加到缓冲区,exit时将缓冲区内容写入到对应的文件。

父子进程对文件的共享:
在这里插入图片描述
父子进程之间的相同与不同:

子进程继承父进程的打开文件,还有用户ID组ID,工作目录根目录等,以及环境、资源限制。重点是对大部分内存内容都采用COW机制共享了。

子进程和父进程的fork返回值不同,PID不同,子进程的关于时间的量将更新为0。

fork可能失败,主要原因:

  • 系统中进程太多,超出了限制
  • 实际用户的ID太多,超出了系统限制

fork函数的用法:

扫描二维码关注公众号,回复: 11607019 查看本文章
  • 父进程希望复制自己并使得父子进程执行不同的代码段。 网络服务中:父进程等待客户端请求,请求到达后父进程调用fork产生子进程去处理,父进程继续等待之后的服务请求
  • 父进程希望执行一个完全不一样的程序。子进程fork返回然后exec即可,shell中的命令基本都是这样实现的。

vfork函数

vfork和fork的调用和返回基本一致,通常只用于:创建一个新进程,该新进程的目的是exec一个新程序。

vfork创建一个子进程,但是该子进程并不对父进程地址空间进行拷贝,甚至不是COW的共享模式,而是直接共享。因此如果让子进程对父进程数据进行读或写都可能产生段错误。主要原因在于,因为vfork产生的子进程是要exec一个新程序的,因此复制父进程的空间是完全多此一举的,故不复制会节省很多时间,就算COW机制也不如索性不复制节约时间。

vfork保证子进程先运行,在它调用exec或者exit后父进程才可以被调用执行。

用几个程序分析一下:
在这里插入图片描述
结果:
在这里插入图片描述
发现有段错误。主要原因是vfork 保证子进程先运行,在子进程调用exec 或exit 之后父进程才可能被调度运行。如果在调用这两个函数之前子进程依赖于父进程的进一步动作,则会导致死锁。这样上面程序中的fork ()改成vfork()后,vfork ()创建子进程并没有调用exec 或exit,所以最终将导致死锁。

修改一下,在子进程执行后执行exit函数:
在这里插入图片描述
运行结果:
在这里插入图片描述

这就很有意思了。父子进程都运行了,但是父进程变量却被子进程修改了,所有说明,这不是拷贝副本或者COW机制,而是直接共享。故如果采用vfork,尽量立刻执行exec。

看上面的例子中另一个程序改成vfork版本:
在这里插入图片描述
运行结果:
在这里插入图片描述
发现,子进程对变量的操作修改了父进程中的变量值,原因还是子进程在父进程的地址空间运行。也会发现,因为对缓冲区也是共享的,故当输出一次后就清空后父进程也不会再次输出printf之前的内容。

猜你喜欢

转载自blog.csdn.net/dingdingdodo/article/details/107206417