linux 内核地址空间
一、内核镜像
在3GB~(3GB+896MB)这段直接/线性映射区域,包含了内核初始化页表swapper_pg_dir,内核镜像等。内核也是由一个elf文件(比如vmlinux)加载启动的,加载后也有text段,data段,bss段等。
二、内存分配
kmalloc和vmalloc
在虚拟内存空间的normal memory区域,内核使用kmalloc()来分配内存,kmalloc()返回的也是虚拟地址,但是分到的内存在物理地址上是连续的(因为是直接映射,在虚拟地址上自然也是连续的)。
在VMALLOC_START和VMALLOC_END之间的区域为vmolloc area,它和normal memory中有8MB的间隔。这部分间隔不作任何地址映射,相当于一个空洞,主要用做安全保护,防止不正确的越界内存访问,因为此处没有进行任何形式的映射,如果进入到空洞地带,将会触发处理器产生一个异常。
在vmolloc area中使用vmalloc()分配内存,具体的分配过程是:
- 根据要分配的内存大小,调用get_vm_area( ),获取vmlist_lock锁以扫描vmlist链表,在vmolloc area中找到一块大小满足要求的空闲内存;
- 调用__vmalloc_area_pages() --> alloc_page(),通过内核的buddy系统获得相应大小的物理页面,关于物理页面的分配请参考这篇文章;
- vmalloc area中的地址映射不再是简单的3GB偏移,因此需要调用map_vm_area(),建立虚拟地址和物理页面的映射关系,并添加到内核页表中。
同kmalloc()相比,vmalloc()分配的内存只能保证在虚拟地址上连续,不能保证在物理地址上连续。在物理地址上连续有什么好处呢?
- 可以更好的根据空间局部性原理利用cache,增加数据访问的速度。
- 由于kmalloc()基于的是直接映射,其虚拟地址和物理地址之间是一个固定的偏移,因此可以利用既有的内核页表,而不需要为新的地址增加新的page table entries,因此其分配速度也比vmalloc()更快。
- 因为物理地址不连续,通过vmalloc()获得的每个page需要单独映射,而TLB资源很有限,因此这将比直接映射造成更严重的TLB thrashing问题。
有连续的物理内存和简单的直接映射关系谁不想要啊,可是如果系统运行久了,内存碎片就多起来,想要找到一块物理上连续的大块内存就越来越困难,这时就只能靠vmalloc()出马了。因为有一些应用场景是需要物理上连续的内存的(比如硬件设备),那是不是如果是没有这个要求的,就用vmalloc()就好了,把宝贵的normal area的地址资源留给那些真正需要的同志呢?
这种做法也有问题,如果大家都谦让着不用kmalloc(),那可能normal area就不能被充分利用起来。公平和效率始终是需要兼顾和平衡的,在内核的实际应用中,如果不是分配大块内存,还是推荐使用更高效的kmalloc()。
vmalloc区域
还记得上篇文章举的那个房间和钥匙的例子么,用户空间的进程通过malloc()分配内存时,获得的只是虚拟地址的使用权,要等到真正往这块内存写数据了,才会获得对应的物理页面,而且是用多少给多少,而不是要多少给多少。内核空间自己的vmalloc()就不一样了,申请的物理内存立刻满足,房间钥匙一起给,在上级单位干活就是不一样啊。
分配到的每个内存区域(以下称vmalloc区域)都用一个vm_struct结构体表示,对这个名字有没有一点眼熟?跟进程地址空间里的vm_area_struct很像是吧,事实上,它们不光是名字相似,组织方式也有类似的地方,vm_struct也是通过一个叫vmlist的单项链表串起来的。
同样都是为了描述一段内存区域,包括这段区域的地址,大小,属性等,那这里可以直接用vm_area_struct吗?可以倒是可以,但是vmalloc区域相对要简单一些,用vm_area_struct来表达就显得复杂了,所以单独有了一个vm_struct,来看下这个数据结构的定义:
二、GFP 掩码
- GFP_ATOMIC:用来从中断处理和进程上下文之外的其他代码中分配内存。从不睡眠。可以用在具有原子性的地方,分配函数。
- GFP_KERNEL:内核内存的正常分配。可能睡眠。在具有原子性的地方,不能用。
- GFP_USER:用来为用户空间页来分配内存; 它可能睡眠。在具有原子性的地方,不能用。
- GFP_NOIO、GFP_NOFS:这个标志功能如同 GFP_KERNEL, 但是它们增加限制到内核能做的来满足请求. 一个 GFP_NOFS 分配不允许进行任何文件系统调用, 而 GFP_NOIO 根本不允许任何I/O 初始化. 它们主要地用在文件系统和虚拟内存代码, 那里允许一个分配睡眠,但是递归的文件系统调用会是一个坏注意.
一般用得比较多的是GFP_KERNEL,GFP_ATOMIC。
三、V区虚拟地址与物理地址的关系
动态内存映射区:动态内存映射区的起始地址要根据物理内存的大小决定,
如果物理内存大于896m,起始地址就是0xC0000000+896,
如果小于896M,起始地址就是0xC0000000 + 物理内存的大小
虚拟上连续,物理上不一定连续!
低于896M的物理内存与虚拟内存在内核空间里是一一对应的,这句话是有问题的,需要分情况说。
如果总得物理内存低于896M,假设为A,对于内核中的一个地址,如果小于A那么这个地址与物理地址只差一个偏移量,如果大于A,那么肯定位于VMALLOC区。如果一个物理地址为X的页被内存使用,即使X小于896,它对应的虚拟地址也有可能不是3G+896。