Linux页面有效性校验方法总结
Linux下如何校验一个page是否是有效的
在Linux系统中,校验一个内存页面(page)是否有效通常需要结合用户空间和内核空间的不同方法。以下是不同场景下的解决方案:
一、用户空间程序检测页面有效性
1. 通过 /proc/self/maps
检查映射范围
- 原理:每个进程的虚拟内存映射信息存储在
/proc/self/maps
中。通过解析该文件,可以判断一个地址是否属于有效的映射区域。 - 实现步骤:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int is_address_valid(void *addr) {
FILE *fp = fopen("/proc/self/maps", "r");
if (!fp) return 0;
uintptr_t target = (uintptr_t)addr;
char line[256];
int valid = 0;
while (fgets(line, sizeof(line), fp)) {
uintptr_t start, end;
if (sscanf(line, "%lx-%lx", &start, &end) == 2) {
if (target >= start && target < end) {
valid = 1;
break;
}
}
}
fclose(fp);
return valid;
} - 优点:无需内核权限,直接检查映射存在性。
- 缺点:无法确认页面当前是否可访问(如权限或物理页是否有效)。
2. 使用 mincore
系统调用
- 原理:
mincore
可判断页面是否在物理内存中(即是否被换出)。 - 示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
int is_page_in_memory(void *addr) {
unsigned char vec;
long page_size = sysconf(_SC_PAGESIZE);
void *page_start = (void *)((uintptr_t)addr & ~(page_size - 1));
if (mincore(page_start, page_size, &vec) == 0) {
return vec & 1; // 最低位为1表示在内存中
}
return 0;
} - 优点:确认物理页是否驻留内存。
- 缺点:需要
CAP_SYS_ADMIN
权限(某些系统),且无法检查权限有效性。
3. 信号处理捕获段错误
- 原理:通过捕获
SIGSEGV
信号判断访问是否有效。 - 示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
static sigjmp_buf jbuf;
static void segv_handler(int sig) {
siglongjmp(jbuf, 1);
}
int is_page_accessible(void *addr) {
struct sigaction sa_old, sa_new;
volatile char test;
sa_new.sa_handler = segv_handler;
sigemptyset(&sa_new.sa_mask);
sa_new.sa_flags = 0;
sigaction(SIGSEGV, &sa_new, &sa_old);
if (sigsetjmp(jbuf, 1) {
sigaction(SIGSEGV, &sa_old, NULL);
return 0;
}
test = *((char*)addr); // 尝试读取
sigaction(SIGSEGV, &sa_old, NULL);
return 1;
} - 优点:直接测试访问权限。
- 缺点:信号处理在多线程中不可靠,存在性能和安全风险。
二、内核模块检测页面有效性
在内核中可以直接访问页表项(Page Table Entry, PTE),检查标志位:
1 |
|
- 关键点:检查
pte_present()
确认页面是否有效,pte_valid()
确认权限。
三、注意事项
- 用户空间 vs 内核空间:用户程序无法直接访问内核数据结构,需通过系统调用或文件接口。
- 性能影响:频繁检查页面有效性可能降低性能。
- 权限问题:某些方法(如
mincore
)需要特权。 - 竞态条件:内存映射可能动态变化,检查结果可能瞬间失效。
总结
- 用户空间:优先使用
/proc/self/maps
结合mincore
,避免信号处理。 - 内核模块:直接检查页表项标志位。
- 替代方案:通过调试工具(如
gdb
)或strace
动态跟踪内存访问。
Linux内核中校验page是不是一个有效页
在Linux内核中,校验一个内存页(page)是否有效,通常需要通过检查页表项(Page Table Entry, PTE)的标志位来判断。以下是具体步骤和实现方法:
核心思路
- 遍历页表:从进程的页全局目录(PGD)逐级向下遍历,直到获取目标地址的页表项(PTE)。
- 检查页表项标志:通过
pte_present()
或pte_valid()
判断页面是否有效。 - 处理大页(Huge Pages):检查PMD或PUD层的大页标志。
具体实现代码
1 |
|
关键函数和宏
页表遍历:
pgd_offset(mm, addr)
:获取PGD条目。p4d_offset(pgd, addr)
:获取P4D条目(五级页表)。pud_offset(p4d, addr)
:获取PUD条目。pmd_offset(pud, addr)
:获取PMD条目。pte_offset_map(pmd, addr)
:获取PTE条目。
有效性检查:
pgd_none(*pgd)
/pgd_bad(*pgd)
:检查PGD是否无效。pte_present(*pte)
:检查PTE的_PAGE_PRESENT
标志位,确认页是否在内存中。pmd_huge(*pmd)
或pud_huge(*pud)
:判断是否为大页。
使用示例
1. 检查用户空间地址的有效性
1 | struct mm_struct *mm = current->mm; // 当前进程的地址空间 |
2. 检查内核空间地址的有效性
1 | struct mm_struct *kern_mm = &init_mm; // 内核空间的mm_struct |
注意事项
锁与并发:
- 遍历页表时需确保地址空间未发生并发修改(如通过
mmap_write_lock(mm)
加锁)。 - 内核模块中操作需避免在原子上下文中不加锁访问。
- 遍历页表时需确保地址空间未发生并发修改(如通过
大页处理:
- 若地址映射为大页(2MB或1GB),需在PMD或PUD层级提前返回有效性。
物理页存在性:
pte_present()
检查的是页是否在内存中(未被交换到磁盘),而pte_valid()
可能包含更多架构特定检查。
地址空间区分:
- 用户空间地址需使用进程的
mm_struct
(如current->mm
)。 - 内核空间地址使用
init_mm
,但需注意直接操作内核页表的复杂性。
- 用户空间地址需使用进程的
替代方案
- 直接访问物理页:通过
virt_to_page()
和PageReserved()
检查,但仅适用于线性映射的内核地址。 - 使用
follow_page()
函数:内核内部函数,可返回页结构体,需处理返回值及错误码。
总结
通过逐级遍历页表并检查PTE的 _PAGE_PRESENT
标志位,可高效判断一个虚拟地址对应的物理页是否有效。需特别注意锁、大页处理和不同地址空间(用户/内核)的上下文差异。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Helloeuler!