本文共 6876 字,大约阅读时间需要 22 分钟。
Linux kernel 成功的两个原因:
正是这两个原因使得Linux kernel可以不断进化和改进。
分层结构的原则:
the dependencies between subsystems are from the top down: layers pictured near the top depend on lower layers, but subsystems nearer the bottom do not depend on higher layers.
这种子系统之间的依赖性只能是从上到下,也就是图中顶部的子系统依赖底部的子系统,反之则不行。
PS:进程上下文切换就是要换掉程序状态字、换掉页表基地址寄存器的内容、换掉 current 指向的 task_struct 实例、换掉 PC ——>也就换掉了进程打开的文件(通过 task_struct 的 files 可以找到)、换掉了进程内存的执行空间(通过 task_struct 的 mem 可以找到);
中心系统是进程调度器Process Scheduler,SCHED:所有其余的子系统都依赖于进程调度器,因为其余子系统都需要阻塞和恢复进程。当一个进程需要等待一个硬件动作完成时,相应子系统会阻塞这个进程;当这个硬件动作完成时,子系统会将这个进程恢复:这个阻塞和恢复动作都要依赖于进程调度器完成。
上图中的每一个依赖箭头都有原因:
进程调度器是 Linux kernel 中最重要的子系统。系统通过它来控制对 CPU 的访问——不仅仅是用户进程对 CPU 的访问,也包括其余子系统对 CPU 的访问。
调度策略模块scheduling policy module:决定哪个进程获得对 CPU 的访问权;调度策略应该让所有进程尽可能公平得共享 CPU。
调度器维护一个数据结构——task list,其中的元素时每个活动的进程 task_struct 实例;这个数据结构不仅仅包含用来阻塞和恢复进程的信息,也包含额外的计数和状态信息。这个数据结构在整个 kernel 层都可以公共访问。
正如前面提到过的,调度器需要调用内存管理器提供的功能,去为需要恢复执行的进程选择合适的物理地址,正因为如此,所以 进程调度器子系统依赖于内存管理子系统。当其他内核子系统需要等待硬件请求完成时,它们都依赖于进程调度子系统进行进程的阻塞和恢复。这种依赖性通过函数调用和访问共享的 task list 数据结构来体现。所有的内核子系统都要读或者写代表当前正在运行进程的数据结构,因此形成了贯穿整个系统的双向数据流。
除了内核层的数据流和控制流,OS 服务层还给用户进程提供注册定时器的接口。这形成了由调度器对用户进程的控制流。通常唤醒睡眠进程的用例不在正常的控制流范围,因为用户进程无法预知何时被唤醒。最后,调度器与 CPU 交互来阻塞和恢复进程,这又形成它们之间的数据流和控制流——CPU 负责打断当前正在运行的进程,并允许内核调度其他的进程运行。
内存管理模块负责控制进程如何访问物理内存资源。通过硬件内存管理系统(MMU)管理进程虚拟内存和机器物理内存之间的映射。每一个进程都有自己独立的虚拟内存空间,所以两个进程可能有相同的虚拟地址,但是它们实际上在不同的物理内存区域运行。MMU 提供内存保护,让两个进程的物理内存空间不互相干扰。内存管理模块还支持交换——将暂时不用的内存页换出到磁盘上的交换分区,这种技术让进程的虚拟地址空间大于物理内存的大小。虚拟地址空间的大小由机器字长决定。
架构相关模块architecture specific module提供访问物理内存的虚拟接口;
架构无关模块architecture independent module负责每个进程的地址映射以及虚拟内存交换。当发生缺页错误时,由该模块负责决定哪个内存页应该被换出内存——因为这个内存页换出选择算法几乎不需要改动,所以这里没有建立一个独立的策略模块。
系统调用接口system call interface为用户进程提供严格的访问接口(malloc 和 free;mmap 和 ummap)。这个模块允许用进程分配和释放内存、执行内存映射文件操作。
内存管理存放每个进程的虚拟内存到物理内存的映射信息。这种映射信息存放在 mm_struct 结构实例中,这个实例的指针又存放在每个进程的 task_struct 中。除了存放映射信息,数据块中还应该存放关于内存管理器如何获取和存储页的信息。例如:可执行代码能够将可执行镜像作为备份存储;但是动态申请的数据则必须备份到系统页中。(这个没看懂,请高手解惑?)
最后,内存管理模块还应该存放访问和技术信息,以保证系统的安全。
内存管理器控制物理内存,当页面失败page fault发生时,接受硬件的通知(缺页中断)—— 这意味着在内存管理模块和内存管理硬件之间存在双向的数据流和控制流。内存管理也依赖文件系统来支持交换和内存映射 I/O——这种需求意味着内存管理器需要调用对文件系统提供的函数接口procedure calls,往磁盘中存放内存页和从磁盘中取内存页。因为文件系统请求非常慢,所以在等待内存页被换入之前,内存管理器要让进程需要进入休眠——这种需求让内存管理器调用进程调度器的接口。由于每个进程的内存映射存放在进程调度器的数据结构中,所以在内存管理器和进程调度器之间也有双向的数据流和控制流。用户进程可以建立新的进程地址空间,并且能够感知缺页错误——这里需要来自内存管理器的控制流。一般来说没有用户进程到内存管理器的数据流,但是用户进程却可以通过 select 系统调用,从内存管理器获取一些信息。
虚拟文件系统为存储在硬件设备上数据提供统一的访问接口。可以兼容不同的文件系统(ext2,ext4,ntf等等)。计算机中几乎所有的硬件设备都被表示为一个通用的设备驱动接口。逻辑文件系统促进与其他操作系统标准的兼容性,并且允许开发者以不同的策略实现文件系统。虚拟文件系统更进一步,允许系统管理员在任何设备上挂载任何逻辑文件系统。虚拟文件系统封装物理设备和逻辑文件系统的细节,并且允许用户进程使用统一的接口访问文件。
除了传统的文件系统目标,VFS 也负责装载新的可执行文件。这个任务由逻辑文件系统模块完成,使得 Linux 可以支持多种可执行文件。
所有文件使用 inode 表示。每个 inode 都记录一个文件在硬件设备上的位置信息。不仅如此,inode 还存放着指向逻辑文件系统模块和设备驱动的的函数指针,这些指针能够执行具体的读写操作。通过按照这种形式(就是面向对象中的虚函数的思想)存放函数指针,具体的逻辑文件系统和设备驱动可以向内核注册自己而不需要内核依赖具体的模块特性。
一个特殊的设备驱动是 ramdisk,这个设备在主存中开辟一片区域,并把它当成持久性存储设备使用。这个设备驱动使用内存管理模块完成任务,所以在 VFS 与对内存管理模块存在依赖关系(图中的依赖关系反了,应该是 VFS 依赖于内存管理模块)、数据流和控制流。
逻辑文件系统支持网络文件系统。这个文件系统像访问本地文件一样,从另一台机器上访问文件。为了实现这个功能,一种逻辑文件系统通过网络子系统完成它的任务——这引入了 VFS 对网络子系统的一个依赖关系以及它们之间的控制流和数据流。
正如前面提到的,内存管理器使用 VFS 完成内存交换功能和内存映射 I/O。另外,当 VFS 等待硬件请求完成时,VFS 需要使用进程调度器阻塞进程;当请求完成时,VFS 需要通过进程调度器唤醒进程。最后,系统调用接口允许用户进程调用来存取数据。不像前面的子系统,VFS 没有提供给用户注册不明确调用的机制,所以没有从VFS到用户进程的控制流。
网络子系统让 Linux 系统能够通过网络与其他系统相连。这个子系统支持很多硬件设备,也支持很多网络协议。网络子系统将硬件和协议的实现细节都屏蔽掉,并抽象出简单易用的接口供用户进程和其他子系统使用——用户进程和其余子系统不需要知道硬件设备和协议的细节。
每个网络对象都被表示为一个套接字socket。套接字与进程关联的方法和 inode 节点相同。通过两个 task_struct 指向同一个套接字,套接字可以被多个进程共享。
当网络子系统需要等待硬件请求完成时,它需要通过进程调度系统将进程阻塞和唤醒——这形成了网络子系统和进程调度子系统之间的控制流和数据流。不仅如此,虚拟文件系统通过网络子系统实现网络文件系统(NFS)——这形成了 VFS 和网络子系统指甲的数据流和控制流。
1、Linux 内核是整个 Linux 系统中的一层。内核从概念上由五个主要的子系统构成:进程调度器模块、内存管理模块、虚拟文件系统、网络接口模块和进程间通信模块。这些模块之间通过函数调用和共享数据结构进行数据交互。
2、Linux 内核架构促进了他的成功,这种架构使得大量的志愿开发人员可以合适得分工合作,并且使得各个特定的模块便于扩展。
转载地址:http://xocia.baihongyu.com/