文章目录
为了弄明白正在运行的进程是什么意思,我们需要知道进程的不同状态。一个进程可以有几个状态(在 Linux
内核里,进程有时候也叫做任务),同时谁先运行也是很重要的
1.进程状态
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
系统里大致分为这几种
1.1 运行态R
我们知道每个进程是一个 task_struct
具体化的 PCB
,以双向链表链接在一起,当需要运行进程时,CPU
会调用头指针,依次调度,如果有新的进程的话,就用尾指针加入到队列后面就行了
运行态R
指的不是正在运行,而是完全准备好了,可随时被调度器调度分配时间片给 CPU
,每个时间片大概是 10ms
,并不是一个进程全部运行完了才运行下一个,而是每个进程运行一点就被放下来,不断 进程切换
,这样可以保证进程不会等待太久而被误杀,所有的进程会在一个时间段内被执行,叫做 并发执行
1.2 阻塞态S(浅度睡眠)
如图所示,对于设备来说在操作系统里也有一个队列链接起来,举个例子,当某个进程在等待键盘输入数据用于读取时,该进程会从原本的运行队列被链入到键盘的队列中,等待键盘该外部设备输入数据,该状态就叫做 阻塞态S
由于 CPU
的速度远远大于设备,因此捕捉运行中的状态很大概率都是 S
状态
1.3 阻塞态D(深度睡眠)
阻塞态其实有两种状态,假设有一个进程要往磁盘中写入 1GB
数据,此时写入到一半的过程中,磁盘的空间满了,该进程就有可能因为写入失败而被操作系统杀掉,数据代码等都没有了,万一这些数据很重要不就完犊子了吗?因此可以将进程设置为 深度睡眠的阻塞态D
,相当于提示系统不能杀掉该进程,很重要!可以随时杀掉或唤醒的进程就是 浅度睡眠的阻塞态S
一般来说,出现一个 D
状态就说明该系统快崩溃了,有多个 D
状态进程说明这个系统基本已经崩溃了
1.4 挂起态T
系统中的进程只要处于闲置状态,一般都可以属于挂起状态,就拿阻塞态来说,当操作系统的内存资源严重不足的时候,就需要调整资源,会将一些不那么重要的进程,保留其 PCB
,数据代码放到磁盘的 swap
区保存,此时就处于 挂起状态
,当需要的时候再把数据交换回去就好了,T
状态出现的时间很短,一般很难捕捉到
1.5 挂起态t(追踪)
当进程被调试器(如 gdb
)追踪时,会显示为小写 t
状态。此时进程因调试需求被暂停,等待调试指令(如断点处)
1.6 终止态X
X
状态的全称是 TASK_DEAD
,表示进程已经完成执行,正处于退出清理阶段。当一个进程终止(无论是正常退出、被信号终止还是崩溃),内核会:
- 回收进程占用的资源(如内存、文件描述符)
- 更新进程表信息(如设置退出码)
- 最终将进程标记为
X
状态,等待父进程通过wait()
系列系统调用 “回收”(即清理进程表项)
这个状态只是一个返回状态,你不会在任务列表里看到这个状态
1.7 僵尸态Z(僵尸进程)
其实在 终止态X
之前还存在一个父子关系的状态:僵尸态Z
,我们将以如图代码为例子,理解僵尸进程
当子进程终止之后,原先的 S
状态就会变成 Z
状态,子进程的 PCB
释放大部分资源,但是仍然有一部分资源(如进程 ID
、退出码、终止原因,尤其是 task_struct
结构体)需要父进程通过 wait()
或 waitpid()
等系统调用回收子进程,获取其退出信息并彻底清除残留数据
进程的退出状态必须被维持下去,因为他要告诉关心它的进程(父进程),你交给我的任务,我办的怎么样了。可父进程如果一直不读取,那子进程就一直处于 Z
状态,维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存task_struct(PCB)
中,换句话说,Z
状态一直不退出,PCB
一直都要维护?是的!那一个父进程创建了很多子进程,就是不回收,是不是就会造成内存资源的浪费?是的!因为数据结构对象本身就要占用内存,想想C语言中定义一个结构体变量(对象),是要在内存的某个位置进行开辟空间!内存泄漏如何避免?后面讲
1.8 孤儿进程
孤儿进程是指其父进程已经终止或退出,而自身仍在运行的子进程
正常情况下,子进程由父进程创建并管理,父进程会监控子进程的运行状态。但如果:
- 父进程意外崩溃或被强制终止(如收到
SIGKILL
信号) - 父进程正常退出但未正确处理子进程
此时,仍在运行的子进程就会成为孤儿进程,因为到最后都是需要退出这个进程的,此时子进程的父进程会变成 1
号进程(操作系统),相当于被操作系统领养了,所以叫 孤儿进程
2.进程优先级
2.1 查看优先级
[zzh_test@hcss-ecs-6aa4 zombie]$ ps -al
F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD
0 S 1002 32721 32562 0 80 0 - 1054 hrtime pts/0 00:00:00 zombie_test
1 Z 1002 32722 32721 0 80 0 - 0 do_exi pts/0 00:00:00 zombie_test <defunct>
0 R 1002 32746 32723 0 80 0 - 38332 - pts/1 00:00:00 ps
利用 ps -al
命令查看,PRI
表示优先级,即进程的优先级,或者通俗点说就是程序被CPU
执行的先后顺序,此值越小,进程的优先级别越高,那 NI
呢?就是我们所要说的 nice
值了,其表示进程可被执行的优先级的修正数值,PRI
值越小越快被执行,那么加入 nice
值后,将会使得 PRI
变为:PRI(new)=PRI(old)+nice
这样,当 nice
值为负值的时候,那么该程序将会优先级值将变小,即其优先级会变高,则其越快被执行,所以,调整进程优先级,在 Linux
下,就是调整进程 nice
值,nice
其取值范围是 -20
至 19
,一共 40
个级别
2.2 修改优先级
修改优先级用 top
命令更改已存在进程的 nice
按 R
键进入修改 nice
值的模式,然后输入对应正在运行的进程的 PID
接着输入范围 -19~20
的 nice
值进行修改
可以看到优先值确实被调整了,一般来说是不需要调整的,只有特定的情况需要调整
2.3 优先级调度本质
CPU
中有一个 runqueue
结构体,存有一个 waiting
哈希表(wait
指针指针指向该表)和一个 running
哈希表(run
指针指针指向该表),由于进程太多了,实际上这个哈希表是个位图,按照特定的计算方式将进程放到比特位里,对于优先级的进程来说,进程 [60,99]
对应比特位 [100,139]
,[0,99]
是别的种类的进程,每个进程 task_struct
按照优先级从上往下,从左到右挂在 running
哈希表,运行完之后就挂到 waiting
哈希表相应的优先级位置,在这过程中可能会有些新创建的进程或者等待的进程,那么将这些进程放在 waiting
哈希表即可,当 running
哈希表执行完之后,其被指向的 run
指针,和 waiting
哈希表的 wait
指针交换,就能调度另一个队列变成运行队列了,依次循环往复即可
🤔那么如何判断该队列已经调度完了?
由于我们这个是位图,只要遍历一遍,每个比特位上是
0
就能判断已经调度完了
3.其他概念
- 竞争性: 系统进程数目众多,而
CPU
资源只有少量,甚至1
个,所以进程之间是具有竞争属性的。为了高效完成任务,更合理竞争相关资源,便具有了优先级 - 独立性: 多进程运行,需要独享各种资源,多进程运行期间互不干扰
- 并行: 多个进程在多个
CPU
下分别,同时进行运行,这称之为并行 - 并发: 多个进程在一个
CPU
下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称之为并发 - 上下文数据: 简单而言即为临时数据,进程切换时,部分上下文数据会被保存到寄存器方便下一次直接启动进程