前言
最近回看《操作系统导论》,发现很多知识只是当时懂了,但懂了并没有内化,今天重新看一遍并记下其中要点,方便之后更快速地复习,提领要点。顺便在撰写笔记的途中,对书中没有说清、说深的知识点进行补充。
第四章 抽象:进程
进程,资源分配和拥有的最小单位
进程就是操作系统为正在运行的程序提供的抽象
操作系统通过 虚拟化 CPU提供一种假象。让一个进程只运行一个时间片,然后切换到其他进程,操作系统提供了多个虚拟CPU的假象。这就是时分共享技术(time sharing)
进程创建
- 创建一个独立的虚拟地址空间
- 通过可执行文件头建立 可执行文件 与 虚拟地址空间 的映射
- 设置 程序计数器 为可执行文件入口地址 启动运行
进程状态
最基本:运行、就绪、阻塞
额外:创建、僵尸、挂起(挂起/阻塞、挂起/就绪)
操作系统通过一些关键的数据结构来跟踪进程的相关信息,这些数据结构可以保存寄存器上下文用于 上下文切换中 恢复进程状态。
第五章 插叙:进程API
都是很基本的系统调用:fork() exec() wait()
exec 本质是从 可执行文件中加载代码和静态数据 并用其 覆写自己的代码段和静态数据,堆、栈等也会被重新初始化。exec 并没有创建一个新的进程,相反,它只是对本身进程进行了覆盖。
事实上,shell 本质蕴含在这一章。即我们输入一个命令,shell 调用 fork 创建一个新进程,然后找到对应命令的可执行文件 exec 之,随后调用 wait 等待进程结束
shell 的重定向 就是 在 exec 之前关闭标准输出并重定向至文件
shell 的管道 就是 pipe 系统调用,一个进程的输出被连接到一个内核管道上,另一个进程的输入在这个管道的另一端,于是一个进程的输出到了下一个进程的输入。
例子:查找当前文件夹下含 tyz 的文件 ls -R | grep tyz
ps 查看当前运行进程,-a 显示全部进程(当你打开一个shell 并 ps 只会显示当前 shell 下的进程) -l 显示详细信息。请直接 ps -al
top 查看系统中进程资源消耗情况(实时)
第六章 机制:受限直接执行
时分共享CPU提出了挑战:性能以及控制权,控制权对操作系统很重要,如果没有控制权,一个进程可能接管机器。
考虑直接执行一个程序
操作系统 | 程序 |
---|---|
在进程列表创建条目,为程序分配内存,将程序加载进内存,根据argc/argv设置程序栈 | |
清除寄存器,执行 call main() | |
执行main(),从 main 中 return | |
释放进程的内存,将进程从内存列表清楚 |
直接执行效率很高,但万一进程希望执行某种受限操作(如像系统请求获得更多内存)时这会很糟糕,我们无法限制其越界行为。
因此,我们引入 用户模式
,在此模式下运行的进程将不能发出 I/O 请求等操作。而对应的 内核模式
可以做任何操作,操作系统就以此种模式运行。
但普通程序也有执行 某种特权操作 的场景,所以有了 系统调用 。有几百个系统调用,它们允许我们 访问文件系统、创建销毁进程、与其他进程通信、分配更多内存等
要执行系统调用,程序必须执行特殊的 陷阱(trap)指令,该指令跳入内核并将特权提升到内核级别,工作完毕后,操作系统调用一个特殊的 从陷阱返回 指令,特权级别降低到用户级。
处理器会将程序计数器等寄存器推送到每个进程的内核栈上。从返回陷阱返回将从栈中弹出这些值,并恢复用户模式程序。
操作系统 | 硬件 | 程序 |
---|---|---|
初始化陷阱表 | ||
记住系统调用处理程序的位置 | ||
操作系统运行(内核模式) | ||
在进程列表创建条目,为程序分配内存,将程序加载进内存,根据argc/argv设置程序栈,用寄存器填充内核栈,从陷阱返回 | ||
从内核栈恢复寄存器,转向用户模式,跳到main | ||
运行main…调用系统调用,陷入操作系统 | ||
处理陷阱,做系统调用的工作,从陷阱返回 | ||
从内核栈恢复旧寄存器,转向用户模式,跳到陷阱之后的程序计数器 | ||
…从main返回,陷入(通过exit) | ||
释放进程的内存,将进程从内存列表清楚 |
进程切换
如果一个进程在CPU上运行,这就意味着操作系统没有运行,那么操作系统就做不了任何事。
解决方法:时钟中断,每隔几毫秒产生一次中断,当前进程停止,运行操作系统的中断处理程序,这时操作系统就拿回了控制权。
在时钟中断时,会进行 上下文切换 ,先保存旧进程的寄存器上下文等信息,然后载入新进程(切换地址空间(涉及到换页)、重设寄存器等)
详细流水线:
p
43
p_{43}
p43
注意:不只切换寄存器、切换地址空间,程序运行时,它们会在CPU高速缓存,TLB,分支预测器等建立大量的状态,上下文切换会刷新它们,所以其成本并不低
- 发生时钟中断时的寄存器由硬件隐式保存
- 操作系统进程切换时的寄存器保存在内存中(进栈出栈)
我们知道了进程切换的细节,下一章自然研究 我们决定切换到哪一个进程
第七章 进程调度:介绍
进程调度做这么一件事:在就绪队列中选择一个进程执行之。
内容了解即可
FIFO
SJF short job first
round robin
第八章 调度:多级反馈队列
多级反馈队列 MLFQ(multi-level feedback queue)
它含有许多独立的队列,每个队列有各自的优先级。
它有许多规则:
规则1 如果A的优先级 > B的优先级,运行A不运行B
规则2 如果A的优先级 = B的优先级,都运行
如何改变优先级?
我们希望 交互型进程 有更高的优先级以提升响应时间,计算密集型的进程优先级更低,于是
规则3:工作进入系统时放在最高优先级(最上层队列)
规则4a:工作用完整个时间片,降低其优先级(移入下一个队列)
规则4b:如果工作在其时间片以内主动释放CPU,优先级不变
这样的话,我们的MLFQ 已经很高效了,对于长作业,它必然每次用尽时间片而优先级降低,对于短作业或 I/O 密集型进程其优先级并不会降低。
问题:1.饥饿 2.如果一个进程故意在时间片用完之前放弃CPU,那么它将愚弄操作系统。 于是
规则5:经过一段时间,将系统中所有工作重新加入最高优先级队列
并更改规则4:一旦工作用完了某一层的时间配额(无论中间放弃多少次)降低其优先级
同时,每一个优先级队列的时间片配额应是不一样的,高优先级队列时间片较短