Linux Kernel 概览
徐小东
2023-01-03
Kernel 的任务
内核是硬件与软件之间的一个中间层。作用是将应用程序的请求传递给硬件,并充当底层驱动程序,对系统中的各种设备和组件进行寻址。
- 从应用程序的视角,内核被认为是一台增强的计算机,将其抽象到一个高层次上。
- 当若干程序并发运行时,可将内核视为资源管理程序。
- 另一种视角是将内核视为库,提供了一组面向系统的命令。系统调用用于向计算机发送请求。
Kernel 架构类型
- monolithic
kernel:单体内核,整个系统在内核空间工作。例子:Linux
- microkernel:微内核,在内核空间尽可能实现系统的最少功能,如:低级地址空间管理、线程管理、进程间通信等,其余功能则在用户空间实现。例子:Minix
- hybrid
kernel:混合型内核,将微内核和单体内核的优势组合在一起。例子:Windows
NT
Linux kernel 简介
Linux kernel 是自由开源、单体、模块化、多任务、类 Unix
的操作系统内核。
进程管理和调度
问题:
- 如何共享 CPU 时间:内核必须决定为各个进程分配多长时间
- 如何在进程之间切换:何时切换到下一个进程,哪个进程是下一个
这两个任务是称之为调度器的内核子系统的职责。
解决方案:
抢占式多任务处理(preemptive
multitasking),各个进程都分配到一定的时间段执行,但重要的进程会比次要的得到更多的
CPU 时间。
进程执行模型
- fork:生成当前进程的一个相同副本,该副本称之为子进程。原进程的所有资源都以适当的方式复制到子进程。
- exec:从一个可执行的二进制文件加载另一个应用程序,来代替当前运行的进程。
进程生命周期
- 运行:该进程此刻正在执行。
- 等待:进程能够运行,但没有得到许可,因为 CPU 分配给另一个进程。
- 睡眠:进程正在睡眠无法运行,因为它在等待一个外部事件。
- 终止:进程关闭。
- 僵尸:这样的进程已经死亡,但仍然以某种方式活着。
进程优先级
- 硬实时进程:具有严格的时间限制,某些任务必须在指定的时限内完成。硬实时进程的关键特征是,它们必须在可保证的时间范围内得到处理。例子:RTLinux
- 软实时进程:硬实时进程的一种弱化形式,写入进程在需要CPU时间时应该能够得到保证,至少优先于所有其他普通进程。
- 普通进程:没有特定时间约束,但仍然可以根据重要性来分配优先级。
进程调度器
- O(1) 调度器:在 Linux 2.6
引入,根据经典的时间片分配的思路来进行整体设计,它可以在常数时间内完成其工作,不依赖于系统上运行的进程数目。时间片的思路就是将
CPU 的执行时间分成一小段一小段的,假如是 5ms
一段。于是多个进程如果要“同时”执行,实际上就是每个进程轮流占用 5ms 的
CPU 时间,而从 1s
的时间尺度上看,这些进程就是在“同时”执行。当然,对于多核系统来说,就是把每个核心都这样做就行了。
- 完全公平调度器(completely fair scheduler),在内核版本 2.6.23
开发期间合并进来,它试图尽可能地模仿理想情况下的公平调度。如果当前有 n
个进程需要调度执行,那么调度器应该在一个比较小的时间范围内,把这 n
个进程全都调度执行一遍,并且它们平分 CPU
时间,这样就可以做到所有进程的公平调度。
所有的可运行进程都按时间在一个红黑树中排序,所谓时间即其等待时间。等待
CPU
时间最长的进程是最左侧的项,调度器下一次会考虑该进程。等待时间稍短的进程在该树上从左至右排序。
内存管理
从进程的角度来看,Linux
将虚拟地址空间划分为两个部分,分别称为内核空间和用户空间。
大多数情况下,单个虚拟地址空间就比系统中可用的物理内存要大。内核和
CPU 必须考虑如何将实际可用的物理内存映射到虚拟地址空间的区域。
可取的方法是用页表来为物理地址分配虚拟地址。虚拟地址关系到进程的用户空间和内核空间,而物理地址则用来寻址实际可用的内存。
多级分页:为减少页表的大小并容许忽略不需要的区域,计算机体系结构的设计会将虚拟地址划分为多个部分。
伙伴系统:快速检测内存中的连续区域,供内核分配连续页。
slab
缓存:分配比页帧小的内存块,并缓存频繁使用的小对象。
I/O
内核必须处理 3 方面的问题:
- 必须根据具体的设备类型和模型,使用各种方法对硬件寻址。
- 内核必须向用户应用程序和系统工具提供访问各种设备的方法。
- 用户空间需要知道内核中有哪些设备可用。
内核与外设通信的方法:
- I/O 端口:内核发送数据给 I/O 控制器。
- I/O
内存映射:将特定外设的端口地址映射到普通内存中,可以像处理普通内存那样操作外设。
- 轮询和中断:通过这两种方法系统可以知道某个设备的数据已经就绪、可以读取。
设备文件:用于访问扩展设备,这些文件建立了与某个设备驱动程序的连接,以支持与扩展设备的通信。
I/O 调度:内核采用的各种用于调度和重排 I/O
操作的算法,称之为 I/O 调度器。
- noop:一个非常简单的 I/O
调度器,将新来的请求按“先来先服务”的原则依次添加到队列,以便进行处理。请求会进行合并但无法重排。
- deadline:它试图最小化磁盘寻道的次数,并尽可能确保请求在一定时间内处理完成。
- cfq:提供了完全公平排队(complete fairness
queuing)的特性。它围绕几个队列展开,所有的请求都在这些队列中排序。同一给定进程的请求,总是在同一队列中处理。
- blkmq:多队列块 I/O 排队机制,在 Linux 3.13 引入。
虚拟文件系统:为支持各种本机文件系统,且同时允许访问其他操作系统的文件,Linux
内核在用户进程(或 C
标准库)和文件系统实现之间引入了一个抽象层。该抽象层称之为虚拟文件系统(Virtual
File System),简称 VFS。
- inode:每个文件(和目录)都有且只有一个对应的
indoe,其中包含元数据(如访问权限、上次修改的日期,等等)和指向文件数据的指针。
- 链接(link):用于建立文件系统对象之间的联系。有两种类型的链接,符号链接与硬链接。
- 编程接口:用户进程和内核的VFS实现之间的接口照例由系统调用组成,其中大多数涉及对文件、目录和一般意义上的文件系统的操作。
Ext 文件系统族:Ext2/3/4
- 碎片:由于这对访问速度有负面影响,文件系统必须尽可能减少碎片产生。
- 有效利用存储空间。
- 维护文件内容的一致性。
- 在评价文件系统的质量时,速度也是一个重要的因素。
每个文件系统都由大量块组组成,在硬盘上相继排布。
块组是 Ext2 文件系统的核心要素。
Ext3:提供了一种日志(journal)特性,记录了对文件系统数据所进行的操作。在发生系统崩溃之后,该机制有助于缩短
fsck 的运行时间。
Ext4:大型文件系统(单文件最大支持 16 TiB,卷最大支持 1
EiB)、持久预分配、日志校验和、透明加密等。
网络
问题:
从程序员的视角来看,程序如何访问网络功能?
解决方案:
网卡的运作方式与普通的块设备和字符设备完全不同,使得经典的 UNIX
箴言“万物皆文件”不再完全适用。
将一种称为套接字的特殊结构用作到网络实现的接口,这种方案现在已经成为工业标准。
内核网络子系统的实现与 TCP/IP 参考模型非常相似。
- 网络访问层:该层主要负责在计算机之间传输信息,与网卡的设备驱动程序直接协作。(
/sys/class/net
)
- 网络层:该层不仅负责发送和接收数据,还负责在彼此不直接连接的系统之间转发和路由分组。(IPv4/IPv6)
- 传输层:两个基于 IP 的主要传输协议分别是 UDP 和
TCP,前者用于发送数据报,后者可建立安全的、面向连接的服务。
- 内核应用层:Linux
采用了内核套接字的概念,使得与用户空间中的套接字的通信尽可能简单。
系统调用
系统调用用于从用户应用程序调用内核例程,以利用内核的一些专门的功能。
- 标准库:不仅是实现内核系统调用的接口集合,其中也提供了许多其他完全在用户空间实现的函数。
- 分类:每个系统调用都通过一个符号常数标识,包括进程管理、时间操作、信号处理、文件系统、内存管理、进程间通信、网络等。
- 追踪系统调用:
strace
。