# 架构
# 系统架构
一个 Linux 计算机包含大量软件、硬件。可以从上到下分为三层:
- User Space
- :用户空间,用于运行除内核以外的所有软件。
- 在用户空间运行的软件,其进程默认处于用户态(user mode)。但该进程可以向下调用系统接口,执行内核代码,此时该进程变为内核态(kernel mode)。
- 例如执行 glibc 的库函数时,进程处于用户态。当库函数调用系统接口时,进程变为内核态。
- Kernel Space
- :内核空间,用于运行内核。
- 内核是操作系统中的核心软件。职责如下:
- 向上,提供一组系统调用接口(System Call Interface,SCI),供用户态进程调用。
- 向下,控制硬件层。例如 CPU 调度、内存分配、磁盘读写。
- Hardware Layer
- :计算机的硬件层,包括 CPU、内存、磁盘等硬件设备。
- User Space
Linux 中,每个进程都运行在一个独立的虚拟内存空间中,并将该空间分成两部分:
- 少部分空间为内核空间,用于存储内核的代码、数据。
- 大部分空间为用户空间。
内核包含多个功能模块,包括但不限于:
- SCI
- Memory Management
- Process Management
- IPC(Inter-Process Communication)
- VFS(Virtual File System):一个抽象的文件系统层。向下管理不同类型的文件系统,向上提供统一的文件系统接口。
- General Block Device Layer :一个抽象的块设备层。向下管理不同类型的硬件设备,向上提供统一的 Block IO 接口。
- Device Driver :设备驱动程序,有很多种,用于管理各种硬件设备。
# 内核态进程
x86 架构的 CPU 设计了 0~3 四种权限。而 Linux 只使用了最高权限 ring 0 和最低权限 ring 3 。
对比用户态进程、内核态进程:
- CPU 执行内核态进程时,处于最高权限 ring 0 ,因此:
- 有权直接控制计算机硬件。
- 有权访问所有内存空间。
- CPU 执行用户态进程时,处于最低权限 ring 3 ,因此:
- 无权直接控制计算机硬件。需要调用系统接口,才能间接控制计算机硬件。
- 只有权访问当前进程的用户内存空间,不能访问其它进程的用户内存空间,不能访问内核内存空间。
- CPU 执行内核态进程时,处于最高权限 ring 0 ,因此:
进程从用户态切换到内核态的过程:
- CPU 进行上下文切换,原本执行用户空间的代码,改为执行内核空间的代码。
- 等内核代码执行完毕,CPU 再次上下文切换,继续执行用户空间的代码。
进程从用户态切换到内核态的几种方式:
- 进程主动调用系统接口,触发一次软中断
- CPU 遇到异常
- 例如遇到缺页异常时,CPU 会执行内核中的缺页异常处理程序。
- CPU 遇到硬中断
# 内核线程
Linux 开机时,kthreadd 进程会创建一些内核线程(kernel thread),用于执行一些内核任务。
- 内核线程是一种特殊的进程。每个内核线程拥有一个不同的 PID ,父进程为 kthreadd 。
对比普通线程、内核线程:
- 用户可以调用 glibc 库函数,创建普通线程。而内核线程只能由 kthreadd 进程创建。
- 普通线程的祖先是 init 进程,PID 为 1 。而内核线程的祖先是 kthreadd 进程,PID 为 2 。所有线程的最初祖先都是 idle 进程,PID 为 0 。
- 普通线程默认处于用户态,可以切换到内核态。而内核线程仅处于内核态,没有虚拟内存空间。
- 普通线程会共用所属进程的 PID 。而每个内核线程会分配一个唯一的 PID 。
内核线程举例:
- kthreadd
- 系统开机时创建的第三个进程,PID 为 2 ,负责创建、管理所有内核线程。
- kworker
- 负责并行执行内核 Workqueue 中的任务,比如定时器、中断、IO 。
- 在 CPU 每个核上运行一个实例。
- migration
- 负责在多个 CPU 核之间迁移运行中的进程,从而避免单核故障、实现负载均衡。
- 在 CPU 每个核上运行一个实例。
- watchdog
- 负责避免系统死机。进程启动之后会打开 /dev/watchdog 文件,不断对它进行写操作。如果在一定时间内(默认为 1 分钟)没有进行写操作,就会通过硬件或软件重启系统。
- 在 CPU 每个核上运行一个实例。
- kauditd
- 负责记录各种系统调用、文件访问的审计日志,保存到 /var/log/audit/* 。
- kblockd
- 负责管理块设备。
- 在系统中只运行一个实例。
- kswapd0
- 负责管理 Swap 分区。
- kthreadd
# eBPF
Linux 的内存分为内核空间、用户空间两部分,前者只允许内核态进程使用。想在内核空间执行自定义程序时,有几种方案:
- LKM(Linux Kernel Module,Linux 内核模块)
- 原理:用户用 C 语言开发一个 Linux 内核模块,编译成扩展名为 .ko 的文件,然后用 insmod 命令加载到内核中。
- 执行命令
lsmod
查看已加载的所有内核模块。
- 执行命令
- 优点:可以在 Linux 内核运行时,加载新模块,而无需重新编译内核。
- 缺点:模块运行出错时,可能导致 Linux 内核崩溃。
- 原理:用户用 C 语言开发一个 Linux 内核模块,编译成扩展名为 .ko 的文件,然后用 insmod 命令加载到内核中。
- eBPF(extended Berkeley Packet Filter,扩展伯克利包过滤器)
- 2014 年,Linux kernel v3.15 添加了 eBPF 功能。
- 原理:
- Linux 的 LLVM 编译器套件中,包含了 BCC(BPF Compiler Collection,BPF编译器集合)。
- 用户可以用 C 语言开发一个 eBPF 程序,用 BCC 编译成字节码文件。然后加载到内核中,基于 JIT 技术即时编译成机器码。
- 当 Linux 内核发生系统调用、网络包等 event 时,会通过 hook 机制自动执行 eBPF 程序。属于事件驱动的工作模式。
- 执行命令
bpftool prog list
,可以查看本机已加载的所有 eBPF 程序。
- 总之,使用 eBPF 技术,用户可以在内核态执行自定义代码,而无需重新编译内核,或加载新模块。
- 加载 eBPF 程序到内核时,会进行多种检查。比如确保该程序没有死循环、不会导致内核崩溃。
- 常见用途:
- 用于过滤网络包。eBPF 工作在内核态,与用户态进程相比,减少了上下文切换,吞吐量更高。
- 用于监控网络包。eBPF 工作在内核态,与用户态进程相比,能收集更多监控指标。
- 用于 debug ,跟踪某些程序的运行。