学益得线上课堂之最简单又最难理解的系统调用-fork

对于刚刚接触Linux的同学,fork一定是大家最头疼的概念,它看起来很简单,但理解起来却十分的复杂。我们先来从一道经典的笔试题开始。

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

int main()
{
    int i;
    for (i = 0; i < 2; i++)
    {   
        fork();
        printf("-");
    }   
    wait(NULL);
    wait(NULL);

    return 0;
}

问:代码一共会输出几个‘-’?

大家可以先打开电脑把代码敲一遍,看看结果怎么样。我们待会再讲。

先从fork的man手册说起吧。

FORK(2)       Linux Programmer's Manual       FORK(2)

NAME
       fork - create a child process

SYNOPSIS
       #include <unistd.h>

       pid_t fork(void);              
         
RETURN VALUE
       On success, the PID of the child process is returned in the parent, and 0 is returned in the child.  On  failure,
       -1 is returned in the parent, no child process is created, and errno is set appropriately.

这英文感觉也不难,所以我就不翻译啦!

fork函数调用一次却返回两次:向父进程返回子进程的ID,向子进程中返回0。这是因为父进程可能存在很多个子进程,所以必须通过返回的子进程ID来跟踪子进程。

看一段代码:

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

int main()
{
    pid_t pid;
    int count=0;

    pid = fork();

    printf( "This is first time, who am i? pid = %d\n", pid );

    count++;
    printf( "count = %d\n", count );

    if (-1 == pid)
    {   
        perror("fork");
    }   
    else if (pid > 0)
    {   
        printf("This is the parent process!\n");
        wait(NULL);
    }   
    else if (pid == 0)
    {   
        printf("This is the child process!\n");
    }

    printf("This is second time, who am i? pid = %d\n", pid);

    return 0;
}

运行结果如下:

在这里插入图片描述
这个结果很奇怪了,为什么13行和32行的printf语句执行两次,而15行的“count++”语句却只执行了一次?

把代码稍微修改一下,并且加上注释:

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

int main()
{
    pid_t pid;
    int count=0;

    /*通过fork系统调用创建了一个新的进程
    这个进程共享父进程的数据和堆栈空间等,
    fork调用是一个复制父进程的过程, 
    fork不像线程需提供一个函数做为入口, 
    fork调用后,新进程的入口就在fork的下一条语句。*/
    pid = fork();

    /*通过pid的值可以判断此时执行的是父进程还是子进程,都有可能 */
    printf( "This is first time, who am i? pid = %d\n", pid );

    if (-1 == pid)
    {   
        perror("fork");
    }   
    else if (pid > 0)
    {   
        printf("This is the parent process!\n");
        /*fork系统调用向父进程返回子进程的pid, 
        count仍然为0, 因为父进程中的count始终没有被重新赋值,   
        这里就可以看出子进程的数据和堆栈空间和父进程是独立的, 
        而不是共享数据。*/
        printf("Parent process count = %d\n", count);
        wait(NULL);
    }   
    else if (pid == 0)
    {   
        printf("This is the child process!\n");
        /*在子进程中对count进行自加1的操作,
        但是并没有影响到父进程中的count值, 
        父进程中的count值仍然为0。*/
        count++;
        printf("Child process count = %d\n", count);
    }

    printf("This is second time, who am i? pid = %d\n", pid);

    return 0;
}

结果如下:

在这里插入图片描述看这个程序的时候,头脑中必须首先了解一个概念:在语句pid=fork()之前,只有一个进程在执行这段代码,但在这条语句之后,就变成两个进程在执行了,这两个进程的代码部分相同,将要执行的下一条语句都是fork下面的判断语句。

父子进程的区别除了进程标识符(process ID)不同外,变量pid的值也不相同,pid存放的是fork的返回值。fork调用的一个奇妙之处就是它仅仅被调用一次,却能够返回两次,它可能有三种不同的返回值:

  • 在父进程中,fork返回新创建子进程的进程ID;
  • 在子进程中,fork返回0;
  • 如果出现错误,fork返回-1。

fork出错可能有两种原因:

  • 当前的进程数已经达到了系统规定的上限,这时errno的值被设置为EAGAIN;

  • 系统内存不足,这时errno的值被设置为ENOMEM。

接下来我们来看看《UNIX环境高级编程》中对fork的说明:

The new process created by fork is called the child process. This function is called once but returns twice. The only difference in the returns is that the return value in the child is 0, whereas the return value in the parent is the process ID of the new child. The reason the child’s process ID is returned to the parent is that a process can have more than one child, and there is no function that allows a process to o^ain the process IDs of its children. The reason fork returns 0 to the child is that a process can have only a single parent, and the child can always call getppid to o^ain the process ID of its parent. (Process ID 0 is reserved for use by the kernel, so it’s not possible for 0 to be the process ID of a child.)

被fork创建的新进程叫做子进程。fork函数被调用一次,却有两次返回。返回值唯一的区别是在子进程中返回0,而在父进程中返回子进程的pid。在父进程中要返回子进程的pid的原因是父进程可能有不止一个子进程,而一个进程又没有任何函数可以得到他的子进程的pid。子进程返回0是因为它只有一个父进程,并且可以通过getppid来获得父进程id。

Both the child and the parent continue executing with the instruction that follows the call to fork. The child is a copy of the parent. For example, the child gets a copy of the parent’s data space, heap, and stack. Note that this is a copy for the child; the parent and the child do not share these portions of memory. The parent and the child share the text segment (Section 7.6).

子进程和父进程都执行在fork函数调用之后的代码,子进程是父进程的一个拷贝。例如,父进程的数据空间、堆栈空间都会给子进程一个拷贝,而不是共享这些内存。

仔细分析后,我们就可以知道:

一个程序一旦调用fork函数,系统就为一个新的进程分配了新的地址空间(包含数据段、代码段、堆栈段)。首先,系统让新的进程与旧的进程使用同一个代码段,因为它们的程序还是相同的;对于数据段和堆栈段,当代码中涉及到写内存操作时,系统则复制一份给新的进程,这样,父进程的所有数据都可以留给子进程;但是,子进程一旦开始运行,虽然它继承了父进程的一切数据,但实际上数据却已经分开,相互之间不再有影响了,也就是说,它们之间不再共享任何数据了。

fork()不仅创建出与父进程数据相同的子进程,而且父进程在fork执行点的所有上下文场景也被自动复制到子进程中,包括:

  • 全局和局部变量

  • 打开的文件句柄

  • 共享内存、消息等同步对象

  • 信号

最后,再来看看文章开头留给大家的问题。

答案是8!

第一次循环:

父进程通过fork创建子进程,然后父子进程都会执行下面的printf语句。两次!

第二次循环:

需要注意的是,因为有了第一次循环,已经创建了子进程,所以父子进程都会进入第二次循环。

父进程第二次循环,再次创建子进程,两个进程都会执行printf语句,所以打印“-”再加两次!

子进程第二次循环,再次创建孙进程,两个进程都会执行printf语句,于是打印“-”再加两次!

不对呀,这样一分析结果是6呀!大家再把代码敲一遍,每次输出的时候,加上换行符:

printf("-\n");

这样修改代码后,结果还真的是6,跟我们分析的一样。但是为什么加上换行符后结果就不一样了呢?

这是因为printf语句有缓冲区(属于行缓冲,遇到换行符才会输出)。所以,对于上述没有换行符程序,printf把“-”放到了缓存中,并没有真正的输出,在fork的时候,缓存被复制到了子进程空间,所以,就多了两个,变成了8个。

什么玩应?

没看懂是吧!我来画张图给大家看看!

在这里插入图片描述
如果这张图还没有看懂的话,那就留言或者私信吧!

更多内容,关注公众号 学益得智能硬件

学益得智能硬件

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
fork是一个系统调用,用于创建一个新的进程。在哈尔滨工业大学的操作系统课程中,学生学习到了fork系统调用的使用和原理。 在操作系统中,每个进程都具有一个唯一的进程ID(PID)和一组资源。当调用fork时,当前进程会被复制,创建一个新的子进程。子进程和父进程具有相同的代码、数据和环境变量等。但是,子进程有自己的独立的内存空间。 fork调用返回两次,一次在父进程中返回子进程的PID,一次在子进程中返回0。这样,父进程可以根据返回的PID来判断fork是否成功,并根据需要进行相应的处理。 fork系统调用被广泛应用于多进程编程中。通过利用fork,可以实现并发执行,提高系统的资源利用率和效率。在操作系统课程中,学生通常学习如何使用fork创建子进程,并使用进程间通信机制进行进程间的数据交换和同步。 通过学习fork系统调用,学生可以了解进程的创建和管理,理解进程的概念和特点,并掌握进程间通信和同步的方法。此外,fork也是其他一些高级系统调用(如exec)的基础,对于学生进一步研究和学习操作系统提供了良好的基础。 总之,哈尔滨工业大学操作系统课程中的fork系统调用是学生学习并发编程和进程管理的重要内容,通过掌握这个系统调用,可以为学生提供丰富的编程经验和操作系统理论基础。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值