引言
操作系统内核的很多设计思想对计算机科学的其他领域和上层应用业务都有很好的借签意义,例如,”错误隔离(Fault Isolation)“思想,某个应用程序的错误不能影响到其他应用程序,更不能影响到操作系统内核自身。在应用业务中,经常提到”资源隔离“,被上层多个业务共同访问的平台性质服务,不能因为某一个业务的突发访问而影响到其他业务的正常访问。针对的对象不同,采用的思路和方法是一样的,即”保护和限制“。
操作系统的发展
早期的计算机主要用于科学计算,输入数据->计算->输出结果,这个时候的操作系统更像是一个运行时库,例如,提供标准的输入输出例行程序,和计算程序链接在一起,降低计算程序的错误发生率。程序的加载、执行、结果输出都是一个非常耗时的行为。
批处理操作系统,循环进行任务的处理:加载任务、运行任务、卸载任务。CPU和I/O系统可以并行工作。
多任务操作系统,多个任务被同时加载进内存,其中一个任务若因需要读取外部数据而暂停,操作系统可以将CPU让给其他任务去执行,CPU的使用率得到了极大提升。
分时操作系统,为了对大量短时交互性的操作作出处理(比如,移动鼠标,键盘输入,网络收发包),需要有一种机制确保CPU的时间能够公平分配给各个任务,不能因为一个长耗时任务导致其他任务得不到处理。
操作系统的作用
她是一个大管家,决定着有限的物理资源如何在多个应用程序之间进行分配,可以停止一个应用程序、启动另外一个应用程序。对各个应用程序进行隔离,避免一个应用程序的Bug影响另外一个应用程序、甚至影响操作系统内核。
她是一个魔术师,对硬件实体进行抽象以简化应用程序的设计与实现。让应用程序误认为自己在独自使用内存和CPU等硬件资源,不需要应用程序自己去考虑如何和别的应用程序进行分享。有些操作系统(比如VMWare)可以虚拟化整个计算机,将某个操作系统当做应用程序运行在另外的操作系统上面。
她是一个粘黏剂,提供通用的服务让应用程序之间的共享变得更加容易;提供抽象层让应用程序不必关注硬件设备细节。
操作系统设计的考虑维度
一款操作系统的设计,通常需要在以下几个维度做权衡:
可靠性,操作系统需要能够保护自己不被病毒、黑客攻击,篡改原先固有的系统设计,保证运行在之上的应用程序能够正常高效的运行。
可用性,可用性和可靠性是互相关联的,容易被黑客攻击篡改或者系统代码有问题导致经常Crash,显然可用性是不高的。以前Windows系统经常出现蓝屏,只能Reset重启解决。
安全性,安全性和可靠性也是互相关联,操作系统的Bug被黑客发现并利用后,运行在之上的应用程序就会存在安全隐患。
灵活性,操作系统的生命周期一般都很长,例如,Microsoft Windows 8 从Windows NT发展而来,而Windows NT在1988年就问世了。操作系统的设计需要应对长时间过程中硬件的不断变化。2002年的个人计算机的内存标配是64MB~128MB左右(1MB大概价值1元),2019年则达到64GB(MacBook Pro 16顶配机型内存)。CPU(多核)和内存的发展,对操作系统的设计有着重要影响,例如,在内存很贵、很小的时候,操作系统按照CPU(核)个数创建Kernel Interrupt Stack,用来在硬件中断、系统调用的时候保存执行上下文信息,到了现在,基本都是为每个进程分别分配一个Kernel Interrupt Stack。操作系统为应用程序提供抽象的通用接口,这样不会因为操作系统的演进而影响应用程序。
性能,操作系统提供了各种限制与保护,例如,错误隔离、内存访问保护,不能因为这些而过多影响应用程序的运行,否则就失去了意义,即”开销“不能太大。操作系统对操作的响应时间需要可预测,例如,键盘操作,有两种情况:1)99%的操作在0.1秒内得到响应,但是1%的操作需要在10秒钟才能得到响应;2)所有操作都在0.2秒内得到响应, 人通常能更适应第二种情况。
适用性,一款操作系统是否流行取决于两点:1)应用程序的丰富程度;2)对硬件的支持程度。Andriod和IOS系统之所以流行,是因为与之兼容的应用程序丰富或者支持的手机品类多。以前的Windows Phone、Symbian等手机系统现在已经几乎看不到了。
进程概念以及代表的意思的变化
随着时间的推移,进程的概念也发生了变化,在早期的UNIX、Linux 版本,进程是程序运行的基本单位,那个时候的”进程“更像是现在的”线程“。在当代多数操作系统中,进程不再是执行的基本单位,执行的基本单位已经让渡给”线程“,进程已经作为一个容器的角色存在。
硬件提供的帮助
特权指令,只有内核才能执行特权指令,应用程序无法执行。
内存保护,当CPU处于用户模式,超过当前进程访问区域的内存访问都是无效的,会引发CPU异常并转到内核模式,内核通常会终止当前进程的执行。
硬件定时器中断,内核和应用程序都运行在CPU上面,若应用程序一直占据着CPU,内核则无机会执行,更别提调度其他应用程序了。在硬件层面,会给每个CPU提供一个硬件定时器定时触发引起硬件中断,中断处理程序会转到内核模式,让内核重新占据CPU,从而可以有机会决定是调度其他应用程序(先保护上个应用程序的执行上下文)还是继续上个应用程序的执行。直到2002年,苹果的MacOS还没有一种强制让应用程序让出CPU的机制,通过设计约定的方式,应用程序需要周期性的调用系统调用去检查是否有其他工作需要执行,若某个应用程序没有遵守这个约定,内核将没有机会获得CPU,所有其他任务都处于等待状态,唯一的解决办法就是重启。
模式的切换
引起从用户模式到内核模式的场景有:中断(定时器中断,I/O请求开始、完成,)、CPU异常(执行特权指令,内存越界访问),系统调用(被内核实现用于代表执行用户模式下的一些操作,例如,读写文件、创建用户进程、网络收发包)。
从内核模式切回到用户模式的场景:上述过程执行完毕后会重新从内核模式切回到用户模式。
系统调用的实现
创建进程、读取键盘输入、写磁盘文件,这些都需要经过内核处理,系统调用将这些过程封装起来从而让应用程序认为这些只是一些库例行程序。内核需要严格校验系统调用传入的参数,避免被恶意程序攻击。
定位系统调用的参数,系统调用的参数是在用户内存,通常是在用户栈里面,用户栈指针可能失效,即使没有失效,用户栈指针也是一个虚地址,系统调用的参数若是一个指针(文件名或者Buffer指针),也是指向的虚地址,需要先将虚地址转换成物理地址,内核才能使用。
将参数内容从用户内存空间拷贝到内核内存空间。
校验参数是否合法、用户是否有权限访问相关资源等。
执行相关过程,执行完毕后将结果拷贝到用户内存空间。
创建执行新进程的步骤
分配和初始化PCB(Process Control Block)。
为进程分配一定的内存空间。
将程序拷贝进刚刚分配的内存空间。
为用户层面的执行分配一个用户栈。
为处理系统调用、中断、处理器异常分配一个内核栈。
将用户的输入参数拷贝到用户内存空间。
将控制权从内核模式转变成用户模式,当一个新进程开始启动的时候,是没有状态需要恢复的,为避免为此场景编写特殊代码,会将进程用户空间的程序计数器、栈指针、处理器状态字保存到内核栈,这样可以通过通用的Pop栈内容的方式跳到新进程的第一条执行指令。
操作系统的启动过程
当主机被通电的时候,首先启动的是BIOS(Basic Input/Output System)程序,这个是固化在只读硬件内存的(Boot ROM)。
BIOS去磁盘或者Flash RAM的指定位置尝试将BootLoader拷贝到内存,并校验BootLoader的合法性、有无被篡改。
待BootLoader加载好后,他会去磁盘或者Flash RAM的指定位置尝试将内核加载到内存,同样会校验内核镜像文件的合法性、有无被篡改。
内核镜像开始运行后,会初始化一系列数据结构,例如中断向量表,然后首先启动第一个进程,一般是用户登录界面。
内核分类
总结
操作系统内核知识还有很多,上面的总结只是一些皮毛,后面再不断增加。