【Linux系统编程】如何创建进程(什么是fork函数?进程创建的原理是什么?)

目录

一、前言

二、 进程创建的初次了解(创建进程的原理)

三、什么是fork函数? 

 初识fork函数

 fork函数的四个为什么? 

⭐为什么fork()要给子进程返回0,给父进程返回子进程pid?

⭐一个函数是如何做到返回两次的?如何理解?

⭐fork()函数究竟在干什么?干了什么?

⭐ 一个变量怎么会有不同的内容呢?

四、总结 

五、共勉 


一、前言

        在之前的博客中,已经详细的讲解了什么是进程包括了进程的概念进程与操作系统的关系如何查看进程等。

        所以本次博客将详细讲解,如何创建一个进程,让大家更加深入的了解进程!!

二、 进程创建的初次了解(创建进程的原理)

创建新进程Linux的下是由父进程来完成的,创建完成的新进程子进程。
新进程的地址空间有两种可能性:

  1. 子进程是父进程的复制品(除了PID和task_struct是子进程自己的,其余的都从父进程复制而来)
  2. 子进程装入另一个程序。

在Linux下的fork函数用于创建一个新的进程使用fork函数来创建一个进程时,子进程只是完全复制父进程的资源。这样得到的子进程和父进程是独立的,具有良好的并发性。但是进程间通信需要专门的机制。

三、什么是fork函数? 

之前我们在Linux下启动一个进程的时候利用的是./可执行程序那是否有其他办法去启动一个进程呢? 

 初识fork函数

当然是有的,那就是使用fork()这个函数。在使用之前呢我们要先去查看一下这个函数该如何使用------ 使用man 手册查询一下 fork 函数的使用

man 2 fork
  • 可以看到,这个函数的功能就是去创建一个子进程,其返回值为pid_t
  • 注意:这里的 pid_t 类型 是无符号整数

【Linux系统编程】如何创建进程(什么是fork函数?进程创建的原理是什么?)_第1张图片

 函数说明:

  • 通过复制调用进程创建一个新进程。

  • fork 有两个返回值。

  • 父子进程代码共享,数据各自私有一份(采用写时拷贝)。

接下来,我们来测试一段代码:

#include 
#include 
#include     // getpid, getppid, fork, sleep
#include   // getpid, getppid

int main()
{
   printf("before: I am a process\n");

   fork();
 
   printf("after: 创建一个新进程\n");

   return 0;
}
  • 通过执行结果我们可以看到,虽然只有一句after: 创建一个新进程但是在【fork】之后却打印了两句

【Linux系统编程】如何创建进程(什么是fork函数?进程创建的原理是什么?)_第2张图片

  那有的同学就会感到非常地好奇,这是为什么呢?

        因为在【fork】之后会产生两个执行的进程。但有同学还是会觉得很怪,这怎么就变成了两个进程了呢?我们可以去查询一下这个单词的意思,发现其确实是有分叉的意思。所以在执行了这个函数后,就会存在两个执行流

【Linux系统编程】如何创建进程(什么是fork函数?进程创建的原理是什么?)_第3张图片

如果想要更加清楚地了解这个函数,我们还需要再查看一下man手册,然后看到

  • 如果成功则会给父进程返回子进程的PID,给子进程返回0
  • 如果失败的话则会给父进程返回-1

【Linux系统编程】如何创建进程(什么是fork函数?进程创建的原理是什么?)_第4张图片


        当我们知道原理后,在代码中输出进程PID,看是否和我们的fork理解一样,让我们一起来验证一下吧!

#include 
#include 
#include     // getpid, getppid, fork, sleep
#include  // getpid, getppid 
int main()
{
  id_t id = getpid();
  printf("before: I am a process,我的进程PID为:%d\n",id);
  
  fork();
  
  printf("after: 创建一个新进程,我的进程PID为:%d, 我的父进程PPID为:%d\n",getpid(),getppid());       
  
  return 0;
}

【Linux系统编程】如何创建进程(什么是fork函数?进程创建的原理是什么?)_第5张图片

 那接下去我们就根据这个返回值去举个例子看看

下面是测试的代码: 

int main()
{
    printf("fork之前:I am a process,pid: %d, ppid: %d\n",getpid(),getppid());
  
    sleep(5);
    printf("开始创建进程啦!\n");
    sleep(1);
  
    pid_t id = fork();
    if(id < 0)
    {
      return 1;
    }
    else if(id == 0)
    {
      while(1)
      {
        // 子进程  
        printf("fork之后, 我是子进程:I am a process,pid: %d, ppid: %d,return id: %d\n",getpid(),getp    pid(),id);                                                                                         
        sleep(1);
      }
    }
    else
    {
      while(1)
      {
        // 父进程
        printf("fork之后,我是父进程:I am a process,pid: %d, ppid: %d,return id: %d\n",getpid(),getp    pid(),id);
        sleep(1);
      }
    }
  
  
   sleep(2);
 
   return 0;
 }
  • 然后将进程挂起后我们来看看,在第一句执行完后父子进程竟然是一起执行的,if...else...分支可以同时进去,并且还有两个死循环在同时跑。这是为什么呢?

【Linux系统编程】如何创建进程(什么是fork函数?进程创建的原理是什么?)_第6张图片

分析:

  • 我们来分析一下这个进程的创建过程:首先我们可以看到我们在这个PPID为【24609】 bash 上执行了一个进程,那么操作系统就会为这个进程分配一个PID为【12484】
  • 接下去这个进程被操作系统调度,执行自己的代码,执行到内部代码的fork函数时,执行流被一分为二,变成了两个执行分支:一个是父进程(它自己),另一个则是子进程(新的分支)

所以现在我们可以得出创建进程的两种方式:

  1. ./运行我们的程序 - - 指令层面---------(bash)
  2. fork() - - 代码层面

fork函数的四个为什么? 

 ⭐为什么fork()要给子进程返回0,给父进程返回子进程pid

       上面我们说到当进程的代码执行到fork()函数的时候,会将执行流一分为二,父子进程通过不同的 id 返回值来区分,以此执行不同的代码块。那其实很好理解了:因为父子进程是两个不同的进程,所以需要根据这个不同的返回值来进程区别

【Linux系统编程】如何创建进程(什么是fork函数?进程创建的原理是什么?)_第7张图片

那有同学说:你这不说了跟没说一样嘛,要区分的话当然得不同了,那为什么父进程得到的是子进程的PID,但是子进程却是0呢,为什么不可以倒过来?

  • 这位同学,你问到点子上了,确实这是它们最大的区别,不过呢这样的返回值还是有原因的。读者可以这么来理解:一个父亲可以有多个孩子,但是呢一个孩子却只能有一个父亲 父亲所获取到的返回值是子进程的PID是由于他要靠不同的PID值来区分不同的孩子;但子进程的返回值都是0的原因在于他一定只对应着某一个父进程,只需让父进程知道它被成功创建出来了即可

【Linux系统编程】如何创建进程(什么是fork函数?进程创建的原理是什么?)_第8张图片

 ⭐一个函数是如何做到返回两次的?如何理解?

上面讲到了因为在某些情况下需要依靠父子进程去执行不同的两段逻辑,所以在创建子进程后父子进程它们分别会得到不同的两个值

  • 那既然在调用了fork()函数后,就肯定需要去返回两次才可以。这里我们再通过画图来分析一下,既然这个fork()是库函数的话,那执行到这一句的时候就一定会跳转到库中的这一逻辑中去执行【创建子进程】的这一步的步骤,但是这还是无法说明他可是有不同的返回值呀?
  • 那我这时候就要问了,最后的这个return语句算是代码吗? 当然了!那我们在上面说到过这个代码呢是父子进程共享的,那么父进程返回一次,子进程返回一次,也就相当于返回了两次
     

【Linux系统编程】如何创建进程(什么是fork函数?进程创建的原理是什么?)_第9张图片 

fork()函数究竟在干什么?干了什么?

         在上面我们讲到过【进程 = 内核数据结构 + 代码与数据】,当我们在执行完fork()函数后,子进程被创建出来,那么它的PCB结构体即 task_struct 会被构建出来,我们知道的是在每个进程的结构体中有PID和PPID这两个成员,而且对于子进程中的PPID恰好就是父进程中的PID所以子进程大部分的属性就是以父进程为模版创建的,相当于把父进程拷贝了一份,对部分属性做了修改
【Linux系统编程】如何创建进程(什么是fork函数?进程创建的原理是什么?)_第10张图片

       可以看出子进程被创建出来后系统中多了一个进程,那么对于父进程来说它有自己的内核数据结构、代码和数据,子进程也按照父进程的PCB模拟了一块出来
 

那我现在要问了:请问子进程的数据和代码呢?也是拷贝出来的吗? 

  • 那有的同学就说:都属于不同的两个进程了,总会有自己的代码和数据吧。诶,这个说得就不对了,对于子进程来说,虽然它有自己的内核数据结构,但它在一创建出来的时候并没有独属于自己的【代码和数据】,而也是使用和父进程一样的同一份代码和数据

【Linux系统编程】如何创建进程(什么是fork函数?进程创建的原理是什么?)_第11张图片
 

 ⭐一个变量怎么会有不同的内容呢?

在我们上面的代码中,我们会发现 id 这个变量会有两个不同的值?这是为什么呢?

 因为父子进程使用的是不同的PCB,所以当CPU调度不同进程访问的是不同的数据,它们在数据上割裂了,那一个进程运行时就不会影响另一个进程了。也就是双方虽然共享数据,但是不能对数据进行修改,若要是想对数据进行修改的话,就对数据进行拷贝,去修改拷贝出去的那一份。

【Linux系统编程】如何创建进程(什么是fork函数?进程创建的原理是什么?)_第12张图片

总结:
当进程里数据没有发生修改时。那么子进程和父进程共享代码和数据!如果有一个进程的数据发送了修改,那么会对数据发送写实拷贝,而代码依旧是共享的
 

【Linux系统编程】如何创建进程(什么是fork函数?进程创建的原理是什么?)_第13张图片


 

如果发生修改,那么父进程的代码数据会进行一份实时拷贝,给子进程。

 

【Linux系统编程】如何创建进程(什么是fork函数?进程创建的原理是什么?)_第14张图片


 

四、总结 

1.子进程是由父进程的模板创建的
2.子进程和父进程一般情况下共享代码和数据,但如果有一方的数据或代码被修改,那么操作系统会写实拷贝一份。
3.fork函数不是真的有2个返回值,而是两个进程都在fork函数执行了返回操作。
4.fork返回父进程子进程的PID,给子进程返回0,进程创建失败返回-1

五、共勉 

以下就是我对【Linux系统编程】如何创建进程的理解,如果有不懂和发现问题的小伙伴,请在评论区说出来哦,同时我还会继续更新对【Linux系统编程】进程状态的理解,请持续关注我哦!!! 

【Linux系统编程】如何创建进程(什么是fork函数?进程创建的原理是什么?)_第15张图片 

你可能感兴趣的:(Linux系统编程,linux,运维,服务器,c++,算法,数据结构)