Linux内存管理之kmalloc 与 __get_free_page()
- 格式:doc
- 大小:41.00 KB
- 文档页数:3
linux 申请大容量内存的方法【原创版4篇】目录(篇1)1.引言:介绍 Linux 系统中申请大容量内存的需求2.内核空间申请内存的方法:讲解 kmalloc 函数及其特点3.用户态申请内核态内存的方法:介绍 brk 系统调用、setfs、getfs 以及 dommap 等方法4.Linux 内存管理机制:概述地址空间、页(page)管理以及物理内存分配5.预留内存和大块内存申请:讨论内核对于大内存申请的优化方法6.结论:总结 Linux 申请大容量内存的方法及特点正文(篇1)在 Linux 系统中,有时我们需要申请大容量的内存空间以满足程序运行的需求。
本文将介绍几种在 Linux 系统中申请大容量内存的方法。
首先,我们可以使用 kmalloc 函数来申请内核空间内存。
kmalloc 函数的原型为:void *kmalloc(size_t size, int flags),其中 size 表示要分配的内存块大小,flags 表示分配标志,常用的有 (会引起睡眠) 和 (不引起睡眠,分配不到,立即返回)。
使用 kmalloc 函数申请的内存位于内核物理内存映射区域,物理上连续,与真实的物理地址只有一个固定偏移。
其次,如果我们需要在内核态使用用户态地址空间,可以采用以下几种方法:使用 brk 系统调用、setfs、getfs 以及 dommap 等。
这些方法可以在内核态和用户态之间映射物理内存,从而实现内核态访问用户态地址空间。
Linux 内存管理机制中,地址空间分为三个区域:DMA、normal 和highmem。
物理内存分配时,内核会根据不同的内存需求选择合适的区域进行分配。
而页(page)是 Linux 内存管理的基本单位,通常一页为 4KB。
在初始化时,内核为每个物理内存页建立一个 page 的管理结构。
针对大内存申请,Linux 内核也提供了一些优化方法。
例如,在申请大容量内存时,内核可以采用伙伴系统进行分配。
一般的,用户空间使用函数malloc在堆上分配内存空间,同样的,在内核空间同样有一套类似的函数来分配空间。
下面的知识会涉及页式管理的内存机制,如果不懂的要先复习一下,在S3C2440数据手册的MMU部分有介绍。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxx一、内核空间和用户空间有什么不同学c语言的时候应该学过,从用户空间看,每个进程都傻乎乎的以为自己有4G 的内存空间,其中位于高地址(3G-4G)的1G空间给内核用,另外的3G(0-3G)都是它一个人独占的。
所以用户空间很慷慨的把3G的空间分了好几个区域,如堆、栈、代码段等。
其中,malloc()分配的空间位于堆,而程序中的自动变量,如你在函数内定义的“int i”,它是放在栈上,同时。
用户空间的栈是可变栈,即随着数据的增多,对应函数的栈空间也会增多。
跟每个用户空间的进程不一样,内核只有1G的空间,同时,除了自己本身有进程运行外,内核还要允许用户空间进程调用系统调用进入内核空间去执行。
所以,内核对此相当吝啬,它规定在内核中的每个进程都只有4KB或8KB(32位下)的定长栈。
出于这样的原因,大的数据结构就不能在栈中分配,只能请求内核分配新的空间来存放数据,如函数kmalloc()。
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxx二、内存的基本单位是字节吗?在介绍分配内存空间的函数前,我们还要了解一下内存是怎么被划分的。
内核不仅知道用户空间中看到的1G内核空间是假的,它还知道实际的物理内存是多少(我的开发板是64M)。
所以,内核的其中一个任务就是,当这段虚假内存中的数据需要调用时,内核把这段虚拟内存与实际的物理内存对应上,运行完后又把两段内存的对应关系撤销掉给另外的虚拟内存用。
linuxfree命令详解本⽂介绍了linux free命令详解,分享给⼤家,具体如下:free 命令显⽰系统内存的使⽤情况,包括物理内存、交换内存(swap)和内核缓冲区内存。
如果加上 -h 选项,输出的结果会友好很多:有时我们需要持续的观察内存的状况,此时可以使⽤ -s 选项并指定间隔的秒数:$ free -h -s 3上⾯的命令每隔 3 秒输出⼀次内存的使⽤情况,直到你按下 ctrl + c。
(Ubuntu 16.04 中默认的 free 版本有 bug,使⽤ -s 选项时报错,所以这张图是在 CentOS 中截的。
)由于 free 命令本⾝⽐较简单,所以本⽂的重点会放在如何通过 free 命令了解系统当前的内存使⽤状况。
输出简介下⾯先解释⼀下输出的内容:Mem ⾏(第⼆⾏)是内存的使⽤情况。
Swap ⾏(第三⾏)是交换空间的使⽤情况。
total 列显⽰系统总的可⽤物理内存和交换空间⼤⼩。
used 列显⽰已经被使⽤的物理内存和交换空间。
free 列显⽰还有多少物理内存和交换空间可⽤使⽤。
shared 列显⽰被共享使⽤的物理内存⼤⼩。
buff/cache 列显⽰被 buffer 和 cache 使⽤的物理内存⼤⼩。
available 列显⽰还可以被应⽤程序使⽤的物理内存⼤⼩。
我想只有在理解了⼀些基本概念之后,上⾯的输出才能帮助我们了解系统的内存状况。
buff/cache先来提⼀个问题: buffer 和 cache 应该是两种类型的内存,但是 free 命令为什么会把它们放在⼀起呢?要回答这个问题需要我们做些准备⼯作。
让我们先来搞清楚 buffer 与 cache 的含义。
buffer 在操作系统中指 buffer cache,中⽂⼀般翻译为 "缓冲区"。
要理解缓冲区,必须明确另外两个概念:"扇区" 和 "块"。
扇区是设备的最⼩寻址单元,也叫 "硬扇区" 或 "设备块"。
linux核心函数Linux 内核是操作系统的核心部分,它提供了操作系统的核心功能,包括进程管理、内存管理、文件系统等。
Linux 内核的源代码中包含了大量的函数,用于实现各种操作系统的功能。
以下是一些Linux 内核中常见的核心函数,它们扮演着关键的角色:1.进程管理函数:–fork():创建一个新的进程。
–exec():在当前进程中执行一个新的程序。
–wait():等待子进程结束。
–exit():终止当前进程。
2.调度和任务管理函数:–schedule():进行进程调度。
–yield():主动让出CPU,将当前进程移动到就绪队列的末尾。
–wake_up_process():唤醒一个等待中的进程。
3.内存管理函数:–kmalloc():在内核中分配内存。
–kfree():释放内核中的内存。
–vmalloc():在虚拟地址空间中分配内存。
4.文件系统函数:–open():打开一个文件。
–read():从文件中读取数据。
–write():向文件中写入数据。
–close():关闭文件。
5.设备驱动函数:–register_chrdev():注册字符设备。
–unregister_chrdev():注销字符设备。
–request_irq():注册中断处理函数。
6.网络函数:–socket():创建套接字。
–bind():将套接字与地址绑定。
–listen():侦听传入连接请求。
–accept():接受传入的连接请求。
7.定时器和时钟函数:–timer_create():创建一个定时器。
–timer_settime():设置定时器的时间。
–gettimeofday():获取当前时间。
8.同步和互斥函数:–spin_lock():获取自旋锁。
–spin_unlock():释放自旋锁。
–mutex_lock():获取互斥锁。
–mutex_unlock():释放互斥锁。
这些函数仅仅是Linux 内核中众多函数的一小部分,Linux 内核的源代码非常庞大而复杂,包含了各种各样的功能和模块。
系统对进程的虚拟内存管理:mm{},vm_area_struct{}系统对物理内存管理:page{}每个进程都可用4G的虚拟地址空间 0~4G,每个进程的页表不同(page table : 内存映射。
Memory mapping)几种内存地址:物理地址(PA),虚拟地址:内核空间: 3~4G共1G, 内核虚拟地址(其中包括896M的内核逻辑地址)又划分为两部分:实际的物理地址直接映射到内核空间,当实际物理内存>896M时,此时要做映射,通过建立页表,只有前896M存在这种映射关系当实际物理内存<896M时,PA全部映射到内核空间,此时内核VA –3G=对应的物理PA,反之亦然。
此部分地址称为内核逻辑地址区域kernellogical address用户空间: 0~3G ,页表映射高端内存: >896M的内存空间以上4G虚拟地址空间分配见本子Figure-1。
重点掌握kernel logical address见课件涉及到的内核结构体:task-struct { mm_struct *mm ;}mm_struct *mm {pgd_t pgd;}vm_area_struct * mmap{ } /* emphasis Linux内核中对应进程内存区域的数据结构,一个进程有多个内存区域,所以有多个vma*/cat /proc/<pid>/map内存映射的两层含义:1. 把VA 映射到对应的 PA 上(对VA的操作即对PA的操作)2. 把一个文件的地址空间(文件打开后有缓存,即文件打开后存在于内存上,占用一定内存空间)映射到进程,让进程可以通过访问内存从而访问文件。
内存映射的基本单位都是VMA,如structfile_operations{int (*mmap) (struct file *, struct vm_area_struct *);}Linux内存管理中,4G的进程地址空间, 0-3G为用户空间,3-4G为内核空间,内核空间中小于896M的虚拟内存可以通过offset容易的映射到物理内存,大于896M的部分通过页表映射到物理内存,假如只有800M内存,会被内核空间完全映射,那用户空间的虚拟地址映射到物理内存哪里?是不是内核空间虽然能够完全映射到物理内存,但是因为不会全部使用物理内存,所以当用户空间需要内存映射时,会从物理内存中空闲的部分进行映射?如果是这样,对于物理内存而言,同时存在着内核空间的映射和当前进程的用户空间的映射Problems:文件打开后被调入内存,称之为缓存下载一个新的内核叶框与页区别页的状态page cache, buffer cachefile inodechar tr = malloc(0);把各个函数都用一下,把每个函数的返回值打印出来,看在哪个空间内。
kmalloc()和vmalloc()介绍kmalloc()用于申请较小的、连续的物理内存1. 以字节为单位进行分配,在<linux/slab.h>中2. void *kmalloc(size_t size, int flags) 分配的内存物理地址上连续,虚拟地址上自然连续3. gfp_mask标志:什么时候使用哪种标志?如下:———————————————————————————————-情形 相应标志———————————————————————————————-进程上下文,可以睡眠 GFP_KERNEL进程上下文,不可以睡眠 GFP_ATOMIC中断处理程序 GFP_ATOMIC软中断 GFP_ATOMICTasklet GFP_ATOMIC用于DMA的内存,可以睡眠 GFP_DMA | GFP_KERNEL用于DMA的内存,不可以睡眠 GFP_DMA | GFP_ATOMIC ———————————————————————————————-4. void kfree(const void *ptr)释放由kmalloc()分配出来的内存块vmalloc()用于申请较大的内存空间,虚拟内存是连续的1. 以字节为单位进行分配,在<linux/vmalloc.h>中2. void *vmalloc(unsigned long size) 分配的内存虚拟地址上连续,物理地址不连续3. 一般情况下,只有硬件设备才需要物理地址连续的内存,因为硬件设备往往存在于MMU之外,根本不了解虚拟地址;但为了性能上的考虑,内核中一般使用 kmalloc(),而只有在需要获得大块内存时才使用vmalloc(),例如当模块被动态加载到内核当中时,就把模块装载到由vmalloc()分配 的内存上。
4.void vfree(void *addr),这个函数可以睡眠,因此不能从中断上下文调用。
malloc(), vmalloc()和kmalloc()区别[*]kmalloc和vmalloc是分配的是内核的内存,malloc分配的是用户的内存[*]kmalloc保证分配的内存在物理上是连续的,vmalloc保证的是在虚拟地址空间上的连续,malloc不保证任何东西(这点是自己猜测的,不一定正确)[*]kmalloc能分配的大小有限,vmalloc和malloc能分配的大小相对较大[*]内存只有在要被DMA访问的时候才需要物理上连续[*]vmalloc比kmalloc要慢。
下面的设备文件,供上层应用程/dev/1、字符型驱动设备你是怎么创建设备文件的,就是序打开使用的文件?命令结合设备的主设备号和次设备号,可创建一个设备文件。
答:mknod自动创建设备文件的方还有UDEV/MDEV评:这只是其中一种方式,也叫手动创建设备文件。
包括创建和删除设备文件,可以动态管理设备文件,式,UDEV/MDEV是运行在用户态的程序,一创建了设备文件。
运行在用户态意味着系统要运行之后。
那么在系统启动期间还有devfs共有三种方式可以创建设备文件。
、写一个中断服务需要注意哪些?如果中断产生之后要做比较多的事情你是怎么做的?2的任务尽量放在后)答:中断处理例程应该尽量短,把能放在后半段(tasklet,等待队列等半段。
包括硬写一个中断服务程序要注意快进快出,在中断服务程序里面尽量快速采集信息,评:也就是中断上方式。
tasklet件信息,然后推出中断,要做其它事情可以使用工作队列或者半部和下半部。
第二:中断服务程序中不能有阻塞操作。
为什么?大家可以讨论。
第三:中断服务程序注意返回值,要用操作系统定义的宏做为返回值,而不是自己定义的之类的。
FAILOK,、自旋锁和信号量在互斥使用时需要注意哪些?在中断服务程序里面的互斥是使用自旋锁3 还是信号量?还是两者都能用?为什么?中断服务例程中的互斥使用使用信号量的进程可以睡眠。
答:使用自旋锁的进程不能睡眠,的是自旋锁,原因是在中断处理例程中,硬中断是关闭的,这样会丢失可能到来的中断。
、原子操作你怎么理解?为了实现一个互斥,自己定义一个变量作为标记来作为一个资源4 只有一个使用者行不行?自己定义一个变量怎么我没懂第二句是什么意思,答:原子操作指的是无法被打断的操作。
可能标记资源的使用情况?其他进程又看不见这个变量评:第二句话的意思是:定义一个变量,比如 int flag =0;if(flag == 0){flag = 1;操作临界区;flag = 0;这样可否?}呢?这两个函数在设计上要注insmod 一个驱动模块,会执行模块中的哪个函数?rmmod5、意哪些?遇到过卸载驱动出现异常没?是什么问题引起的?这两个函数在设计时要注意什么?卸载函数。
linux内存相关指标
在Linux系统中,有几个重要的内存相关指标可用于监控和管理系统内存的使
用情况。
以下是一些常见的Linux内存指标:
1. Total(总内存):这是系统中总共可用的内存量,包括物理内存和交换空
间。
2. Used(已使用内存):已经被分配给进程使用的内存量,包括正在使用的物
理内存和交换空间。
3. Free(空闲内存):尚未被分配给任何进程使用的内存量,包括未使用的物
理内存和未使用的交换空间。
4. Buffers(缓冲区):用于存储正在写入磁盘的数据的缓冲区所使用的内存
量。
5. Cached(缓存):用于存储经常访问的文件数据的缓存所使用的内存量。
6. Swap(交换空间):当内存不足时,用于将不活动的内存页交换到磁盘上的
一块特殊的空间。
Swap被视为延伸的物理内存。
这些指标可以通过命令`free`来查看,例如:
```
$ free -h
total used free shared buff/cache available
Mem: 7.7G 3.5G 1.2G 239M 3.0G 3.0G
Swap: 2.0G 392M 1.6G
```
除了`free`命令之外,还可以使用`top`、`htop`、`procfs`等工具来查看和监
控系统内存使用情况。
这些内存指标对于诊断性能问题、优化内存使用以及了解系统健康状况都非常
有用。
在运行Linux服务器或进行性能调优时,了解和监控内存指标可以帮助您更
好地管理系统资源。
kmalloc用法
kmalloc函数在Linux内核中的应用非常广泛,常用来申请内核空间分配数据,例如:Linux内存中的内存管理。
它还可以用来给一个新的内核线程、一个新的内核实体(例如:内存页)申请内存空间,因此也可以用来分配新的内核结构,例如:task_struct(任务结构)和文件系统数据结构(例如:inode)。
kmalloc函数有2个可选参数,第一个是要申请的内存大小,可以使用宏定义来表示,例如:GFP_KERNEL和GFP_ATOMIC,分别表示申请内核空间和申请原子空间。
第二个参数是标志,可以用来指定内存分配时的详细设置,例如:是否允许占用中断时间,是否允许内存休眠,是否允许分配失败,等等。
因此,kmalloc函数在操作系统内核的实现中占有非常重要的地位。
Linux内存管理之kmalloc 与__get_free_page()分类:P.OS-操作系统& 内核L.Linux 开发2010-04-05 00:55 1153人阅读评论(1) 收藏举报在设备驱动程序中动态开辟内存,不是用malloc,而是kmalloc,或者用get_free_pages 直接申请页。
释放内存用的是kfree,或free_pages.对于提供了MMU(存储管理器,辅助操作系统进行内存管理,提供虚实地址转换等硬件支持)的处理器而言,Linux提供了复杂的存储管理系统,使得进程所能访问的内存达到4GB。
进程的4GB内存空间被人为的分为两个部分--用户空间与内核空间。
用户空间地址分布从0到3GB(PAGE_OFFSET,在0x86中它等于0xC0000000),3GB到4GB为内核空间。
内核空间中,从3G到vmalloc_start这段地址是物理内存映射区域(该区域中包含了内核镜像、物理页框表mem_map等等),比如我们使用的VMware虚拟系统内存是160M,那么3G~3G+160M这片内存就应该映射物理内存。
在物理内存映射区之后,就是vmalloc 区域。
对于160M的系统而言,vmalloc_start位置应在3G+160M附近(在物理内存映射区与vmalloc_start期间还存在一个8M的gap 来防止跃界),vmalloc_end的位置接近4G(最后位置系统会保留一片128k大小的区域用于专用页面映射)kmalloc和get_free_page申请的内存位于物理内存映射区域,而且在物理上也是连续的,它们与真实的物理地址只有一个固定的偏移,因此存在较简单的转换关系,virt_to_phys()可以实现内核虚拟地址转化为物理地址:#define __pa(x) ((unsigned long)(x)-PAGE_OFFSET)extern inline unsigned long virt_to_phys(volatile void * address){return __pa(address);}上面转换过程是将虚拟地址减去3G(PAGE_OFFSET=0XC000000)。
与之对应的函数为phys_to_virt(),将内核物理地址转化为虚拟地址:#define __va(x) ((void *)((unsigned long)(x)+PAGE_OFFSET))extern inline void * phys_to_virt(unsigned long address){return __va(address);}virt_to_phys()和phys_to_virt()都定义在include/asm-i386/io.h中。
1、kmalloc() 分配连续的物理地址,用于小内存分配。
2、__get_free_page() 分配连续的物理地址,用于整页分配。
至于为什么说以上函数分配的是连续的物理地址和返回的到底是物理地址还是虚拟地址,下面的记录会做出解释。
kmalloc() 函数本身是基于slab 实现的。
slab 是为分配小内存提供的一种高效机制。
但slab 这种分配机制又不是独立的,它本身也是在页分配器的基础上来划分更细粒度的内存供调用者使用。
也就是说系统先用页分配器分配以页为最小单位的连续物理地址,然后kmalloc() 再在这上面根据调用者的需要进行切分。
关于以上论述,我们可以查看kmalloc() 的实现,kmalloc()函数的实现是在__do_kmalloc() 中,可以看到在__do_kmalloc()代码里最终调用了__cache_alloc() 来分配一个slab,其实kmem_cache_alloc() 等函数的实现也是调用了这个函数来分配新的slab。
我们按照__cache_alloc()函数的调用路径一直跟踪下去会发现在cache_grow() 函数中使用了kmem_getpages()函数来分配一个物理页面,kmem_getpages() 函数中调用的alloc_pages_node() 最终是使用__alloc_pages() 来返回一个struct page 结构,而这个结构正是系统用来描述物理页面的。
这样也就证实了上面所说的,slab 是在物理页面基础上实现的。
kmalloc() 分配的是物理地址。
__get_free_page() 是页面分配器提供给调用者的最底层的内存分配函数。
它分配连续的物理内存。
__get_free_page() 函数本身是基于buddy 实现的。
在使用buddy 实现的物理内存管理中最小分配粒度是以页为单位的。
关于以上论述,我们可以查看__get_free_page()的实现,可以看到__get_free_page()函数只是一个非常简单的封状,它的整个函数实现就是无条件的调用__alloc_pages() 函数来分配物理内存,上面记录kmalloc()实现时也提到过是在调用__alloc_pages() 函数来分配物理页面的前提下进行的slab 管理。
那么这个函数是如何分配到物理页面又是在什么区域中进行分配的?回答这个问题只能看下相关的实现。
可以看到在__alloc_pages() 函数中,多次尝试调用get_page_from_freelist() 函数从zonelist 中取得相关zone,并从其中返回一个可用的struct page 页面(这里的有些调用分支是因为标志不同)。
至此,可以知道一个物理页面的分配是从zonelist(一个zone 的结构数组)中的zone 返回的。
那么zonelist/zone 是如何与物理页面关联,又是如何初始化的呢?继续来看free_area_init_nodes() 函数,此函数在系统初始化时由zone_sizes_init() 函数间接调用的,zone_sizes_init()函数填充了三个区域:ZONE_DMA,ZONE_NORMAL,ZONE_HIGHMEM。
并把他们作为参数调用free_area_init_nodes(),在这个函数中会分配一个pglist_data 结构,此结构中包含了zonelist/zone结构和一个struct page 的物理页结构,在函数最后用此结构作为参数调用了free_area_init_node() 函数,在这个函数中首先使用calculate_node_totalpages() 函数标记pglist_data 相关区域,然后调用alloc_node_mem_map() 函数初始化pglist_data结构中的struct page 物理页。
最后使用free_area_init_core()函数关联pglist_data 与zonelist。
现在通以上分析已经明确了__get_free_page() 函数分配物理内存的流程。
但这里又引出了几个新问题,那就是此函数分配的物理页面是如何映射的?映射到了什么位置?到这里不得不去看下与VMM 相关的引导代码。
在看VMM 相关的引导代码前,先来看一下virt_to_phys() 与phys_to_virt 这两个函数。
顾名思义,即是虚拟地址到物理地址和物理地址到虚拟地址的转换。
函数实现十分简单,前者调用了__pa( address ) 转换虚拟地址到物理地址,后者调用__va(addrress ) 将物理地址转换为虚拟地址。
再看下__pa __va 这两个宏到底做了什么。
#define __pa(x) ((unsigned long)(x)-PAGE_OFFSET)#define __va(x) ((void *)((unsigned long)(x)+PAGE_OFFSET))通过上面可以看到仅仅是把地址加上或减去PAGE_OFFSET,而PAGE_OFFSET 在x86 下定义为0xC0000000。
这里又引出了疑问,在linux 下写过driver 的人都知道,在使用kmalloc() 与__get_free_page() 分配完物理地址后,如果想得到正确的物理地址需要使用virt_to_phys() 进行转换。
那么为什么要有这一步呢?我们不分配的不就是物理地址么?怎么分配完成还需要转换?如果返回的是虚拟地址,那么根据如上对virt_to_phys() 的分析,为什么仅仅对PAGE_OFFSET 操作就能实现地址转换呢?虚拟地址与物理地址之间的转换不需要查页表么?代着以上诸多疑问来看VMM 相关的引导代码。
直接从start_kernel() 内核引导部分来查找VMM 相关内容。
可以看到第一个应该关注的函数是setup_arch(),在这个函数当中使用paging_init() 函数来初始化和映射硬件页表(在初始化前已有8M内存被映射,在这里不做记录),而paging_init() 则是调用的pagetable_init() 来完成内核物理地址的映射以及相关内存的初始化。
在pagetable_init() 函数中,首先是一些PAE/PSE/PGE 相关判断与设置,然后使用kernel_physical_mapping_init() 函数来实现内核物理内存的映射。
在这个函数中可以很清楚的看到,pgd_idx 是以PAGE_OFFSET 为启始地址进行映射的,也就是说循环初始化所有物理地址是以PAGE_OFFSET 为起点的。
继续观察我们可以看到在PMD 被初始化后,所有地址计算均是以PAGE_OFFSET 作为标记来递增的。
分析到这里已经很明显的可以看出,物理地址被映射到以PAGE_OFFSET 开始的虚拟地址空间。
这样以上所有疑问就都有了答案。
kmalloc() 与__get_free_page() 所分配的物理页面被映射到了PAGE_OFFSET 开始的虚拟地址,也就是说实际物理地址与虚拟地址有一组一一对应的关系,正是因为有了这种映射关系,对内核以PAGE_OFFSET 启始的虚拟地址的分配也就是对物理地址的分配(当然这有一定的范围,应该在PAGE_OFFSET与VMALLOC_START 之间,后者为vmalloc() 函数分配内存的启始地址)。
这也就解释了为什么virt_to_phys() 与phys_to_virt() 函数的实现仅仅是加/减PAGE_OFFSET 即可在虚拟地址与物理地址之间转换,正是因为了有了这种映射,且固定不变,所以才不用去查页表进行转换。
这也同样回答了开始的问题,即kmalloc() / __get_free_page() 分配的是物理地址,而返回的则是虚拟地址。
正是因为有了这种映射关系,所以需要将它们的返回地址减去PAGE_OFFSET 才可以得到真正的物理地址。