3.5 KiB
3.5 KiB
既然你已经理解了 task_struct 到 mm_struct 再到 vm_area_struct(VMA)这一套“账本”系统,那么**页表项(Page Table Entry, PTE)**就是这个系统中最后也是最关键的“映射条目”。
简单来说,VMA 只是在内核里划地盘(虚拟地址范围),而 PTE 才是真正把虚拟内存指向物理内存的“指路牌”。
在 x86-64 架构下,一个标准的页表项通常是 64 位(8 字节)。我们可以把它拆解为两个核心部分:物理页框号(PFN)和标志位(Flags)。
1. 页表项的逻辑构成
一个 PTE 的 64 位并不是乱排的,它的结构大致如下:
| 位范围 (Bits) | 名称 | 作用 |
|---|---|---|
| 0 | P (Present) | 存在位。1 表示在物理内存中,0 表示不在(可能在 Swap 分区或未分配)。 |
| 1 | R/W (Read/Write) | 读写位。0 表示只读,1 表示可读写。 |
| 2 | U/S (User/Supervisor) | 权限位。0 表示仅内核可访问,1 表示用户态也可访问。 |
| 3 | PWT / PCD | 缓存策略控制(通常由驱动或内核底层管理)。 |
| 5 | A (Accessed) | 访问位。只要该页被读或写过,硬件会自动置 1。这是 LRU 换出算法的重要依据。 |
| 6 | D (Dirty) | 脏位。页面被写入过则置 1。如果是脏页,换出时必须写回磁盘。 |
| 7 | PS (Page Size) | 决定这是指向 4KB 页还是 2MB 大页。 |
| 12 ~ 51 | Physical Address | 物理地址(PFN)。这是最核心的部分,存放 4KB 对齐的物理内存基地址。 |
| 63 | NX (No-Execute) | 禁止执行位。如果置 1,该页的数据不能作为指令执行(防范缓冲区溢出攻击)。 |
2. 为什么地址位只有 12-51 位?
你可能会好奇,为什么物理地址不占满整个 64 位?
- 低 12 位(0-11):由于内存页通常是 4KB 对齐的($2^{12} = 4096$),物理地址的低 12 位永远是 0。所以这 12 位被挪作他用,变成了上面的
P,R/W,A,D等状态标志位。 - 高位:目前的处理器通常只支持 48 位或 52 位的物理寻址,所以 52 位之后的部分目前是保留或用于特殊属性(如 NX 位)。
3. 与 VMA 的协作关系
这是一个非常容易混淆的点:VMA 里有权限,PTE 里也有权限,听谁的?
答案是:两者共同作用,取交集。
- 第一关 (软件检查):当你访问一个地址,内核先去红黑树找 VMA。如果 VMA 标记这块地只能读,而你想写,内核直接发
SIGSEGV(段错误)。 - 第二关 (硬件检查):如果 VMA 允许,但 PTE 里的
R/W位是 0(比如因为“写时复制” Copy-on-Write 机制),CPU 硬件会触发缺页中断,让内核来处理。
4. 深度思考:脏位 (Dirty Bit) 的意义
对于你这种偏向底层研究的开发者来说,Dirty Bit 非常重要:
- 如果一个物理页是“干净”的(和磁盘内容一致),内存紧张时可以直接丢弃。
- 如果
Dirty Bit是 1,说明数据被改过了,内核必须先把它同步回磁盘(Swap 或文件系统),才能回收这个页。
接下来你可能感兴趣的:
既然页表项这么精细,但在 64 位系统下,如果每个进程都搞一个平铺的页表,会占用巨大的内存(几个 GB 甚至更多)。
你想了解一下 Linux 是如何通过“多级页表(Multi-level Page Tables)”来节省这些空间的吗?