fork函数详解_全纯函数是什么

fork函数详解_全纯函数是什么从最简单(基础)的一个例子说起,应该说是最基础而不是简单,下面的这个最基础的例子其实并不简单,因为有很多细节。我们需要从fork函数的定义开始说起:man手册官方定义thisfunctioncreatesanewprocess.Thereturnvalueisthezerointhechildandtheprocess-idnumberofthechildintheparent,or-1uponerror.这个函数创建一个新的进程。在子进

从最简单(基础)的一个例子说起,应该说是最基础而不是简单,下面的这个最基础的例子其实并不简单,因为有很多细节。
我们需要从fork函数的定义开始说起:

man 手册官方定义
this  function  creates a new process. The return value is the zero in
the child and the process-id number of the child in the parent,
or -1 upon error.
这个函数创建一个新的进程。在子进程中返回0,在父进程中返回子进程的进程id,发生错误则返回-1。

第一次看的时候非常的奇怪,一个函数返回两次?是的,在调用fork后,fork函数后面的所有代码会执行两遍。下面通过一个例子来解释fork函数定义的含义。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
/** *最基础的fork例子 **/
int main(int argc, char const *argv[])
{ 
   
    pid_t pid;
    //判断1
    if ((pid=fork()) < 0)
    { 
   
        perror("fork error");
    }
    //判断2
    else if (pid == 0)//子进程
    { 
   
         printf("child getpid()=%d\n", getpid());
    }
    //判断3
    else if(pid > 0)//父进程
    { 
   
        printf("parent getpid()=%d\n", getpid());
    }

    return 0;
}

这是一个最基本的例子。我们先运行一下代码。

parent getpid()=13725
child getpid()=13726

非常的神奇,两个判断的代码都执行了。这是非常不可思议的,但fork函数确实实现了这样的功能。也就是在fork函数后面的代码都会执行2遍。 这就是为什么两个判断都会被执行的原因。
现在来梳理一下成功fork的执行流程
第一步: pid=fork(),如果成功那么pid就有一个非0正值。否则返回-1。
第二步: 因为pid>0,所以进入判断3。这是在父进程。
第三步: 父进程的代码执行完了,程序又会把fork后面的函数再执行一遍,此时pid的值变为0,所以进入判断2。

这里要解释下getpid()函数,先看下他的定义:

man手册官方定义:
DESCRIPTION
       The getpid() function shall return the process ID of the calling process.

RETURN VALUE
       The getpid() function shall always be successful and no return value is reserved to in‐
       dicate an error.

getpid()获取调用他的进程的id,如果失败不会有返回值。

也就是说哪个进程调用getpid,就返回这个进程的pid。所以如果你想要获得子进程的pid,那么只要在判断2里面调用getpid就可以了。

令人迷惑的pid_t pid变量

还有一个需要解释的就是我们自己定义的这个pid_t pid变量。这个变量非常具有迷惑性。因为在很多书上都取这个名字,好像这个变量就是进程的pid。这是错误的。
这个变量的真正含义应该是return value of the fork(),也就是fork函数的返回值,而且返回值并不一定就是pid,也可能是错误值-1。
下面是这个变量的一种错误用法,试图用这个变量来输出父子进程的pid。

int main(int argc, char const *argv[])
{ 
   
    pid_t pid;
    //判断1
    if ((pid=fork()) < 0)
    { 
   
        perror("fork error");
    }
    //判断2
    else if (pid == 0)
    { 
   
        printf("child pid=%d\n",pid);
    }
    //判断3
    else
    { 
   
        printf("parent pid=%d\n",pid);
    }

    return 0;
}

这个一个错误的例子,程序的目的是试图通过pid变量来获取父子进程的pid。
输出结果:

parent pid=15077
child pid=0

这种做法是完全错误的,不要这么干!因为这个pid变量的命名实在是太有迷惑性了。判断2里面的pid会永远输出0,而判断3里面的pid并不是父进程的pid,实际上是子进程的pid。正确的做法是通过第一个例子的getpid函数来获取。

pid_t pid这个变量的唯一作用就是用来做三个条件判断。
pid_t pid这个变量的唯一作用就是用来做三个条件判断。
pid_t pid这个变量的唯一作用就是用来做三个条件判断。
不要拿他做别的事情。也许取名叫process_status会比较好。

父子进程的调用流程

前面的例子展示了fork最基本的用法。下面通过一个例子来解释fork函数的调用细节。

int main(){ 
   
	fork();//fork1
	fork();//fork2
	printf("hello\n");
	return 0;
}

问printf一共打印了几次?创建了几个子进程?
第一个问题非常好回答,执行一下程序就知道了。一共是输出了4次hello字符串。为什么是4次呢?可以做下面的图分析:
在这里插入图片描述

假设我们的main进程pid是1001,注意看左边的1,2,4进程其实都是main进程1001。进程3,6是同一个进程1002。所有一共有1001,1002,1003,1004四个进程。也就是只要数叶子节点就行了。其中1个是main进程,其它3个是子进程。有多少个进程就输出多少次hello字符串。也就是只有4,5,6,7执行了printf。

int main(){ 
   
	fork();//fork1
	fork();//fork2
	fork();//fork3
	printf("hello\n");
	return 0;
}

如果程序改成这样,结果是类似的,一共有8个进程,其中一个main进程,7个子进程。如果在程序最后加上sleep函数让进程一直存在,那么你可以在进程管理器里面查看到对应的进程和pid,如下图。
在这里插入图片描述

进程管理

既然生成了子进程,那么就需要管理这些子进程,那么谁来管呢?当然是谁生成谁负责。这其中有非常多的细节。看下面这个基本例子。通过getppid(有两个p)获取父进程的pid。

int main(){ 
   
	fork();//fork1
	fork();//fork2
	printf("ppid is %d\n",getppid());
	printf("hello\n");
	return 0;
}

输出结果

ppid =4564
hello 
ppid =26134
hello 
ppid =26135
ppid =26134
hello 
hello 

这个结果顺序是随机的,我们发现第一个ppid好像有点奇怪,另外三个pid都是差不多的。这个进程实际是main进程,他的parent是shell,因为我们的程序是在shell里面执行的。而shell的pid是4564(每次系统启动可能发生变化)。这还是比较好理解的。但有时候输出可能是下面这种情况。

ppid =4564
hello 
ppid =26570
ppid =26570
hello 
hello 
ppid =1
hello 

最后一个ppid是1,这是怎么回事呢?这是因为父进程在子进程结束之前先结束了。子进程没有了父进程,变成了孤儿进程。这时候init进程就会把这个孤儿进程收为他的“养子”,而init进程就成了孤儿进程的养父。在Linux系统中,init进程的id为1。这也就是ppid为1的原因。
可见父进程是没有办法在自己消亡的时候回收子进程的。

参考:Unix/Linux fork前传

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/220322.html原文链接:https://javaforall.net

赞 (0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 面渣逆袭:三万字,七十图,详解计算机网络六十二问(收藏版)

    面渣逆袭:三万字,七十图,详解计算机网络六十二问(收藏版)新年第一篇,开工大吉,虎年“豹”富!面渣逆袭系列继续,三万字+七十图详解六十二道网络面试题!强烈建议收藏!

    2022年10月3日
  • Python爬虫:URL管理器及其实现方式、网页下载器、网页解析器原理及其实现原理!

    Python爬虫:URL管理器及其实现方式、网页下载器、网页解析器原理及其实现原理!Python爬虫之URL管理器:Python爬虫:URL管理器实现方式:Python爬虫之网页下载器:urllib2实现网页下载器的三种方法:具体代码:              Python网页解析器:…

    2022年5月2日
  • 舵机控制原理详解「建议收藏」

    舵机控制原理详解「建议收藏」控制信号由接收机的通道进入信号调制芯片,获得直流偏置电压。它内部有一个基准电路,产生周期为20ms,宽度为1.5ms的基准信号,将获得的直流偏置电压与电位器的电压比较,获得电压差输出。最后,电压差的正负输出到电机驱动芯片决定电机的正反转。当电机转速一定时,通过级联减速齿轮带动电位器旋转,使得电压差为0,电机停止转动。舵机的控制一般需要一个20ms左右的时基脉冲,该脉冲的高电平部

    2022年6月15日
  • RPM安装命令总结[通俗易懂]

    RPM安装命令总结[通俗易懂]在Linux操作系统下,几乎所有的软件均通过RPM进行安装、卸载及管理等操作。RPM的全称为RedhatPackageManager,是由Redhat公司提出的,用于管理Linux下软件包的软件。Linux安装时,除了几个核心模块以外,其余几乎所有的模块均通过RPM完成安装。RPM有五种操作模式,分别为:安装、卸载、升级、查询和验证。1)用RPM安装软件包,最简单的

    2022年6月12日
  • 堆和栈的区别(队列和栈的区别)

    堆(Heap)与栈(Stack)是开发人员必须面对的两个概念,在理解这两个概念时,需要放到具体的场景下,因为不同场景下,堆与栈代表不同的含义。一般情况下,有两层含义:(1)程序内存布局场景下,堆与栈表示的是两种程序内存分区;(2)数据结构场景下,堆与栈表示两种常用的数据结构。1.程序内存分区——堆与栈栈由操作系统自动分配释放,用于存放函数的参数值、局部变量的值等,其操作方式类…

    2022年4月12日
  • 鱼骨图实践

    鱼骨图实践

    2022年1月19日

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号