虚拟地址建立页表映射到物理内存之后,CPU 可以访问和使用虚拟内存,此时 MMU 会使用页表控制访问,例如当 CPU 对虚拟内存写操作时,MMU 检测到虚拟内存映射物理内存的页表,页表的 R/W 标志位清零,那么 CPU 没有权限对虚拟内存进行写操作,此时会触发页故障。之前的内容都在将页表里标志位的作用,那么内核是如何给页表分配属性的? 不同的架构页表的标志位差异巨大,Linux 为了给软件一个统一的视角,那么定义了一套宏,用于设置页表的同时屏蔽硬件的差异.
Linux 直接定义了一系列宏,软件可以无需关注硬件差异,直接设置页表,例如 PAGE_KERNEL 用于给内核空间虚拟地址构建页表,那么用户空间就无法访问这段虚拟内存,另外该宏具体包含了 _PAGE_PRESENT、_PAGE_RW、_PAGE_ACCESSED、_PAGE_GLOBAL、_PAGE_DIRTY、_PAGE_NX 标志,因此可以知道 PAGE_KERNEL 宏设置的权限包含了这些属性,但这些 bit 在硬件的位置可以不用关心,因此 Linux 提供的页表属性宏可以方便的构建页表. 那么接下来通过一个实践案例讲解如何使用页表属性宏,其在 BiscuitOS 上的部署逻辑如下:
cd BiscuitOS
make menuconfig
make
# 源码目录
# Module
cd BiscuitOS/output/linux-6.0-x86_64/package/BiscuitOS-PAGING-PGPROT-default/
# 部署源码
make download
# 在 BiscuitOS 中实践
make build
实践案例由两部分组成,其中一个为上图的用户空间程序,其先通过 open 函数打开 “/dev/BiscuitOS-PageTable” 节点,然后调用节点提供的 mmap() 函数分配一段虚拟内存,并在 42 行对虚拟内存进行读操作,接着在 44 行对虚拟内存进行写操作,最后就是回收内存和关闭节点.
实践案例的另外一部分由上图的内核模块构成,其由一个 MSIC 驱动框架构成,向用户空间透传 “/dev/BiscuitOS-PageTable” 节点,并对该节点实现 mmap 接口,当用户空间打开节点并调用 mmap 分配虚拟内存时,那么最终会调用到 BiscuitOS_mmap() 函数,函数的处理逻辑很简单,其在 24 行设备即将建立页表的属性为 PAGE_READONLY, 然后在 25 行将 vma vm_flags 的 VM_WRITE 标志去掉,以此将虚拟内存映射为一段只读区域,函数最后在 27 行调用 remap_pfn_range() 函数建立页表映射. 那么接下来现在 BiscuitOS 上进行实践:
当 BiscuitOS 启动之后,直接运行 RunBiscuitOS.sh 脚本,其会自动运测试所需的组件,可以看到用户进程 APP 运行之后,可以看到进程可以正常对这段内存执行读操作,但是当写操作的时候就会触发 SIGBUS 错误导致进程异常退出,可以看到 mmap 分配的内存是只读内存。实践案例至此结束,通过上面的案例可以知道,软件无需关心硬件页表的位图,只需使用 Linux 提供的宏即可建立所需的页表,这样大大降低了程序维护的难度. Linux 还提供了以下页表属性宏:
- PAGE_NONE: 不存在的内核页
- PAGE_SHARED: 可读可写不可执行的用户空间内存
- PAGE_SHARED_EXEC: 可读可写可执行的用户空间内存
- PAGE_COPY_NOEXEC: 只读不可执行的用户空间内存,ROM
- PAGE_COPY_EXEC: 只读可执行的用户空间内存
- PAGE_COPY: 只读的用户空间内存
- PAGE_READONLY: 只读不可执行的用户空间内存
- PAGE_READONLY_EXEC: 只读可执行的用户空间内存
- PAGE_KERNEL: 可读可写不可执行的内核空间内存
- PAGE_KERNEL_NOENC: 可读可写不可执行且不加密的内核空间内存
- PAGE_KERNEL_RO: 只读不可执行的内核空间内存
- PAGE_KERNEL_EXEC: 可读可写可执行的内核空间内存
- PAGE_KERNEL_ROX: 只读可执行的内核空间内存
- PAGE_KERNEL_NOCACHE: 可读可写不可执行,且 Uncache 的内核空间内存
- PAGE_KERNEL_LARGE: 可读可写不可执行的内核空间大页内存
- PAGE_KERNEL_VVAR: 对所有用户进程可见的只读不可执行内存
- PAGE_KERNEL_IO: 可读可写不可执行的内核空间 MMIO
- PAGE_KERNEL_IO_NOCACHE: 可读可写不可执行,且 Uncache 的内核空间 MMIO