linux rmap接口用法及示例

Linux 中的 rmap(Reverse Mapping)是一种内核机制,用于跟踪物理内存页到虚拟内存地址的映射关系。它的核心作用是高效地管理内存页的引用关系,例如在内存回收(如 kswapd)、页面迁移、NUMA 优化等场景中快速找到所有映射到某个物理页的虚拟地址。以下是其接口的用法及示例说明:


一、核心概念

  • 反向映射:传统的页表是进程虚拟地址到物理页的映射,而反向映射(rmap)记录了物理页到所有映射它的虚拟地址的逆向关系。
  • 数据结构:内核通过 struct pagestruct anon_vmastruct anon_vma_chain 等结构实现反向映射。

二、关键接口与函数

以下是 Linux 内核中与 rmap 相关的关键接口:

1. page_referenced()

  • 作用:统计一个物理页被访问(referenced)的次数,用于页面回收算法(LRU)。
  • 参数
    • struct page *page:目标物理页。
    • int is_locked:页面是否已加锁。
    • struct mem_cgroup *memcg:内存控制组。
    • unsigned long *vm_flags:返回关联的 VMA 标志。
  • 返回值:被访问次数。
  • 示例
    1
    2
    unsigned long referenced;
    referenced = page_referenced(page, 0, NULL, NULL);

2. try_to_unmap()

  • 作用:解除物理页的所有映射(用于页面回收或迁移)。
  • 参数
    • struct page *page:目标物理页。
    • enum ttu_flags flags:操作标志(如 TTU_IGNORE_MLOCK 忽略 mlock 的页)。
  • 返回值:成功返回 SWAP_SUCCESS,失败返回错误码。
  • 示例
    1
    2
    3
    int ret = try_to_unmap(page, TTU_IGNORE_MLOCK);
    if (ret == SWAP_SUCCESS)
    printk("Page unmapped successfully\n");

3. rmap_walk()

  • 作用:遍历所有映射到某个物理页的虚拟地址,并对每个映射执行回调函数。
  • 参数
    • struct page *page:目标物理页。
    • struct rmap_walk_control *rwc:控制回调函数的结构体。
  • 示例
    1
    2
    3
    4
    5
    struct rmap_walk_control rwc = {
    .rmap_one = my_rmap_callback,
    .arg = my_arg,
    };
    rmap_walk(page, &rwc);

三、使用场景与示例

场景 1:统计页面访问次数

在内存回收时,内核需要判断哪些页面是“活跃”的:

1
2
3
4
5
6
struct page *page = ...; // 获取目标物理页
unsigned long referenced;
referenced = page_referenced(page, 0, NULL, NULL);
if (referenced > 0) {
// 页面近期被访问过,避免回收
}

场景 2:解除所有映射(页面迁移)

在迁移物理页时,需要先解除所有旧的映射:

1
2
3
4
5
struct page *page = ...; // 目标页
int ret = try_to_unmap(page, TTU_MIGRATION);
if (ret == SWAP_SUCCESS) {
// 解除映射成功,可以迁移页面
}

场景 3:自定义反向映射遍历

遍历所有映射到某页的虚拟地址,并打印信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static int my_rmap_callback(struct page *page, struct vm_area_struct *vma,
unsigned long addr, void *arg) {
printk("Page is mapped at virtual address 0x%lx in PID %d\n",
addr, vma->vm_mm->pid);
return 0; // 继续遍历
}

void analyze_page_mappings(struct page *page) {
struct rmap_walk_control rwc = {
.rmap_one = my_rmap_callback,
.arg = NULL,
};
rmap_walk(page, &rwc);
}

四、注意事项

  1. 锁机制:操作 rmap 时需要确保页面和 VMA 的锁(如 page_lockmmap_lock)已被正确获取。
  2. 性能:频繁操作反向映射可能影响性能,需谨慎使用。
  3. 内核版本差异:不同内核版本的 rmap 接口可能略有变化,需参考对应版本的源码(如 mm/rmap.c)。

五、参考资料

  1. Linux 内核源码:mm/rmap.cinclude/linux/rmap.h
  2. 《Understanding the Linux Kernel》(第 3 版)
  3. Kernel Documentation: Reverse Mapping

通过上述接口和示例,可以更灵活地操作 Linux 内存管理中的反向映射机制。

Linux内核中根据vma获取pid代码示例

在 Linux 内核中,要从一个虚拟内存区域(VMA,struct vm_area_struct)获取关联进程的 PID,可以通过访问 VMA 所属的内存描述符(struct mm_struct),再从中获取关联的进程(struct task_struct)。以下是代码示例及详细说明:


代码示例

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
#include <linux/sched.h>   // 包含 task_struct 定义
#include <linux/mm.h> // 包含 vm_area_struct 定义

/**
* 根据 VMA 获取进程的 PID(tgid,即线程组 ID,通常等同于用户态的进程 ID)
* @vma: 目标虚拟内存区域
* 返回值: 关联进程的 PID,若失败则返回 -1
*/
pid_t get_pid_from_vma(struct vm_area_struct *vma) {
struct mm_struct *mm;
struct task_struct *task;
pid_t pid = -1;

if (!vma) {
pr_err("VMA is NULL\n");
return -1;
}

// 获取 VMA 关联的 mm_struct
mm = vma->vm_mm;
if (!mm) {
pr_err("mm_struct is NULL\n");
return -1;
}

// 安全增加 mm_users 计数,防止 mm 被释放
if (!mmget_not_zero(mm)) {
pr_err("mm_users is zero\n");
return -1;
}

// 获取 mm_struct 的 owner(关联的 task_struct)
task = mm->owner;
if (!task) {
pr_err("task_struct is NULL\n");
goto out_put_mm;
}

// 获取进程的 PID(tgid 是线程组 ID,通常等于用户态的进程 ID)
pid = task->tgid;

out_put_mm:
mmput(mm); // 释放 mm_users 计数
return pid;
}

关键步骤说明

  1. 有效性检查

    • 检查 vma 是否为 NULL(例如在非法上下文中调用)。
    • 检查 vma->vm_mm 是否为 NULL(某些特殊 VMA 可能没有关联的 mm_struct)。
  2. **安全引用 mm_struct**:

    • 使用 mmget_not_zero(mm) 增加 mm->mm_users 计数,防止访问过程中 mm_struct 被释放。
    • 如果 mm_users 已为 0(例如进程已退出),直接返回错误。
  3. **获取 task_struct**:

    • 通过 mm->owner 获取关联的进程描述符 task_struct
    • 检查 task 是否为 NULL(理论上不应发生,但需防御性编程)。
  4. 提取 PID

    • 使用 task->tgid 获取进程的线程组 ID(即用户态可见的 PID)。
    • 若需获取线程的 PID(轻量级进程 ID),可改用 task->pid
  5. 释放资源

    • 使用 mmput(mm) 减少 mm_users 计数,确保引用计数平衡。

使用场景示例

在内核模块中遍历进程的 VMA 并打印 PID:

1
2
3
4
5
6
7
8
9
10
11
12
13
void iterate_vmas_and_print_pid(struct mm_struct *mm) {
struct vm_area_struct *vma;
pid_t pid;

// 遍历所有 VMA
for (vma = mm->mmap; vma; vma = vma->vm_next) {
pid = get_pid_from_vma(vma);
if (pid != -1) {
pr_info("VMA [0x%lx-0x%lx] is mapped by PID %d\n",
vma->vm_start, vma->vm_end, pid);
}
}
}

注意事项

  1. 并发安全

    • 操作 mm_structtask_struct 时需确保上下文安全(例如持有 mmap_lock 或使用 RCU)。
    • 若在中断上下文或非进程上下文中调用,需额外验证可行性。
  2. 内核版本差异

    • mm->owner 的可用性:在较旧内核(如 2.6.32 之前)中可能不存在,需确认目标内核版本。
    • 新版本内核中可能引入更精细的锁机制(如 mmap_read_lock())。
  3. 线程与进程

    • task->tgid 表示线程组 ID(即用户态 PID),task->pid 表示线程的轻量级 PID。
    • 若需获取创建 VMA 的具体线程 PID,可使用 task->pid
  4. 调试信息

    • 可通过 pr_info("Process name: %s\n", task->comm) 打印进程名。

参考资料

  1. Linux 内核源码:
    • include/linux/mm_types.hvm_area_struct 定义)
    • include/linux/sched.htask_struct 定义)
    • kernel/fork.cmmget_not_zerommput 实现)
  2. 《Linux Kernel Development》(Robert Love 著)
  3. Linux Cross Reference(在线源码检索)

通过上述代码和说明,可以安全地在 Linux 内核中根据 VMA 获取关联进程的 PID。