在进程的学习之前首先需要理解并掌握冯诺依曼体系结构及操作系统的相关概念
1. 冯诺依曼体系结构
最终我们得到的结论是:
在数据层面上
- CPU不与外部设备进行交互,而是直接和内存交互
- 所有的外部设备需要数据载入,只能载入到内存中。数据从内存中写出,也一定是需要写到外部设备当中
==程序需要运行就必须加载到内存当中,为什么呢?
因为冯诺依曼体系结构规定CPU需要执行我的代码/访问我的数据就只能从内存中读取==
2. 操作系统
经过上面的描述,我们清楚的知道了管理的本质是对数据进行管理,管理方法是先描述再组织,但是以上都是操作系统管理硬件的方式,那么操作系统是如何管理软件的呢?
3. 进程概念
4. 进程指令
==命令行上启动的进程,一般情况下(不是特殊情况)它的父进程都是bash!==
5. fork 创建子进程
6. 进程状态
6.1 为什么要存在进程状态
6.2 R- 运行状态
6.3 阻塞状态
假设CPU在执行处于运行状态的某个进程时(该进程处于运行队列当中),发现该进程要往磁盘上写入数据(fwrite),但是CPU的运行效率和磁盘相比不是一个量级的,CPU并不想等待磁盘做出响应(太慢),所以CPU将该进程从运行队列中拿出,将该进程链入磁盘的等待队列当中,CPU继续调度运行队列中的其他进程。
==对于CPU来讲,CPU永远执行的是处于运行状态的进程,一旦该进程需要访问外设资源时,CPU就会将其从运行队列中拿出,链入到对应外设的等待队列当中==
当该进程处于外设的等待队列中时,该进程也就不是R状态,我们将此时的进程状态称之为阻塞状态。
而上述的挪动操作并不是将加载到内存的代码和数据进行挪动而是针对进程的task_struct(PCB)对象放入到不同的队列当中。
当磁盘准备完毕(可以写入的状态)时,操作系统读取到磁盘当前的状态,发现磁盘的等待队列中存在进程,将该进程改为R状态,并重新放入到运行队列当中。
所谓的进程的不同状态,本质是进程(PCB)处于不同的队列当中,等待某种资源(位于运行队列当中就是在等待CPU资源,位于外设队列当中就是在等待外设资源)
6.4 挂起状态
假设当前内存中存在3个进程需要等待磁盘资源才能运行,也就是这三个PCB都处于阻塞状态在等待磁盘资源的到来,当磁盘资源准备完毕后这3个进程会不会被立即调度呢?
答案是不会,操作系统会先将这三个进程的阻塞状态改为R状态,进入到运行队列当中等待CPU调度
但是这种等待外设资源的进程可能需要等待很长时间,而在等待期间,该进程不会被调度,而其代码和数据又会在内存中占用空间,如果内存出现内存不够的情况下该如何处理呢?
将该进程的代码和数据暂时保存到磁盘当中(就节省了内存的空间),这部分省下的空间可以给操作系统使用
这种策略是针对所有进程的,并不是只针对几个
我们把这种将进程的代码和数据暂时保存到磁盘上的现象称之为进程被挂起了
挂起并不代表释放,该进程的内核数据结构(PCB)还存在于内存当中,当该进程要调度时,再将其代码和数据载入到内存当中,将该进程的状态改为R,再放入运行队列当中最后再运行。
将进程的相关数据加载或保存到磁盘当中,称为内存数据的换入换出
6.5 阻塞 vs 挂起
==阻塞不一定挂起,但是挂起一定阻塞==
7. Linux下的状态
上述的进程状态都是在宏观操作系统的角度来看待的,接下来我们来看看Linux操作系统下时如何实现这些进程状态
/*
* The task state array is a strange "bitmap" of
* reasons to sleep. Thus "running" is zero, and
* you can test for combinations of others with
* simple bit tests.
*/
static const char * const task_state_array[] = {
"R (running)", /* 0 */
"S (sleeping)", /* 1 */
"D (disk sleep)", /* 2 */
"T (stopped)", /* 4 */
"t (tracing stop)", /* 8 */
"X (dead)", /* 16 */
"Z (zombie)", /* 32 */
};
该数组中保存的就是Linux下的进程状态信息
'R'状态 -- 运行状态
'S'状态 -- 休眠状态
S状态对应的就是操作系统中阻塞状态的一种
'T'状态 -- 暂停状态
当前进程处于暂停状态,暂停状态属于阻塞的一种,该进程可以被挂起。
对于用户而言,该进程代码没有被执行,被暂停了。至于是否被挂起,则是由操作系统决定的
经过上述学习,T暂停状态和S休眠状态都是阻塞状态中的一种,至于是否挂起都是取决于操作系统,而操作系统认为挂起状态并不需要暴露给用户,用户只需要关系进程当前是什么状态即可!
'D' 状态 -- 深度睡眠
'S'状态 被称为浅度睡眠状态,该状态的进程可以被终止
而处于'D'状态深度睡眠的进程无法被终止
在通常使用操作系统时,都不会出现该状态,一般出现在数据库的开发"高IO/高并发"的场景当中
现在来模拟该场景:
假设操作系统中存在进程A 包含10万条用户数据写入到磁盘当中,当进程A被加载到内存当中时,进程A需要访问磁盘资源并写入,磁盘在写入时,进程A等待磁盘写完并返回....
假设此时操作系统资源吃紧,操作系统采用挂起进程的策略将内存中的空间腾出来,但是还是无法解决问题,此时操作系统发现了游手好闲的进程A(啥事不干) 且当前操作系统资源紧缺,Linux操作系统就将进程A杀死了(不杀自己就要死了)......
此时磁盘将数据写完,发现写入的过程中出现部分失败的数据,要告诉进程A相关信息,但是此时进程A已经被杀死了,没有进程接收退出信息。数据就被丢失....
此时用户前来了,需要写入的数据被丢失,当前数据非常重要该问责谁呢?
那么此时用户就给操作系统下规定:凡是需要往磁盘写入重要数据的进程,给个免死金牌'D'状态,无法再被操作系统杀死,只能通过断电或者进程自己醒来解决(只有高IO时,才能看见D状态)
't' 状态 -- 被追踪
'X'状态 -- 死亡
'X'状态表示的是死亡状态,这个状态只是一个返回状态,你不会在任务列表里看到这个状态。
在Linux操作系统当中无法验证,因一旦进程死亡,操作系统会立即或者延时(相较于CPU而言,非常快)将其所占用资源回收(PCB,代码在内存所在空间等),所以无法查看
'Z'状态 -- 僵尸
僵死状态(Zombies)是一个比较特殊的状态。
当进程退出并且父进程没有读取到子进程退出的返回代码时就会产生僵死(尸)进程
僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。
所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态
僵尸进程的危害
进程的退出状态必须被维持下去,因为他要告诉关心它的进程(父进程),你交给我的任务,我办的怎么样了。可父进程如果一直不读取,也就意味着子进程就一直处于Z状态!!!
维护退出状态本身就是需要数据维护,也属于进程基本信息,所以保存在task_struct(PCB)中,换句话说,Z状态一直不退出,PCB一直都要维护
==那一个父进程创建了很多子进程,就是不回收,是不是就会造成内存资源的浪费?==
是的!因为数据结构对象本身就要占用内存,PCB需要一直维护处于僵尸状态的进程(而且该进程无法使用kill命令杀死,因为进程已经退出了,无法响应)属于"占着茅坑不拉屎"(占用内存资源)
==那么是否会造成内存泄漏呢?== 会的!
7. 孤儿进程
8. 进程优先级
9. 进程切换
10. 其他概念
竞争性:
系统进程数目众多,而CPU资源只有少量,甚至1个,所以进程之间是具有竞争属性的。为了高效完成任务,更合理竞争相关资源,便具有了优先级
举例:学生在食堂打饭是需要排队(进程在运行时的运行队列,根据优先级进行排队执行)
独立性:
多进程运行,需要独享各种资源,多进程运行期间互不干扰
举例:我们在使用手机(Linux操作系统)时,迅雷下载中止会影响QQ运行嘛?/ 打游戏闪退会导致抖音也闪退嘛?
很明显不会,所以进程是具有独立性的
==我们已经知道不相干的进程具有独立性,那么对于父子进程呢?==
并行:
多个进程在多个CPU下分别,同时进行运行,这称之为并行
存在多个CPU,可以使得在同一时刻可以有多个进程分别在各个CPU上运行,称之为并行
并发:
多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称之为并发
如果只有1个CPU,那么在同一时刻只能存在1个进程在运行
但是我们都知道,进程并不是运行起来就独占CPU资源直到结束的,而是时间片轮转的策略
(比如:获得CPU资源后,只能使用10毫秒的时间,不管是否运行结束都需要替换下去,要么进入运行队列中重新排队,要么进行等待)