Linux 内核态和用户态
引言
在学习操作系统的过程中间,经常能听到内核态、用户态的说法,但是一直没有详细研究过。现在也有了 ARM 架构的基础,索性研究一下。本篇文章建立在读者已有 ARM 架构基础的前提下,建议不了解的读者,可以读一读之前写过的几篇文章:
ARM 学习笔记(一)
ARM 学习笔记(二)
本篇文章中,对 ARM 架构相关的知识不会再做详细讲解,所有细节前面链接中的文章都有涉及。
1、用户态与内核态的虚拟地址空间布局
在 ARM32(ARMv7-A)架构中,虚拟地址空间的管理完全依赖于 MMU(Memory Management Unit) 的页表机制。系统通过页表将虚拟地址转换为物理地址,并通过页表项中的访问权限位控制访问权限。
TTBR 寄存器与页表基址
ARMv7-A 体系结构提供了两个页表基址寄存器:
- TTBR0(Translation Table Base Register 0)
- TTBR1(Translation Table Base Register 1)
每个寄存器都保存一张页表的物理基地址。
这两个寄存器共同决定了整个虚拟地址空间的映射。
- TTBR0 通常用于映射用户空间(User Space)
- TTBR1 通常用于映射内核空间(Kernel Space)
通过 Translation Table Base Control Register(TTBCR) 可以配置地址分界点(split point),确定虚拟地址的哪一部分由 TTBR0 还是 TTBR1 负责。
TTBR 访问权限与异常级别
TTBR0 和 TTBR1 寄存器的访问权限受到特权级限制:
- 只有 PL1(Privileged Level 1) 及以上(即内核态)才能访问或修改
- 运行在 PL0(User mode) 的用户程序无法读取或更改这两个寄存器,因此用户态无法直接获知或篡改页表基址
用户态与内核态的虚拟地址划分
在典型的 Linux on ARM32 系统中,虚拟地址空间按如下方式划分(假设 4 GB 虚拟空间):
- 每个进程拥有独立的用户空间(由 TTBR0 页表描述)
- 所有进程共享同一个内核空间映射(由 TTBR1 页表描述)
进程切换时的页表管理
当发生进程切换(Context Switch)时:
- TTBR0(用户页表)会被更新,以加载新的进程地址空间;
- TTBR1(内核页表)保持不变,所有进程共享相同的内核映射。
这种设计的优点是:
- 避免在切换进程时反复重建或刷新内核页表;
- 减少 TLB(Translation Lookaside Buffer)失效,提高切换效率;
- 保证所有进程都能在陷入内核态时访问相同的内核代码和数据。
2、用户态与内核态之间的隔离机制
在 Linux 操作系统中,用户态(User Mode)与内核态(Kernel Mode)之间的访问隔离机制是保障系统安全与稳定性的核心基础。
用户态应用程序运行在受限的权限级别下,无法直接访问硬件设备或内核数据结构。当用户程序需要执行特权操作(例如访问文件系统、分配内存或进行 I/O 操作)时,必须通过 系统调用(System Call) 接口请求内核代为完成。
内核态则运行在最高特权级,拥有对所有硬件资源和内存空间的完全访问权限。它负责处理中断、异常和系统调用请求,从而在提供服务的同时,防止用户程序越权访问或破坏系统资源。
以 ARMv7-A(ARM32) 架构为例,这种访问限制通过 页表项(Page Table Entry) 中的访问控制位实现:
- AP(Access Permission bits) 用于区分用户态与特权态的读写访问权限;
- PXN(Privileged eXecute Never)位 则用于禁止特权级(内核态)在特定内存区域执行代码,从而增强系统安全性。
这种基于硬件的权限隔离机制确保了:
用户态程序只能通过受控的接口与内核交互,而不会直接干扰系统核心或其他进程的运行。
3、用户态与内核态的切换与通信
常见的,用户态的应用程序可以通过以下几种方式来访问内核态的资源:
- 系统调用
- shell 脚本、库函数等方式,也可以实现访问内核资源,但是本质上是对系统调用的封装,所以还是属于 “系统调用” 这一类
- 深入 ARM-Linux 的系统调用世界
- 中断:当 CPU 在执行用户态的进程时,外围设备可能向 CPU 发出相应的中断信号,这时 CPU 会暂停执行下一条指令,转到与中断信号对应的处理程序去执行,也就是切换到了内核态,执行中断处理程序
- 异常:当 CPU 在执行用户态的进程时,发生了一些没有预知的异常,这时当前运行进程会切换到处理此异常的内核相关进程中,也就是切换到了内核态,如缺页异常
- 信号:内核通过信号通知用户态进程发生了某些事件,用户态程序可以注册信号处理函数来响应特定的信号事件。例如,SIGTERM 和 SIGINT 等信号就是用于通知进程终止或中断的信号
- ioctl:这是内核较早的一种用户态和内核态的交互方式。用户态程序通过命令的方式调用 ioctl 函数,然后内核态分发到对应驱动处理,最后将处理结果返回到用户态
- procfs/sysfs:在 Linux 中,procfs 和 sysfs 是特殊的文件系统,用于内核与用户空间之间的信息交互。procfs提供了内核和进程的各种信息,而 sysfs 则提供了设备和驱动的信息。用户态程序可以通过读取这些文件系统中的文件来获取内核信息,也可以通过写入特定的文件来配置内核或驱动