原文发布于微信公众号 - 云服务与SRE架构师社区(ai-cloud-ops),作者李勇。
最近在学习威斯康星大学的CS-537课程:操作系统导论[1],笔者计划用五篇文章来结束这个课程,目的包括:
这门课的教材是本课程的教授Remzi夫妇合著的,名为《Operating Systems: Three Easy Pieces》[3],中译本就叫《操作系统导论》。
操作系统的定位是计算机资源(CPU,内存,硬盘,各种I/O设备等)的管理者。最早的计算机系统一次只运行一个程序,操作系统是作为库函数的形式存在的,这种模式无法充分的利用计算机资源,对于早期造价动辄数百万美元的计算机来说,这是巨大的浪费,因此人们引入了现代的操作系统来支持方便的多进程并发执行,允许多个用户同时运行他们的程序。具体来说,操作系统提供了这么三个要素:
为什么说单个程序不能充分利用计算机资源呢?这跟计算机的存储器层次结构有关,计算机中有各种各样的存储器:CPU上的寄存器、一二级缓存,内存、硬盘……这些存储器的容量、性能和成本各不相同,一个典型的存储器层次结构如下:
越是靠近上层(CPU)存储器的性能越好,但是容量越小,(每字节)存储成本越高;越是远离CPU,存储器的性能越差,但是容量越大,(每字节)存储成本越低。比如,CPU访问一级缓存缓存只需要1个时钟周期,而进行磁盘I/O可能需要上千万个时钟周期。程序在进行I/O操作的时候,CPU实际是空闲的,这时候可以让CPU运行其他程序,提供计算机资源的利用率。
另一方面,为了弥补高速CPU到低速I/O设备之间的差距,在存储器之间引入了多层的缓存,比如本地硬盘作为网络的缓存,内存(DRAM)作为硬盘的缓存,SRAM作为内存的缓存。由于局部性原理的存在,这个存储器层次结构通常工作得很好。所谓得局部性原理包含两项:
操作系统提供了进程这个抽象概念,一个进程就是一个正在运行的程序。根据Steam 2020年5月的调查,现在主流的PC配置是64位的4核物理CPU和16G内存[1],而目前x86_64的PC上通常会运行几十上百个进程,每个进程拥有256TB的的虚拟内存。正是通过CPU和内存虚拟化,操作系统提供了这种幻象:似乎每一个进程都有一个独占的CPU和一片巨大的独占内存。
在深入这些细节以前,我们先来看看计算机上运行一个进程需要维护些什么状态信息:
操作系统通过分时复用的方式实现了CPU的虚拟化,运行进程A一段时间后,主动或被动地把这个进程的状态信息写入物理内存然后从物理内存中读取另一个进程B的状态信息,从而恢复进程B的运行。
进程在其生命周期中,始终处于以下三个状态中的一个:
这是一个理想化的状态,Linux中进程还有一些别的状态
内核中有一个数据结构叫做Process Control Block(PCB),用来记录上面提到的各种信息,每个进程都有一个对应的PCB。
下面来考虑实现CPU虚拟化要解决的两个核心问题:
计算机系统采用了一种叫Limited Direct Execution的机制,通过硬件和操作系统的协作解决了这两个问题。在具体实现上,CPU中有一个状态位,表明了当前运行在什么模式下:
如果用户进程需要发起特权操作,必须通过操作系统内核来进行,操作系统提供了很多这样的服务入口,这就是系统调用,比如说打开一个文件用到的open()
系统调用。这些系统调用看起来像是一个普通的函数,而内部实现上只是把系统调用的编号,和对应的参数放到栈上某个特定的位置,然后调用trap指令,这个指令会完成以下几个操作:
内核检查参数和权限和合法性,然后执行相应的处理,无论结果如何,最终调用return-from-trap指令返回用户进程,具体过程如下:
可以看到,用户进程直接运行在CPU上, 因此保证了性能,而通过内核模式和用户模式的区分保证了安全,这里主要的损耗在于上下文切换带来的开销。
内核调用return-from-trap之前还会检查进程是否有待处理的信号,如果有的话在这里触发信号处理函数。
Limited Direct Execution 存在一个问题,一个进程可能会长久地占用CPU,导致其他进程无法得到服务,那么这个进程什么时候把控制权还给操作系统,让操作系统调度其他进程呢?很自然地,一个合理的时间点是触发系统调用的时候,操作系统可能会决定先执行另一个进程。但如果是一个无限循环,中间没有任何系统调用呢?一些早期的系统如Mac OS采用了合作式的调度方案,长期运行的进程需要周期性地让出CPU,比如在循环体中加入一个yield()
之类的系统调用,允许操作系统调度其他进程。这个方案治标不治本,存在这些场景:
yield()
一直没有运行这种情况下,唯一能打破这种循环的方法只有重启。要解决这个问题,操作系统仍然需要硬件的协助。硬件中有个计时器可以编程为每隔一定的时间(比如每十毫秒)就发起一个时钟中断,它会挂起当前运行的进程,跳转到操作系统预先设置的中断处理函数中。在这里,操作系统可以决定是继续运行这个进程,或是调度别的进程。这就是抢占式调度。
程序运行的过程中会遇到各种各样的异常情况,在计算机启动的时候,操作系统就需要为各种异常指定对应的处理函数。CPU在执行完一条指令之后,总是会检查是否存在异常,如果有则触发对应的异常处理函数,否则继续执行下一条指令。
《CS:APP》中把异常分为四类:
类别 | 原因 | 异步/同步 | 返回行为 | 例子 |
---|---|---|---|---|
中断(intterrupt) | 来自I/O设备的信号 | 异步 | 总是返回到下一条指令 | 时钟中断 |
陷阱(trap) | 有意的异常 | 同步 | 总是返回到下一条指令 | 系统调用 |
故障(fault) | 潜在可恢复的错误 | 同步 | 可能返回当前指令 | 缺页异常 |
终止(abort) | 不可恢复的错误 | 同步 | 不会返回 | 硬件错误 |
其中异步和同步的区别是:异步中断是由CPU外部的设备产生的,而同步异常是执行某条指令产生的结果,比如除零错误。
现在可以完整的描述这个协议了
OS启动(内核模式) | 硬件 | |
---|---|---|
初始化异常处理函数 | ||
记下异常处理函数的内存地址 | ||
启动中断计时器 | ||
启动计时器,每隔一段时间中断CPU | ||
OS运行(内核模式) | 硬件 | 进程(用户模式) |
进程A正在运行 | ||
时钟中断 | ||
1. 把进程A的寄存器保存到A的内核栈中 | ||
2. 切换到内核模式 | ||
3. 跳转到trap处理函数(系统调用) | ||
处理这个系统调用 | ||
调用stwich()切换进程 | ||
1. 把进程A的内核寄存器保存到A的PCB中 | ||
2. 从进程B的PCB中还原B的内核寄存器 | ||
3. 切换到进程B的内核栈 | ||
调用return-from-trap,返回到B中 | ||
1. 从进程B的内核栈中还原B的寄存器 | ||
2. 切换到用户模式 | ||
3. 跳转到进程B的程序计时器(Program Counter) | ||
进程B运行 |
注意:
上面描述了进程切换的机制,接下来讨论进程调度的策略,也就是说每次操作系统要调度一个进程的时候,选择运行哪一个进程。通常来说,我们有两种类型的工作负载:
下面来看看两种常见的调度策略
多级反馈队列(Multi-Level Feedback Queue)致力于提高系统的整体响应时间。
操作系统中维护多个进程队列,从高到底依次为每个队列分配不同的优先级:高优先级的进程分配较短的时间片,保证快速响应;低优先级的进程分配较长的时间片,保证其高吞吐量。具体调度策略如下:
然而我们不知道每个进程的工作模式是交互式的还是非交互式,因此先假设他们都是需要快速响应的交互式进程:
还存在一个问题,如果有大量高优先级任务,那么低优先的任务可能会被饿死,因此:
与MLFQ相对的,按比例共享调度(Proportional Share Scheduling)的目标是让各个进程公平地获取CPU时间。它最简单的形式叫做彩票调度(lottery scheduling):假设系统使用100张彩票(编号为0-99),每次随机选择一张来决定运行哪个进程,进程A持有75张(编号为0-74),进程B持有剩余的25张(编号75-99)。任务调度器每次计算出一个0-99之间的随机值,如果落在0-74之间则运行进程A,反之运行进程B,这样保证了两个进程总体获得的CPU时间跟它们持有的票据数量一致(75%:25%)。
最后还有个问题,怎么为进程分配票据(或者说权重),可以跟nice值关联起来。
Linux当前采用的进程调度器叫做完全公平调度器(Completely Fair Scheduler/CFS),内部采用红黑树,实现了跟按比例共享调度类似的目标。之前采用的进程调度器为O(1),其实现类似前面说的多级反馈队列。
不怎么务正业的程序员,BUG制造者、CPU0杀手。从事过开发、运维、SRE、技术支持等多个岗位。原Oracle系统架构和性能服务团队成员,目前在腾讯从事运营系统开发。
[1]
CS-537课程:操作系统导论: http://pages.cs.wisc.edu/~remzi/Classes/537/Spring2018/
[2]
《TCP/IP那些事》: https://coolshell.cn/articles/11564.html
[3]
《Operating Systems: Three Easy Pieces》: http://pages.cs.wisc.edu/~remzi/OSTEP/
本文分享自 云服务与SRE架构师社区 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体同步曝光计划 ,欢迎热爱写作的你一起参与!