Linux设备驱动之内存管理

对于包含 MMU 的处理器而言, Linux 系统提供了复杂的存储管理系统,使得进程所能访问的内存达到 4GB。进程的 4GB 内存空间被分为两个部分—用户空间与内核空间。用户空间地址一般分布为 0~3GB(即 PAGE_OFFSET),这样,剩下的 3~4GB 为内核空间。
内核空间申请内存涉及的函数主要包括 kmalloc()、__get_free_pages()和 vmalloc()等。
通过内存映射,用户进程可以在用户空间直接访问设备。

内核地址空间

每个进程的用户空间都是完全独立、互不相干的,用户进程各自有不同的页表。而内核空间是由内核负责映射,它并不会跟着进程改变,是固定的。内核空间地址有自己对应的页表,内核的虚拟空间独立于其他程序。用户进程只有通过系统调用(代表用户进程在内核态执行)等方式才可以访问到内核空间。

Linux 中 1GB 的内核地址空间又被划分为物理内存映射区、虚拟内存分配区、高端页面映射区、专用页面映射区和系统保留映射区这几个区域,如图所示。

内核地址空间

保留区

Linux 保留内核空间最顶部 FIXADDR_TOP~4GB 的区域作为保留区。

专用页面映射区

紧接着最顶端的保留区以下的一段区域为专用页面映射区(FIXADDR_START~FIXADDR_TOP),它的总尺寸和每一页的用途由 fixed_address 枚举结构在编译时预定义,用__fix_to_virt(index)可获取专用区内预定义页面的逻辑地址。

高端内存映射区

当系统物理内存大于 896MB 时,超过物理内存映射区的那部分内存称为高端内存(而未超过物理内存映射区的内存通常被称为常规内存),内核在存取高端内存时必须将它们映射到高端页面映射区。

虚存内存分配区

用于 vmalloc()函数,它的前部与物理内存映射区有一个隔离带,后部与高端映射区也有一个隔离带。

物理内存映射区

一般情况下,物理内存映射区最大长度为 896MB,系统的物理内存被顺序映射在内核空间的这个区域中。

虚拟地址与物理地址关系

对于内核物理内存映射区的虚拟内存,使用 virt_to_phys()可以实现内核虚拟地址转化为物理地址, virt_to_phys()的实现是体系结构相关的,对于 ARM 而言, virt_to_phys()的定义如代码:

static inline unsigned long virt_to_phys(void *x) { return __virt_to_phys((unsigned long)(x)); } /* PAGE_OFFSET 通常为 3GB,而 PHYS_OFFSET 则定于为系统 DRAM 内存的基地址 */ #define __virt_to_phys(x) ((x) - PAGE_OFFSET + PHYS_OFFSET) 内存分配

在 Linux 内核空间申请内存涉及的函数主要包括 kmalloc()、__get_free_pages()和 vmalloc()等。kmalloc()和__get_free_pages()( 及其类似函数) 申请的内存位于物理内存映射区域,而且在物理上也是连续的,它们与真实的物理地址只有一个固定的偏移,因此存在较简单的转换关系。而vmalloc()在虚拟内存空间给出一块连续的内存区,实质上,这片连续的虚拟内存在物理内存中并不一定连续,而 vmalloc()申请的虚拟内存和物理内存之间也没有简单的换算关系。

kmalloc() void *kmalloc(size_t size, int flags);

给 kmalloc()的第一个参数是要分配的块的大小,第二个参数为分配标志,用于控制 kmalloc()的行为。

flags

最常用的分配标志是 GFP_KERNEL,其含义是在内核空间的进程中申请内存。 kmalloc()的底层依赖__get_free_pages()实现,分配标志的前缀 GFP 正好是这个底层函数的缩写。使用 GFP_KERNEL 标志申请内存时,若暂时不能满足,则进程会睡眠等待页,即会引起阻塞,因此不能在中断上下文或持有自旋锁的时候使用 GFP_KERNEL 申请内存。

在中断处理函数、 tasklet 和内核定时器等非进程上下文中不能阻塞,此时驱动应当使用GFP_ATOMIC 标志来申请内存。当使用 GFP_ATOMIC 标志申请内存时,若不存在空闲页,则不等待,直接返回。

其他的相对不常用的申请标志还包括 GFP_USER(用来为用户空间页分配内存,可能阻塞)、GFP_HIGHUSER(类似 GFP_USER,但是从高端内存分配)、 GFP_NOIO(不允许任何 I/O 初始化)、 GFP_NOFS(不允许进行任何文件系统调用)、 __GFP_DMA(要求分配在能够 DMA 的内存区)、 __GFP_HIGHMEM(指示分配的内存可以位于高端内存)、 __GFP_COLD(请求一个较长时间不访问的页)、 __GFP_NOWARN(当一个分配无法满足时,阻止内核发出警告)、 __GFP_HIGH(高优先级请求,允许获得被内核保留给紧急状况使用的最后的内存页)、 __GFP_REPEAT(分配失败则尽力重复尝试)、 __GFP_NOFAIL(标志只许申请成功,不推荐)和__GFP_NORETRY(若申请不到,则立即放弃)。

使用 kmalloc()申请的内存应使用 kfree()释放,这个函数的用法和用户空间的 free()类似。

__get_free_pages ()

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/14646.html