近来看了本书,忽然对于地址空间有些纳闷。在深入理解linux内核中把地址分为三类:逻辑地址(汇编语言中操作数地址或指令的地址,对于80x86的cup,逻辑地址是段+段内偏斜地址)、线性地址(也叫虚拟地址)和化学地址。但在StottMaxwell的《LinuxCoreKernelCommentrary》中确是这样分的:逻辑地址(也叫虚拟地址)、线性地址和化学地址。根据386CPU总设计师JohnCrowford的解释,虚拟地址是保护模式下段和段内偏斜量组成的地址,而逻辑地址就是代码段内偏斜量,或称进程的逻辑地址。虽然对于linux来说,这三种说法都没错,因为linux下并不主张将程序分段,而是主张分页,所以就算是在80x86的体系结构下,段的基地址也是0。为此逻辑地址、线性地址、虚拟地址在linux中虽然是相同的。所以对于linux下的elf可执行文件来说linux虚拟主机,代码段的起始地址0x08048000既是逻辑地址,也是线性地址也是虚拟地址。
1x86的数学地址空间布局:
化学地址空间的底部以下一段空间,被PCI设备的I/O显存映射抢占,它们的大小和布局由PCI规范所决定。640K~1M这段地址空间被BIOS和VGA适配器所抢占。
Linux系统在初始化时,会依照实际的化学显存的大小,为每位化学页面创建一个page对象,所有的page对象构成一个mem_map链表。
进一步,针对不同的用途,Linux内核将所有的数学页面界定到3类显存管理区中,如图,分别为ZONE_DMAlinux用户空间初始化,ZONE_NORMAL,ZONE_HIGHMEM。
ZONE_DMA的范围是0~16M,该区域的数学页面专门供I/O设备的DMA使用。之所以须要单独管理DMA的数学页面,是由于DMA使用化学地址访问显存,不经过MMU,而且须要连续的缓冲区,所以为了才能提供数学上连续的缓冲区,必须从数学地址空间专门界定一段区域用于DMA。
ZONE_NORMAL的范围是16M~896M,该区域的数学页面是内核能否直接使用的。
ZONE_HIGHMEM的范围是896M~结束,该区域即为高档显存,内核不能直接使用。
2linux虚拟地址内核空间分布
在kernelimage下边有16M的内核空间用于DMA操作。坐落内核空间高档的128M地址主要由3部份组成,分别为vmallocarea,持久化内核映射区,临时内核映射区。
因为ZONE_NORMAL和内核线性空间存在直接映射关系,所以内核会将频繁使用的数据如kernel代码、GDT、IDT、PGD、mem_map链表等置于ZONE_NORMAL里。而将用户数据、页表(PT)等不常用数据放到ZONE_HIGHMEM里,只在要访问那些数据时才完善映射关系(kmap())。例如,当内核要访问I/O设备储存空间时,就使用ioremap()将坐落化学地址低端的mmio区显存映射到内核空间的vmallocarea中,在使用完以后便断掉映射关系。
3linux虚拟地址用户空间分布
用户进程的代码区通常从虚拟地址空间的0x08048000开始,这是为了易于检测空表针。代码区之上便是数据区,未初始化数据区,堆区,栈区,以及参数、全局环境变量。
4linux虚拟地址与化学地址映射的关系
Linux将4G的线性地址空间分为2部份,0~3G为userspace,3G~4G为kernelspace。
因为开启了分页机制,内核想要访问数学地址空间的话,必须先构建映射关系,之后通过虚拟地址来访问。为了才能访问所有的数学地址空间,就要将全部数学地址空间映射到1G的内核线性空间中,这似乎不可能。于是,内核将0~896M的数学地址空间一对一映射到自己的线性地址空间中,这样它便可以随时访问ZONE_DMA和ZONE_NORMAL里的化学页面;此时内核剩下的128M线性地址空间不足以完全映射所有的ZONE_HIGHMEM,Linux采取了动态映射的方式,即按需的将ZONE_HIGHMEM里的化学页面映射到kernelspace的最后128M线性地址空间里,使用完以后释放映射关系,以供其它化学页面映射。即使这样存在效率的问题,而且内核虽然可以正常的访问所有的数学地址空间了。
5linux中可执行程序与虚拟地址空间的映射关系
虚拟显存区域(VMA,VirtualMemoryArea)是Linux中进程虚拟地址空间中的一个段,在Windows上面叫虚拟段。当操作系统创建线程后suse linux 下载,会在进程相应的数据结构中设置一个.text段的VMA,它在虚拟空间中的地址为0x08048000~0x08049000,它对应ELF文件中的偏斜为0的.text。可以查看操作系统为运行的进程维护的信息:
从前面的图可以看出,虚拟空间地址为0x08048000~0x08049000的VMA映射为elf文件中的一个段(segment),而且是按整页进行映射的。
因为linux下的ELF可执行文件会有好多个段(section),所以假如把每位section都映射为一个VMA,这么没有一个页大小的段(section)也会被映射为一个页的VMA,这样就浪费了数学空间,因为不足会用0补充。故elf有一个装载的段(segment)linux用户空间初始化,与上面的段(section)不同,后面的段(section)主要用于链接,而段(segment)主要用于装载进显存。
可以看出段(segment)02包含了好多的段(section),那链接器如何将段(section)合并到一个段(segment)中的呢?可以通过段(section)的权限来合并,如以代码段为代表的权限为可读可执行权限;以数据段和BSS段为代表的权限为可读可写的段;以只读数据为代表的权限为只读权限。
ELF与Linux进程虚拟空间映射关系如右图所示:
虽然把多个段(section)合并到几个段(segment),每位段(segment)还是又很能形成较大的页内碎片,如何解决这个问题呢?Unix巧妙的通过各个段(segment)接壤部份共享一个数学页来解决这个问题。
说明:该文原网址为:。