【UNIX】进程复制函数fork()的那点事儿

fork()函数是unix-like环境下的系统调用,在Windows系统上是没有的,要在Windows系统上面使用这个函数,可以安装类UNIX模拟环境如Cygwin,使用示例见文末。
首先看一段代码:

#include <iostream>
#include <unistd.h>
using namespace std;
int main()
{
   for(int i=0; i<2; i++){
      fork();
     cout<<”#”;
   }
   return 0;
}

请问这段程序打印出多少个”#”?

其实这是2016阿里巴巴校招的一道选择题,记得不太清楚了但大概就是这个样子。当时我只知道fork()是复制当前进程,产生的新进程会从当前位置继续往下执行,fork英文就有分叉的意思。为了便于说明,把上面循环代码拆开,变成:

{
    fork();
    cout<<”#”;
    fork();
    cout<<”#”;
}

我是这样推算的,设n为输出的”#”数量(方便计算),main()进程明显执行两次cout<<“#”和两次fork()。先加上主进程的两个输出,于是n=2;
然后第一次执行fork()的时候,从当前进程复制出一条新进程A,然后A从当前位置往下执行,A继续执行的代码是:
cout<<“#”;
fork();
cout<<“#”;
执行完A进程后n=4。在进程A里还有一个fork();于是继续生成进程B,进程B接着执行的剩下的代码:cout<<“#”;此时n=5;
在main()里还有第二个fork(),产生新的进程C,进程C接着执行接下来的代码:cout<<“#;此时n=6;
于是推算的答案是6。
好像没什么问题,实践是检验真理的唯一标准,在linux环境下执行这段程序,输出结果是:
########
一共8个,我又算了一遍,没错,是8个。 (°o°;)
百思不得解,也不知道我错在哪了,如果我想得到错在哪,那我就不会错了(这什么鬼逻辑 -_-|||),最后在网上找到了大牛给的答案:一个fork的面试题,终于恍然大悟,还是前辈给力,心里非常感谢!

根据前辈的讲解,就是缓冲区被复制导致的问题,把上面代码的cout<<“#”;改成cout<<“#”<<endl;就会输出6个”#”。

于是,我决定要会一会这个坑人的fork()。

参考维基百科和百度百科,fork的其作用是复制当前进程,产生一个新的进程,除了进程ID之外,进程信息和原始进程完全相同,这就解释了上面奇怪的结果。fork成功调用一次则返回两个值,一个函数怎么会返回两个值呢?返回的两个值其实是返回在不同进程中的,相当于在两个进程中调用fork(),实际上在单一线程中调用一个函数只会有一个返回值,fork函数的神奇之处是,进入fork函数后,进程分岔了,变成一个父进程和一个子进程,两个进程都执行在fork()函数中,因此两个进程执行完fork()后都要返回一个值。调用一个函数返回两个值这样的说法会让人觉得云里雾里,个人认为应该这样描述:进入fork()函数后,进程会劈成两个进程继续往下执行,fork()只有一个返回值,只是在两个进程中都要返回。调用fork()后,在子进程返回0,父进程则返回子进程ID,出错返回-1,这样就可以区分不同进程了。

另外,fork一条线程后,父进程和子进程共享已分配的内存空间,当子进程需要写入时系统才会分配新的空间并复制一份父进程的进程信息给子进程,当然,进程ID是不同的。这样的设计对共享大量内存空间的多条进程很有意义。
下面一段验证调用fork后,对变量修改的情况。

#include <iostream>
#include <unistd.h>
using namespace std;
int main()
{
    int value=1;
    cout << "初始化时value值为:"<<value << endl;
    cout << "开始调用fork()函数"<< endl;
    int pid=fork();
    //下面代码已经在不同进程中执行,因此要判断pid的值
    if (pid == 0)//在子进程中执行
    {
        cout << "子进程修改value前的值为:"<< value << endl;
        value+=1;
        cout << "子进程给value加1后其值为:" << value << endl;
    }
    else if (pid > 0)//在父进程中执行
    {
        cout << "父进程修改value前的值为:" << value << endl;
        value+=2;
        cout << "父进程给value加2后其值为:" << value << endl;
    } 
    return 0;
}

输出结果为:
初始化时value值为:1
开始调用fork()函数
父进程修改value前的值为:1
父进程给value加2后其值为:3
子进程修改value前的值为:1
子进程给value加1后其值为:2
从结果可以证实,在两条进程中对value的修改都是互不影响的,这验证了系统会为新进程分配内存空间并复制原进程的数据。但无法验证是不是“其中一条进程写入内存时才复制”,博主暂时没想到办法去验证,或许以后会想到好的办法。到这里,对fork已经有了清晰的认识,但就这样完了吗?并没有,还有待深入研究,只是暂时没时间陷太深啦。

【在Cygwin上编译C++简单使用示例】
在windows系统上安装Cygwin,安装时要选择附带G++编译器等工具。
打开Cygwin后,输入命令:($是系统提示符,接着才是命令)
$ vi test.cpp
上面新建了一个test.cpp文件,并且打开可以编辑了。
此时编辑器处在命令模式,要按i进入插入模式,输入以下代码:
#include<iostream>
using namespace std;
int main()
{
    cout<<“Hello World!”<<endl;
    retun 0;
}
按Esc返回命令模式,输入:wq回车,保存并退出。
输入编译命令:
$ g++ test.cpp –o hello
-o参数后面是生成的exe程序名,输入命令ls可以看到在当前目录下生成了一个hello.exe文件。
执行hello.exe程序,输入命令:
$ ./hello
可以看到输出:
Hello World!


【2016-04-23 17:45:23】

发表评论

电子邮件地址不会被公开。 必填项已用*标注