跳到主要内容

系统内核和运行环境

canteen模型

我们知道, 操作系统是在用户(软件)和硬件之间的接口, 方便用户更好的管理硬件和数据等资源.

我们把操作系统比作一个餐厅. 你作为用户或者软件要使用资源(吃饭), 一定要去访问餐厅(你也可以叫外卖, 这就是计算机网络). 餐厅有两个不同的空间: 就餐区和厨房. 对于饭菜的不同处理, 需要在不同的空间进行. 比如, 进餐一般在就餐区, 但是炒菜备餐一定是在厨房的厨师进行

  1. 必须在厨房是因为有明火, 有其他客人需要的食物,如果你强行要在就餐区动手,那一定非常危险.
  2. 必须是厨师动手, 因为厨房重地, 闲人免进. 如果你强行进去要对你的食物动手, 那仍然会产生(1)中的危险.

同样的在操作系统中, 内核态就是厨房, 操作系统内核就是厨师. 用户态就是就餐区, 只能针对你自己的数据(食物)做一些只需要你参与就能进行的活动.

那么什么是系统调用呢, 是位于就餐区的你主动向厨房发出通信(比如上菜,询问时间,菜品有问题),就需要通过服务员(系统调用).

在上述的类比下,我们可以更快理解操作系统及其运行环境.

内核

系统内核(Kernel):其实也是一组程序,这组程序重点在于管理计算机的所有活动以及驱动系统中的所有硬件. 比如,你的内核不支持 TCP/IP 协议,那么无论你购买什么网卡都不能支持上网。(拿本例来说, 如果厨师不会做,那么食材再豪华,都做不了你要的菜)

  • 同一个操作系统不能运行在不同硬件架构: 操作系统的内核层直接参考硬件规格写成
  • 操作系统只是在管理整个硬件资源: 所以需要各种应用程序来驱动它工作

功能

内核主要负责计算机系统相关的资源分配与管理,最重要的就是CPU(燃气灶和锅)与内存(菜品原材料)了,所以至少有以下几个功能:

  • 系统调用接口(服务员)
  • 程序管理: 如多任务环境下,需要管理 CPU 什么时候执行哪个任务的指令(多个客户多种订单,燃气灶怎么用)
  • 内存管理: 内存很重要,当内存不足时,内核一般都会提供虚拟内存的功能,使用内存交换(Swap)功能, 如果厨房需要的菜品不够就把暂时不用的菜存入库房, 留出空间, 增购不够的菜品放进来.
  • 文件系统: 例如数据的输入输出(I/O)等工作
  • 设备驱动: 硬件的管理是内核主要工作之一,驱动程序就是需要做的事情,现在有可加载模块功能,可以将驱动程序编写成模块,就不需要重新编译内核了(后面会讲到)
  • 其他具体项目: 时钟管理CLK,中断机制INT,原语Atomic Operation,系统控制的状态信息的数据结构(JCBPCBDCB、链表、消息队列、缓冲区、空闲区登记表、内存分配表等)

系统调s用

如果不需要和操作系统通信,就不需要系统调用
  • malloc,动态分配内存。偶尔需要和操作系统通信申请大块内存,但在当前进程已经有足够的内存的情况下,是不用系统调用的
  • spin lock。就是通过循环来进行的锁操作。它靠CPU的原子内存操作来实现互斥,也不需要系统调用。但是,如果是blocking lock,就是那种如果无法获取,就阻塞等待的那种锁,就需要,因为要告诉操作系统“请让我停下来,然后在锁准备好的时候叫醒我”
  • 写文件。如果是用内存映射的形式写文件,那么只要一开始用mmap系统调用映射内存,到最后用系统调用同步一下,中间就是普通的内存读写了,也不需要系统调用。但是,如果你用write函数,那么……没准,看操作系统如何实现了
  • 共享内存的进程间通信。和写文件一样,建立内存映射的过程需要系统调用,就是让两个进程的地址空间有重叠。但是,一旦建立好了,只要写内存就可以了,另一个进程是可以看到写入的内容的,因为本质就是从同样的物理内存地址读东西
  • math.h里的东西。这些东西就是数值运算,会翻译成特殊CPU指令,但不是系统调用
  • 异常处理(C++的throw、catch什么的),这些是特殊的控制流,可能需要特殊的库函数来做,这些库函数知道编译器生成的代码和栈的形状。但这完全在用户态就能做了,还是不涉及系统调用
  • C标准库里的数据结构(数组、链表、映射什么的)和算法(查找、排序…),就是普通的C代码实现的
  • 字符串处理函数(strcmp,strcpy,strcat,strstr什么的)还有内存拷贝(memcpy,memset什么的),有可能会为了性能而用汇编实现,但还是不需要系统调用

结构

  1. 分层结构。便于调试和验证,扩充和维护。效率低,不能跨层调用,系统调用执行时间长。(用户->服务员->厨师->厨师)
  2. 模块化。模块之间逻辑清晰,易于维护,多模块可以同时开发。支持动态加载新的内核模块,比如设备驱动程序文件、系统模块到内核,增强os适用性。任何模块都可以直接调用其他模块,无需进行消息传递通信,效率高。模块接口定义不一定合理。模块间相互依赖,不利于调试和验证。
  3. 宏内核。性能高,内核内部功能可以相互调用。庞大功能复杂难维护。内核中一个功能模块出错,可能导致整个系统崩溃。
  4. 微内核。内核小功能少易维护可靠。内核外的某个功能模块出错不会导致系统崩溃。性能低,需要频繁切换用户态和核心态。用户态下的功能模块不可以直接相互调用,只能通过内核的消息传递来间接通信。
  5. 外核。外核可以直接给用户进程分配实体具象的硬件资源。进程可以更灵活的使用硬件资源。减少了虚拟硬件资源的映射层,提高效率。降低了系统的一致性,使系统变得复杂。

运行环境

刚开机是内核态(餐厅开门肯定是先开厨房),然后操作系统主动让出cpu使用权

用户态到内核态

这3种方式是系统在运行时由用户态转到内核态的最主要方式,其中系统调用可以认为是用户进程主动发起的,异常和外围设备中断则是被动的 中断引发,硬件自动完成变态功能,操作系统将重新夺回CPU使用权。用户堆栈切换为系统堆栈,这个系统堆栈也是属于该进程的。

  1. 系统调用(主动):用户态进程通过系统调用申请使用操作系统提供的服务程序完成工作。而系统调用的机制其核心还是使用了操作系统为用户特别开放的一个中断来实现,例如Linux的int 80h中断.(System Call: 所有的硬件由内核管理,那么开发程序就由参考硬件函数变为参考内核功能,所以操作系统通常都会提供一整组开发接口给工程师使用。)
  2. 异常:当CPU在执行运行在用户态下的程序时,发生了某些事先不可知的异常,这时会触发由当前运行进程切换到处理此异常的内核相关程序中,也就转到了内核态,比如缺页异常、除零异常等
  3. 外围设备的中断:当外围设备完成用户请求的操作后,会向CPU发出相应的中断信号,这时CPU会暂停执行下一条即将要执行的指令转而去执行与中断信号对应的处理程序,如果先前执行的指令是用户态下的程序,那么这个转换的过程自然也就发生了由用户态到内核态的切换。比如硬盘读写操作完成,系统会切换到硬盘读写的中断处理程序中执行后续操作等
备注

内核态到用户态只需要中断返回指令.

Loading Comments...