技术的乐趣在于分享,欢迎多多交流,多多沟通。
全部博文(877)
分类: LINUX
2014-06-19 16:34:37
最近看了本书,突然对于地址空间有些疑惑。在深入理解linux内核中把地址分为三类:逻辑地址(汇编语言中操作数地址或指令的地址,对于80x86的cup,逻辑地址是段+段内偏移地址)、线性地址(也叫虚拟地址)和物理地址。但在Stott Maxwell的《Linux Core Kernel Commentrary》中确是这样分的:逻辑地址(也叫虚拟地址)、线性地址和物理地址。按照386 CPU总设计师 John Crowford的解释,虚拟地址是保护模式下段和段内偏移量组成的地址,而逻辑地址就是代码段内偏移量,或称进程的逻辑地址。其实对于linux来说,这三种说法都没错,由于linux下并不主张将程序分段,而是主张分页,所以即使是在80x86的体系结构下,段的基地址也是0。因此逻辑地址、线性地址、虚拟地址在linux中其实是相同的。所以对于linux下的elf可执行文件来说,代码段的起始地址0x08048000既是逻辑地址,也是线性地址也是虚拟地址。
1 x86的物理地址空间布局:
物理地址空间的顶部以下一段空间,被PCI设备的I/O内存映射占据,它们的大小和布局由PCI规范所决定。640K~1M这段地址空间被BIOS和VGA适配器所占据。
Linux系统在初始化时,会根据实际的物理内存的大小,为每个物理页面创建一个page对象,所有的page对象构成一个mem_map数组。
进一步,针对不同的用途,Linux内核将所有的物理页面划分到3类内存管理区中,如图,分别为ZONE_DMA,ZONE_NORMAL,ZONE_HIGHMEM。
ZONE_DMA的范围是0~16M,该区域的物理页面专门供I/O设备的DMA使用。之所以需要单独管理DMA的物理页面,是因为DMA使用物理地址访问内存,不经过MMU,并且需要连续的缓冲区,所以为了能够提供物理上连续的缓冲区,必须从物理地址空间专门划分一段区域用于DMA。
ZONE_NORMAL的范围是16M~896M,该区域的物理页面是内核能够直接使用的。
ZONE_HIGHMEM的范围是896M~结束,该区域即为高端内存,内核不能直接使用。
2 linux虚拟地址内核空间分布
在kernel image下面有16M的内核空间用于DMA操作。位于内核空间高端的128M地址主要由3部分组成,分别为vmalloc area,持久化内核映射区,临时内核映射区。
由于ZONE_NORMAL和内核线性空间存在直接映射关系,所以内核会将频繁使用的数据如kernel代码、GDT、IDT、PGD、mem_map数组等放在ZONE_NORMAL里。而将用户数据、页表(PT)等不常用数据放在ZONE_ HIGHMEM里,只在要访问这些数据时才建立映射关系(kmap())。比如,当内核要访问I/O设备存储空间时,就使用ioremap()将位于物理地址高端的mmio区内存映射到内核空间的vmalloc area中,在使用完之后便断开映射关系。
3 linux虚拟地址用户空间分布
用户进程的代码区一般从虚拟地址空间的0x08048000开始,这是为了便于检查空指针。代码区之上便是数据区,未初始化数据区,堆区,栈区,以及参数、全局环境变量。
4 linux虚拟地址与物理地址映射的关系
Linux将4G的线性地址空间分为2部分,0~3G为user space,3G~4G为kernel space。
由于开启了分页机制,内核想要访问物理地址空间的话,必须先建立映射关系,然后通过虚拟地址来访问。为了能够访问所有的物理地址空间,就要将全部物理地址空间映射到1G的内核线性空间中,这显然不可能。于是,内核将0~896M的物理地址空间一对一映射到自己的线性地址空间中,这样它便可以随时访问ZONE_DMA和ZONE_NORMAL里的物理页面;此时内核剩下的128M线性地址空间不足以完全映射所有的ZONE_HIGHMEM,Linux采取了动态映射的方法,即按需的将ZONE_HIGHMEM里的物理页面映射到kernel space的最后128M线性地址空间里,使用完之后释放映射关系,以供其它物理页面映射。虽然这样存在效率的问题,但是内核毕竟可以正常的访问所有的物理地址空间了。
5 linux中可执行程序与虚拟地址空间的映射关系
虚拟内存区域(VMA,Virtual Memory Area)是Linux中进程虚拟地址空间中的一个段,在Windows里面叫虚拟段。当操作系统创建线程后,会在进程相应的数据结构中设置一个.text段的VMA,它在虚拟空间中的地址为0x08048000~0x08049000,它对应ELF文件中的偏移为0的.text。可以查看操作系统为运行的进程维护的信息:
从上面的图可以看出,虚拟空间地址为0x08048000~0x08049000的VMA映射为elf文件中的一个段(segment),并且是按整页进行映射的。
由于linux下的ELF可执行文件会有很多个段(section),所以如果把每个section都映射为一个VMA,那么没有一个页大小的段(section)也会被映射为一个页的VMA,这样就浪费了物理空间,由于不足会用0补充。故elf有一个装载的段(segment),与前面的段(section)不同,前面的段(section)主要用于链接,而段(segment)主要用于装载进内存。
可以看出段(segment)02包含了很多的段(section),那链接器怎样将段(section)合并到一个段(segment)中的呢?可以通过段(section)的权限来合并,如以代码段为代表的权限为可读可执行权限;以数据段和BSS段为代表的权限为可读可写的段;以只读数据为代表的权限为只读权限。
ELF与Linux进程虚拟空间映射关系如下图所示:
即使把多个段(section)合并到几个段(segment),每个段(segment)还是又很能产生较大的页内碎片,怎样解决这个问题呢?Unix巧妙的通过各个段(segment)接壤部分共享一个物理页来解决这个问题。
参考:http://www.cnblogs.com/zszmhd/archive/2012/08/29/2661461.html、深入理解linux内核、Linux Core Kernel Commentrary、程序员的自我修养。