[linux内存管理] 第016篇 /proc/iomem的详细解析

0. 前言

此节点是显示memblock的这部分内存的具体使用情况的。我们可以看到这部分内存很明显不属于虚拟地址,而是物理地址,和设备树中的地址保持一致!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
spring:/ # cat /proc/iomem
00208000-00208fff : 208000.qcom,ipcc qcom,ipcc@208000
00400000-00bfffff : 400000.pinctrl pinctrl@400000
01400000-015effff : 1400000.clock-controller clock-controller@1400000
01628000-01629fff : 1628000.qcom,msm-eud eud_base
0162a000-0162afff : 162b000.hsphy eud_enable_reg
0162b000-0162b113 : 162b000.hsphy hsusb_phy_base

...

82a00000-864fffff : System RAM
85200000-85efffff : reserved
8b41c000-8b7fffff : System RAM
9b800000-bb7fffff : System RAM
a0010000-a1dcffff : Kernel code
a1dd0000-a249ffff : reserved
a24a0000-a331ffff : Kernel data
a7fff000-a7ffffff : reserved
af20b000-af27afff : reserved

那这个节点的信息是如何收集并打印出来的呢?

1. request_standard_resources

bootmem_init函数结束后,有一个函数request_standard_resources。这个函数的功能如下:

  • 将memblock.memory挂载到iomem_resource资源树下, 资源树是一颗倒挂的树
  • request_resource:将设备实体登记注册到总线空间链
  • 在遍历memblock.memory过程中,会检查kernel_code,kernel_data是否属于某region,如果是则挂载到该region下。
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
static void __init request_standard_resources(void)
{
struct memblock_region *region;
struct resource *res;
unsigned long i = 0;
size_t res_size;

//内核代码段的起始位置
kernel_code.start = __pa_symbol(_stext);
//内核代码段的结束位置
kernel_code.end = __pa_symbol(__init_begin - 1);
//内核数据段的起始位置
kernel_data.start = __pa_symbol(_sdata);
//内核数据段的结束位置
kernel_data.end = __pa_symbol(_end - 1);

// memblock.memory的数量
num_standard_resources = memblock.memory.cnt;
res_size = num_standard_resources * sizeof(*standard_resources);
// 在物理内存中分配空间
standard_resources = memblock_alloc(res_size, SMP_CACHE_BYTES);
if (!standard_resources)
panic("%s: Failed to allocate %zu bytes\n", __func__, res_size);

// 遍历memblock的每个内存区域
for_each_mem_region(region) {
res = &standard_resources[i++];
// 判断是否为保留区域
if (memblock_is_nomap(region)) {
res->name = "reserved";
res->flags = IORESOURCE_MEM;
} else {
// 如果不是保留区域,标记为'System RAM'
res->name = "System RAM";
res->flags = IORESOURCE_SYSTEM_RAM | IORESOURCE_BUSY;
}
// 将页帧编号转换为物理地址
res->start = __pfn_to_phys(memblock_region_memory_base_pfn(region));
res->end = __pfn_to_phys(memblock_region_memory_end_pfn(region)) - 1;

// 将内存区域注册到iomem_resource
request_resource(&iomem_resource, res);

// 如果kernel_code和kernel_data的地址范围在当前的内存区域,将其作为子资源注册到对应的System RAM
if (kernel_code.start >= res->start &&
kernel_code.end <= res->end)
request_resource(res, &kernel_code);
if (kernel_data.start >= res->start &&
kernel_data.end <= res->end)
request_resource(res, &kernel_data);
#ifdef CONFIG_KEXEC_CORE
/* Userspace will find "Crash kernel" region in /proc/iomem. */
if (crashk_res.end && crashk_res.start >= res->start &&
crashk_res.end <= res->end)
request_resource(res, &crashk_res);
#endif
}
}

这里就是建立资源树,有下面几个部分:

  • 内核代码段和数据段
  • 平台设备的资源
  • crash kernel

2. /proc/iomem的注册

/proc/iomem的注册位于kernel/resource.c中

2.1 ioresources_init

1
2
3
4
5
6
7
static int __init ioresources_init(void)
{
proc_create_seq_data("ioports", 0, NULL, &resource_op, &ioport_resource);
proc_create_seq_data("iomem", 0, NULL, &resource_op, &iomem_resource);
return 0;
}
__initcall(ioresources_init);

关键点分析

  1. proc_create_seq_data 函数:
    • 用于创建 /proc 文件系统中的条目。
    • 第一个参数 “iomem” 指定了条目的名称(即 /proc/iomem)。
    • 最后一个参数 &iomem_resource 指定了该条目的数据来源,即全局的 iomem_resource 资源树。
  2. __initcall(ioresources_init):
    • 指定 ioresources_init() 在内核初始化阶段的 init 部分运行,确保 /proc/iomem 在系统启动时被正确创建。

2.2 数据来源iomem_resource

iomem_resource 是一个全局变量,定义如下:

1
2
3
4
5
6
struct resource iomem_resource = {
.name = "PCI mem",
.start = 0,
.end = -1,
.flags = IORESOURCE_MEM,
};

iomem_resource 的作用:

  • 它是内核的物理内存资源树的根节点,记录了系统中所有内存相关的物理地址范围。
  • 所有资源(如 System RAM、设备寄存器等)都会通过 request_resource() 等接口被注册到该资源树中。

2.3 数据显示逻辑

当你查看 /proc/iomem 时,实际是内核通过以下逻辑从 iomem_resource 中读取并格式化数据:

2.3.1 资源树的遍历

核心遍历代码在 r_start()r_next() 中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
static void *r_start(struct seq_file *m, loff_t *pos)
{
struct resource *p = PDE_DATA(file_inode(m->file));
loff_t l = 0;

read_lock(&resource_lock);
for (p = p->child; p && l < *pos; p = next_resource(p))
l++;
return p;
}

static void *r_next(struct seq_file *m, void *v, loff_t *pos)
{
struct resource *p = v;
(*pos)++;
return (void *)next_resource(p);
}

r_start():获取资源树的起始节点。
r_next():遍历资源树中的每个节点。

2.3.2 数据格式化输出

1
2
3
4
5
6
7
8
9
10
11
12
13
static int r_show(struct seq_file *m, void *v)
{
struct resource *root = PDE_DATA(file_inode(m->file));
struct resource *r = v;
unsigned long long start, end;

start = r->start;
end = r->end;

seq_printf(m, "%08llx-%08llx : %s\n", start, end, r->name ? r->name : "<BAD>");
return 0;
}

seq_printf:将资源的起始地址 (start)、结束地址 (end) 和名称 (r->name) 格式化并输出。
输出格式:每行输出一个资源的地址范围及其描述。例如:

1
2
00000000-0009fbff : System RAM
0009fc00-0009ffff : reserved

总结

  1. 注册过程:
    • /proc/iomem 在系统启动时由 ioresources_init() 注册。
    • 数据来源于 iomem_resource 资源树。
  2. 数据构建:
    • 系统内存和设备寄存器等资源通过 request_resource() 或设备树解析注册到 iomem_resource
  3. 显示内容:
    • /proc/iomem 显示的是 iomem_resource 中所有资源的地址范围和用途,便于查看和调试系统物理内存布局。

通过这些机制,/proc/iomem 提供了一个全局视图,方便开发者管理和调试资源分配问题。