1.进程的状态
1.1进程的状态在PCB中就是一个变量
一般用宏来定义,例如:
#define RUNNING 1
#define BLOCK 2
struct task_struct中的int status
1.2并行和并发
CPU执行代码,不是把进程代码执行完毕,才执行下一个,而是给每一个进程预分配一个时间片,基于时间片进行调度轮转(单CPU下),叫做并发。CPU切换和运行速度非常快,所以用户感知不到。
并发:多个进程在一个CPU下,采用进程切换的方式,在一段时间内,让多个进程都得以推进,称之为并发。
并行:多个进程在多个CPU下分别,同时进行运行,这称之为并行。
1.3时间片
Linux/windows民用级别的操作系统,基本都是分时操作系统(相对的有实时操作系统,比如在汽车中实时操作系统和分时操作系统都有,比如在踩刹车时,这个进程必须高优先级处理,并且要处理就必须尽快处理完的,这种叫做实时操作系统),调度任务追求公平。
1.4进程具有独立性
每个进程拥有独立的地址空间和运行环境,彼此隔离,互不干扰。
1.5等待的本质
等待的本质是连入目标设备,CPU不调度了。
运行和阻塞的本质是让不同的进程处在不同的队列中。占有CPU资源和占用设备资源,表现出来的状态分别叫做运行和阻塞。卡住的本质是CPU不调度了,键盘没数据,在等待,或者系统没数据了。
1.5.1什么叫阻塞
列入到设备的等待队列的就是阻塞状态。
1.5.2什么叫运行状态
只要进程在运行队列中,该进程就叫做运行状态,表示我已经准备好了,可以被CPU调度了。所以一般就绪和运行状态是一样的。
1.5.3CPU的运行队列
操作系统内部都要给每一个CPU提供一个运行队列struct rqueue,就是在操作系统内部的一个结构体。
该队列中包含的属性:
- int nums即包含有多少进程
- 其他属性
- task_struct *head指向task_struct(运行进程的PCB)也即所有需要被调度的进程都连入到这个运行队列中
CPU调度时,只需要找到该CPU对应的rqueue,FIFO调度算法选择进程,即基于时间片轮转的先进先出的调度队列。
eg:IO操作,执行scanf【封装有系统调用,去帮操作系统查键盘上有没有数据,如果没有就变成阻塞,不把该进程连入到队列里,而是连入到设备上,把该进程的PCB连入到struct device中的wait_queue队列中,有数据了,操作系统就把这个进程的PCB再连入到运行队列】,如果CPU发现没有按键输入,该进程就不能被继续执行了,该进程会变成阻塞状态,等待底层硬件准备好。
1.5.4操作系统如何管理硬件
先描述
struct device
{
int tpye;//表示是哪个设备
int status;//表示设备的状态
//管理时间
//其他属性
struct device *next;//设备之间连接
task_struct wait_queue;//阻塞的进程都到设备的这个队列中等待
}
再组织:五个硬件设备,那么就有五个struct device
OS管理硬件转化成对链表的增删查改(通过驱动层获取数据)。
1.6挂起状态
- 背景:处于阻塞状态的进程的PCB对应有代码和数据,是占内存的,但是目前还没有被调度时,这部分内存资源其实是浪费的。这个时候,如果内存资源严重不足,OS就会把正在阻塞状态的进程的代码和数据换出到磁盘中,只留下其PCB。后来键盘有数据了,OS就把该进程的代码和数据换入回来,然后再把PCB中的阻塞改为运行,并把该PCB连入到运行队列中。
- 在内存资源严重不足时,所有在等待外设的进程都有可能被换出。
- 磁盘中有一个专门用来换入换出的交换分区(swap分区),本质是用时间换空间。
- 云服务器中的swap分区功能一般禁掉。
- 换入换出的本质是IO,所以比较慢。
- 被换出的进程,此时的状态叫做阻塞挂起状态。
- 也有运行挂起,风险较大,一般不。
- 换入换出解决不了的时候,OS会优先保证自己的安全,所以可能会把一些占用资源最多的进程直接结束。比如一些应用程序闪退了。
2.Linux进程的状态
2.1R运行状态
ps axj | grep code查看进程code
ps axj | head -1 && ps axj | grep code查看进程,看到状态是S+,+代表是前台跑的。
去掉printf代码后,变成R+状态
因为99%的时候都是在IO(printf),太慢了,所以只有很少的时候在运行队列里,大部分时候是在等待的。
2.2S休眠状态
本质是阻塞等待状态
修改代码为scanf再printf
在等待阻塞的状态时,是可以被信号中断的kill -9 3086,这叫做可中断睡眠浅睡眠。
2.3D深度睡眠,不可被杀掉的状态
disk磁盘,也是阻塞等待的状态的一种。不可中断睡眠,深睡眠。
磁盘用来存取数据,永久的。
背景:
- 一个进程A的任务是保存一整天银行的交易记录,这个进程要把数据写到磁盘中。
- 进程把数据交给磁盘。
- 磁盘保存数据非常慢,保存数据非常花时间。
- 磁盘正在写入数据的期间,进程A在阻塞等待磁盘把数据写完,所以进程A从运行队列中剥离,进到磁盘的等待队列中,此时进程A的状态是D。此时OS发现此时内存资源严重不足了,但是进程A是深度睡眠,不可被杀掉的状态,进程A就可以得到磁盘的写入结果,成功或者失败,这样数据就不会在系统层面丢失了。
等待磁盘的时候必须是D状态,防止数据丢失。
一般查到进程是D状态,说明系统快挂掉了。
2.4T暂停状态
kill -l查看信号
- 19信号是用来暂停一个进程
- kill -19 4770停止进程4770
- 18信号继续
- kill -18 4770继续进程
- 表示进程做了非法但是不致命的操作,被OS暂停了。
前台进程S+
- ./code &再回车就把前台任务变成后台任务了。
- ctrl c可终止
后台进程S
- 暂停再开始,进程会到后台运行
- ctrl c无法禁止
- kill -9 4770才能杀掉
为什么要放在后台
- 方便继续命令行操作等
2.5t追踪暂停状态
gdb code
打断点。
当进程被追踪时,断点停下,进程状态就是t。
2.6X死亡状态
1.进程为什么会被创建?
进程创建出来是为了完成用户的任务。
2.进程退出后,任务完成了吗?
- 通过进程执行的结果,告知父进程或OS,我把任务完成的如何。
- echo $?用来记录最近程序退出时的退出信息,0表示进程执行成功,非0表示执行出错。
3.main函数返回0,就是告诉父进程(bash)我的执行结果是正确的。
2.7Z僵尸进程
1.先进入Z状态,然后才有X状态。
2.为什么要有Z状态?
维持退出信息,方便父进程或OS来查询。
3.进程退出后,代码不会执行了,所以代码和数据不需要存在了。首先可以立即释放的就是进程对应的程序信息数据。
4.进程退出,要有退出信息(进程的退出码int code(main函数的返回值))保存在自己的task_struct内部。
5.管理结构task_struct必须被OS维护起来,方便用户未来进行获取进程的退出信息。
6.创建的时候,先创建内核数据结构再加载代码和数据。
7.退出的时候,先释放代码和数据,再维护内核数据结构,此时进程的这个状态叫做僵尸状态,此时父进程或OS可以通过内核数据结构获取进程退出信息。
2.7.1看到僵尸进程
1.创建子进程
2.父子进程同时存在
3.让子进程退出,父进程存活,但是让父进程什么都不做
4.杀掉子进程,Z状态
Z状态,如果没人回收我(默认没人管,一般需要父进程读取子进程信息,子进程才会自动退出,后面说怎么做),我会一直僵尸🧟♂️那么task_struct会一直存在,一直消耗内存。即内存泄漏了。
5.父在,子退,子进程僵尸🧟♀️
语言层面的内存泄漏问题,如果常驻内存的进程中出现内存泄漏,(不退出,会一直占用内存,即内存泄漏)影响比较大。
6.malloc出来的空间,这个进程退出后,申请的空间也会释放(因为申请的空间属于程序的数据)
2.8孤儿进程
父退,子在
1.父进程退了为什么没有僵尸?
因为父进程的父进程是bash
2.子进程的ppid变成了1?
1代表system
子进程的PPID变成1了,代表被系统自动领养了。
子进程未来想退出了,系统会自动回收子进程。
3.被领养的进程默认回到后台运行,所以ctrl c无法杀掉,只能kill -9 pid