进程状态并详解S和D状态
#define TASK_RUNNING 0x0000 // 运行或就绪(在运行队列)
#define TASK_INTERRUPTIBLE 0x0001 // 可中断睡眠(S状态)
#define TASK_UNINTERRUPTIBLE 0x0002 // 不可中断睡眠(D状态)
#define __TASK_STOPPED 0x0004 // 暂停(收到 SIGSTOP)
#define __TASK_TRACED 0x0008 // 被调试器追踪
#define EXIT_DEAD 0x0010 // 终止(最终状态)
#define EXIT_ZOMBIE 0x0020 // 僵尸进程
1.运行状态不一定非得是cpu在跑这个进程,只要是在跑或者在运行队列就行
2.浅睡眠状态S,比如sleep导致的,pcb挂到sleep创建的定时器的等待队列
3.深睡眠状态D,比如write时磁盘没准备好导致的,pcb挂到键盘的等待队列,深睡眠一般是进程进行IO时发生的
假如进程处于浅睡眠状态,如sleep,那进程会被挂在sleep创建的定时器的等待队列里,然后每次时钟中断,执行对应中断函数,会更新定时器,一旦等待时间够了,中断函数就会调用wake_up内核函数来唤醒等待进程,弄到运行队列里
假如进程处于D深度睡眠状态,如write时磁盘没准备好,进程会被挂在键盘的等待队列里,磁盘准备好后,键盘会发出硬件中断请求,然后cpu陷入内核,执行对应中断方法,中断函数会调用wake_up内核函数,使进程唤醒
进程在获取锁的时候,如果获取失败会切换到锁的等待队列上,等到其他进程执行完毕,unlock释放锁,unlock里面会调用wake_up内核函数,使锁上的进程切换到运行队列
wake_up内核函数(不是系统调用)
1.将进程的状态变成R
2.将进程切换到运行队列里
下面是详细过程:
1. 浅睡眠(S状态)—— 以 sleep()
为例
流程
- 进程调用
sleep(3)
- 用户态
sleep()
→ 内核nanosleep()
系统调用 → 设置高精度定时器(hrtimer
)。
- 用户态
- 加入等待队列
- 进程状态设为
TASK_INTERRUPTIBLE
(S状态),挂入定时器的等待队列。
- 进程状态设为
- 时钟中断处理
- 每次时钟中断(如
tick_sched_timer()
)检查定时器是否到期。 - 到期时:调用
wake_up()
将进程移回运行队列,状态改为TASK_RUNNING
。
- 每次时钟中断(如
2. 深睡眠(D状态)—— 以 write()
写入磁盘文件为例
流程
- 进程调用
write()
- 陷入内核-> 若磁盘写入繁忙,进程设为
TASK_UNINTERRUPTIBLE
(D状态),挂入磁盘设备的等待队列
- 陷入内核-> 若磁盘写入繁忙,进程设为
- 硬件中断触发
- 磁盘准备就绪 → 发送中断请求(IRQ)→ CPU 陷入内核,执行磁盘中断处理程序
- 中断处理唤醒
- 中断程序确认写入完成 → 调用
wake_up()
唤醒磁盘等待队列中的进程
- 中断程序确认写入完成 → 调用
3. 锁竞争 —— 以 mutex
为例
流程
- 进程A 抢锁失败
- 调用
mutex_lock()
→ 若锁被占用,进程设为TASK_UNINTERRUPTIBLE
,挂入锁的等待队列。
- 调用
- 进程B 释放锁
- 调用
mutex_unlock()
→ 检查等待队列 → 调用wake_up()
唤醒进程A。
- 调用
- 进程A 重新调度
- 被唤醒后状态改为
TASK_RUNNING
,参与调度。
- 被唤醒后状态改为
理解进程状态S和D的差别,不会被信号打断究竟在说什么
scanf的底层是read,read的时候stdin没数据,read会将进程设为S状态,pcb挂在stdin的等待队列,然后cpu会重新调度其他进程,按下ctrl+c,假如此时cpu运行的是进程B,触发硬件中断,cpu陷入内核,保存寄存器到进程B的内核栈,执行中断函数,会给等待队列中pcb中加入SIGINT信号并检查状态是S那就wake_up唤醒,然后切换到进程B用户态,等到cpu重新调度进程A,从上次read的阻塞点继续执行,会先判断有没有要处理的信号,如果有SIGINT,被忽略那就继续阻塞,如果不忽略,那就read返回EINTR,系统调用read执行完在即将切换回用户态时 ,在这个安全点处理信号SIGINT
假如进程A write时磁盘没有就绪,那就调用schedule()重新调度其他进程,write把进程A设为D状态,挂到磁盘等待队列,ctrl+c键盘硬件中断,执行中断函数,将磁盘等待队列里进程设置SIGINT的信号,然后检查进程状态是D状态,于是不会调用wake_up。直到磁盘准备就绪,发送硬件中断请求,可能在发生中断前cpu正在跑进程B,那么就陷入内核,把寄存器保存到进程B的内核栈,执行中断函数调用wake_up内核函数唤醒进程,然后切换到进程B用户态,等到cpu重新调度进程A,从write的阻塞点继续执行,直接开始写无论信号有没有被设置,write系统调用结束后在切换回用户态前执行SIGINT的执行函数
不会被信号打断有两点
1.设置信号后不会调用wake_up使进程唤醒
2.重新调度进程从阻塞点开始运行,不会检查信号的设置,而是执行剩余逻辑