Linux内核的概念体系结构

2.3 Overview of the Kernel Structure

下图展示了Linux内核的五个子系统之间的关系,箭头由依赖子系统指向被依赖的子系统:

Figure 2.2: Kernel Subsystem Overview

从图中可以看出来,【进程调度子系统】是内核的核心子系统:其它子系统都对【进程调度子系统】有依赖,因为其它子系统都有暂停(suspend)和恢复(resume)进程的功能需要。通常,某子系统会在等待硬件操作而暂停进程,尔后在操作完成后恢复进程。例如,当进程发一条网络消息后,网络接口可能需要暂停进程,直到硬件成功地完成信息的发送。在消息被发出后,网络接口返回一个代码表示操作的成功与否,然后恢复进程。其它子系统(内存管理子系统、虚拟文件系统、进程互通信子系统)都有类似的原因而依赖于进程调度子系统。 各个子系统之间的其它依赖关不那么明显,但也同等重要:

进程调度与内存管理:进程调度子系统【使用】内存管理子系统来调整进程的物理内存映射表,这个调整动作发生进程被恢复时。(KEMIN:陈莉君的《深入分析 Linux内核源代码》里说进程调度依赖内存管理在于“在多道程序环境下,程序要运行必须为之创建进程,而创建进程的第一件事,就是要将程序和数据装入内存”,这就要看进程创建是不是进程调度的功能了。)

进程互通信与内存管理:进程互通信子系统依赖内存管理实现【共享内存互通信机制】,这种机制允许两个进程除了访问自己的私有内存外,还可访问一段共同的内存区域。

虚拟文件系统与网络接口:虚拟文件系统【利用】网络接口实现网络文件系统(NFS),也利用内存管理子系统实现RAMDISK设备。

内存管理与虚拟文件系统:内存管理子系统利用虚拟文件系统实现对换机制(swapping),这也是内存管理子系统依赖于进程调度的唯一原因。当一个进程读取的内存页被换出(swapped out)时,内存管理向文件系统发出请求,同时,挂起当前正在运行的进程。

除上图所示的依赖关系以外,内核中的所有子系统还要依赖一些共同的资源,但在图中并没有显示出来。这些资源包括所有子系统都用到的子过程,例如分配和释放内存空间的子过程,打印警告或错误信息的子过程,还有系统的调试例程等。这些公共子过程是默认全局可用的,大家都依赖它们。

3.1 Process Scheduler Architecture

3.1.1 Goals

进程调度子系统负责控制对CPU的访问,包括来自用户进程的访问和内核其它子系统的访问。

3.1.2 Modules

进程调度子系由四个主要的模块组成:

  • 调度策略模块负责裁决(judging)哪个进程将访问CPU;为了提高分配CPU的公平性,调度策略需经万分设计。
  • 体系相关模块组(Architecture-specific modules)负责隐藏特定的体系功能细节,为上一层(体系无关模块)提供一系列公共抽象接口(比如暂停或恢复进程的操作)。这些模块主要任务是进行进程切换……
  • 体系无关模块(architecture-independent module)负责与调度策略模块通信,获取调度信息,然后调用体系相关模块进行进程切换。此外,本模块还要调用内存管理模块,确保被选中的进程的内存数据被正确的恢复(KEMIN:进程被调出CPU后再次进入CPU前,内存状态可能不完整,请看内存管理子系统)。
  • 系统调用接口模块…………

Figure 3.1: Process Scheduler Subsystem in Context

3.1.3 Data Representation

调度程序负责【维护】(maintains)一个数据结构——任务列表(task list),每个活动的进程对应列表上的一项。这个数据结构包含了进程暂停和恢复的足够信息;此外还有一些日志和账户管理信息。这个数据在整个内核层都是可见的。

b.有想过进程控制块除了作为调度程序进行计算的根据外,还有更高的一层的更本质的原因吗?进程控制块里的状态信息种类存在的理由是什么呢?进程控制块的另一个名字叫上下文数据块(context block),有上下文证明发生了断裂现象。可以回想过去单道计算的情景,计算机要完成计算任务必须是软硬结合,更直接的理解是,硬件(处理器)和程序是连体的,后来多道技术的需要产生了软硬断裂现象。所以进程控制块的信息一部分是用来维护这种断裂的恢复的(其中有体系相关的数据),也有一部分是用作恢复策略的根据。

3.1.4 Dependencies, Data Flow, and Control Flow

前面提到,【进程调度子系统】对【内存管理子系统】是有依赖的。而其它子系统因为某些操作而需要暂停当前进程而依赖于【进程调度子系统】。依赖的体现是子系统间的函数调用和对任务列表(task list)的共享访问。对任务列表(也就是进程控制块)可读可写的的子系统与进程调度子系统间有双向的(bi-directional)数据流。

除了内核层存在数据和控制流外,系统服务层(O/S services)为用户进程提供了注册(register)定时通知(timer notification)的接口,这就是《An Introduction to Software Architecture 》提到过的【隐式调用风格】。通过隐式调用,调度程序可以把控制流反转回进程,当有通知事件直接调用用户进程(的注册处理函数)。一般认为唤醒睡眠状态的进程的动作不算是一种控制流,因为用户不能检测到这个操作。最后,调度程序与CPU通信,暂停和恢复进程运行,这个动作有数据流和控制流进出两子系统。

隐式调用风格(implicit invocation style)

The idea behind implicit invocation is that instead of invoking a procedure directly, a component can announce (or broadcast) one or more events. Other components in the system can register an interest in an event by associating a procedure with the event. When the event is announced the system itself invokes all of the procedures that have been registered for the event. Thus an event announcement implicitly'' causes the invocation of procedures in other modules.

依赖是什么?

④倚靠;依仗:信赖|依赖|赖以生存。

dependency n.依靠, 信赖, 从属, 从属物, 属国, 属地

dependency (名) 附属国, 附属地; 附属物; 依靠, 信赖; 瘾

dependence when you need something or someone all the time, especially in order to continue existing or operating:

控制流与数据流

控制流

  • 专注的问题是程序的整体控制轨迹是怎样的
  • 数据参与控制任务的完成,但它不是主要的
  • 论证关于计算(computation)的次序(order)

数据流

  • 专注的问题是数据是怎样在各计算步骤间移动的
  • 随着数据的移动,控制也被激活
  • 论证关于数据的可用性(availability)、数据转化(transformation)和数据延迟(latency)

3.2 Memory Manager Architecture

3.2.1 Goals

【内存管理子系统】负责控制进程对内存资源的访问。这个任务可能需要硬件机构(比如MMU)辅助来实现进程逻辑地址到机器物理地址的映射。这个映射是基于每个进程的,所以每个进程都拥有属于自己的逻辑地址空间。此外,内存管理子系统实现了【对换机制】,负责将暂时不使用的内存页转移到外存。

3.2.2 Modules

内存管理子系统由三个模块组成:

  • 体系相关模块:与进度管理子系统的体系相关模块功能类似,对硬件体系抽象一些虚拟接口;
  • 体系无关模块:本模块执行所有地址映射和虚拟内存对换,负责包括决定当产生缺页事件时哪个内存页将被替换。与进程调度子系统不同,本子系统没有另外的替换策略模块,因为替换很少更改,没有必须独立出来产生不必要的性能损耗。
  • 系统调用模块为用户进程提供有限的访问接口。用户进程可以通过这个接口进行存储分配和释放,还可以执行内存映射文件I/O。

3.2.3 Data Representation

【内存管理子系统】为每个进程维护一张地址映射表。这张映射表的首地址被保存到该进程的进程控制块(KEMIN:具实现请看源码 ),以作关联。此外,为了实现内存页调度,映射表还要提供关于页调度信息。最后,为了安全性,映射表还要有权限和账户信息。

Figure 3.2: Memory Manager subsystem in context

3.2.4 Data Flow, Control Flow, and Dependencies

【内存管理子系统】负责控制内存硬件,并对当产生缺页事件时接收并处理这个事件。所以,在内存管理子系统和内存硬件之间有双向的数据和控制流。另外,内存管理子系统【利】用文件系统实现【对换】和【内存映射I/O】。具体内存管理子系统通过函数调用,调用文件系统,实现页面替换或对换。由于文件I/O相对较慢,内存管理子系统得先暂停当前进程,这个功能产生了一个对进程调度子系统的调用依赖。又因为每个进程的内存映射表都是保存到进程调度程序的数据结构的,所以在内存管理子系统和进程调度程序之间有双向的数据流。

KEMIN:前面说了,地址映射表是作为一个指针引用保存在进程控制块的,这里又说映射表是直接保存在调度程序的数据结构,有少许前后不对应,不过使用的术语也不一样,不够严谨。不过各子系统的功能实现需要相应的数据结构是无容置疑的。具体细节有待再澄清……

3.3 Virtual File System Architecture

Figure 3.3: Virtual File System in Context

3.3.1 Goals

【虚拟文件系统】设计的目的是为了给计算机输入输出操作(包括磁盘数据和其它硬件设备操作)一个一致的视图。几乎所有计算机硬件设备都是通过一个通用的设备驱动接口(generic device driver interface)连接入主机(KEMIN:指设备与CPU之间都有一个控制器适配,看这里 )。虚拟文件系统则走得更远,透过虚拟文件系统,系统管理员可以把任何物理设备上的任何逻辑文件系统挂接(mount)入主机(KEMIN: 这里的任何物理设备包括非磁盘的字符设备,但是像声卡显卡这样的物理设备并没有文件系统,这部分怎么理解?)。逻辑文件系统提高了各操作系统标准的兼容性,容许开发人员使用不同的策略实现文件系统。虚拟文件系统通过抽象既隐藏了物理设备的细节,也隐藏了逻辑文件系统的特殊细节,用户进程可使用一个通用的接口访问文件数据,而不必知道这些数据到底是在物理设备还是在逻辑设备上。

除了一般文件系统的功能目标外,虚拟文件系统也负责加载可执行程序。逻辑文件系统模块负责实现这个功能,有了它,Linux可支持多种可执行文件格式。

3.3.2 Modules

  • 设备驱动模块组:每一种系统支持的设备(硬件控制器)都要有一相相应的驱动程序模块。由于市面上存在大量不兼容的硬件设备,所以系统有很多设备驱动程序。Linux系统最常见的升级是添加新的设备驱动程序。
  • 设备无关模块:这个模块隐藏了所有设备的细节,提供一致的接口给逻辑文件系统;
  • 逻辑文件系统组:每一种系统支持的文件系统都有相应的逻辑文件系统模块;
  • 系统无关接口:本模块对逻辑文件系统再包装,把所有资源封装成两个文件接口,面向数据块的(block-oriented)和面向字符的(character-oriented)。

3.3.3 Data Representation

所有文件都通过一个叫i-nodes的数据结构来表征。i-node结构内有文件数据块在物理设备内的位置信息。此外,i-node还保存有指向[逻辑文件系统模块]的函数和设备驱动的函数。通过这种保存函数指针的方式,逻辑文件系统和设备驱动可以把自己注册到内核,内核对这些模块没有依赖,从而变得很灵活。

3.3.4 Data Flow, Control Flow, and Dependencies

虚拟磁盘(ramdisk)是一种特殊的外存设备,它的驱动程序需要内存管理子系统协助实现。因此,【设备驱动模块组】对内存管理子系统有依赖,也有控制流和数据流。(KEMIN:这里让我想到,不是所有模块都是保存到内存的么?那不是所有模块都对内存管理子系统有依赖?看来对上面的概念图本质不是十分的理解……)

网络文件系统是一种特殊的文件系统。这个文件系统的文件不在本地,网络文件系统(NFS)的【逻辑文件系统】模块利用网络子系统实现功能。因此,在【逻辑文件系统组】与网络子系统之有依赖、控制流和数据流。

虚拟文件系统与进程调度子系统间有依赖前面已经说了,这里具体依赖的模块是虚拟文件系统内的【系统无关接口】。

最后,系统调用接口为用户进程提供了保存和读取文件数据的功能。和前面的子系统不同,虚拟文件系统没有为用户进程提供隐式调用机制,因此,没有由虚拟文件系统到用户进程的控制流(KEMIN:注意控制流的方向性)。

3.4 Network Interface Architecture

3.4.1 Goals

网络子系统实现了Linux系统通过网络[联接]到其它系统。实现网络互联的硬件设备有很多种,相应的网络协议也很多。网络子系统对硬件和协议都进行了抽象,让用户进程或内核其它子系统通过一致的接口使用网络功能。

3.4.2 Modules

  • 网络设备驱动:每种设备一个驱动程序;
  • 设备无关接口模块:在设备驱动与上层之间定义一个一致的设备无关的接口;
  • 网络协议模块组:负责现实不同的[网络传输协议];
  • 协议无关接口模块:在网络协议模块与上层之间定义一个一致的协议无关的接口;

最后,系统调用接口为用户进程一定的应用接口。

Figure 3.4: Network Interface Subsystem in Context

3.4.3 Data Representation

每个网络对象通过一个叫套接字(socket)的数据结构表征。Socket用与i-node类似的方式关联到进程(控制块)。

3.4.4 Data Flow, Control Flow, and Dependencies

前面说了,因为网络操作较慢,网络子系统与进程调度子有依赖。此外,为了协助虚拟文件系统实现[网络文件系统](NFS),两子系统间有依赖。

4. Conclusions

Linux内核在整个Linux系统中占据一层,层内可再按功能角色在概念上分为五个主要的子系统:进程调度、内存管理、虚拟文件系统、网络接口和进程互通信接口。这些子系统【利用】函数调用和共享数据结构来交互协作。

在最高一层,Linux内核的体系风格(architectural style)很接近Garlan的Shaw提出的Data Abstraction风格;当打开各子系统的黑箱子,会发现他们的另一种体系风格——分层。子系统由粒度更小的多个子系统组成,而交互只发生在邻近的层。

Linux内核的这种清晰的概念体系结构保证了Liunx的成功。成功的主要原因是因为清晰的概念结构能更好组织开发人员,也为系统提供了很好的可扩展性。Linux内核体系结构必须支持大批独立的志愿者参与开发。为了满足这一条,内核开发工作量最大的部分——硬件驱动、文件系统和网络协议——必须要以可扩展的方式实现。

Linux构架师们当初就选择一种数据抽象技术来提供这种可扩展性:所有的硬件设备驱动以独立模块实现,并通过统一的接口接入内核。这样,单独的开发者可以在最小量知会其它内核开发者的情况下为内核添加新的设备驱动。由一大批开发者参与开发的Linux内核大获成功,也证明这一策略的正确性。

Linux内核的另一个重要特点是它的很好的可移植性。内核体系结构将每个子系统中硬件相关的代码划分到独立的模块来提供很好的移植性。因为只需将这部分体系和硬件相关的代码重新在新硬件平台上实现就可将linux移植,工作量大大降低。

裸男
Nakeman.cn 2023 Build by Gatsby and Tailwind, Deploy on Netlify.