frok 与 vfork

一、首先看一个程序:

#include <stdio.h>
#include <sys/wait.h>
#include <unistd.h>

Int main()
{
Int i = 0;
For(i = 0 ; i < 2 ; i++)
{
Fork();
Printf(“-”);
}

Wait(NULL);
Wait(NULL);

Return 0;
}

上面这个程序一共输出多少个“-”?
  相信对fork()函数有所了解的,可能会说出是6个,但是结果真的是这样的吗?其实不然,正确的输出是8个。为什么会是这样呢?下面我们就来揭晓:
  其实很简单,不知道有人注意没,上面的程序的printf语句中没有换行。首先,我们都知道,fork时,子进程会复制父进程的进程空间,包括指令,变量值,程序调用栈,环境变量,缓冲区等。上面那个程序之所以输出8个是因为printf语句有缓存,该语句将“-”放入了缓存中,而没有真正输出,所以fork时,子进程会复制父进程的缓冲区,就多出了两个“-”。
  用图来表示可能更清楚些。
  这里写图片描述
  
  相信说到这,大家都明白了,上述程序之所以打印出8个“-”,是因为上图中的①和②进程复制了各自的父进程缓冲区,所以那两个进程的缓冲区就各自都有两个“-”,导致打印结果会多出两个“-”。
  若是在输出语句中加上“\n”,或在之后加上fflush(stdout)刷新缓冲区就会是正常的6个。,加“\n”的原因是因为标准输出是行缓冲,遇到”\n”的时候会刷出缓存。
  
补充说明:
①、linux下的设备有块设备和字符设备,块设备是一块一块的进行数据存取,字符设备是一次存取一个字符的设备。磁盘和内存是块设备;字符设备如键盘和串口。块设备一般都有缓存,而字符设备一般没有。

②、程序遇到“\n”,或是EOF,或是缓中区满,或是文件描述符关闭,或是主动flush,或是程序退出,就会把数据刷出缓冲区。需要注意的是,标准输出是行缓冲,所以遇到“\n”的时候会刷出缓冲区,但对于磁盘这个块设备来说,“\n”并不会引起缓冲区刷出的动作,那是全缓冲,你可以使用setvbuf来设置缓冲区大小,或是用fflush刷缓存。
  
二、为什么vfork的子进程调用return会将整个程序挂掉而使用exit()不会挂掉

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main()
{
    int a = 0;
    pid_t pid = vfork();

    if(pid < 0)
    {
        printf("vfork error!\n");
    }
    else if(pid == 0)
    {
        a++;
        return 0;
    }
    else
    {
        printf(" a = %d\n",a);
    }

    return 0;
}

运行结果:
这里写图片描述

为什么会产生上面的结果?
  因为函数栈父子进程共享,在vfork中return了,那么,这就意味main()函数return了。
在子进程中调用return的过程:
  1)子进程的main() 函数 return了,于是程序的函数栈发生了变化。
  2)而main()函数return后,通常会调用 exit()或相似的函数(如:_exit(),exitgroup())
  3)这时,父进程收到子进程exit(),开始从vfork返回,父进程的栈都被子进程给释放掉了,无法执行

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

注意:如果你调用 exit() 函数,还是会有问题的,正确的方法应该是调用 _exit() 函数,因为 exit() 函数 会 flush 并 close 所有的 标准 I/O ,这样会导致父进程受到影响。(这个情况在fork下也会受到影响,会导致一些被buffer的数据被flush两次。

三、最后对fork和vfork做一总结
① fork和vfork的差别
  fork是创建一个子进程,并把父进程的内存数据copy到子进程中,父子进程执行顺序不确定;
  vfork是创建一个子进程,并和父进程的内存数据share一起用,保证子进程先执行;当子进程调用exit或exec之后,父进程再往下执行。
  
②、vfork的好处
  起初,只有fork,但很多程序在fork一个子进程之后就exec一个外部程序,于是fork需要copy父进程的数据这个动作就毫无意义,而且这样干很重,所以就有了vfork,这样成本比较低。因此,vfork就是为exec而生

③、关于fork的优化
因为fork太重,每次创建一个子进程都会将父进程的数据copy到子进程中,而这步操作在很多时候是不必要的(子进程不需要对该内存修改),所以就有了写时拷贝(COW)的技术产生。也就是说,对于fork后并不是马上拷贝内存,而是只有当发生修改的时候,才会从父进程中拷贝到子进程中,这样fork后立马执行exec的成本就非常小了。所以,Linux的Man Page中并不鼓励使用vfork() 。

猜你喜欢

转载自blog.csdn.net/z_ryan/article/details/80806059