Linux内核五大子系统:协同共生的技术基石

在当今的科技世界中,操作系统是连接硬件与软件的关键桥梁,而 Linux 作为一款广泛应用且极具影响力的开源操作系统,其内核的设计与实现更是备受瞩目。Linux 内核就如同一个庞大而精密的机器,其中的五大子系统犹如这个机器的核心部件,它们相互协作、协同共生,共同构建起了一个稳定、高效、强大的操作系统内核。让我们一同深入探索 Linux 内核这五大子系统之间的奇妙关系,揭开它们协同工作的神秘面纱。

一、引言

Linux 内核的五大子系统在整个操作系统中扮演着至关重要的角色,它们相互依赖、协同工作,共同构建了一个稳定、高效的系统环境。本文将深入探讨这五个子系统的关系,帮助读者更好地理解 Linux 内核的工作原理。

Linux 内核主要由进程调度(SCHED)、内存管理(MM)、虚拟文件系统(VFS)、网络接口(NET)和进程间通信(IPC)这五个子系统组成。这些子系统虽各自独立运行,但又紧密相连,共同为操作系统的稳定运行提供支持。

进程调度子系统控制系统中的多个进程对 CPU 的访问,确保多个进程能在 CPU 中 “微观串行,宏观并行” 地执行。它处于系统的中心位置,内核中其他的子系统都依赖它,因为每个子系统在运行过程中都可能需要挂起或恢复进程。

内存管理子系统主要负责控制多个进程安全地共享主内存区域。它为进程提供了内存分配、回收和保护等功能。同时,内存管理子系统还与其他子系统有着密切的联系。例如,进程调度依赖于内存管理来有效地为进程分配资源,确保进程在运行时能够获得足够的内存空间。

虚拟文件系统(VFS)为用户空间程序提供了文件和文件系统相关的接口。它通过建立一个抽象层,使不同类型的文件系统能够在 Linux 内核中协同工作。VFS 利用网络接口支持网络文件系统(NFS),也利用内存管理支持 RAMDISK 设备。同时,内存管理子系统也会利用虚拟文件系统支持交换操作。

网络接口子系统分为网络协议与设备驱动两层,支持多种网络协议。它为系统提供了网络连接和数据传输的功能。网络接口子系统与虚拟文件系统也有着一定的联系,例如虚拟文件系统可以利用网络接口支持网络文件系统。

进程间通信(IPC)子系统是 Linux 内核用于支持各进程之间消息传递与资源共享的机制。它提供了多种通信方式,如管道、共享内存、消息队列和信号量等。进程间通信子系统要依赖内存管理支持共享内存通信机制,这种机制允许两个进程除了拥有自己的私有空间,还可以存取共同的内存区域。

二、Linux五大子系统

2.1进程调度(SCHED)

进程调度处于 Linux 内核的中心位置,负责管理 CPU 资源的分配,确保多个进程能够高效运行。进程分为不同状态,如 TASK_RUNNING(正在运行或处于就绪状态)、TASK_INTERRUPTIBLE(可被中断的等待状态)、TASK_UNINTERRUPTIBLE(不可被中断的等待状态)、TASK_ZOMBIE(已完成执行但等待父进程处理)和 TASK_STOPPED(因外部信号被暂停)。进程调度还依赖内存管理,因为进程的创建与执行都需要在内存中进行。

最开始,Linux的调度算法很简单。所有进程都有一个优先级,这个优先级是时刻变化的:当一个进程用了较多的CPU时,内核就把它的优先级调低,而当一个进程用了很少的CPU时,内核就把它的优先级调高,表示它很需要CPU资源了。每次进程切换时,内核扫描可运行进程的链表,计算进程的优先级,然后选择“最佳”进程来运行。当进程数目较大时,选择“最佳”进程所消耗的时间会比较长,这种算法开销太大。内核把进程分为实时进程和普通进程,实时进程的优先级是静态设定的,而且始终大于普通进程的优先级。

一共有3中调度策略:SCHED_FIFO, SCHED_RR和SCHED_NORMAL。其中SCHED_FIFO采用先进先出的策略,最先进入runqueue的进程会首先运行,除非它主动放弃使用CPU,否则会一直占用。SCHED_RR则在进程间论转分配CPU时间。这两种策略针对实时进程,SCHED_NORMAL策略针对普通进程。

后来,Linux 2.6采用了一种叫做O(1)的调度程序,该算法的名字就透露出它能在恒定的时间内选出“最佳”进程。这个算法很复杂,事实上很不美观。调度器为每一个CPU维护了两个进程队列数组:active(时间片未用完的进程)数组和expire(时间片用完的进程)数组。数组中的元素保存着某一优先级的进程队列指针。系统一共有140个不同的优先级,因此这两个数组大小都是140。

当需要选择当前最高优先级的进程时,调度器不用遍历整个runqueue,而是直接从active数组中选择当前最高优先级队列中的第一个进程。每次时钟tick中断中,进程的时间片(time_slice)被减一。当time_slice为0时,调度器判断当前进程的类型,如果是交互式进程或者实时进程,则重置其时间片并重新插入active数组。如果不是交互式进程则从active数组中移到expired数组。这样实时进程和交互式进程就总能优先获得CPU。然而这些进程不能始终留在active数组中,否则进入expire数组的进程就会产生饥饿现象。

当进程已经占用CPU时间超过一个固定值后,即使它是实时进程或者交互式进程也会被移到expire数组中。当active数组中的所有进程都被移到expire数组中后,调度器交换active数组和expire数组。当进程被移入expire数组时,调度器会重置其时间片,因此新的active数组又恢复了初始情况,而expire数组为空,从而开始新的一轮调度。Linux在2.6.23版本以后,使用了“完全公平调度算法”(CFS)。

完全公平调度算法(CFS)

CFS不依靠实时优先级来调度,进程得到的也不是确定的时间片,而是一个CPU的使用百分比。如果有2个进程,则它们各自能得到50%的CPU使用时间。CFS的做法是对每一个进程记录它使用CPU的时间,然后选择使用CPU最少的一个进程作为下一个运行的进程。也就是说,如果一个可运行的进程(没有被阻塞)得到的CPU时间比其他进程少,那么就认为内核对它不公平,就把下一次运行的机会让给它。而每个进程的nice值,则作为一个权重,在计算使用了多少CPU时间时加权进去。比如一个nice值较高(优先级较低)的进程明明跑了了50ms的时间,由于它的nice值比较高,CFS就给它多算点时间。选择下一个进程时,由于是要选得到CPU时间最少的进程,那么这个nice值较高的进程就排到后面去了,正好体现出了它优先级低的属性。

CFS抛弃了active/expire数组,而使用红黑树选取下一个被调度进程。所有状态为RUNABLE的进程都被插入红黑树。在每个调度点,CFS调度器都会选择红黑树的最左边的叶子节点作为下一个将获得cpu的进程。简单地说,红黑数是一个时间复杂度为O(log n)的自平衡二叉搜索树。它提供了一种方式,能够以较小时间对树中的节点位置进行调整,使key值最小的一个节点就在树的最左边。下

2.2内存管理(MM)

内存管理为多个进程提供安全的内存共享机制,维护虚拟内存与物理内存之间的映射关系。Linux 使用虚拟内存技术,通常为每个进程分配 4GB 的地址空间,其中 0 - 3GB 为用户空间,3 - 4GB 为内核空间。内存管理还实现页面及缓冲区管理,如利用 slab 分配器提高内存使用效率,并通过交换机制防止物理内存耗尽。

Linux的内存管理主要分为两部分:物理地址到虚拟地址的映射,内核内存分配管理(主要基于slab)。

在虚拟内存机制中,进程访问的是虚拟地址,而不是直接访问物理内存地址。内核会将虚拟地址映射到实际的物理地址,这个过程通过页表来完成。虚拟内存使得操作系统可以提供更大的内存空间,即使物理内存不足,虚拟内存可以通过将部分数据存储在硬盘的交换分区(swap)中来扩展内存空间。例如,当物理内存耗尽时,操作系统可以将不常用的页面移到交换分区,为新的页面腾出空间。这样,即使物理内存只有 4GB,一个进程也可以认为它拥有更大的内存空间。

此外,虚拟内存还实现了进程隔离。每个进程都有独立的虚拟地址空间,防止了不同进程之间的内存访问冲突。不同进程的虚拟地址可以相同,但通过页表映射到不同的物理地址,从而保证了进程之间的独立性。据统计,在实际应用中,虚拟内存机制可以使系统同时运行更多的进程,提高了系统的并发性能。

虚拟内存的整体流程可分为 4 步:

  • CPU 产生一个虚拟地址

  • MMU 从 TLB 中获取页表,翻译成物理地址

  • MMU 把物理地址发送给主存

  • 主存将地址对应的数据返回给 CPU

图片

虚拟地址与物理地址的映射关系如下图所示:

图片

虚拟内存系统将虚拟内存分割成固定大小的块,也被称为虚拟页。类似地,物理内存也被分割成固定大小的块,称为物理页。要实现虚拟页与物理页之间的映射关系,我们需要一种叫作页表的数据结构。页表实际上就是一个**页表条目(Page Table Entry,PTE)**的数组。

每条 PTE 都由一个有效位和一个 n 位地址组成。如果 PTE 的有效位为 1,则 n 位地址表示相应物理页的起始位置,即虚拟地址能够在物理内存中找到相应的物理页。如果 PTE 的有效位为 0,且后面跟着的地址为空,那么表示该虚拟地址指向的虚拟页还没有被分配。如果 PTE 的有效位为 0,且后面跟着指向虚拟页的地址,表示该虚拟地址在物理内存中没有相对应的物理地址,指向该虚拟页在磁盘上的起始位置,我们通常把这种情况称为缺页。

此时,若出现缺页现象,MMU 会发出一个缺页异常,缺页异常调用内核中的缺页处理异常程序,该程序会选择主存的一个牺牲页,将我们需要的虚拟页替换到原牺牲页的位置。我们目前为止讨论的只是单页表,但在实际的环境中虚拟空间地址都是很大的(一个32位系统的地址空间有 2^32 = 4GB,更别说 64 位系统了)。在这种情况下,使用一个单页表明显是效率低下的。

常用方法是使用层次结构的页表。假设我们的环境为一个 32 位的虚拟地址空间,它有如下形式:虚拟地址空间被分为 4KB 的页,每个 PTE 都是 4 字节。内存的前 2K 个页面分配给了代码和数据.之后的 6K 个页面还未被分配。再接下来的 1023 个页面也未分配,其后的 1 个页面分配给了用户栈。

虚拟地址空间构造的二级页表层次结构(真实情况中多为四级或更多),一级页表( 1024 个 PTE 正好覆盖 4GB 的虚拟地址空间,同时每个 PTE 只有 4 字节,这样一级页表与二级页表的大小也正好与一个页面的大小一致都为 4KB)的每个 PTE 负责映射虚拟地址空间中一个 4MB 的片(chunk),每一片都由 1024 个连续的页面组成。二级页表中的每个PTE负责映射一个 4KB 的虚拟内存页面。

2.3虚拟文件系统(VFS)

虚拟文件系统提供统一的接口,让各类文件系统在 Linux 环境中共存。它通过隐藏具体文件系统的操作细节,使得用户可以用相同的方式访问不同类型的文件。VFS 使用超级块和索引节点的概念来管理文件系统的信息,即使在不同格式的存储介质上,也能实现文件的无缝操作。例如,用户可以直接在 ext4 和 FAT32 文件系统之间移动文件而不需担心底层差异。VFS 利用网络接口支持网络文件系统(NFS),也利用内存管理支持 RAMDISK 设备。同时,内存管理利用虚拟文件系统支持交换,交换进程(swapd)定期由调度程序调度。

vFS主要通过以下几种关键数据结构来实现:

  • struct file_system_type:表示一种具体的文件系统类型,包含该文件系统的操作函数,如挂载、卸载等。

  • struct super_block:每个已挂载的文件系统都有一个超级块(super block),其中保存了有关该文件系统的信息,如大小、状态和操作函数。

  • struct inode:表示文件或目录的元信息,包含该对象的属性,例如权限、大小、时间戳等。每个inode对应于实际存储设备上的一个对象。

  • struct dentry:目录项,用于管理路径名到inode之间的映射,它使得名字空间解析变得高效。

  • struct file:代表打开的文件实例,包括指向相应inode和dentry的数据结构,以及当前读写位置等信息。

当应用程序请求某个文件操作时,例如打开一个文件,VFS执行以下步骤:

  • 路径解析:VFS从给定路径开始,通过查找dentry缓存进行路径解析。如果dentry存在,直接返回;如果不存在,则根据父目录中的内容查找。

  • 获取inode信息:一旦找到目标dentry,相应的inode会被加载以获取元信息。这可能涉及到与具体底层设备交互,以读取或更新inode信息。

  • 打开文件描述符:为打开的文件创建一个file结构,并在其中保存与该file相关联的信息,如指向相应inode和当前偏移量等。

  • 调用相应操作函数:VFS通过dispatch机制,将实际请求转发到正确类型的底层文件系统。底层实现会根据请求完成具体操作(例如读取、写入)。

  • 关闭与释放资源:当操作完成后,VFS负责适当地关闭file结构,并释放相关资源,以确保内存管理效率。

2.4网络接口(NET)

网络接口子系统分为网络协议与设备驱动两层,支持多种网络协议如 TCP/IP、UDP 等。网络协议通过统一的数据包收发接口,使上层协议独立于特定硬件;而设备驱动负责实现与硬件设备的通信。此外,网络缓冲区的设计提升了数据包处理的稳定性,保障了网络传输的高效性与可靠性。

在早期的操作系统中例如fedora13或者ubuntu15之前网卡命名的方式为eth0,eth1,eth2,属于biosdevname命名规范。当然这是针对intel网卡的命名规则,对于realtek类型的网卡会命名为ens33。但是这个编号往往不一定准确对应网卡接口的物理顺序,为了能够方便定位和区分网络设备,目前linux的主流操作系统采用一致网络设备命名(CONSISTENT NETWORK DEVICE NAMING)规范。

一致网络设备命名规则(CONSISTENT NETWORK DEVICE NAMING

命名规范为:设备类型 + 设备位置

2.5 进程间通信(IPC)

进程间通信是 Linux 内核用于支持各进程之间消息传递与资源共享的机制。Linux 提供多种 IPC 方式,如管道(用于父子进程间的单向数据传输)、共享内存(多个进程可以直接访问同一块内存区域,速度快,但需结合信号量进行访问控制)、消息队列(存储在内核中的消息链表,支持优先级和随机查询)、信号量(用于控制进程间的同步与互斥,避免资源竞争)。进程间通信要依赖内存管理支持共享内存通信机制,这种机制允许两个进程除了拥有自己的私有空间,还可以存取共同的内存区域。

为了更直观地理解进程间通信机制的结构,我们可以通过以下图示来展示:

图片

用户通过通信接口让通信中枢建立通信信道或传递通信信息。例如,在使用共享内存进行进程间通信时,用户通过特定的系统调用接口(通信接口)请求内核空间的通信中枢为其分配一块共享内存区域,并建立起不同进程对该区域的访问路径。

进程间通信机制的类型

⑴共享内存式

通信中枢建立好通信信道后,通信双方之后的通信不需要通信中枢的协助。这就如同两个房间之间打开了一扇门,双方可以直接通过这扇门进行交流,而不需要中间人的帮忙。

但是,由于通信信息的传递不需要通信中枢的协助,通信双方需要进程间同步,以保证数据读写的一致性。否则,就可能出现数据踩踏或者读到垃圾数据的情况。比如,多个进程同时对共享内存进行读写操作时,需要通过信号量等机制来确保在同一时间只有一个进程能够进行写操作,避免数据冲突。

⑵消息传递式

通信中枢建立好通信信道后,每次通信还都需要通信中枢的协助。这种方式就像一个中间人在两个房间之间传递信息,每次传递都需要经过中间人。

消息传递式又分为有边界消息和无边界消息。无边界消息是字节流,发过来是一个一个的字节,要靠进程自己设计如何区分消息的边界。有边界消息的发送和接收都是以消息为基本单位,类似于一封封完整的信件,接收方可以明确地知道每个消息的开始和结束位置。

三、子系统之间的关系

3.1 进程调度与内存管理

进程调度与内存管理这两个子系统相互依赖。在多道程序环境下,为了让程序运行,必须为其创建进程,而创建进程的首要任务就是将程序和数据装入内存。这种紧密的关系体现了 Linux 内核子系统之间的协同工作。例如,在设备驱动编程中,当资源不能满足时,驱动会调度其他进程执行,并使当前进程进入睡眠状态,直到所需资源被释放。此时,内存管理子系统负责为新的进程分配内存空间,而进程调度子系统则控制着进程对 CPU 的访问,确保各个进程能够在合适的时机运行。

3.2进程间通信与内存管理

进程间通信子系统依赖内存管理支持共享内存通信机制。这种机制允许两个进程除了拥有自己的私有空间,还可以存取共同的内存区域。例如,当两个进程需要进行数据交换时,内存管理子系统会为它们分配共享内存空间,使得进程间通信更加高效。通过这种方式,进程间通信子系统能够实现多个进程之间的消息传递与资源共享,提高系统的整体性能。

3.3虚拟文件系统与网络接口

虚拟文件系统利用网络接口支持网络文件系统(NFS),同时也利用内存管理支持 RAMDISK 设备。通过网络接口,虚拟文件系统可以实现对远程文件系统的访问,为用户提供更加便捷的文件管理方式。例如,在企业环境中,用户可以通过网络文件系统访问其他服务器上的文件,而无需将文件复制到本地。此外,虚拟文件系统还可以利用内存管理子系统支持 RAMDISK 设备,提高文件系统的性能。

3.4内存管理与虚拟文件系统

内存管理利用虚拟文件系统支持交换,同时内存管理向文件系统发出请求,挂起当前正在运行的进程。当一个进程存取的内存映射被换出时,内存管理子系统会向虚拟文件系统发出请求,将该进程的内存映射保存到磁盘上。同时,内存管理子系统会挂起当前正在运行的进程,以释放 CPU 资源。这种机制可以有效地防止物理内存耗尽,提高系统的稳定性。例如,当系统内存不足时,内存管理子系统会将不常用的进程内存映射换出到磁盘上,以释放内存空间。当需要再次访问这些进程时,内存管理子系统会从磁盘上读取相应的内存映射,并恢复进程的运行。

四、全文总结

Linux内核主要由五大子系统组成:进程调度、内存管理、虚拟文件系统(VFS)、网络接口和进程间通信。

  • 进程调度:负责控制进程对CPU的访问。

  • 内存管理:负责为所有进程分配内存,管理虚拟内存和物理内存。

  • 虚拟文件系统:提供了一个标准的接口,用于访问不同的文件系统类型。

  • 网络接口:提供了网络通信的抽象模型,包括网络协议栈。

  • 进程间通信:管理不同进程之间的消息传递。

Linux 内核的五大子系统虽各自独立,但相辅相成,共同构成了 Linux 操作系统的核心。深入理解这些子系统的结构与功能及其相互关系,有助于开发者更好地优化应用程序,提高系统性能。

以下是一个简单的示例代码,演示如何在内核模块中注册一个简单的字符设备驱动:

#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/major.h>
 
static int __init hello_init(void) {
    int ret = register_chrdev(233, "hello", &fops);
    if (ret < 0) {
        printk("Register device failed with error code %d\n", ret);
        return ret;
    }
    printk("Registered device with major number 233\n");
    return 0;
}
 
static void __exit hello_exit(void) {
    unregister_chrdev(233, "hello");
    printk("Unregistered device with major number 233\n");
}
 
module_init(hello_init);
module_exit(hello_exit);
 
MODULE_LICENSE("GPL");

这段代码通过调用register_chrdev函数,在内核中注册了一个新的字符设备,主设备号为233。当模块被加载时,会打印一条消息,当模块被卸载时,会注销该设备并打印一条消息。这个过程演示了内核模块如何与这五大子系统中的一个(这里是内存管理)进行交互。

随着技术的发展,Linux 内核将在未来的技术创新中继续发挥重要作用。开发者可以通过深入了解这些子系统的关系,为系统的优化和开发提供指导,进一步提升 Linux 操作系统的性能和稳定性。在不断变化的技术环境中,Linux 内核的模块化设计和高效性将使其能够适应各种新的挑战和需求。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值