【Linux操作系统】简学深悟启示录:进程状态&&优先级

文章目录

为了弄明白正在运行的进程是什么意思,我们需要知道进程的不同状态。一个进程可以有几个状态(在 Linux 内核里,进程有时候也叫做任务),同时谁先运行也是很重要的

1.进程状态

bash 复制代码
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 查看优先级

bash 复制代码
[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 其取值范围是 -2019,一共 40 个级别

2.2 修改优先级

修改优先级用 top 命令更改已存在进程的 nice

R 键进入修改 nice 值的模式,然后输入对应正在运行的进程的 PID

接着输入范围 -19~20nice 值进行修改

可以看到优先值确实被调整了,一般来说是不需要调整的,只有特定的情况需要调整

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 下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称之为并发
  • 上下文数据: 简单而言即为临时数据,进程切换时,部分上下文数据会被保存到寄存器方便下一次直接启动进程

希望读者们多多三连支持

小编会继续更新

你们的鼓励就是我前进的动力!