0. 前言

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

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下。
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

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 是一个全局变量,定义如下:

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() 中:

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 数据格式化输出

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) 格式化并输出。
输出格式:每行输出一个资源的地址范围及其描述。例如:

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 提供了一个全局视图,方便开发者管理和调试资源分配问题。