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
    #include <stdio.h>
    #include <stdint.h>

    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
    #include <unistd.h>
    #include <sys/mman.h>

    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
    #include <signal.h>
    #include <setjmp.h>

    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
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
28
29
30
31
#include <linux/mm.h>

int is_page_valid(struct vm_area_struct *vma, unsigned long address) {
pgd_t *pgd;
p4d_t *p4d;
pud_t *pud;
pmd_t *pmd;
pte_t *pte;

pgd = pgd_offset(vma->vm_mm, address);
if (pgd_none(*pgd) || pgd_bad(*pgd))
return 0;

p4d = p4d_offset(pgd, address);
if (p4d_none(*p4d) || p4d_bad(*p4d))
return 0;

pud = pud_offset(p4d, address);
if (pud_none(*pud) || pud_bad(*pud))
return 0;

pmd = pmd_offset(pud, address);
if (pmd_none(*pmd) || pmd_bad(*pmd))
return 0;

pte = pte_offset_map(pmd, address);
if (!pte || !pte_present(*pte))
return 0;

return 1;
}
  • 关键点:检查 pte_present() 确认页面是否有效,pte_valid() 确认权限。

三、注意事项

  1. 用户空间 vs 内核空间:用户程序无法直接访问内核数据结构,需通过系统调用或文件接口。
  2. 性能影响:频繁检查页面有效性可能降低性能。
  3. 权限问题:某些方法(如 mincore)需要特权。
  4. 竞态条件:内存映射可能动态变化,检查结果可能瞬间失效。

总结

  • 用户空间:优先使用 /proc/self/maps 结合 mincore,避免信号处理。
  • 内核模块:直接检查页表项标志位。
  • 替代方案:通过调试工具(如 gdb)或 strace 动态跟踪内存访问。

Linux内核中校验page是不是一个有效页

在Linux内核中,校验一个内存页(page)是否有效,通常需要通过检查页表项(Page Table Entry, PTE)的标志位来判断。以下是具体步骤和实现方法:


核心思路

  1. 遍历页表:从进程的页全局目录(PGD)逐级向下遍历,直到获取目标地址的页表项(PTE)。
  2. 检查页表项标志:通过 pte_present()pte_valid() 判断页面是否有效。
  3. 处理大页(Huge Pages):检查PMD或PUD层的大页标志。

具体实现代码

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#include <linux/mm.h>
#include <linux/pgtable.h>

/**
* is_page_valid - 检查虚拟地址对应的物理页是否有效
* @mm: 目标地址空间的内存描述符(如 current->mm 用户空间,&init_mm 内核空间)
* @address: 要检查的虚拟地址
*
* 返回 1 表示有效,0 表示无效。
*/
int is_page_valid(struct mm_struct *mm, unsigned long address) {
pgd_t *pgd;
p4d_t *p4d;
pud_t *pud;
pmd_t *pmd;
pte_t *pte;

// 1. 获取页全局目录(PGD)
pgd = pgd_offset(mm, address);
if (pgd_none(*pgd) || pgd_bad(*pgd))
return 0;

// 2. 获取P4D(五级页表支持)
p4d = p4d_offset(pgd, address);
if (p4d_none(*p4d) || p4d_bad(*p4d))
return 0;

// 3. 获取页上层目录(PUD)
pud = pud_offset(p4d, address);
if (pud_none(*pud) || pud_bad(*pud))
return 0;

// 4. 处理大页(PUD级别的大页)
if (pud_huge(*pud) && pud_present(*pud))
return 1;

// 5. 获取页中间目录(PMD)
pmd = pmd_offset(pud, address);
if (pmd_none(*pmd) || pmd_bad(*pmd))
return 0;

// 6. 处理大页(PMD级别的大页)
if (pmd_huge(*pmd) && pmd_present(*pmd))
return 1;

// 7. 获取页表项(PTE)
pte = pte_offset_map(pmd, address);
if (!pte)
return 0;

// 8. 检查PTE的存在标志
if (pte_present(*pte)) {
pte_unmap(pte);
return 1;
}

pte_unmap(pte);
return 0;
}

关键函数和宏

  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条目。
  2. 有效性检查

    • pgd_none(*pgd) / pgd_bad(*pgd):检查PGD是否无效。
    • pte_present(*pte):检查PTE的 _PAGE_PRESENT 标志位,确认页是否在内存中。
    • pmd_huge(*pmd)pud_huge(*pud):判断是否为大页。

使用示例

1. 检查用户空间地址的有效性

1
2
3
4
5
6
7
8
struct mm_struct *mm = current->mm; // 当前进程的地址空间
unsigned long user_addr = 0x7ffd12345678;

if (is_page_valid(mm, user_addr)) {
printk("用户空间地址 0x%lx 有效\n", user_addr);
} else {
printk("地址无效或未映射\n");
}

2. 检查内核空间地址的有效性

1
2
3
4
5
6
struct mm_struct *kern_mm = &init_mm; // 内核空间的mm_struct
unsigned long kern_addr = __pa_symbol(&some_kernel_symbol); // 内核符号的物理地址

if (is_page_valid(kern_mm, kern_addr)) {
printk("内核空间地址 0x%lx 有效\n", kern_addr);
}

注意事项

  1. 锁与并发

    • 遍历页表时需确保地址空间未发生并发修改(如通过 mmap_write_lock(mm) 加锁)。
    • 内核模块中操作需避免在原子上下文中不加锁访问。
  2. 大页处理

    • 若地址映射为大页(2MB或1GB),需在PMD或PUD层级提前返回有效性。
  3. 物理页存在性

    • pte_present() 检查的是页是否在内存中(未被交换到磁盘),而 pte_valid() 可能包含更多架构特定检查。
  4. 地址空间区分

    • 用户空间地址需使用进程的 mm_struct(如 current->mm)。
    • 内核空间地址使用 init_mm,但需注意直接操作内核页表的复杂性。

替代方案

  • 直接访问物理页:通过 virt_to_page()PageReserved() 检查,但仅适用于线性映射的内核地址。
  • 使用 follow_page() 函数:内核内部函数,可返回页结构体,需处理返回值及错误码。

总结

通过逐级遍历页表并检查PTE的 _PAGE_PRESENT 标志位,可高效判断一个虚拟地址对应的物理页是否有效。需特别注意锁、大页处理和不同地址空间(用户/内核)的上下文差异。