ioremap函数分析

ioremap函数分析nbsp 开始之前 先说一下 ioremap 的作用 ioremap 主要是把寄存器做映射 为什么要映射 内核空间只能访问虚拟地址的 3 4G 的地址空间 通常 3 4G 的空间一部分是映射物理内存 通常默认不会映射寄存器 如果想要访问某个寄存器 则需要把这个寄存的虚拟地址映射到高端内存上 这样内核空间才能直接访问 nbsp 下面这篇文章 对 3 4G 的内核空间和 io 映射分析的比较好 值得好好看一下 ht

 

开始之前,先说一下ioremap的作用,ioremap主要是把寄存器做映射。

为什么要映射?

内核空间只能访问虚拟地址的3~4G的地址空间,通常3~4G的空间一部分是映射物理内存,通常默认不会映射寄存器,如果想要访问某个寄存器,则需要把这个寄存的虚拟地址映射到高端内存上。这样内核空间才能直接访问。

 

下面这篇文章,对3~4G的内核空间和io映射分析的比较好,值得好好看一下。

https://blog.csdn.net/godleading/article/details/

 

打开源码,搜索ioremap,可以看得到有三个函数原型

ioremap函数分析

其中一个是在include/sam-generic/目录下,仔细看它里面的源码,它并没有做任何形式的映射,只有在不开MMU的时候才可能用它

ioremap函数分析

剩下的两个都在下面函数中定义的

ioremap函数分析

从名字上可以看到我们是使用ram相关的,同时我也是在source insght中没加其它架构相关的源码,所以__arch_ioremap中也没高亮

 

上面相关的函数比较多,就以最长用的ioremap函数为例来分析

#define ioremap(cookie,size) __arm_ioremap(cookie, size, MT_DEVICE)

我们知道第一个参数是寄存器的起始物理地址,第二个是要映射的寄存器的范围

__arm_ioremap第三个参数,表示上面列出来ioremap的几种形式,我们主要是使用普通的形式,下面的分析都默认以传入参数0来分析

#define MT_DEVICE 0 #define MT_DEVICE_NONSHARED 1 #define MT_DEVICE_CACHED 2 #define MT_DEVICE_WC 3

查找,共有两个函数,一个是nommu版本的,如下,直接返回传入地址,肯定不是我们要分析的

ioremap函数分析

另一个则是我们需要分析的内容,这里它除了调用传入的三个参数外,还调用了另一个函数作为它的入口参数。。

void __iomem * __arm_ioremap(unsigned long phys_addr, size_t size, unsigned int mtype) { return __arm_ioremap_caller(phys_addr, size, mtype, __builtin_return_address(0)); } 

__builtin_return_address(0)的含义是,得到当前函数返回地址,即此函数被别的函数调用,然后此函数执行完毕后,返回,所谓返回地址就是那时候的地址。如下所示。

6000 0000: ldr r0, =0x666 6000 0004: bl call_func /* 函数调用 */ 6000 0008: str r0, =0x555 /* 调用返回地址 */

所以这个__arm_ioremap函数第四个参数的位置是调用ioremap函数完后,下一条指定的地址

 

 

接下来就看这个函数做了什么吧,

#ifdef __ASSEMBLY__ /* 是否把X强制转换成XY还是X */ #define _AC(X,Y) X #define _AT(T,X) X #else #define __AC(X,Y) (XY) #define _AC(X,Y) __AC(X,Y) #define _AT(T,X) ((T)(X)) #endif #define PAGE_SHIFT 12 /* 1和1UL对我们是一样的,这里是把1<<12位,即确定一个页的大小为0x1000即4096字节 */ #define PAGE_SIZE (_AC(1,UL) << PAGE_SHIFT) /* 0x1000 - 1 --> 0xfff 后 ~0xfff,在32位系统中是0xffff,f000,这里用这个的作用是 * mmu的内存管理最小是一个页,即4096字节,所以要叶对齐时&使用 */ #define PAGE_MASK (~(PAGE_SIZE-1)) #define __phys_to_pfn(paddr) ((paddr) >> PAGE_SHIFT) /* 物理地址到所属页 */ void __iomem *__arm_ioremap_caller(unsigned long phys_addr, size_t size, unsigned int mtype, void *caller) { unsigned long last_addr; unsigned long offset = phys_addr & ~PAGE_MASK; /* 确定物理地址在页内的偏移 */ unsigned long pfn = __phys_to_pfn(phys_addr); /* 找到物理地址所在的页地址 */ /* * Don't allow wraparound or zero size * 注释说的很明白,不允许映射的范围为0,也不允许结束地址小于起始地址(即phys_addr + size - 1 > 0xffff,ffff) */ last_addr = phys_addr + size - 1; if (!size || last_addr < phys_addr) return NULL; /* 这里传入参数分别是 页位置 页内偏移 映射大小 类型(我们默认是0) caller(ioremap后面的指定的地址) */ return __arm_ioremap_pfn_caller(pfn, offset, size, mtype, caller); } 

 

/* 仔细追这里面每个宏的定义,可以发现是页表的一些定义Hardware page table definitions. */ static struct mem_type mem_types[] = { [MT_DEVICE] = { /* Strongly ordered / ARMv6 shared device */ .prot_pte = PROT_PTE_DEVICE | L_PTE_MT_DEV_SHARED | L_PTE_SHARED, .prot_l1 = PMD_TYPE_TABLE, .prot_sect = PROT_SECT_DEVICE | PMD_SECT_S, .domain = DOMAIN_IO, }, [MT_DEVICE_NONSHARED] = { /* ARMv6 non-shared device */ .prot_pte = PROT_PTE_DEVICE | L_PTE_MT_DEV_NONSHARED, .prot_l1 = PMD_TYPE_TABLE, .prot_sect = PROT_SECT_DEVICE, .domain = DOMAIN_IO, }, ..................... [MT_MEMORY] = { /* 内存 */ .prot_sect = PMD_TYPE_SECT | PMD_SECT_AP_WRITE, .domain = DOMAIN_KERNEL, }, [MT_ROM] = { /* NOR 之类n */ .prot_sect = PMD_TYPE_SECT, .domain = DOMAIN_KERNEL, }, [MT_MEMORY_NONCACHED] = { .prot_sect = PMD_TYPE_SECT | PMD_SECT_AP_WRITE, .domain = DOMAIN_KERNEL, }, };

 

ioremap函数分析

上面的表,其实就是我们页表中包括cache、权限管理等一些组合起来的配置,做成表后方便后建立页表时直接使用其中某一项。

#define __pfn_to_phys(pfn) ((pfn) << PAGE_SHIFT) /* 页到物理地址转换 */ /* * ARMv6 supersection address mask and size definitions. */ #define SUPERSECTION_SHIFT 24 #define SUPERSECTION_SIZE (1UL << SUPERSECTION_SHIFT) /* 0x */ #define SUPERSECTION_MASK (~(SUPERSECTION_SIZE-1)) /* 0xff00,0000 */ const struct mem_type *get_mem_type(unsigned int type) { return type < ARRAY_SIZE(mem_types) ? &mem_types[type] : NULL; } void __iomem * __arm_ioremap_pfn_caller(unsigned long pfn, unsigned long offset, size_t size, unsigned int mtype, void *caller) { const struct mem_type *type; int err; unsigned long addr; struct vm_struct * area; /* * High mappings must be supersection aligned * 对高映射,即大于4G空间映射,必须是16M对齐,我们32位的一般不会出错在这里 */ if (pfn >= 0x1000 && (__pfn_to_phys(pfn) & ~SUPERSECTION_MASK)) return NULL; type = get_mem_type(mtype); /* 我们传入的MT_DEVICE(设备[寄存器]),可以得到对应的在表中的该项的地址 */ if (!type) return NULL; /* * Page align the mapping size, taking account of any offset. * 将页面大小对其, 例如起始地址0x5fff,fff8,范围0x20字节,则也要映射两页 */ size = PAGE_ALIGN(offset + size); /* 找到一块满足size大小的区域 */ area = get_vm_area_caller(size, VM_IOREMAP, caller); if (!area) return NULL; /* 将来映射好后,在高端内存区域的首地址 */ addr = (unsigned long)area->addr; /* 下面就是根据高端内存区域动态映射区域查找的地址,以及页信息,进行映射*/ #ifndef CONFIG_SMP /* 我们不是多核CPU,所以要运行 */ if (DOMAIN_IO == 0 && (((cpu_architecture() >= CPU_ARCH_ARMv6) && (get_cr() & CR_XP)) || cpu_is_xsc3()) && pfn >= 0x && !((__pfn_to_phys(pfn) | size | addr) & ~SUPERSECTION_MASK)) { area->flags |= VM_ARM_SECTION_MAPPING; err = remap_area_supersections(addr, pfn, size, type); } else if (!((__pfn_to_phys(pfn) | size | addr) & ~PMD_MASK)) { area->flags |= VM_ARM_SECTION_MAPPING; err = remap_area_sections(addr, pfn, size, type); } else #endif err = remap_area_pages(addr, pfn, size, type); if (err) { vunmap((void *)addr); return NULL; } flush_cache_vmap(addr, addr + size); return (void __iomem *) (offset + addr); /* 返回的地址是映射到内核空间的地址了 */ }

ioremap函数分析

#define VMALLOC_OFFSET (8*1024*1024) /* 8M */ #define VMALLOC_START (((unsigned long)high_memory + VMALLOC_OFFSET) & ~(VMALLOC_OFFSET-1)) #define VMALLOC_END (0xFC000000) /* GFP_KERNEL这个标志位分配内存的一个选项,GFP_KERNEL是内核内存分配时最常用的,无内存可用时可引起休眠 */ #define GFP_KERNEL (__GFP_WAIT | __GFP_IO | __GFP_FS) struct vm_struct *get_vm_area_caller(unsigned long size, unsigned long flags, void *caller) { /* */ return __get_vm_area_node(size, 1, flags, VMALLOC_START, VMALLOC_END, -1, GFP_KERNEL, caller); } static struct vm_struct *__get_vm_area_node(unsigned long size, unsigned long align, unsigned long flags, unsigned long start, unsigned long end, int node, gfp_t gfp_mask, void *caller) { static struct vmap_area *va; struct vm_struct *area; /* #define VM_IOREMAP 0x00000001 /* ioremap() and friends */在前面定义传进来的 */ BUG_ON(in_interrupt()); if (flags & VM_IOREMAP) { int bit = fls(size); if (bit > IOREMAP_MAX_ORDER) bit = IOREMAP_MAX_ORDER; else if (bit < PAGE_SHIFT) bit = PAGE_SHIFT; align = 1ul << bit; } size = PAGE_ALIGN(size); /* 页对齐 */ if (unlikely(!size)) return NULL; /* 申请一个struct vm_struct空间,存放未使用空间信息初始化 */ area = kzalloc_node(sizeof(*area), gfp_mask & GFP_RECLAIM_MASK, node); if (unlikely(!area)) return NULL; /* * We always allocate a guard page. 每次都多申请一个保护页面 */ size += PAGE_SIZE; /* start 和 end 分别为 VMALLOC_START 和 VMALLOC_END align 为 1 * 已经使用的 vm 的信息分别存在各个 vmap_area 结构体中 * 所有的 vmap_area 结构体都在红黑树 vmap_area_root 中 * alloc_vmap_area 函数的主要功能是,查找红黑树 vmap_area_root ,找到 start 和 end 之间满足 size 大小的未使用空间, */ va = alloc_vmap_area(size, align, start, end, node, gfp_mask); if (IS_ERR(va)) { kfree(area); return NULL; } /* 将该结构体插入到红黑树 */ insert_vmalloc_vm(area, va, flags, caller); return area; } /* 初始化该结构体,并将其插入到红黑树中 */ static void insert_vmalloc_vm(struct vm_struct *vm, struct vmap_area *va, unsigned long flags, void *caller) { struct vm_struct *tmp, p; vm->flags = flags; vm->addr = (void *)va->va_start; vm->size = va->va_end - va->va_start; vm->caller = caller; va->private = vm; va->flags |= VM_VM_AREA; write_lock(&vmlist_lock); for (p = &vmlist; (tmp = *p) != NULL; p = &tmp->next) { if (tmp->addr >= vm->addr) break; } vm->next = *p; *p = vm; write_unlock(&vmlist_lock); }

主要做了下面几件重要的事:

1.参数检查,确定不是RAM,地址不要越界等

2.在VMALLOC_START到VMALLOC_END之间找到所需要大小的地址空间。

3.建立页表

 

iounmap

明显就是做一些,移除红黑树节点,释放内存空间之类的事情了。

 

说明:

在一个进程中,同一个(一组)寄存器可以多次ioremap,每次ioremap,都会为其建立新的页表,即也会有不同的虚拟地址。但这些虚拟地址都是映射的同一片物理地址,所以无论操纵那一个ioremap返回的虚拟地址,最终都操作能够的是那块物理地址。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/218234.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月18日 上午7:44
下一篇 2026年3月18日 上午7:45


相关推荐

  • 语义分割最新算法_nonnegative integers

    语义分割最新算法_nonnegative integers翻译来自:https://gist.github.com/khanhnamle1994/e2ff59ddca93c0205ac4e566d40b5e88语义分割方面的资源:https://github.com/mrgloom/awesome-semantic-segmentation1.什么是语义分割语义分割是当今计算机视觉领域的关键问题之一。从宏观上看,语义分割是一项高层次的任务,…

    2022年4月19日
    121
  • 物联网网络架构_物联网技术有哪些

    物联网网络架构_物联网技术有哪些系列文章目录提示:这里可以添加系列文章的所有文章的目录,目录需要自己手动添加例如:第一章Python机器学习入门之pandas的使用提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档文章目录系列文章目录前言一、pandas是什么?二、使用步骤1.引入库2.读入数据总结前言提示:这里可以添加本文要记录的大概内容:例如:随着人工智能的不断发展,机器学习这门技术也越来越重要,很多人都开启了学习机器学习,本文就介绍了机器学习的基础内容。提示:以下是本篇文章正文内容,下面案例

    2026年1月17日
    3
  • 命令行卸载java_卸载java「建议收藏」

    命令行卸载java_卸载java「建议收藏」有小伙伴经常会遇到Java没有卸载干净的情况,造成重新安装JDK能正常安装,接着安装JRE的时候总是报1603错误。虽然说JRE安装报错了没安装上,但是eclipse、IntelliJIDEA和AndroidStudio都能正常打开和使用,然而在命令行里却无法使用。小编今天和大家分享一下怎样彻底的卸载java,有需要的小伙伴不妨接着往下看。方法一:直接卸载,步骤比较繁琐,但是也能彻底卸载干净。1…

    2022年5月19日
    58
  • claude code安装教程:Windows/macOS/Linux安装与使用攻略~

    claude code安装教程:Windows/macOS/Linux安装与使用攻略~

    2026年3月15日
    2
  • Kafka的Log存储解析

    Kafka的Log存储解析Kafka 的 Log 存储解析标签 空格分隔 kafka 引言 Kafka 中的 Message 是以 topic 为基本单位组织的 不同的 topic 之间是相互独立的 每个 topic 又可以分成几个不同的 partition 每个 topic 有几个 partition 是在创建 topic 时指定的 每个 partition 存储一部分 Message 借用官方的一张图 可以直观地看到 topic 和 partit

    2026年3月18日
    2
  • OpenClaw API 龙虾一键配置生成工具 第三方中转站自定义模型API Key BaseUrl

    OpenClaw API 龙虾一键配置生成工具 第三方中转站自定义模型API Key BaseUrl

    2026年3月13日
    2

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号