从用户和操作系统的角度来看,它们并不在乎内核的内部是怎么样的,而是在乎内核提供了什么功能。 而我们内核的开发者来说,我们不仅要直到内核提供什么功能,还要知道内核的功能如何实现。但是我们不同想到一个功能实现一个功能,而是先要对内核进行设计,这样才能做出好的内核。

首先我们要先搞清楚内核当中有什么?

内核的功能

从抽象的角度看,内核是计算机资源的管理者。方便用户对资源的使用。 计算机的资源分为两类:硬件资源软件资源

  • 硬件资源
    1. 总线。负责连接其它各种设备。
    2. CPU。中央处理器,负责执行程序和处理数据运算。
    3. 内存。负责存储运行时的代码和数据。
    4. 硬盘。负责持久化存储用户文件数据。
    5. 网卡。负责计算机之间的通信。
    6. 显示。负责显示任务。
    7. 各种IO设备。如显示器、键盘、鼠标。
  • 软件资源:各种形式的数据,如文件,软件程序等。

内核管理这些资源,所以其内部逻辑上大致如下:

  1. 进程管理。CPU是执行程序的,而内核把运行的程序抽象为进程。
  2. 内存管理。数据和程序都存放在内存当中,内核使用内存管理来进行内存的分配和释放。由于内存资源的宝贵,优秀的内核一定有优秀的内存管理,这是一个重点也是一个难点。
  3. 文件管理。内核把用户的数据抽象成文件,为了管理文件,方便用户进行查询、读写,形成了文件系统。
  4. 图形系统。用来管理显卡,因为操作系统都支持GUI的。
  5. 网络协议栈。网卡用来完成网络通信,网络通信的各种协议,最终形成了网络协议栈,又称网络组件。
  6. I/O管理器。把鼠标、键盘、显示器等IO设备在内核中抽象成I/O管理器。

除了必要组件,一些厂商提供的例如安全组件等,需要厂商自己为这些组件编写驱动程序。

了解了内核的功能,那么如何设计内核结构?我们先了解一下经典的内核结构。

内核结构

宏内核结构

宏内核结果是最早、最简单适用的内核结构。

宏内核结构就是将进程管理、内存管理、IO管理以及其它模块的功能全部实现编译成一个大程序,并且运行在程序器的特权模式下,通过API对外提供服务。

宏内核架构

以宏内核内存分配功能的服务过程为例,查看宏内核的工作原理:

  1. 应用程序调用内存分配的API函数。
  2. 处理器切换到特权模式,开始运行内核代码。
  3. 内存管理模块按照算法,分配一块内存。
  4. 把分配的内存块的首地址,返回给内存分配的API函数。
  5. 内存分配的API函数返回,处理器运行用户模式下的应用程序,应用程序就获得了一块可用内存的首地址。

宏内存的缺点非常明显:没有模块化、没有移植性、高度耦合在一起,一个组件出现问题,所有的组件都可能出现问题。 而且开发一个新功能也需要重新编译、链接、安装内核。 宏内核唯一的优点就是性能好。

微内核结构

微内核正好与宏内核相反,将尽可能少的功能放入内核当中:只有进程调度、处理中断、内存空间映射、进程间通信等。

这样的内核是不能完成什么实际功能的。开发者将进程管理、内存管理、设备管理、文件管理等做成一个个的服务进程,由这些服务进程来完成宏内核提供的功能。

微内核定义了一种很好的进程间通信机制 —— 消息

应用程序要请求相关服务,会向内核发送一条与此服务对应的消息,内核会将这个消息转发给对应的服务进程,最终由服务进程完成相关的服务,将结果返回给应用程序。

sequenceDiagram
	应用程序 ->> 微内核: 消息
	微内核 ->> 服务进程: 转发消息
	服务进程 -->> 应用程序: 响应结果

服务进程的变成模式就是不断循环处理来自其它进程的消息,完成相关的服务程序。结构图如下: 同样以微内核完成内存分配功能的服务过程为例来理解微内核的工程原理,流程如下:

  1. 应用程序发送内存分配的消息。发送消息的函数是微内核提供的,相当于系统API。
  2. 处理器切换到特权模式,开始运行内核代码。
  3. 微内核停止当前进程的执行,并根据消息包的数据,将消息包转发给对应的服务进程。这里是内存管理服务。
  4. 内存管理服务进程收到消息,分配一块内存。
  5. 内存管理服务进程将内存块的地址以消息的形式发送给微内核,然后继续等待下一条消息。
  6. 微内核把包含内存块地址的消息返回给发送内存分配消息的应用程序。
  7. 处理器运行用户模式下的应用进程,应用进程就得到了一块分配的内存块。

微内核的系统结构非常清晰,利用协作开发,具有良好的移植性,扩展性,伸缩性。 但是缺点同样明显,应用程序、微内核、服务进程之间来回多次消息传递,包括服务进程切换的开销,系统的性能大打折扣。 商业系统不采用的微内核的原因就是因为性能差

分离硬件的相关性

程序是运行在机器上的,所以一定会是硬件相关的。x86硬件平台和ARM硬件平台上的指定肯定不一样。 为了屏蔽底层的硬件,我们给内核分一个层,例如window的HAL层,Linux的arch层。

计算机领域有一句经典的名言:任何问题都可以通过加入一个中间层来解决。

我们增加了一个抽象层,在抽象层的上下两层独立的发展。把操作硬件和处理硬件功能差异的代码抽象出来,形成一个独立的软件抽象层,对外提供接口,这样上层可以通过接口进行开发,下层可以根据对应的硬件平台实现对应的功能代码。

以进程的调度为例,我们需要通过进程调度算法选择一个将要运行的程序,然后通过进程切换来运行选中的程序。 无论硬件平台如何,进程的调度算法是相同的,但是进程的切换是与硬件平台相关的,不同的硬件平台的机器上下文也是不同的。所以我们可以将进程的调度算法写在一个独立的层中。

这样对于不同的平台,只要提供对应的硬件相关层,移植到另一个平台上了。这就是分离硬件相关性的好处。

内核的选择

通过上面的学习与了解。设计的内核可以分为三个大层:

  1. 内核接口层
  2. 内核功能层
  3. 内核硬件层

内核接口层选择一套UNIX接口的子集:

  1. 接口少,但功能齐全
  2. 接口的代码就是检查参数是否合法,不合法就返回相关错误,接着调用下层完成功能的核心代码。

内核功能层主要实现各种实际功能,分为各种模块:

  1. 进程管理
  2. 内存管理
  3. 中断管理
  4. 设备管理

内核硬件层包括硬件平台相关的代码:

  1. 初始化
  2. CPU控制
  3. 中断处理
  4. 物理内存管理
  5. 平台其它相关功能。

这里吸取了微内核的优点,将功能内核的功能进行缩减。

对于文件系统、网络组件、其它功能就交由设备管理。需要某个功能就开发一个虚拟设备的驱动,这样可以方便功能的扩展。 同时驱动加载之后,就成为了内核的一部分,而不用像微内核一样作为服务进程运行,性能强劲。