许久以来,我对内核不甚了解,“操作系统的底层”,这就是我对内核的全部理解。
简单记录一下我和AI谈论几个小时后的一些理解。
1. Linux是内核
严格来说,Linux属于内核层,完整的操作系统还包括用户空间的软件和工具。
GNU/Linux通常指由Linux内核、GNU工具链、Shell、C库以及其他用户空间软件共同组成的完整系统。Ubuntu、CentOS、Debian等发行版,都是在Linux内核之上整合了不同软件包的操作系统发行版。
2. 关于嵌入式
我一直很疑惑:为嵌入式设备(STM32、ESP32等)编写代码与在电脑上编写C代码有什么区别?为什么STM32上写代码调用一些FreeRTOS API等内核API可以一路找到底层HAL、LL操作直到寄存器行为,而在电脑上编写代码使用printf、scanf等系统函数时,往往看不到它们的完整实现呢?
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内核
│
▼
文件系统、驱动和硬件