[Linux系统编程——Lesson3.进程概念 ]
目录
前言
二、 什么是进程?
🧐引出进程
🧐进程的基本概念
🧐理解进程
🌟描述进程--PCB(进程控制块)
🌟组织进程
三、查看进程
🎈通过 ps 命令查看进程
🎈通过 ls / proc 命令查看进程
🎈通过系统调用获取进程标示符
四、进程概念总结
1️⃣进程的本质
2️⃣进程的 ID(PID)
3️⃣描述进程--PCB(进程控制块)
结束语
前言
在学习了【Linux系统编程】中的 [Linux系统编程——Lesson1.冯 • 诺依曼体系结构 ] 和 [Linux系统编程——Lesson2.操作系统详解 ] 之后,我们已经对系统应该有了不错的了解,接下里我们将继续深入的了解操作系统最重要的的功能之一:进程管理。
🧐 那么操作系统是如何进行进程管理的呢?------很简单,先把进程描述起来,再把进程组织起来!
❓那么进程又是什么呢?------所以本次博客将从进程讲起!
二、 什么是进程?
🧐引出进程
首先我们来看一个问题:
❓ 操作系统能不能一次运行多个程序呢?---------当然可以啦!!
因为运行的程序有很多,所以 OS 需要将这些运行的程序管理起来。
我们将这些正在运行的程序称之为进程。(注意:是正在运行的程序叫进程,而不是程序本身)❓如何管理这些进程呢?—— 先描述,再组织!
操作系统会创建一个描述和控制该进程的结构体。这个结构体称之为进程控制块(PCB,Processing Control Block),里面包含了该进程几乎所有的属性信息,同时通过进程控制块也可以找到该进程的代码和数据。
在 Linux 中,进程控制块就是 struct task_struct 结构体。
描述好所有进程了,还需要将所有进程的 PCB 给组织起来(通过双链表的方式),此时操作系统只需要拿到双链表的头指针,就可以找到所有进程的 PCB。
OS 把对进程的管理就转换成了,对数据结构中 PCB 的管理,即对双链表的增删查改操作。
假设这里有一个可执行程序 test,它存储在磁盘上,就是一个普通文件,当我们 ./test 运行此程序,操作系统会做以下事情:将该程序从磁盘加载到内存中,并为该程序创建对应的进程,申请进程控制块(PCB)。
PCB 存在意义与进程概念总结
- PCB(进程控制块)的存在核心目的:为操作系统(OS)提供管理进程的 “抓手”。
操作系统需对系统中所有进程进行创建、调度、资源分配、状态监控及终止等管理操作,而 PCB 作为内核为每个进程专门创建的数据结构,记录了进程的关键信息(如进程 ID、状态、资源占用情况、程序计数器等),是 OS 识别、控制和管理进程的唯一依据,没有 PCB,OS 无法实现对进程的有效管控。- 当前对进程的定义理解:进程 = 程序(代码 + 数据) + 内核申请的与该进程对应的数据结构(PCB)。
进程是程序的 “动态执行实例”,其构成包含两大核心部分:
- 用户空间的程序本身:即进程执行所需的代码(指令集合)与数据(代码运行过程中操作的变量、常量等),是进程运行的 “物质基础”。
- 内核空间的 PCB(进程控制块):是 OS 为进程分配的 “管理档案”,记录进程的元信息,使进程具备被 OS 管理的属性,是进程能够在系统中 “存活” 并被调度执行的关键。
简言之,程序(代码 + 数据)是静态的文件,而加上 PCB 后,才构成了能被 OS 管理、可动态执行的进程。
🧐进程的基本概念
首先读者要知道的是,什么是进程?
【课本概念】:程序的一个执行实例,正在执行的程序等
【内核观点】:担当分配系统资源(CPU时间,内存)的实体
❓对于课本中的观点大家可能会觉得难以理解,为何正在执行的程序就是一个进程呢。我们可以在Windows下按[Ctrl + shift + ESC]打开任务管理器查看一下:可以看到左上角的这个【进程】标志,代表呢我们下面所运行的程序都是一个进程
👉 这也就表明了在一个操作系统中不仅只能运行一个进程,还可以运行多个进程
❓但是呢,进程不仅仅可以像上面这样去理解。我们来思考一个问题:程序是文件吗?
相信读者肯定很清楚,文件是存放在磁盘中的,磁盘呢则是属于外设。这一块我们在冯·诺依曼体系结构有讲得很清楚,对于CPU来说,它是只会和内存打交到的,所以磁盘中的数据需要先加载到内存中才可以被执行
那么,当可执行文件被加载到内存中时,该程序就成为了一个【进程】
总结:
进程 = 程序(代码 + 数据) + 内核申请的与该进程对应的数据结构(PCB)。
- 程序(代码 + 数据):代码是程序的指令集合,规定了进程要执行的操作。数据则包括程序运行时所需的输入数据、中间结果和输出数据等。通常代码是共享的,而数据对于每个进程来说一般是私有的。例如,多个文本编辑进程可共享同一套编辑代码,但各自处理不同的文本数据。
- 进程控制块(PCB):是进程属性的集合,用于描述进程的相关信息。在 Linux 操作系统下,PCB 具体为 task_struct 结构体。PCB 包含标识符、状态、优先级、程序计数器、内存指针、上下文数据、I/O 状态信息、记账信息等内容,操作系统通过对 PCB 的管理来实现对进程的调度和资源分配等操作。
该公式表明,进程是由程序的代码和数据部分,以及用于管理进程的 PCB 共同构成的。操作系统通过 PCB 来对进程进行 “先描述,再组织”,实现对进程的创建、调度、终止等管理操作,使得多个进程能够在计算机系统中有序运行。
🧐理解进程
在了解了进程的基本概念后,我们来更加进一步地认识一下进程管理是如何实现的?
- 那肯定是---- 先描述进程----在组织进程
🌟描述进程--PCB(进程控制块)
首先读者要清楚我们人是怎么辨别一个事物的:没错,就是通过其各种属性!
那么在 Linux 中,我们使用一个结构体去描述一个进程,因为 Linux 的内核源代码都是使用C语言来写的,而 结构体 则是C语言里面的一些知识,不懂的同学可以先去了解一下。并且呢这个结构体还有名字的,它叫做【进程控制块】----- PCB
- 课本中的叫法呢是:PCB(Process Control Block)
- Linux操作系统下的PCB是:task_struct
这个结构体呢就是组织了各种各样的属性,才可以去很好地描述一个进程
以下就是这个task_struct的所有结构信息:(后续我们会逐一讲解!)
- 标示符: 描述本进程的唯一标示符,用来区别其他进程。
- 状态: 任务状态,退出代码,退出信号等。
- 优先级: 相对于其他进程的优先级。
- 程序计数器: 程序中即将被执行的下一条指令的地址。
- 内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
- 上下文数据: 进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]。
- I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
- 记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
- 其他信息
转换为代码形式的话就可以是下面这样
struct PCB{进程的编号进程的状态进程的优先级...相关的指针信息 };
既然知道了如何去描述一个PCB结构体,我们就要来知道操作系统对一个进程总共会做哪些事情
- 为该进程创建对应的PCB对象
- 将该进程的代码和数据加载到内存中
所以,很多教科书在介绍进程的时候只会说它在计算机内部是一个PCB对象,其实对于一个进程来说:应该是由操作系统为其创建出来的 PCB对象 + 其数据代码 组成的
总结:👉 【进程】 = 内核PCB数据结构对象(描述你这个进程所有的属性值) + 你自己的代码和数据(在磁盘当中所形成的可执行程序)
🌟组织进程
上面谈到了操作系统如何利用PCB去描述一个相关的进程,接下去我们来说说如何去组织进程
大学的校园我们都知道,比中学的大多了。但是呢在学校里每个学生他的人身是自由的,不可能每个人拿着一个牌子,上面自己写上个人的基本信息,这是不现实的。比方说学院的辅导员想要找一个学生的话,一定是通过在学籍系统中找到这个学生的所在行记录,才可以对其进行相关的操作,读者可以认为这个信息记录即为我们在上面所讲到的【PCB对象】
因此操作系统如果要去管理一个进程的话只需要找到某个进程的PCB对象即可,通过这个PCB对象就可以找到相对应的代码和数据
- 那这个PCB结构体中就一定存在一个东西叫做结构体指针,里面保存了某个内容的地址,方便我们很快地知道其它对应的PCB信息
struct PCB{进程的编号进程的状态进程的优先级...相关的指针信息struct PCB* next; };
这样可能还是抽象了一点,我们再举个形象点的案例
- 读者可能是在校大学生,亦或者是已经工作的人。但是无论是谁都要去投递简历找工作。如果读者有当过面试官的话一定可以知道每天公司都会收到成千上百份的简历,因为面试官无法看到投递者这个人,所以其只能通过简历来识别这个人,那么读者就可以认为每一份简历就相当于是每个进程所专属的【PCB对象】
- 所以面试官对于面试者的管理就是对简历的管理。让进程去某个队列里排队,不是让代码去排队,而是进程的PCB结构体对象去排队
我在一开始的时候就讲到过,在内存中不仅仅是跑着一个进程,而是有可能会存在多个进程。那对于这多个进程要如何去起到关联呢❓
这一点其实我们在讲操作系统的时候也有讲到过,我们学习编程语言,像C/C++、Java,可以帮助我们去很好地描述一个抽象的事物;我们学习数据结构与算法,可以帮助我们更好地去组织相同的对象数据
那么在Linux内核中,最基本的组织进程的是用task_struct的方式,采用【双向链表】组织的
🎯那有了上面的这个理论,操作系统在对于进程的管理就转换成了对于链表的增删查改
三、查看进程
明白了操作系统如何去描述并组织进程,接下去我们就切身地来看一看进程长什么样吧
下面呢是我们要进行测试的代码:
#include <stdio.h>2 #include <stdlib.h>3 #include <unistd.h>4 #include <sys/types.h>5 int main()6 {7 // 死循环8 while(1)9 {10 pid_t id = getpid();11 //pid_t fid = getppid();12 //printf("I am a process! pid : %d, ppid : %d\n",id,fid);13 printf("I am a process! pid : %d\n",id);//程序停留一秒在输出 14 sleep(1);15 }16 17 return 0;18 }
🎈通过 ps 命令查看进程
① 通过
ps ajx
命令查看进程
- 查看当前我们正在运行的进程
② 此时我们来查看以下当前运行程序的进程
ps ajx | head -1 && ps ajx | grep mytest
解析:
ps ajx
—— 查看当前系统中所有进程head -1
—— 获取第一行grep mytest
—— 过滤只带【mytest】的进程
🧐 那有同学可能会问:为什么在过滤进程的时候会有
grep --color=auto mytest
这个东西呢?
- 当
grep
在进行过滤的时候自己也要变成一个进程,也可以看到他们使用grep
命令的时候也带【mytest】关键字的,所以在过滤的时候把自己也过滤出来了。这也侧面证明了所有指令在运行的时候都是进程
- 但如果我们不想看到这个也是有办法的,那就是在【grep】命令后面加个
-v grep
把其过滤掉即可ps ajx | head -1 && ps ajx | grep mytest | grep -v grep
🎈通过 ls / proc 命令查看进程
我们都清楚根目录下有很多的路径:
注意上面的proc目录,它是一个内存文件系统,里面放的是当前系统实时的进程信息。我们进入此目录看看:
- 但是呢,上面这些呢是全部的进程,若我们只是想要查看某个进程的话就要根据其
PID
值去进行对应的查找。这个PID
呢就是我们在上面在介绍task_struct
这个Linux下的PCB结构体的时候所讲到的【标识符】这个东西,它是 描述本进程的唯一标示符,用来区别其他进程
- 但是这么看不够清晰,我们以列表的形式来进行查看。这里我们主要关注两个:一个是
cwd
目录,另一个则是exe
首先我们来看到的是这个【exe】,很明显它是一个可执行文件,那就是我们在当前目录下的
mytest
这个可执行文件
接下去的话就是这个【cwd】了,其意思为current work directory
当前进程的工作目录接下来,我们来详细说一这个 cwd
看如下的代码:
#include <stdio.h> #include <stdlib.h> #include <unistd.h>int main(){printf("我的PID:%d\n",getpid()); FILE* fp = fopen("log.txt","w"); if(fp==NULL){return 1;}fclose(fp);printf("新建文件完成\n");sleep(50);return 0;}
在当前的【proc.c】程序中,创建一个log.txt 的文件,创建好后,大家觉得这个文件会出现在哪里呢? 我们运行以下看看
我们发现在【proc.c】程序中创建的 log.txt文件出现在当前目录下,这是为什么呢❓
我们来根据程序中的进程标识符来检查一个这个进程
此时我们发现进程中的存有cwd---当前工作目录,所以导致在此程序中创建的任何文件,在没有指定放在哪里的时候,常见的文件会默认放在进程指定的cwd----当前工作目录下
🎈通过系统调用获取进程标示符
上面我们有讲到了这个
PID
进程标示符,是通过ps
这个命令来查看的,那我们能否直接获取这个PID
呢在上面我们使用ps ajx查看到了当前进程所对应的 PID,但是呢这相当于是遍历操作,如果我没有加grep mytest的话出来的进程数就会很多了
那现在我们所要做到的就是对一个单独的进程去获取其 PID,此时我们能想到的就是通过库函数来实现。在之前的文章中我们又说到过对于操作系统而言它是不会相信任何人的,所以会提供给用户一些系统调用(库函数),那我们只需要通过这个系统调用即可获取到当前进程的 PID 值
- 那首先呢,我们先要去查询一下这个
getpid()
怎么使用,那还是使用到我们的老朋友man
man 2 getpid
- 进去之后看到,有两个库函数,那如果要使用这两个库函数的话就需要引入对应的头文件
下面我给出一段命令,它可以实时监控当前系统的进程
while :;do ps ajx | head -1 && ps ajx | grep mytest | grep -v grep; echo "------------------------------------------------------------"; sleep 1; done;
- 然后就让我们来观察一下其是否真的可以获取到当前进程的PID,首先运行上面的这段指令,我们看到了当前系统中并不存在有关mytest的进程,但是呢在我们把mytest这个可执行程序运行起来的时候,右侧就突然就多出了一条进程的相关信息
- 后一核对相关的PID值就发现确实是当前运行起来的这个进程
- 但是呢当我在将当前这个进程给结束之后再去把它起起来的时候,就发现当前这个进程的
PID
值发生了变化
🧐其实的话,这个现象是很正常的,每次重新启动进程其 PID 值是会出现不同的情况
- 举个很简单的例子来说吧,小王在高考结束完后上了一所不是很理想的大学🏫,在开学前两天时学习为其分配了对应的学号。但是呢小王却并不满意自己所待的这个学校,所以就退了学继续参加高考,在又一次的高考结束后他还是被原来的这所学校给录取。但是呢我们可以知道,即使你进了一个学校两次,但是学号却不一定是一样的
- 这也就是为什么一个进程在启动两次后会出现不同PID值的原因
刚才我们在通过【man】手册查看
getpid()
这个函数的时候,还看到了getppid()
这个函数,它是获取当前进程的父进程的 PID
- 这个
PPID
呢就在PID
的左边
- 下面是改进的测试代码
printf("I am a process, my id is: %d, parent is: %d\n", getpid(), getppid());
- 马上来看一下是否真的可以获取到
- 接下去我们再来观察一下现象:通过3次结束子进程,我们观察到了子进程确实每次都会发生变化,但是呢对于父进程而言却不会发生任何的变化,这是为什么呢?
- 我们可以先去查看一下这个父进程是什么
ps ajx | head -1 && ps ajx | grep 18866
- 然后我们就可以发现这个父进程原来是
bash
,它可以执行我们所输入的命令
原因解析:
1️⃣每次在登录XShell的时候,系统会为我们单独再创建一个Bash进程,即命令行解释的进程,帮我们在显示器中打印出对话框终端[LLZ@iZf8z3lh8un7rc5rk1ney3Z lesson13]$ ----- 【父进程----ppid】
2️⃣ 我们在命令行中输入的所有指令都是Bash进程的子进程,Bash进程只负责命令行的解释,具体执行出问题的时候只会影响它的子进程
ls /proc/18866 -- 子进程【PID】
上面这样解释可能还是比较抽象,一样来举个例子
- 还记得,我们在讲解shell运行原理的时候曾经说到过王婆是一位资本家,她为了不损坏自己的名声呢,在别人找她说媒的时候会派遣一些实习生去,即使实习生出了问题她的名誉也不会受到影响
这就可以对照到父进程与多个子进程,可以有多个子进程在这个父进程上面运行,即使某一个子进程突然出问题终止了,还有其他子进程在运行,此时父进程也是有保障的,所以子进程每次都会发生变化,但是父进程永远都不会变化
四、进程概念总结
1️⃣进程的本质
进程(Process) 是程序的一次执行实例,是操作系统进行资源分配和调度的基本单位。
- 从用户角度看:进程是正在运行的程序
- 从内核角度看:进程是一个包含了程序代码、数据、资源和状态信息的实体
- 每个进程都有自己独立的地址空间、文件描述符、寄存器状态等
在 Linux 中,进程由进程控制块(PCB) 描述,Linux 内核中对应的结构体是
task_struct
,包含了进程的所有信息。2️⃣进程的 ID(PID)
每个进程都有一个唯一的标识符:
- PID(Process ID):进程唯一标识符,非负整数
- PPID(Parent PID):父进程 ID,标识创建当前进程的进程
- 特殊 PID:
- PID=0:调度进程(内核进程)
- PID=1:init 进程(系统启动后第一个用户进程)
- PID=2:kthreadd 进程(内核线程管理)
可以通过
getpid()
和getppid()
系统调用获取当前进程和父进程的 PID:#include <unistd.h> pid_t getpid(void); // 返回当前进程PID pid_t getppid(void); // 返回父进程PID
3️⃣描述进程--PCB(进程控制块)
进程控制块(Process Control Block,PCB)是操作系统中用来描述和管理进程的重要数据结构。每个进程在创建时,操作系统都会为其分配一个 PCB,它是操作系统管理进程的核心,也是进程存在的唯一标志
- 作用:
- 唯一标识进程:通过 PCB,操作系统可以区分不同的进程,每个进程都有一个唯一的进程 ID(PID)作为标识。
- 存储进程状态:保存进程当前的运行状态,如正在运行、等待资源等,便于操作系统了解进程的运行情况,进行相应的调度和管理。
- 资源管理:记录进程使用的 CPU、内存、文件等资源信息,操作系统可根据这些信息进行资源分配和回收,确保资源的合理利用。
- 调度和切换:在进程切换时,操作系统通过 PCB 保存和恢复进程的上下文,使进程能够在下次调度时继续正确执行。
- 组成部分:
- 进程标识信息:包括进程 ID(PID)、父进程 ID(PPID)、用户 ID 和组 ID 等,用于唯一标识进程及其所属用户和用户组,同时体现进程间的父子关系。
- 进程状态信息:常见的进程状态有新建、就绪、运行、等待、终止等,PCB 中保存着进程的状态以及状态变化的相关信息。
- 进程上下文信息:主要包括 CPU 寄存器值、程序计数器、处理器状态字(PSW)等。这些信息在进程切换时被保存和恢复,以保证进程再次执行时能从正确的位置继续。
- 内存管理信息:描述进程在内存中的布局和使用情况,如代码段、数据段、堆和栈的地址,页表指针等,用于虚拟内存管理和进程内存空间的分配与管理。
- 资源管理信息:记录进程打开的文件、正在使用的输入 / 输出设备,以及与进程同步和通信相关的信号量等信息,反映进程对系统资源的占用情况。
- 进程调度信息:包含进程的优先级、时间片、调度队列指针等,用于操作系统根据调度算法决定进程的执行顺序和 CPU 分配。
- 进程间通信信息:如进程接收和发送的信号、通过管道通信的相关信息以及进程共享的内存区域等,支持进程之间的通信和同步。
结束语
以下就是我对【Linux系统编程】进程概念的理解
感谢你的三连支持!!!