Linux kmap用法及示例

kmap 是 Linux 内核中用于将高端内存(High Memory)页面临时映射到内核虚拟地址空间的 API。它在 32 位系统(地址空间有限)中尤为重要,64 位系统中通常直接使用线性映射。


核心概念

  1. 高端内存(High Memory)
    在 32 位 Linux 中,内核虚拟地址空间有限(通常 1GB)。当物理内存 > 896MB 时,超出部分称为高端内存,无法永久映射到内核空间,需动态映射。
  2. kmap 作用
    为高端内存页面分配一个临时内核虚拟地址,使内核能访问其内容。
  3. 映射限制
    映射槽位有限,需及时用 kunmap 释放。

函数原型

1
2
3
4
5
6
7
#include <linux/highmem.h>

// 映射页面到内核地址空间
void *kmap(struct page *page);

// 解除映射
void kunmap(struct page *page);

使用步骤

  1. 获取目标页的 struct page*(如通过 alloc_page() 或页面缓存)。
  2. 调用 kmap(page) 获取内核虚拟地址。
  3. 通过返回的虚拟地址访问页面内容。
  4. 使用完成后调用 kunmap(page) 释放映射。

示例代码

示例 1:映射并修改页面内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <linux/highmem.h>

void modify_page_content(struct page *page) {
char *virt_addr;

// 映射页面
virt_addr = kmap(page);
if (!virt_addr) {
pr_err("kmap failed\n");
return;
}

// 访问页面内容(示例:填充数据)
memset(virt_addr, 0xAB, PAGE_SIZE); // 填充整个页面

// 修改特定位置
virt_addr[0] = 'H';
virt_addr[1] = 'i';

// 解除映射
kunmap(page);
}

示例 2:从页面缓存读取文件内容

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
#include <linux/fs.h>
#include <linux/highmem.h>
#include <linux/pagemap.h>

void read_file_page(struct address_space *mapping, pgoff_t index) {
struct page *page;
char *virt_addr;

// 从页面缓存获取页面
page = read_mapping_page(mapping, index, NULL);
if (IS_ERR(page)) {
pr_err("Error reading page\n");
return;
}

// 映射页面
virt_addr = kmap(page);
if (!virt_addr) {
pr_err("kmap failed\n");
put_page(page); // 减少引用计数
return;
}

// 打印前 16 字节内容
print_hex_dump(KERN_INFO, "Page Data: ", DUMP_PREFIX_OFFSET, 16, 1, virt_addr, 16, true);

// 清理
kunmap(page);
put_page(page); // 释放页面引用
}

关键注意事项

  1. 映射生命周期
    • kmap 映射是临时的,用完后必须立即调用 kunmap
    • 避免长期持有映射(可能导致槽位耗尽)。
  2. 原子上下文
    kmap 可能睡眠(等待空闲槽位),不可在中断上下文或持有自旋锁时使用!此时需用 kmap_atomic(无睡眠,但映射仅限当前 CPU)。
  3. 64 位系统
    64 位地址空间充足,kmap 通常直接返回 page_to_virt(page)(低开销),但为保持代码可移植性,仍需正确使用 kmap/kunmap
  4. 错误处理
    kmap 失败时返回 NULL(极罕见),需检查返回值。

kmap vs kmap_atomic

特性 kmap kmap_atomic
是否睡眠 可能睡眠(等待槽位) 永不睡眠
适用上下文 进程上下文 进程/中断上下文
映射作用域 全局(跨 CPU) 当前 CPU(迁移时失效)
性能开销 较高 极低
解除映射函数 kunmap(page) kunmap_atomic(addr)

常见使用场景

  1. 文件系统操作(读写页面缓存)。
  2. 网络栈处理数据包(skb_frag_t 页面)。
  3. 驱动程序访问用户空间页面(如 get_user_pages() 获取的页)。
  4. 内存管理子系统(如页交换、迁移)。

提示:现代内核中,许多操作已封装高层 API(如 kmap_local_page()),但理解 kmap 机制仍有助深入内核开发。