一文带你轻松入门Linux中的『进程』-2

简介: 一文带你轻松入门Linux中的『进程』

三、创建进程

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

1、fork初识

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


man fork
  • 可以看到,这个函数的功能就是去创建一个子进程,其返回值为pid_t

image.png然后我们来测试一段代码:


printf("before: only one line\n");
fork();
printf("after: only one line\n");
sleep(1);
  • 通过执行结果我们可以看到,虽然只有一句after: only one line,但是在【fork】之后却打印了两句

image.png

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

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

image.png

  • 如果想要更加清楚地了解这个函数,我们还需要再查看一下man手册,然后看到
  • 如果成功则会给父进程返回子进程的PID,给子进程返回0
  • 如果失败的话则会给父进程返回-1

image.png

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

下面是测试的代码:


1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <sys/types.h>
  4 
  5 int main(void)
  6 {
  7     printf("begin: 我是一个进程, pid: %d, ppid: %d\n", getpid(), getppid());
  8 
  9     pid_t id = fork();
 10     if(id == 0){
 11         // 子进程
 12         while(1)
 13         {
 14             printf("我是一个子进程, pid: %d, ppid: %d\n", getpid(), getppid());
 15             sleep(1);
 16         }
 17     }
 18     else if(id > 1){
 19         // 父进程
 20         while(1)
 21         {
 22             printf("我是一个父进程, pid: %d, ppid: %d\n", getpid(), getppid());          
 23             sleep(1);
 24         }
 25     }
 26     else{
 27         printf("error, fork创建子进程失败\n");
 28     }
 29     return 0;
 30 }
  • 然后将进程挂起后我们来看看,在第一句执行完后父子进程竟然是一起执行的,if...else...分支可以同时进去,并且还有两个死循环在同时跑。这是为什么呢?

2、疑难解答

读者一定会对上面种种现象感到非常地疑惑,在本小节我将会为你解答这些疑惑

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

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

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

image.png

接下去我们就通过一些具体的问题来更加深入地了解一下fork()与进程之间的关系?

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

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

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

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

image.png


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

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

image.png

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

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

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

image.png

那对于这个代码而言我们就要有更多的思考了🤔

  • 既然父子进程共享同一段代码的话,我们再来看看fork()之后会有什么现象。可以看到同一句代码被重复执行了两次

image.png💬 那我此时还想问的是:既然跑的都是同一段代码,那还要子进程干嘛呢?直接父进程去跑个两遍不就好了

  • 既然子进程被创建出来的话,那一定是有它的作用的,上面我们所看到的只是一段很简单的逻辑,但是在现实的开发中却会存在很复杂的逻辑,可能需要父子进程去执行不同的两段逻辑,所以这才使得【父进程】与【子进程】得到了两种不同的返回值,我们才可以对其去进行判断

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

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

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

image.png


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

  • 上面我们讨论到了父子进程会去共享同一段代码,但是呢这个数据子进程该去对待呢?还是和父进程用同一个吗?

image.png👉 这里要提出一点:在任何平台,进程在运行的时候是具有独立性的,不会影响另一个进程

  • 在上面我们有看过这张图,在一个操作系统中是可以同时运行多个进程的,但是呢如果我们的【XShell7】突然闪退了,会影响【Chrome浏览器】吗?—— 这当然是不会的!

image.png

  • 但是呢,也并不是所有的数据都牵扯【独立性】,就好比我们在家里的茶几上都会有水杯,那么家里的每个人都是可以使用水杯的,这个互不影响

image.png

但是呢此时我们的子进程和父进程所维护的数据是同一块,这就免不了出现【并发修改】的问题

  • 所以我们不能让父进程和子进程共享同一块数据。所以子进程呢就会把数据单独拷贝一份。因为父子进程使用的是不同的PCB,所以当CPU调度不同进程访问的是不同的数据,它们在数据上割裂了,那一个进程运行时就不会影响另一个进程了

image.png

那么我现在又要提出疑问了,子进程每次在创建之后都会去拷贝一份这个数据,会存在问题吗?

  • 刚才我们谈到子进程要去拷贝数据的原因是在于【并发修改】的问题,但若是子进程只是读取数据但是不修改呢?也需要去完整地拷贝一份数据吗?不,完全不需要!这会使得资源消耗过大
  • 在一上来不会直接给子进程拷贝一份父进程的数据,在子进程刚被创建的时候,代码和数据全部都是被共享的,只有当操作系统识别到子进程要对父进程中的数据做修改时,才会在系统的某一个位置开辟一段空间,然后在修改的时候不去修改父进程内部的这个数据,而是去修改拷贝出来的这块数据 ——> ==此为父子进程之间数据层面的写时拷贝==

image.png如果对上面这个不太理解的话可以看看 string类中的写时拷贝 ,这两块知识点是联动的🌊

那看完了上面的这些内容后,我们再来谈谈刚才所说到的fork()函数的返回值问题

  • 因为是当前的父进程去调用的这段代码,所以最后在返回的时候父进程直接进行写入即可,到这个id中,但是呢对于子进程来说就不一样了,这里会发生一个写时拷贝。那也就导致了父子进程最后所获取到的值不一样的原因

image.png


【总结一下】:

  • 当我们调用fork()之后,子进程就被创建出来了,父子进程就共享后续的代码了,但是呢父和子会由各自执行return从而造成两次返回,在【id】层面上发生写时拷贝,让父子进程的【id】变成不同的值。使得可以在后续对不同的【id】值进行变换,从而形成一个分流,让父子去执行不同的代码块,所以父进程和子进程就可以去执行不同的逻辑了。 —— 这就叫做fork

四、总结与提炼

最后来总结一下本文所学习的内容:book:

  • 首先的话我们初步了解到了进程的基本概念,知道了进程不仅仅是一个 ==正在执行的程序==,而且要让一个程序成为了一个进程,就必须让其 加载到内存中
  • 除了对进程有一个基本的了解后,我们还需要去理解什么是进程:因为管理的本质是【先描述,再组织】,所以我们要先去描述一个进程,使用的是PCB叫做【进程控制块】,在Linux里为task_struct。由此我们知道了 进程 = 内核PCB数据结构对象 + 你自己的代码和数据;知道如何去描述进程后,我们还要学习如何去组织进程,在Linux中我们采取的【双向链表】进行组织的
  • 那么在描述并组织完多个进程之后,我们就可以去查看这些进程了,所采用的方式有三种:pstopls;当然,在查看进程的时候主要关注的是PIDPPID这两个属性值,分别代表的是 当前进程的标识符当前进程的父进程标识符;当然,这两个标识符不仅仅是可以通过指令来查看,而且还可以通过操作系统提供给我们的【库函数】来查看,查看 man手册 可以发现这两个函数为getpid()getppid()
  • 除了【指令层面】的./运行我们的程序外,我们还可以从【代码层面】的fork()来创建进程,后者可以帮我们去创建出一个子进程,从四个问题来步步分析fork()的底层,我们可以知道在 fork 之后的代码会被父子进程所共享,而且它们所获取到返回值会因为子进程的 写时拷贝 而不同,所以父子进程才得以执行不同的代码逻辑

以上就是本文所要介绍的内容,感谢您的阅读:rose:

相关文章
|
21天前
|
算法 Linux 调度
深入理解Linux操作系统的进程管理
本文旨在探讨Linux操作系统中的进程管理机制,包括进程的创建、执行、调度和终止等环节。通过对Linux内核中相关模块的分析,揭示其高效的进程管理策略,为开发者提供优化程序性能和资源利用率的参考。
46 1
|
9天前
|
存储 监控 Linux
嵌入式Linux系统编程 — 5.3 times、clock函数获取进程时间
在嵌入式Linux系统编程中,`times`和 `clock`函数是获取进程时间的两个重要工具。`times`函数提供了更详细的进程和子进程时间信息,而 `clock`函数则提供了更简单的处理器时间获取方法。根据具体需求选择合适的函数,可以更有效地进行性能分析和资源管理。通过本文的介绍,希望能帮助您更好地理解和使用这两个函数,提高嵌入式系统编程的效率和效果。
62 13
|
16天前
|
SQL 运维 监控
南大通用GBase 8a MPP Cluster Linux端SQL进程监控工具
南大通用GBase 8a MPP Cluster Linux端SQL进程监控工具
|
24天前
|
运维 监控 Linux
Linux操作系统的守护进程与服务管理深度剖析####
本文作为一篇技术性文章,旨在深入探讨Linux操作系统中守护进程与服务管理的机制、工具及实践策略。不同于传统的摘要概述,本文将以“守护进程的生命周期”为核心线索,串联起Linux服务管理的各个方面,从守护进程的定义与特性出发,逐步深入到Systemd的工作原理、服务单元文件编写、服务状态管理以及故障排查技巧,为读者呈现一幅Linux服务管理的全景图。 ####
|
29天前
|
缓存 算法 Linux
Linux内核的心脏:深入理解进程调度器
本文探讨了Linux操作系统中至关重要的组成部分——进程调度器。通过分析其工作原理、调度算法以及在不同场景下的表现,揭示它是如何高效管理CPU资源,确保系统响应性和公平性的。本文旨在为读者提供一个清晰的视图,了解在多任务环境下,Linux是如何智能地分配处理器时间给各个进程的。
|
1月前
|
存储 运维 监控
深入Linux基础:文件系统与进程管理详解
深入Linux基础:文件系统与进程管理详解
85 8
|
1月前
|
网络协议 Linux 虚拟化
如何在 Linux 系统中查看进程的详细信息?
如何在 Linux 系统中查看进程的详细信息?
93 1
|
1月前
|
Linux
如何在 Linux 系统中查看进程占用的内存?
如何在 Linux 系统中查看进程占用的内存?
|
1月前
|
算法 Linux 定位技术
Linux内核中的进程调度算法解析####
【10月更文挑战第29天】 本文深入剖析了Linux操作系统的心脏——内核中至关重要的组成部分之一,即进程调度机制。不同于传统的摘要概述,我们将通过一段引人入胜的故事线来揭开进程调度算法的神秘面纱,展现其背后的精妙设计与复杂逻辑,让读者仿佛跟随一位虚拟的“进程侦探”,一步步探索Linux如何高效、公平地管理众多进程,确保系统资源的最优分配与利用。 ####
71 4
|
1月前
|
Unix Linux Shell
linux入门!
本文档介绍了Linux系统入门的基础知识,包括操作系统概述、CentOS系统的安装与远程连接、文件操作、目录结构、用户和用户组管理、权限管理、Shell基础、输入输出、压缩打包、文件传输、软件安装、文件查找、进程管理、定时任务和服务管理等内容。重点讲解了常见的命令和操作技巧,帮助初学者快速掌握Linux系统的基本使用方法。
78 3