许久以来,我对内核不甚了解,“操作系统的底层”,这就是我对内核的全部理解。

简单记录一下我和AI谈论几个小时后的一些理解。

1. Linux是内核

严格来说,Linux属于内核层,完整的操作系统还包括用户空间的软件和工具。

GNU/Linux通常指由Linux内核、GNU工具链、Shell、C库以及其他用户空间软件共同组成的完整系统。Ubuntu、CentOS、Debian等发行版,都是在Linux内核之上整合了不同软件包的操作系统发行版。

2. 关于嵌入式

我一直很疑惑:为嵌入式设备(STM32、ESP32等)编写代码与在电脑上编写C代码有什么区别?为什么STM32上写代码调用一些FreeRTOS API等内核API可以一路找到底层HAL、LL操作直到寄存器行为,而在电脑上编写代码使用printfscanf等系统函数时,往往看不到它们的完整实现呢?

MPU与MMU

MPU (Memory Protection Unit,内存保护单元):基于地址区域设置访问权限,例如只读、可执行、不可访问。它通常直接使用物理地址,不负责虚拟地址映射。 MMU (Memory Management Unit,内存管理单元):负责虚拟地址到物理地址的映射,并提供页级的读、写、执行权限控制。它是支持进程隔离和虚拟内存的关键硬件。

大多数Cortex-M芯片没有MMU,部分型号集成了MPU。

MPU和MMU都属于CPU的内存保护或管理硬件。MPU主要设置地址区域的访问权限,MMU负责虚拟地址到物理地址的映射,并配合进程隔离和虚拟内存使用。

在x86上,应用程序的权限通常用Ring 0/Ring 3表示。Cortex-M使用特权模式(Privileged)和非特权模式(Unprivileged)。普通裸机程序和大多数普通FreeRTOS任务都运行在特权模式下,可以直接访问芯片的地址空间。

嵌入式编程涵盖多个层次。裸机工程通常是直接运行在硬件上的固件;使用FreeRTOS时,工程中同时包含RTOS内核、驱动和应用任务。

FreeRTOS本身就是一个RTOS内核。它负责任务调度、上下文切换、队列、信号量、互斥锁和软件定时器等内核服务。FreeRTOS与Linux内核在运行方式上存在差异:FreeRTOS通常和应用代码一起编译、一起链接成一个固件镜像;普通版FreeRTOS中,应用任务和内核通常都运行在特权模式。FreeRTOS API也不一定都直接对应寄存器操作,调度器和同步原语主要操作内核数据结构,移植层、异常处理和设备驱动部分会涉及CPU或外设硬件。

3. 何为内核

说了这么多,那么内核到底是什么?

内核是操作系统的核心,开机后持续运行,负责进程调度、内存管理、文件系统、设备驱动和网络等,并向应用提供系统调用接口。

~~这不是废话么。~~我们给单片机编写的软件同样会管理任务、资源和硬件,区别在于嵌入式软件通常面向特定功能,通用性和资源规模与桌面操作系统不同。

在有MPU与MMU的设备上,内核与应用之间的边界会更加清晰。

4. 在有MPU的设备上

MPU使用统一的物理地址空间,并为不同区域设置访问权限:

  • 是否允许读取;
  • 是否允许写入;
  • 是否允许执行;
  • 是否允许非特权代码访问。

例如,在FreeRTOS-MPU中,任务可以运行在非特权模式。任务访问受保护的内核资源时,通过SVC异常进入特权代码:

非特权任务

    │ SVC

CPU切换到特权异常处理模式


FreeRTOS内核执行受保护操作

这样可以通过配置MPU来实现各个任务内存互不干扰、应用代码无法访问硬件, 只能通过内核提权访问、防止栈溢出等功能。

5. 在有MMU的设备上

Linux运行在具有MMU的处理器上。MMU可以为每个进程建立独立的虚拟地址空间:

进程A的虚拟地址0x400000 ──► 物理内存区域A
进程B的虚拟地址0x400000 ──► 物理内存区域B

不同进程即使使用相同的虚拟地址,实际访问的也可能是不同的物理内存。进程之间通过地址空间隔离,应用程序通过系统调用请求内核服务:

应用程序


glibc的read()、write()等包装函数


syscall / svc指令


CPU切换到内核态


Linux内核


文件系统、驱动和硬件