前言
该篇文章是基于 进程概念(一) 的后续 ,前篇文章主要是为讲述进程概念做了一些铺垫知识,而本篇文章着重对进程本身进行介绍。
1. 进程概念
上一篇文章在讲述冯诺依曼的时候,已经明确了,在该体系下,所有要被运行的程序,一定会先被加载到内存,进而由内存再与 cpu 进行交互!
所以,一个已经加载到内存中的程序(或 正在运行的程序),叫做进程。
这跟我们开机是同理的,开机的本质,就是启动操作系统这个软件,而开机的过程计算机所做的工作无非就是,将外设中的操作系统加载到内存中,所以进程也是这个道理。
2. 理解进程
假设,磁盘中存储了一个名为 test(代码 + 数据) 的程序,而今天我们想要运行这个可执行程序,在冯诺依曼体系中,决定了该程序在被运行之前,一定是先被加载到内存当中的。
但是,一个操作系统,不仅仅只有这一个进程,它是可以同时运行多个进程的!有些进程可能是正在被运行的,有些在休眠等等。而面对各种各样的进程,那操作系统肯定是需要将它们进行管理的!
那么现在的问题是,操作系统如何管理的这些进程呢? ---- 先描述,再组织的思路
先通过面向对象的思想,将每一个进程的所有属性描述起来,而面对这些进程,无非就是新增一个进程,删除一个进程等操作,那么就需要再将这些已经描述起来的进程,通过某种数据结构组织起来。
所以,任何一个进程,在加载到内存的时候,形成真正的进程时,操作系统要先创建描述进程(属性) 的结构体对象 ---- PCB(process control block) ---- 也即进程控制块!
那么操作系统在创建描述进程的结构体时,是如何对进程进行描述的? ----- 这其实就跟我们自然生活中是相似的。我们在描述某一个人时,我们一定是说,xxx 叫啥,身高体型,单/双眼皮,什么发型,什么穿衣搭配等属性,甚至是 ta 的肖像画!
所以在描述一个人或事物时,我们一定是通过他们的属性进行描述的,我们也是通过属性去认识某一个人或事物的。
而今天我在描述某一个人物时,这个人是佛山的,幼年体弱多病,但后面拜陈华顺为师,习得一套咏春拳术,并且发挥到了极致,曾经一度响彻佛山等地区,当我描述到这里的时候,我甚至不需要说他叫什么,大家就已经知道我在说谁了!所以,只要属性足够多,那么这些属性的集合,就是我们要描述的目标对象!
所以操作系统怎么描述进程? ---- 本质就是将这个要描述的进程的一堆属性放在一起,合起来,它就叫做进程!所以 PCB 的本质就是属性的集合!
而操作系统是 c 语言写的,所以操作系统想要描述进程,只能用 struct 结构体
进行描述,所以 PCB 本质就是操作系统内定义的一个 struct 结构体类型!
那么进程里面都有哪些属性呢?(此处不展开详谈,后续文章会对进程的常见属性进行详细介绍)
struct PCB
{
进程编号 // 为了让操作系统可以找到某一个进程
进程的状态 // 标记着进程当前的运行状态
优先级 // 同样在运行中的进程,谁先谁吗慢?
....
struct PCB* next; // 相关的指针信息
}
在创建进程的时候,操作系统首先会先创建一个描述进程的结构体对象,并对其内部属性进行初始化,而刚刚也说了,一个进程要被运行,该进程的代码和数据也要被加载到内存当中!比如我们通过 c++ 写的代码,最后经过编译形成的可执行程序,这就是我们的代码+ 数据,而当我们要运行这个程序的时候,不仅仅要将 代码和数据 加载到内存中,同时要为这个进程创建一个进程的结构体对象!
故事时刻:在学校里,我要成为该学校的一名学生,并且在该学校上课!我不仅仅需要考上这个学校,同时,我的各种信息需要在学校的教务系统中存在(即被录用),这样我才可以称作这个学校的一名学生。而张三作为我朋友的身份,来到我学校参观,虽然他在我的学校里面,但是学校的教务系统中,并没有张三这名学生!因此判定他不是我们学校的学生。所以,进程也是如此!操作系统将进程的代码和数据加载到内存之后,还会为进程创建一个描述该进程的 进程控制块(pcb)。
所以什么是进程? ----- 只有代码和数据,并不能称为进程,而是 PCB + 代码和数据,这样的结合体,才能够称为进程!
即,进程 = 内核 PCB 数据结构对象 + 代码和数据 (而 PCB 就是描述这个进程的所有属性)
而既然 PCB 就是描述这个进程的所有属性,那么它就一定需要有一个指针信息,指向与该进程所匹配的代码和数据,这样才能够通过 PCB 找到该进程的所有代码和数据,然后交给 cpu 运行,这就完成了对一个进程的管理!
在该故事的另一方面,学校在对学生进行管理的时候,只需要在教务系统上,对学生的信息进行管理即可(比如学校要开除李四,并不需要找到他的人,只需要将其从教务系统中移除即可)。所以操作系统也是如此!在管理进程的时候,根本就不看这个进程的代码和数据,而是对这个进程的 PCB 进行管理!在对多个进程进行管理的时候,只需要在每个进程中多新增一个指针类型的链接属性,将每个进程组织起来,那么操作系统在管理这些进程的时候,就等同于对数据结构进行增删查改!
为了更好的理解,操作系统只针对 PCB 进行管理,而不是进程的代码和数据,下面再举一个例子。
在企业中,hr 为了筛选人才,会对投递该岗位的所有面试者进行筛选。但是,hr 并不需要对人本身进行筛选,ta 只需要对该面试者所投递的简历进行筛选,最终安排这些投递简历的人排队面试。而投递简历的人,也并不需要就真的在公司排队!而是简历在排队!当面试官想要考虑你的时候,自然会根据你简历中的指针字段(即电话)联系到你,安排你的面试。而类似这种简历与你本人分开进行管理的现象,就与操作系统对进程的管理是相似的,操作系统只需要对进程的 PCB 进行管理,只要有需要,它可以通过 PCB 中的指针字段,找到这个进程所对应的代码和数据!
3. linux中的PCB
上面的一切,只是讲述所有操作系统对待进程的管理的模式,具体思路就是上面那么做的。但是具体某一款操作系统在进程管理上可能都存在一定的差别。所以接下来就 linux 系统中的进程作进一步介绍。
在 linux 系统中,所谓的 PCB 叫做 task_struct
,里面包括了描述进程的所有属性。可以理解为 PCB 是所有操作系统对进程的统称,而 task_struct
则是 PCB 的其中一种!
task_ struct 内容分类:
标识符: 描述本进程的唯一标识符,用来区别其他进程。
状态: 任务状态,退出代码,退出信号等。
优先级: 相对于其他进程的优先级。
程序计数器: 程序中即将被执行的下一条指令的地址。(其作用就相当于进程运行了一段时间后,因为系统调度等原因,停止了对该进程的执行而后续回来继续执行的时候,需要知道上次执行到什么地方了。也好比我们看书,今天看完不想看了之后,会在此处做一下标记,方便后续继续向下观看)
内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针(比如记录了该进程所匹配的代码和数据的存储位置)
上下文数据: 进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]。
I/O状态信息: 包括显示的I/O请求,分配给进程的 I/O 设备和被进程使用的文件列表。
记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。(保证系统调度的公平等)
其他信息
接下来,我们来见一见所谓的进程!
ps axj | head -1 && ps axj | grep test
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
2724 4957 4957 2724 pts/0 4957 S+ 1001 0:00 ./test
3711 4961 4960 3711 pts/1 4960 S+ 1001 0:00 grep --color=auto test
// 而这其中的 PID 就是进程的唯一标识符
查看进程的方法还有如下
ls /proc
// 该目录是 linux 系统中动态运行的所有进程的相关信息
我们还可以证明一下刚刚的 test 进程运行之后,在系统中的 proc 中是真实存在的!
这里顺带解释一下,为什么我们过滤出来的进程会有两个,其中一个还是 grep …,虽然 grep 是一个过滤器工具,是一条指令来着,但它本质上也是一个可执行程序,而当程序运行进来,叫进程,所以当我们在执行 grep 这条指令的时候,等同于我们在运行 grep 这个进程,而 grep 后面也带着 test 这样的关键字,因此在过滤的时候,不仅有 test 自己的进程信息,还有执行 grep 时创建的进程的信息。
在进程的相关信息里,我们要着重关注两个属性,一个是 cwd(当前进程的工作目录),一个是 exe( PCB中的内存指针执向的可执行程序的信息)。其中的当前工作目录,就涉及到其它方面的知识。
你有没有想过一个问题,当你在 touch 一个文件的时候,你并没有指定文件要创建在哪,但是文件都会创建在你当前所在目录中。换一个说法,当你在调 C 语言中的 fopen 打开文件的时候 fopen("tmp.txt", "w")
,也只是指明了文件名,并没有指明文件的所在路径!而当程序运行起来后,却可以精准的找到你指定的文件,并且打开它,最后对该文件进行读写。
---- 这是因为进程中存在一个属性,叫做 cwd ,它记录了当前进程的工作目录!因此 touch 也好,执行这条指令的时候,就如同一个进程,而每个进程的属性里面都会记录当前的工作目录,所以当你touch的时候,并不需要指定路径,也可以将文件创建在你当前所处的目录下!fopen 也是如此,编译完成后形成的可执行程序,一但允许,进程中就有 cwd 这个属性,在进程的层面上,进程看到的是 cwd/tmp.txt
,所以才能够顺利的找到该文件!
由于篇幅问题,关于 linux 中的 task_struct 的内容属性,在后续文章会继续介绍,感兴趣的请移步。。。。。
如果感觉该篇文章给你带来了收获,可以 点赞👍 + 收藏⭐️ + 关注➕ 支持一下!
感谢各位观看!