ELF文件中segment的offset和paddr
大家如果有看过CSAPP的第七章,应该可以记得其中有说过一个段的地址和偏移之间的关系
注意这里说的段是可执行ELF的段(segment),而不是目标文件的段(section)
其中有讲到,对于地址addr和偏移offset,以及对齐align,有这样一个关系,即addr % algin == offset % align
不知道大家当初刚看到这一段的时候是不是也感觉非常迷惑,到底是为什么呢?
首先明确,这里的align指的是页对齐,而不是指定的段对齐,也就是说,align的值一般就是0x100
,即一个页的大小
然后在继续往下说之前,我们要先明白,offset在文件中其实是可以或多或少的体现出这个对应的段的大小的,所以直觉上来讲,不考虑我们的程序是从0x8048000
开始加载的情况下,其实offset应该是和段地址相同的,因为就相当于直接将文件映射到地址空间中
但是再注意,我们不同的段是要对应不同的访问权限等若干由操作系统控制的属性的,所以不同的段是不能被放到同一个页中的。(如果可以的话,病毒就可以通过某种方式通过在数据段溢出来修改代码了)
但是随之而来的问题就是,尽管我们会将相同权限的段进行合并,也仍然会出现内存碎片导致内存利用率不高。因为段的大小不可能总是页大小的整数倍。
那么对于这种问题,要怎么解决呢?从根本上想,我们的问题是来源于多个段不能存在于同一个页中。同时要注意,我们这里说的是虚拟地址空间
这里引用程序员的自我修养中的一张图来辅助理解
这张是物理内存和虚拟地址一一对应的,即满足了虚拟地址中的页中不包含不同的段
而这里就是产生大量内存碎片的地方,即段与段的接壤处,每一个段的结尾,因为我们把这里的每一个页都实际的映射到了物理内存中
而实际上,我们不必一一对应的去映射进程空间和物理内存中的页,我们可以将接壤处的页映射两份,一份给前一个段,一份给后一个段。
这样的话,我们就可以把段叠在一起,而不用去考虑跨段的页怎么处理
看这副图
其实相当与我们的文件直接映射到了物理内存中,以页为单位,但是这次不需要再考虑页对齐。但是进程空间还是需要考虑页对齐,而我们优化的点就在于不再让进程空间和物理内存一一映射。所以我们仍然满足了进程空间中段是独立的,并且在物理内存中,我们复用了段接壤处的页。
通过这种机制,我们弥补了由虚拟内存带来的页机制导致的内存碎片,可以大大提高内存利用率
那么回到开始的问题,为什么offset和addr有这样的关系呢
这里注意addr是我们进程空间中段加载的地址,刚才我们说了,对于段接壤处的页我们需要映射两次。看上面的图,在seg0和seg1之间就会出现一个空缺,这里就是相差了一个页大小
但是不要误会,这里是虚拟地址空间,不会影响到物理内存的
那么本来我们的addr是应该和offset相等的,如果是不考虑页的影响(即想象文件直接映射物理内存,而我们直接访问)
但是这里会出现由两次映射页导致的0x1000
的偏移,所以我们在不考虑这个偏移的情况下,offset和addr就是相同的,即addr % algin == offset % align
总结一下,就是不考虑页的情况下,addr和offset本身应该是相等的(注意不考虑装载的首地址),我们通过一些技巧优化了段的存储结构,使我们的文件可以近似的直接映射到内存中,但是进程空间需要在段接壤处多加上一个页。于是在mod align
的意义下,页大小就被消掉了,我们就得到了这个关系式。
文章评论