这个小节是很难理解的,有时候会涉及到一些底层硬件的知识。
操作系统最重要的一个角色是它要作为系统资源的管理者,而操作系统对系统资源的管理工作其实就是在内核部分来实现的。在Docker容器里,只需要有Linux的内核其实就可以实现Linux的所有的功能了。
既然操作系统内核它是系统资源的管理者,它作为管理者这样的一个角色,有的时候就有可能让CPU执行一些比较特殊的指令,比如说内存清零的指令。这种指令会对其他程序的运行造成很严重的影响。像这样的特权指令,运行之后是有可能影响到其他程序的正常运行的。所以这样的特权指令,就应该只允许我们系统的“管理者”——也就是操作系统内核来使用。也就是说假如这一段程序是内核程序的话,那这些指令当中可以出现特权指令。而假如说这段程序是普通的应用程序的话,那么这其中就不应该包含特权指令,而只能使用非特权的指令。比如说让CPU做加减乘除运算的一系列的指令就是非特权的指令。我们的CPU在设计和生产的时候就划分了,哪些指令是属于特权指令,哪些指令是属于非特权指令。所以CPU在拿到一条指令的时候,其实它是可以区分出它到底是特权指令还是非特权指令的。那么问题就出现了,CPU在执行程序的时候,它只会负责把这些指令一条一条地执行,虽然它能够分辨出哪些指令是特权指令,哪些指令是非特权指令,但是它又怎么分辨出此时正在执行的这个指令它到底是一个应用程序的指令还是一个内核程序的指令呢?
为了让CPU能够区分此时正在运行的这些指令是属于应用程序还是内核程序?CPU会被划分成两种状态,一种叫“内核态”,一种叫“用户态”。那怎么区分CPU到底处于哪种状态呢?在CPU当中,会有一个寄存器,也就是一个存数据的地方,叫做程序状态寄存器PSW(Program Status Word)。这个寄存器当中会有一个二进制位,这个二进制位为1的时候表示CPU此时是处于内核态的,而二进制为0的时候表示CPU此时处于用户态。当然也有一些CPU有可能是0表示内核态,1表示用户态。但是这个无关紧要,我们只需要知道用二进制的方式其实就可以实现对CPU状态的一个标记。那用户态和内核态它们还有各自的别名,用户态又可以叫目态,然后内核态又可以叫做管态。那这两个术语大家也需要注意一下。CPU要怎么实现这两种状态之间的切换?
首先当我们开机的时候需要加载我们的操作系统,然后这个操作系统就需要进行一些初始化的工作。那系统初始化的工作其实就是由操作系统当中的某一些内核程序来完成的,所以在开机的过程当中需要执行内核程序,因此在这个时候CPU肯定是需要处于内核态,它需要来执行系统初始化相关的这一系列的内核程序的这些指令。那么当我们开机完成之后,用户就可以启动某一些应用程序。那这个应用程序要正常运行的话,肯定需要让CPU执行它的这一系列的指令。但是我们刚才不是说CPU此时正在执行的是内核程序吗?而如果说要让它运行这个应用程序的话,怎么实现这个事情呢?那此时如果操作系统的内核想让这个应用程序开始运行的话,那么这个内核程序就需要执行一条特权指令,这个特权指令会把PSW的标志位从内核态转变为用户态,这样的话就完成了CPU状态的一个切换。接下来操作系统内核就会让出CPU的使用权,
让这个应用程序上CPU运行,而此时CPU已经被切换为用户态了,所以接下来我们的应用程序会在用户态下运行。
那CPU会执行这个应用程序的一条一条的指令。
那此时一个小故事发生了,假如说此时有一个猥琐的黑客,在这个应用程序当中植入了一条特权指令的话会发生什么事呢?
首先CPU在读入这条指令的时候,其实它就已经能够判断这条指令是一条特权指令了。但是CPU又检查了自己的PSW寄存器,发现自己此时是处于用户态的,这样CPU就能够知道我此时正在运行的其实是应用程序而不是内核程序。而一个应用程序竟然他妈想要用一个特权指令,那这个事情坚决不能让它干,所以这样的一个非法事件会引起一个中断信号。当CPU检测到这个中断信号之后,它就会立即强行变态,
强行变成核心态,并且CPU会拒绝执行这条特权指令。
接下来CPU会暂停这个应用程序后面的这一系列的指令代码,转而会执行一个处理中断信号的内核程序。
接下来CPU就在内核态下来执行这个内核程序相应的这一系列的指令。
所以刚才发生了这个中断信号之后,让操作系统内核又重新夺回了CPU的控制权。接下来操作系统内核程序会对这个中断进行相应的处理,等处理完了之后它才会把CPU的使用权再重新还给应用程序,
所以从这个故事当中我们就可以很形象地看到CPU从内核态切换回用户态是执行了一条修改PSW标志位的一个特权指令来完成的。执行了这个特权指令之后,就意味着操作系统内核要主动地让出CPU的使用权了。当CPU切换为用户态之后,就可以在CPU上运行用户程序。而CPU的状态从用户态又切换为内核态,是由中断引发的,然后由CPU硬件自动地完成这个变态的过程,并且CPU变回内核态之后,它会停止运行当前正在运行的应用程序,转而运行一个内核程序,所以说其实触发了一个中断信号就意味着操作系统会强行重新夺回CPU的使用权。
但凡我们需要操作系统来介入,开展管理工作的时候,就会触发一个中断信号。那具体有哪些中断,
我们的CPU在运行程序的过程,其实就是在执行一条一条机器指令的过程。而系统中存在两种程序,一种是内核程序,一种是应用程序。内核程序是整个系统的管理者,所以它需要使用到一些特权指令,并且特权指令只有内核程序可以使用。那当CPU的状态是内核态的时候,说明此时正在跑的这些指令是属于内核程序的。当CPU属于用户态的时候,说明此时它正在跑的是应用程序的指令。那由一系列的内核程序就组成了操作系统的内核,它是整个系统最核心最接近硬件的部分。那我们还强调了CPU是如何变态的,内核态到用户态的转变是用一条特权指令实现的。而用户态到内核态的转变是由中断引起的,然后由CPU硬件自动完成。那这个小节的内容都是十分重要的,很容易在选择题当中进行考察。
而内核程序它是整个系统的管理者,它是一个管理者的角色。那我们的计算机在刚启动的时候其实在上边跑的肯定是内核程序,只不过在时机合适的时候内核程序会把CPU的使用权主动地让给应用程序。那这部分是第二章进程部分我们会具体学习的内容,这儿暂且先不展开。总之一个应用程序在上CPU运行之后,它就会一直运行下去,除非发生了中断,而一旦发生中断就会让CPU立即停止此时正在执行的应用程序,转而执行相应的内核程序。
所以中断是让这个管理者,也就是这个操作系统内核重新夺回CPU使用权的一个唯一的途径。而中断会使CPU由用户态变回内核态。所以我们可以思考一下,假如说没有中断机制的话,那么一旦一个应用程序上CPU运行了,那么这个应用程序就可以一直地运行下去。那如果一个CPU一直在运行同一个应用程序的话,我们又怎么可能实现多道程序并发这个事情呢?所以没有中断技术,肯定就没有办法实现多道程序并发。甚至可以说没有中断技术,就没有操作系统。总之操作系统内核它是一个管理者,当它想要把CPU使用权让给应用程序的时候,那么它会自愿地用一个特权指令完成这个事情。但是当它想要把CPU的使用权重新给夺回来的时候,那么它也可以通过中断的这种方式来实现。那结合上一小节当中我们讲的例子,相信这个已经不难理解了。
内中断的产生和当前执行的指令是有关系的,中断信号来源于CPU内部。而外中断恰恰相反,它和当前执行的指令无关,中断信号来源于CPU的外部。那首先我们来看几个内中断的例子。
内中断,也就是说这种中断的产生和当前执行的指令有关。就比如说我们上一小节当中提到的这个例子,
一个应用程序它运行在用户态,然后CPU会依次处理它的这些指令。
但是假如说有一个猥琐的黑客,在这个应用程序当中尝试植入一条特权指令的话,
那么当CPU拿到这条特权指令的时候,它发现此时正在处于用户态,此时在运行的是应用程序。
于是这个非法的事件会触发一个中断信号,
CPU会拒绝执行这一条特权指令。
接下来CPU会自动地转变为内核态,并且会开始执行处理这个中断信号相关的内核程序。于是在发生了这样非法的事情之后,这个系统的管理者又重新夺回了CPU的控制权,所以这就是一个很典型的内中断的例子。CPU在执行当前的一条指令的时候,由这条指令引发了一个中断。
有的时候即使CPU执行的是非特权指令,也有可能会引发内中断。比如说CPU在执行一个除法指令的时候,它发现除数是零,那在这种情况下肯定也不能让这个程序继续执行下去,所以这种情况也会引发一个内中断。总之如果当前执行的这个指令本身是非法的或者说指令的一些参数是非法的,那么这种情况下就会产生一个内中断的信号。
接下来再看另一个内中断的例子。一个应用程序运行在用户态,
但是有的时候应用程序它是想要请求操作系统内核为它提供一些服务的,
那在这种情况下应用程序就会执行一条特殊的指令叫做陷入指令。
那这个指令会引发一个内部中断信号,接下来CPU又会转向这个中断信号相应的处理程序,进行这个中断信号的处理。
所以可以看到,当一个应用程序执行了陷入指令的时候,就意味着这个应用程序主动地把CPU的使用权还给操作系统内核,想让操作系统内核为它提供某些服务。
那我们之前提到的所谓的系统调用,其实就是用这个陷入指令来完成的。需要强调的一点是,陷入指令它是一个比较特殊的指令,但是它并不是特权指令。因为我们可以想一下,这个应用程序它是运行在用户态的嘛,在用户态下可以执行陷入指令的话,那这个陷入指令肯定也不是特权指令了。总之刚才提到的这个中断信号,其实和当前执行的这条指令是有关系的,这个中断信号是来自于CPU的内部。
那接下来我们要看几个外中断的例子。外中断和当前执行的指令无关,这个中断信号来自于CPU的外部。来看第一个例子,叫做时钟中断。这种中断信号是由计算机当中的一个硬件叫做时钟部件来发出的,这个时钟部件会每隔一段时间给CPU发一个中断信号。通过这个时钟中断信号就可以实现多道程序并发运行了。那我们来看一下这个故事是怎么样的。首先应用程序一它运行在用户态,
然后CPU会执行它的这些指令。
当它执行了两条应用程序一的指令的时候,这个时钟部件发现此时已经过了50ms了,那么它会给CPU发送一个中断信号。注意这个中断信号和当前执行的指令是没有关系的,它来自于CPU的外部。然后根据我们之前学习到的知识我们知道,当CPU检测到一个中断信号的时候,它会先暂停此时正在运行的应用程序,
然后转而执行一个相应的内核程序来处理这个中断信号。
所以接下来CPU会对这个时钟中断信号进行处理,并且转为内核态。那在内核态下,CPU开始执行这个内核程序来处理刚才收到的中断信号。
然后这个内核程序执行的过程当中发现,应用程序一刚才已经用了50ms的这个时间了,
所以接下来为了公平起见,我想让第二个程序上CPU运行。于是接下来这个内核程序会把CPU的使用权给第二个应用程序,
接下来又切换为用户态,然后第二个应用程序开始上CPU运行。
而当第二个应用程序执行了一系列的指令之后,
又过了50ms,此时这个时钟部件又会给CPU发送一个来自于外部的中断信号。那它呢CPU还是会回去执行和这个中断信号相关的那个处理程序,
所以接下来它又会开始执行这个内核程序。那接下来这个操作系统内核发现程序二已经用了50ms了,所以接下来公平起见我又想让程序一接着上去运行,
于是它会把CPU的使用权让给应用程序一,然后应用程序一就可以往下执行它的那一系列的指令了。所以刚才的故事中我们就可以很清晰地看到了两个应用程序是怎么在中断机制的支持下并发运行的,一直不断地相互切换。所以可以看到中断在现代的这个计算机当中到底有多大的作用。那除了这个时钟部件发出的中断信号之外,
有时候也会有来自于I/O设备也就是Input/Output,也就是输入输出设备发来的这种中断信号。比如说某一个应用程序可能会请求打印机的打印服务,那打印机在打印输出完成了之后会向CPU发送中断信号,用来通知CPU我的任务已经完成了。接下来CPU会用这个中断信号相对应的内核程序来对这个中断信号进行处理。总之我们这儿提到的这两种中断,中断的信号都来自于CPU的外部和当前执行的指令是没有关系的。CPU在每一个指令执行结束的时候,都会例行地检查一下是否有外部中断的信号需要我来处理。
刚才我们列举了一些内中断和外中断的例子,相信通过这些例子大家可以更直观地感受到中断这种机制它的一个强大的作用。那我们要分辨一个中断是内中断还是外中断,只需要判断这个中断信号的产生和当前执行的指令是否有关就可以了。那我们刚才讲解的过程中使用的是内中断和外中断这两个术语,因为内、外这两个形容词可以让大家很形象地理解到它们俩之间的一个本质的区别。不过其实在很多教材、还有很多学校的考试当中,内中断一般会称为异常,外中断一般就直接称为中断,所以考试当中遇到的中断一般来说是这个狭义的中断,它特指外中断,而我们这个小节当中之前一直在说的中断其实是指的是这种广义的中断,所以大家要理解它们的区别可以用内、外这两种方式来理解,但是在做题的过程当中更需要注意的是中断和异常这两个术语。那在接下来的讲解中,我们会经常使用到异常这个术语,大家需要知道它其实指的就是内中断。那么异常又可以进一步的划分为这样的三种。第一种叫做陷阱、陷入(trap),这个其实就是我们刚才所提到的由陷入指令引发的那种异常,其实这种异常是程序故意引发的。当一个程序想要请求操作系统的服务的时候,它就会故意地执行这条指令,那其实这也是系统调用实现的一个原理,而系统调用的内容我们会在下一小节再继续展开。第二种异常称为故障(fault),故障是一种由错误条件引起的并且可能被内核程序修复的问题,内核程序修复故障之后会把CPU的使用权还给应用程序,让它继续执行下去。比如说我们在第三章当中会学习到的缺页故障,那这个地方大家暂时理解不了没有关系,等学完了第三章再回来细细品味。第三种异常叫做终止(abort)。终止(abort)是由致命的错误引起的,这种错误一般是内核程序无法修复的,所以一般来说当发生了这种类型的异常之后,内核程序就不会再把CPU的使用权还给引发终止的那个应用程序。一般来说会直接地把这个应用程序直接给干掉,让它终止掉。比如说我们刚才提到的两个例子,整数除以零,这种错误那其实就是程序bug,也不是操作系统能够修复的,那是程序本身的问题。或者非法地使用特权指令,那这样的程序也应该直接把它干掉,不应该再把CPU的使用权再还给它,所以这是第三种终止。那异常或者说内中断的这三种分类方式,大家需要注意一下,曾经考过。那么以上就是中断的一个分类。
中断机制背后的基本原理。有的中断是整数除以零,有的中断是时钟部件而引起的,而有的中断又是应用程序自己引发的。那很显然这么多的中断的类型,不可能用同一个程序来处理,所以CPU会根据中断信号的不同来找到和这个中断信号相对应的中断处理程序。那具体的做法是CPU在检测到中断信号之后,会根据这个中断信号的类型来查询中断向量表,然后通过这个中断向量表找到各种类型的中断所对应的中断处理程序。很显然我们之前讲了那么多例子,大家应该也能够想到,中断处理程序它其实就是一种内核程序,肯定也需要运行在内核态的。那么其实这就是中断机制的一个基本的实现原理。但是具体的硬件上应该怎么实现这是计算机组成原理那门课要教大家的,如果不考那门课的同学,其实理解到这儿,就差不多了。
这个小节当中我们介绍了中断和异常。中断的作用十分重要,它是让CPU从用户态变回内核态的一种唯一的方式,它是让操作系统强行地夺回CPU控制权的一种方式。在引入了中断机制之后,才能让操作系统正常地工作,才能实现多道程序并发运行这个美好的事情。那中断的分类这个知识点大家可以在根据课后的习题进行巩固,最后我们简要地提了一下中断机制的基本实现原理。总之,分为这样的两个步骤,CPU首先会检查中断信号,那对于内中断信号来说,是CPU在执行指令的时候就会进行检查的,它会检查是否有异常的发生。而外中断信号的检查是在每个指令周期的末尾,也就是CPU每执行完一条指令之后,它都会例行地来检查是否有外部中断信号需要处理。而当CPU检测到一个中断信号之后,又会根据这个中断信号来查询中断向量表,然后找到这个类型的中断信号所对应的中断处理程序来进行处理。
操作系统它作为用户和计算机之间的接口,它需要向上提供一些简单的服务,那么给用户使用的就是命令接口GUI,而程序接口又由一堆系统调用组成,所以其实系统调用就是应用程序程序员来请求操作系统内核服务的一个途径,它和我们平时编程的时候使用的函数调用其实是很类似的。
但是系统调用又和普通的库函数的调用又有一定的区别。其实我们在写程序的时候是可以用汇编语言的方式来直接请求这个系统调用服务的,但是现在的编程更多的是使用高级语言来编程,所以我们一般会直接使用那些高级语言提供的库函数,但是这些高级语言的库函数在底层其实也会用到操作系统提供的系统调用功能来请求操作系统的服务,所以系统调用应该是比高级语言的库函数更为底层的一个接口。我们的裸机之上是操作系统,操作系统向上层提供的接口是系统调用,使上面的这些库函数、应用程序能够通过系统调用的方式来请求操作系统的内核的服务。然后在操作系统之上各种各样的高级编程语言,会用库函数的方式来封装这些系统调用,然后向更上层的这些应用程序的程序员来暴露一些更好用的编程接口,不过并不是所有的库函数在底层都会使用系统调用。很多库函数比如说C语言里的math.h,里边就会提供很多取绝对值之类的数学运算相关的库函数,这些库函数的功能不需要特权指令也可以完成,所以像这一类的库函数它在底层也并不需要使用系统调用。但是又有了库函数,比如说像创建一个新文件,这个事情必须请求操作系统内核的服务才可以完成,所以对于创建新文件这样的库函数来说,它在底层就肯定需要使用操作系统提供的系统调用服务。所以这是系统调用和库函数的一个区别。
所以由于系统当中有各种各样的并发的进程,而这些并发的进程又需要共享地使用类似于打印机设备这样的共享资源,但是这样的共享资源其实是需要各个进程互斥地共享的。那怎么实现对共享资源的互斥访问呢?最好的方式就是让操作系统内核也就是这个系统的管理者来对共享资源进行统一的管理,然后上层的那些应用程序只能通过系统调用的方式来请求操作系统给它分配这种资源,之后这个进程才可以对这种共享资源进行使用和访问。而各个进程的请求会由操作系统内核来协调处理,保证它们并发运行的时候不会发生这种奇奇怪怪的事情。因此从这个例子当中,我们就可以看到“系统调用”的功能是必须存在的。
系统调用按功能分类可以分为这样的一些系统调用,什么设备管理、文件管理、进程控制、进程通信、内存管理。可以看一下Linux操作系统向上提供了哪些系统调用。那到底哪些功能需要用系统调用的方式对外提供呢?其实都有一个共同的特点。凡是和共享资源有关的操作,比如说像对内存的这种分配与回收,内存是一种共享资源,I/O设备也是一种共享资源,文件也是共享资源,总之只要是对共享资源的访问那肯定是需要通过系统调用来进行。因为这些共享资源是有限的,所以操作系统会对这些共享的资源进行统一的管理和分配,因此应用程序在使用这些资源的时候就必须通过系统调用的方式请求操作系统内核来帮它进行接下来的处理。这样的话我们就可以保证系统的稳定性和安全性,防止非法操作。相信通过之前打印机的例子大家能够有比较直观的体会。
那么假设一个应用程序想要进行系统调用,它在背后需要做一些什么事情呢?一个应用程序它运行在用户态,然后这个应用程序的各个指令会被CPU一次执行,当它想要发出系统调用的时候,
它需要用传参数的指令给CPU的寄存器当中传递一些必要的参数。比如说在某一个寄存器当中,放入了一个参数1,这个参数1是指明了我此次要进行哪种类型的系统调用。比如说像Linux里边的fork系统调用,
那传递参数的指令可能会有多条,主要要看我们的这个系统调用需要传递几个参数。操作系统会根据应用程序提供的这些参数来判断它想要的到底是哪种类型的服务。那当这些参数都放到了寄存器当中之后,应用程序就会执行一条特殊的指令叫做陷入指令,这个指令我们在上一小节中也有简单地提及。
这个陷入指令的执行会引发一个内中断,那CPU在检测到这个内部中断的信号之后它发现这个内部中断信号是由trap指令引起的。于是这个CPU接下来就会暂停运行这个应用程序,转而去执行处理陷入指令的那个处理程序。那这个处理程序就是系统调用入口程序,显然接下来要执行的这个程序肯定是属于内核程序。
因此它需要在内核态来运行。我们可以说这个程序其实也是某一种中断处理程序,只不过它处理的是由陷入指令引发的那个内中断。
那接下来这个系统调用的入口程序会检查寄存器里的这些参数,通过第一个参数它会知道此时这个应用程序它想要的是这种类型的系统调用服务。于是接下来这个入口程序,就会调用与特定的系统调用类型
所对应的处理程序,
然后让这个程序上CPU运行。那这个系统调用处理程序在执行的时候,就可以根据应用程序传递的其他的参数来看一下它所需要的具体是哪一些服务。
那当这个系统调用被处理完了之后,CPU又会转回用户态,
然后接着执行之前的这个应用程序。对系统调用背后的过程应该是有了比较直观的体会。
再把之前的库函数、系统调用把它给串一串。我们普通的程序员可以用高级的编程语言来写我们的代码,然后在我们的代码中可以调用这个高级编程语言提供的一些库函数,
但是有的库函数内部其实是使用到了操作系统提供的系统调用的。具体来说一个系统调用过程是需要先传递系统调用所需要用的参数,接下来要执行一条很特殊的指令,叫做陷入指令。执行了陷入指令之后,就意味着这个应用程序把CPU的控制权主动地交还给了操作系统的内核,用这样的方式来请求操作系统内核的服务,所以陷入指令执行了之后,就会产生一个内中断,然后CPU会转向执行一个处理系统调用的内核程序。而这个内核程序显然是需要运行在核心态。当这个系统调用处理完了之后,它又会返回原先的这个应用程序,让应用程序接着往下执行。那我们需要注意一些可能会作为考点的小细节,第一这个陷入指令它其实是在用户态下执行的。它是一个很特殊的指令但是它并不是特权指令,它是一个非特权指令。在执行了这个指令之后,会主动地引发一个内中断,让CPU进入核心态,运行内核程序。第二点需要注意的是,我们发出系统调用请求这个动作是在用户态下进行的,但是对系统调用的相应处理是在核心态下完成的。那最后我们还需要注意陷入指令另外两种名称,它又可以称为trap指令,访管指令,在不同的教材当中可能使用的这个术语不一样,但是要知道它们指的其实都是同一个东西。
系统调用和库函数它们的区别,另外大家需要注意,在系统当中,有各种各样并发的进程,这些进程会争抢着使用各种各样的系统资源。那这些资源应该由操作系统来统一地掌管,所以只要是对共享资源的访问、操作就肯定需要用系统调用的方式来实现。那我们在这个小节当中还需要重点掌握的是系统调用背后的一个过程。那需要注意一条很特殊的指令,陷入指令或者叫trap指令、访管指令。如果一个应用程序执行了陷入指令,就意味着这个应用程序主动地把CPU的控制权还给了操作系统,然后用这种方式来请求操作系统的服务。
操作系统的体系结构。
操作系统的体系结构,也就是操作系统的内核应该怎么设计这样一个问题。那这个小节的内容我们只需要做简要的了解就可以了,我们考试中常考的是这样的两种体系结构,一种叫大内核,一种叫微内核。
那经过我们之前的学习我们知道,计算机系统的层次结构是这个样子的。
但是操作系统的内部其实还可以再进行进一步的划分,一部分是内核的功能,另一部分是非内核的功能。操作系统最核心的那些功能需要放在操作系统的内核当中,比如说时钟管理、中断处理还有原语(设备驱动、CPU切换等)另外还有之后我们要学习的进程管理、存储器管理、设备管理等等,这些功能都是要放在操作系统内核当中的。那这儿提到的时钟管理,其实就是用我们之前提到过的时钟中断来实现了计算机计时的功能。想要实现程序并发,就必然离不开时钟管理这个很重要的内核功能,那中断处理就不再多解释了,之前已经举了很多例子。另外还有一种特殊的程序叫做原语,原语这种程序它具有原子性。所谓的原子性就是说,这种程序要么就一气呵成地全部运行完成,要么就是不运行。它的执行过程是不可被中断的,也即是说在执行原语的这一小段程序的过程当中,即使有外部中断信号过来了,那CPU也会继续把原语执行完成才去处理那个外部中断信号。总之我们这儿列举的最下面这一层这三个东西,是和硬件结合最为紧密的,所以它们必须放在操作系统的内核当中。那其实像Ubuntu、Centos等等这些我们耳熟能详的Linux操作系统,这些系统的开发团队他们主要干的事情其实是在实现非内核的功能,而这些个操作系统的内核使用的就是Linux的内核。
总之内核是操作系统最核心、最基本的部分,它由一系列的内核程序组成。这些内核程序必须运行在内核态。那刚才我们提到的最底层的这三个部分是与硬件关联最紧密的模块,这些功能是必须放在内核当中的。还有一些管理相关的功能,像进程管理、存储器管理,对于这些功能的管理更多的是对数据结构的一个操作,而不会直接涉及到硬件,所以有的操作系统并不把这些管理功能放在内核当中,
而只在内核当中保留与硬件接触最紧密的这些部分。因此这就引出了两种截然不同的内核的设计方法。
把所有的这些功能都包含在操作系统内核当中的,这种结构就叫做大内核。而如果内核当中只保留与硬件关系最紧密的这些部分,那么这种内核就叫做微内核。那我们需要注意的是,如果采用的是微内核的这种结构的话,那么属于内核的这些功能是需要运行在内核态的,而不属于内核的上面的这些功能,就需要运行在用户态。这会对我们系统的性能造成一定的影响。
那我们用更直观的例子来体会这一点。那假设现在有两种体系结构的系统,第一个系统它采用的是大内核的体系结构,那么由于进程管理、存储管理等等这些功能都是被划分在内核当中的,所以这些功能的处理都需要运行在内核态,而只有应用程序是运行在用户态的。而对于采用微内核结构的操作系统来说,只有和硬件联系最紧密的这些功能,被划分在了内核当中,只有这些功能是需要在内核态下才可以执行的,而其他的这些功能模块在用户态下就可以运行。那现在来看这样一个故事,假设现在这个应用程序想要请求操作系统的服务,并且这个服务的背后需要同时涉及到进程管理、存储管理、设备管理这几个功能。如果采用的是大内核的体系结构的话,那么应用程序向操作系统提出服务的请求,这个时候CPU会从用户态切换为核心态,然后开始运行这一系列的内核程序。而如果采用的是微内核的体系结构的话,应用程序向操作系统提出服务的请求。接下来操作系统的这几个模块,都需要为应用程序服务。而进程管理这个模块在处理应用程序的请求的时候,它同样也需要得到内核的支持,所以这个模块对内核的访问就涉及到了CPU从用户态转到内核态,服务完成了之后又会从内核态再转回用户态。然后同样的,存储管理、设备管理这两个模块它们在完成相应的工作的时候,同样也需要得到内核的支持。因此每一个模块都需要请求内核的服务,那每一次请求内核的服务都会涉及到一个CPU状态转换的过程。因此如果我们采用的是大内核的体系结构的话,那么应用程序的这个请求只需要两次变态就可以了。而如果采用的是微内核的体系结构的话,那么整个过程的处理就需要有六次变态。这儿需要注意的是,这个CPU的状态转换,这个过程其实是有成本的,需要消耗不少的时间,因此频繁地切换CPU的状态是会降低系统性能的。大家在考试的时候不要使用变态这个词,这个只是我们为了方便描述然后使用的一种描述方式。大家在考试答题的时候需要写的正规一点,就是要说成是CPU状态的转换。
这个小节我们介绍的是操作系统的体系结构,分为大内核和微内核。通过刚才的例子大家也能够体会它们俩的一个区别。大内核的优点就是性能高,因为应用程序在请求内核服务的时候,这个变态的过程会比较少。而微内核的缺点呢,是需要频繁地在核心态和用户态之间切换,所以它的性能会更低一些。不过微内核的优点是它的内核功能很少,所以结构清晰,方便程序员维护。而大内核由于它们把很多很多功能都放在内核里,所以内核代码就会变得比较庞大,结构混乱,难以维护。那典型的大内核操作系统,像Linux、Unix这些都是大内核的。然后微内核的操作系统的话大家可以去看一下Windows NT,当然这些并不是考试考察的重点,考试的时候只会考察这两种体系结构它们的优缺点,大家只要能够有个印象就可以。