大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。
Jetbrains全家桶1年46,售后保障稳定
从最简单(基础)的一个例子说起,应该说是最基础而不是简单,下面的这个最基础的例子其实并不简单,因为有很多细节。
我们需要从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的原因。
可见父进程是没有办法在自己消亡的时候回收子进程的。
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/220322.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...