进程控制块与进程状态
复习
- 系统调用:用户程序怎样安全地请求内核服务
- 程序如何变成进程:区分静态的程序文件和运行中的进程
- 进程的创建与结束:父子进程、退出状态和资源回收
TL;DR
- 内核用进程控制块(PCB) 记录一个进程的全部信息
- PCB 里有身份、运行现场、占用资源和当前状态
- 进程有三种基本状态:运行、就绪、阻塞
- 状态随事件彼此转换,内核据此决定让谁上 CPU
正文
内核同时管着成百上千个进程。它总得有个地方,把每个进程的情况记下来。这个“档案”,就是进程控制块(PCB,Process Control Block)。
一份进程档案
PCB 是内核为每个进程维护的数据结构。前面几章零零散散提到的信息,最后都收在这里:
- 身份:进程编号,父子是谁
- 运行现场:PC 值、各寄存器的内容、栈的位置
- 资源:占用了哪些内存、打开了哪些文件
- 状态:它是正在跑,还是闲着,还是在等
- 调度信息:优先级等,供内核决定谁先上 CPU
可以说,PCB 就是进程在操作系统里的“户口本”。找到 PCB,就等于找到了这个进程的一切。
三种基本状态
一个进程,随时处在下列三种基本状态之一:
- 运行(Running):正占用 CPU,在跑
- 就绪(Ready):一切都准备好了,只差一个 CPU
- 阻塞(Blocking):在等某个事件发生,就算给它 CPU 也没用
“就绪”和“阻塞”最容易混。区别在于:
- 就绪,是缺 CPU
- 阻塞,是缺事件(比如等磁盘读完、等用户按键)
状态怎么变
状态不是固定的,它会随事件来回转换:
┌──────────── 被调度 ────────────┐
▼ │
┌───────┐ 时间片用完 ┌───────┐ │
│ 就绪 │ ───────────────>│ 运行 │───┘
└───────┘ <───────────────└───────┘
▲ 事件完成 │ 等待事件
│ ▼
│ ┌───────┐
└───────────────────│ 阻塞 │
└───────┘
- 就绪 → 运行:内核选中了它,把 CPU 交给它
- 运行 → 就绪:时间片用完(不能让一个进程一直霸着 CPU)
- 运行 → 阻塞:它要等一件事,比如发起了一次磁盘读取
- 阻塞 → 就绪:它等的事情完成了,重新排队等 CPU
顺带一提:阻塞的进程不会“占着 CPU 空等”。它一进入阻塞,内核就把 CPU 让给别的就绪进程,自己去等事件——这正是不让 CPU 浪费的关键。
队列
把同状态的进程排到一起,管理起来就方便了:所有就绪的进程排成就绪队列,谁被调度就看这个队列;等待不同事件的进程,分别排在各自的阻塞队列里。
于是内核的调度工作,很大程度上就是:在就绪队列里挑一个,运行它;它一旦让出 CPU,再挑下一个。
既然多个就绪进程要轮流上 CPU,那“换人”这件事就必须做利索。下一章,我们看内核怎样干净地把 CPU 从一个进程交给另一个——上下文切换。
思考题
一个进程发起磁盘读取后进入阻塞,另一个进程还在就绪队列里等着。如果内核不把前者的状态从“运行”改成“阻塞”,会发生什么?为什么“就绪”和“阻塞”必须分开?
小结
知识点
- PCB 记录进程的身份、现场、资源与状态
- 三种基本状态:运行、就绪、阻塞
- 就绪缺 CPU,阻塞缺事件
- 状态随调度与事件相互转换
- 同状态的进程排成队列,便于管理
参考资料
- Wikipedia(zh):进程控制块:PCB
- Wikipedia(zh):进程状态:运行、就绪与阻塞
思考题答案(仅供参考)
如果不把它改成阻塞,内核会以为它还能继续跑,于是反复把它调度上 CPU;可它实际在等磁盘,上 CPU 也只会再次发现“还没好”,白白浪费 CPU 时间。把“就绪”和“阻塞”分开,内核才能一眼看出:哪些进程给了 CPU 就能干活,哪些进程给了也是白给。这样才能把 CPU 只分给真正能用它的进程,避免空转。
协议
本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。
封面图
设计师 | 南国微雪