mark_rodata_ro是 x86-64 架构中用于将内核的只读数据段.rodata和代码段.text在页表级别设置为只读的初始化函数。它的核心目的是利用硬件的内存保护机制防止内核启动后意外或恶意的修改。核心作用硬件级写保护在内核启动早期所有内存页都是可写的因为内核需要修改页表、初始化数据结构。当初始化基本完成、即将启动第一个用户进程之前内核会调用mark_rodata_ro将那些“本不该被修改”的内存区域内核代码和只读数据在页表项中清除写权限。这个函数由mark_readonly()在CONFIG_STRICT_KERNEL_RWXy时调用。值得注意的是CONFIG_STRICT_KERNEL_RWX在 x86、arm64 和 s390 上是强制启用的无法通过make menuconfig关闭。实现逻辑该函数在 x86-64 上的典型实现位于arch/x86/mm/init_64.c核心步骤如下确定保护范围通常从_stext内核代码段起始开始到__end_rodata只读数据段结束为止。在某些实现中起始地址会跳过第一页_stext PAGE_SIZE因为内核页全局目录kernel_pg_dir位于该页中。设置页表属性通过set_memory_ro()或类似机制将指定范围内的页面标记为只读。在早期版本中使用的是change_page_attr_addr()配合PAGE_KERNEL_RO。刷新 TLB修改页表后必须刷新 TLB转换检测缓冲区以确保 CPU 使用新的权限。通常通过global_flush_tlb()或flush_tlb_all()完成。非可执行标记对于.rodata段之后的数据区域还会调用set_memory_nx()将其标记为不可执行防止数据被当作代码执行。与其他机制的关系mark_rodata_ro与内核的动态代码修改机制存在交互ftrace/kprobes这些机制需要临时修改内核代码。因此内核引入了kernel_set_to_readonly标志和set_kernel_text_rw()等函数允许在需要时临时将代码段恢复为可写完成修改后再重新保护。CONFIG_DEBUG_RODATA在较新的内核中这个配置选项已被CONFIG_STRICT_KERNEL_RWX取代。旧版的mark_rodata_ro就是由CONFIG_DEBUG_RODATA控制的。安全意义这个函数是内核自我加固Self-Hardening的关键一步。通过将代码和只读数据标记为只读它显著增加了利用内核漏洞的难度——攻击者无法直接覆写内核代码或关键数据结构来执行恶意代码。这也是现代内核安全基线的核心组成部分。调用时机与调用者mark_rodata_ro()的调用关系非常清晰它位于free_initmem()之后、启动第一个用户进程之前是内核从“初始化阶段”切换到“运行阶段”的标志性步骤之一。这个函数由kernel_init()PID 1即 init 进程的内核线程部分在启动流程中调用。典型的调用顺序可以简化为rest_init() 创建 kernel_init (PID 1) ↓ kernel_init() 等待 kthreadd (PID 2) 完成 ↓ kernel_init_freeable() 执行 do_basic_setup() 等初始化 ↓ free_initmem() 释放 __init 代码和数据段 ↓ mark_rodata_ro() 将 .text 和 .rodata 设为只读 ↓ run_init_process() 启动第一个用户态进程 (/sbin/init)内核文档注释明确写道“Linux calls mark_rodata_ro() after freeing .init code and prior to calling the first init userspace process”。这句话精炼地概括了它的位置。为什么必须在这个时机调用这个时机是精心选择的有三个关键原因__init代码已释放在free_initmem()之后所有标记为__init的函数仅在初始化期间运行一次所占用的内存已经被归还给伙伴系统。mark_rodata_ro()此时再将.text和.rodata设为只读不会影响任何还在运行的初始化代码。__ro_after_init数据已定型内核中有一类特殊数据如phys_base它们在初始化期间需要被写入但初始化完成后就永不改变。这些数据被放在专门的段中在mark_rodata_ro()执行时一并被设为只读从而在运行期间受到硬件写保护。第一个用户进程尚未启动此时系统仍处于纯粹的内核态没有用户态代码运行。在用户态程序开始执行前完成只读保护可以确保内核的攻击面在最早的时刻就被最小化。架构相关的实现mark_rodata_ro()是一个架构相关的函数每个支持严格内核内存保护CONFIG_STRICT_KERNEL_RWX的架构都需要实现它。在 x86 上它的实现位于arch/x86/mm/init_64.c通过set_memory_ro()和set_memory_nx()修改页表项然后刷新 TLB 使新的权限生效。