Featured image of post Linux 内核架构:操作系统的核心

Linux 内核架构:操作系统的核心

深入理解 Linux 内核架构、用户空间与内核空间、系统调用接口以及内核的五大子系统

Linux 内核架构:操作系统的核心

引言

如果说 Linux 操作系统是一辆车,那么**内核(Kernel)**就是这辆车的发动机。它是操作系统的核心,负责管理硬件资源,并为用户程序提供服务。在这篇文章中,我们将深入探讨 Linux 内核的架构和各个子系统。

什么是内核?

内核的定位

内核是操作系统的核心组件,它是硬件和软件之间的桥梁:

    ┌─────────────────────────────────────────────────┐
│                  应用程序                        │
│         (Browser, Text Editor, Server)          │
├─────────────────────────────────────────────────┤
│              系统调用接口 (SCI)                  │
├─────────────────────────────────────────────────┤
│                 内核空间                         │
│  ┌───────────────────────────────────────────┐  │
│  │  进程管理  │  内存管理  │  VFS  │  网络   │  │
│  └───────────────────────────────────────────┘  │
├─────────────────────────────────────────────────┤
│                  硬件层                          │
│     CPU  │  RAM  │  磁盘  │  网卡  │  设备      │
└─────────────────────────────────────────────────┘
  

内核的主要职责

  1. 进程管理:创建、调度、终止进程
  2. 内存管理:虚拟内存、页面置换、内存分配
  3. 文件系统:文件存储、检索、权限管理
  4. 设备控制:硬件设备驱动和通信
  5. 网络功能:网络协议栈、数据包路由

用户空间 vs 内核空间

为什么需要分离?

现代 CPU 提供了不同的特权级别(Ring 0-3),Linux 使用其中的两个:

  • 内核空间(Ring 0):最高权限,可直接访问硬件
  • 用户空间(Ring 3):受限权限,通过系统调用访问内核

这种分离带来的好处:

安全性:防止用户程序直接操作硬件导致系统崩溃 ✅ 稳定性:进程之间相互隔离,一个崩溃不影响其他进程 ✅ 抽象性:提供统一的接口,隐藏硬件复杂性

地址空间布局

在 x86-64 架构的 Linux 系统中:

  flowchart TB
    subgraph 用户地址空间 [用户空间 - 128TB]
        A1[代码段<br/>Text Segment]
        A2[数据段<br/>Data Segment]
        A3[堆<br/>Heap - 向上增长]
        A4[内存映射段<br/>Memory Mapping]
        A5[栈<br/>Stack - 向下增长]
    end

    subgraph 内核地址空间 [内核空间 - 128TB]
        B1[内核代码]
        B2[内核数据]
        B3[内核映射]
    end

    A5 -.->|128TB边界| B1
    A5 -.->|0x0000...FFFF| B1

    style A1 fill:#e3f2fd
    style A2 fill:#e3f2fd
    style A3 fill:#bbdefb
    style A4 fill:#90caf9
    style A5 fill:#64b5f6
    style B1 fill:#ffccbc
    style B2 fill:#ffab91
    style B3 fill:#ff8a65

用户空间(0x0000000000000000 - 0x00007FFFFFFFFFFF)

  • 每个进程都有独立的用户空间
  • 通过 malloc() 分配的内存来自堆
  • 栈用于函数调用和局部变量

内核空间(0xFFFF800000000000 - 0xFFFFFFFFFFFFFFFF)

  • 所有进程共享同一个内核空间
  • 只有内核代码能访问
  • 用户程序通过系统调用进入内核空间

系统调用:桥梁

系统调用是用户程序请求内核服务的唯一合法方式。

    // 示例:使用 write 系统调用
#include <unistd.h>
#include <string.h>

int main() {
    const char *msg = "Hello, Kernel!\n";
    // write() 是系统调用的包装函数
    write(STDOUT_FILENO, msg, strlen(msg));
    return 0;
}
  

系统调用流程

  sequenceDiagram
    participant U as 用户程序
    participant L as 标准库(glibc)
    participant K as 内核

    U->>L: 调用 write()
    L->>L: 设置系统调用号
    L->>K: 软中断(int 0x80/syscall)
    Note over K: 内核态执行
    K->>K: 检查参数
    K->>K: 执行写入操作
    K->>L: 返回结果
    L->>U: 返回给用户程序

可以使用 strace 命令查看程序使用的所有系统调用:

    strace ls /tmp
  

单内核 vs 微内核

单内核(Monolithic Kernel)

Linux 采用单内核架构,所有核心服务都在内核空间运行。

优点

  • 高性能:组件间通信开销小
  • 简单:代码组织直观

缺点

  • 稳定性风险:一个组件崩溃可能影响整个系统
  • 扩展性差:添加新功能需要重新编译内核

微内核(Microkernel)

只保留最基本的机能(进程间通信、调度、低级内存管理)在内核,其他服务作为用户空间进程运行。

代表系统:Minix, QNX, GNU Hurd

优点

  • 稳定性高:服务崩溃不致命
  • 灵活性强:可以动态替换服务

缺点

  • 性能开销:频繁的用户态-内核态切换
  • 复杂度高:IPC 机制设计复杂

Linux 的混合方案

Linux 通过 可加载内核模块(LKM) 在单内核中实现了类似微内核的灵活性:

    # 查看已加载的内核模块
lsmod

# 加载模块
sudo modprobe nfs

# 卸载模块
sudo modprobe -r nfs

# 查看模块信息
modinfo nfs
  

内核模块的优势

  • 📦 动态加载,无需重启系统
  • 🔌 扩展内核功能
  • 🐛 便于调试和开发

Linux 内核的五大子系统

  mindmap
  root((Linux 内核))
    进程调度 SCHED
      CFS调度器
      实时调度
      CPU亲和性
      负载均衡
    内存管理 MM
      虚拟内存
      页表管理
      交换空间
      内存分配器
    虚拟文件系统 VFS
      统一接口
      文件系统抽象
      缓存管理
      dentry/inode
    网络协议栈 NET
      TCP/IP
      Socket接口
      路由和过滤
      Netfilter
    进程间通信 IPC
      管道
      消息队列
      共享内存
      信号量

1. 进程调度(SCHED)

职责:决定哪个进程在什么时候使用 CPU

调度器演进

  • Linux 2.4:O(1) 调度器
  • Linux 2.6:完全公平调度器(CFS)
  • Linux 3.x+:改进的 CFS,支持实时任务

CFS(完全公平调度器)

CFS 的设计理念:理想情况下,每个进程应该获得公平的 CPU 时间

    // 伪代码:CFS 调度逻辑
struct cfs_rq {
    struct load_weight load;
    u64 exec_clock;
    u64 min_vruntime;
    struct rb_root tasks_timeline;  // 红黑树
    struct rb_node *rb_leftmost;
};

// 选择下一个运行的进程
struct task_struct *pick_next_task_fair(struct rq *rq) {
    struct cfs_rq *cfs_rq = &rq->cfs;
    struct rb_node *left = cfs_rq->rb_leftmost;
    // 返回红黑树最左边的节点(vruntime 最小的任务)
    return rb_entry(left, struct task_struct, run_node);
}
  

关键概念

  • vruntime:虚拟运行时间,值越小表示越需要运行
  • nice 值:进程优先级,范围 -20(最高)到 19(最低)
  • 红黑树:高效管理可运行进程

调度策略

    # 查看进程调度策略
ps -eo pid,cmd,policy,rtprio,ni | head -10

# POLICY 列含义:
# FF - FIFO 实时调度
# RR - 轮转实时调度
# TS - 分时调度(普通进程)
# IDL - 空闲进程
  

2. 内存管理(MM)

职责:管理虚拟内存、物理内存的映射和分配

虚拟内存概念

每个进程都认为自己拥有独立的、连续的地址空间,但实际上:

  flowchart LR
    A[虚拟地址 0x1000] -->|MMU| B[物理地址 0x8000]
    A2[虚拟地址 0x2000] -->|MMU| B2[物理地址 0x9000]
    A3[虚拟地址 0x3000] -->|MMU| B3[未分配<br/>触发缺页中断]

    style A fill:#e3f2fd
    style B fill:#fff3e0
    style A3 fill:#ffebee

页表结构

x86-64 使用四级页表:

    虚拟地址: 48 位
┌─────┬─────┬─────┬─────┬─────┬─────────┐
│ PML4│ PDP │ PD  │ PT  │ 页内偏移  │
│ 9bit│ 9bit│ 9bit│ 9bit│   12bit  │
└─────┴─────┴─────┴─────┴─────────┘
   ↓     ↓     ↓     ↓      ↓
  查表  查表  查表  查表   最终物理地址
  

伙伴系统和 Slab 分配器

伙伴系统:管理物理页面,避免外部碎片

    order 0: 4KB 页面
order 1: 8KB (2个连续4KB页面)
order 2: 16KB (4个连续4KB页面)
...
order 10: 4MB (1024个连续4KB页面)
  

Slab 分配器:为内核小对象分配内存,减少内部碎片

    // 内核对象缓存示例
struct kmem_cache *task_struct_cachep;

task_struct_cachep = kmem_cache_create("task_struct",
    sizeof(struct task_struct), 0,
    SLAB_HWCACHE_ALIGN|SLAB_PANIC, NULL);

// 从缓存中分配
struct task_struct *tsk = kmem_cache_alloc(task_struct_cachep, GFP_KERNEL);
  

3. 虚拟文件系统(VFS)

职责:为不同文件系统提供统一接口

VFS 架构

  flowchart TB
    A[用户程序] -->|open, read, write| B[VFS 层]

    B --> C[Ext4]
    B --> D[XFS]
    B --> E[Btrfs]
    B --> F[NFS]
    B --> G[ProcFS]

    C --> H[块设备层]
    D --> H
    E --> H

    F --> I[网络层]
    G --> J[内核数据结构]

    style B fill:#fff3e0
    style C fill:#e3f2fd
    style D fill:#e3f2fd
    style E fill:#e3f2fd

四个核心对象

    // 1. super_block:整个文件系统的信息
struct super_block {
    struct list_head s_list;      // 所有超级块链表
    struct file_system_type *s_type;
    const struct super_operations *s_op;
    unsigned long s_magic;        // 文件系统魔数
    // ...
};

// 2. inode:文件的元数据
struct inode {
    umode_t i_mode;               // 文件类型和权限
    uid_t i_uid;
    gid_t i_gid;
    loff_t i_size;                // 文件大小
    struct inode_operations *i_op;
    // ...
};

// 3. dentry:目录项,连接路径名和 inode
struct dentry {
    struct dentry *d_parent;      // 父目录
    struct qstr d_name;           // 文件名
    struct inode *d_inode;        // 关联的 inode
    // ...
};

// 4. file:打开的文件
struct file {
    struct path f_path;
    struct file_operations *f_op;
    loff_t f_pos;                 // 当前读写位置
    // ...
};
  

文件系统类型

    # 查看支持的文件系统
cat /proc/filesystems

# 查看已挂载的文件系统
mount | column -t

# 常见文件系统:
# ext4 - 扩展文件系统第4版
# xfs - 高性能日志文件系统
# btrfs - 写时复制(Copy-on-Write)文件系统
# nfs - 网络文件系统
# proc, sysfs - 虚拟文件系统
  

4. 网络协议栈(NET)

职责:实现 TCP/IP 协议栈

网络分层模型

  flowchart TD
    A[应用层<br/>HTTP/FTP/SSH] -->|Socket API| B[传输层<br/>TCP/UDP]
    B -->|Segment| C[网络层<br/>IP]
    C -->|Packet| D[数据链路层<br/>Ethernet]
    D -->|Frame| E[物理层<br/>网卡驱动]

    style B fill:#e3f2fd
    style C fill:#fff3e0
    style D fill:#f3e5f5

Socket 缓冲区(sk_buff)

网络数据包在内核中的表示:

    struct sk_buff {
    // 这些指针管理数据区域
    char *head, *data;
    unsigned int len, truesize;

    // 协议层特定信息
    union {
        struct tcphdr *th;
        struct udphdr *uh;
        struct icmphdr *icmph;
    } h;

    union {
        struct iphdr *iph;
        struct ipv6hdr *ipv6h;
    } nh;

    union {
        struct ethhdr *eth;
    } mac;

    struct net_device *dev;  // 发送/接收的网卡
    // ...
};
  

数据包接收流程

  sequenceDiagram
    participant N as 网卡
    participant D as 驱动
    participant S as 协议栈
    participant U as 用户程序

    N->>D: 硬件中断
    D->>D: 分配 sk_buff
    D->>S: netif_rx()
    Note over S: 软中断下半部<br/>处理数据包
    S->>S: IP 层处理
    S->>S: TCP/UDP 层处理
    S->>U: 放入接收队列
    U->>U: read() 读取数据

5. 进程间通信(IPC)

职责:提供进程间数据交换和同步机制

IPC 机制对比

机制 特点 速度 复杂度 适用场景
管道 单向数据流 简单父子进程通信
命名管道 无亲缘关系进程 进程间简单通信
消息队列 带类型的数据 需要格式化的消息
共享内存 直接内存访问 大数据量交换
信号量 同步原语 - 进程同步
Socket 网络通信 跨主机通信

管道示例

    #include <unistd.h>
#include <stdio.h>

int main() {
    int pipefd[2];
    pid_t pid;
    char buf[32];

    // 创建管道
    pipe(pipefd);

    pid = fork();
    if (pid == 0) {
        // 子进程:写入数据
        write(pipefd[1], "Hello from child", 16);
        close(pipefd[1]);
    } else {
        // 父进程:读取数据
        read(pipefd[0], buf, 32);
        printf("Parent received: %s\n", buf);
        close(pipefd[0]);
    }
    return 0;
}
  

内核源代码结构

获取源代码

    # 从 kernel.org 下载
wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.6.1.tar.xz
tar -xf linux-6.6.1.tar.xz
cd linux-6.6.1

# 或使用 git
git clone https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
cd linux
  

目录结构

    linux/
├── arch/           # 特定 CPU 架构代码(x86, ARM, RISC-V 等)
├── block/          # 块设备层(磁盘 I/O 调度)
├── crypto/         # 加密算法
├── Documentation/  # 内核文档
├── drivers/        # 设备驱动程序
│   ├── net/        # 网络设备驱动
│   ├── gpu/        # GPU 驱动
│   └── char/       # 字符设备驱动
├── fs/             # 文件系统(ext4, xfs, nfs 等)
├── include/        # 头文件
├── init/           # 内核初始化代码
├── ipc/            # 进程间通信
├── kernel/         # 核心子系统(调度器、信号、能力)
├── lib/            # 通用库函数
├── mm/             # 内存管理
├── net/            # 网络协议栈
├── scripts/        # 构建和配置脚本
├── security/       # 安全框架(SELinux, AppArmor)
├── sound/          # 声音子系统(ALSA)
└── virt/           # 虚拟化支持(KVM)
  

配置和编译

    # 安装依赖
sudo apt install build-essential libncurses-dev bison flex libssl-dev

# 配置内核
make menuconfig    # 文本界面配置
# 或
make defconfig     # 使用默认配置

# 编译(根据 CPU 核心数并行编译)
make -j$(nproc)

# 安装模块
sudo make modules_install

# 安装内核
sudo make install
  

实用工具和命令

内核信息查看

    # 查看内核版本
uname -r

# 查看内核参数
cat /proc/cmdline

# 查看内核统计信息
cat /proc/stat

# 查看内存信息
cat /proc/meminfo

# 查看中断信息
cat /proc/interrupts

# 动态查看内核日志
dmesg | tail -f
  

性能分析工具

    # 系统调用追踪
strace -c ls           # 统计系统调用次数
strace -e trace=open,read,write ls  # 追踪特定系统调用

# 内核活动追踪
perf top              # 实时性能分析
perf record -g ./myapp    # 记录性能数据
perf report               # 查看报告

# 网络统计
ss -s                 # 套接字统计
netstat -s            # 网络协议统计

# 进程监控
top                   # 实时进程信息
htop                  # 增强版 top
  

总结

Linux 内核是现代技术世界中最复杂、最重要的软件项目之一。通过这篇文章,我们了解了:

核心概念

  1. 用户空间和内核空间:隔离和保护
  2. 系统调用:用户程序和内核之间的桥梁
  3. 单内核 + 模块化:性能和灵活性的平衡
  4. 五大子系统:进程、内存、VFS、网络、IPC

内核设计哲学

  • KISS 原则:Keep It Simple, Stupid
  • 性能优先:关键路径优化
  • 可移植性:支持多种硬件架构
  • 开源协作:全球开发者共同维护

下一步

在后续的文章中,我们将深入探讨:

  • 进程管理:CFS 调度器、进程生命周期
  • 内存管理:分页机制、内存分配器
  • 文件系统:Ext4 原理、VFS 实现
  • 网络栈:TCP/IP、Socket 编程

“Read the fucking source code.” — Linus Torvalds

理解内核最好的方式就是阅读源代码。推荐从 kernel/ 目录开始,逐步深入各个子系统。


← 返回系列概述: Linux 概述:从 Unix 到开源革命


参考资源

A winner is just a loser who tried one more time.
Robust AI
使用 Hugo 构建
主题 StackJimmy 设计