全部博文(44)
分类:
2010-03-08 13:28:04
图2. 内核image整体布局
图2显示了内核image的布局,其中深色部分位于内核的虚拟地址空间3G~4G,共有text、data、note三个segment,其中note segment又是包含在text segment中。每个segment包含多个section,后面我们会讲到这些section是如何生成的。在这之前,需要了解链接脚本用到的两个地址:虚拟地址(VMA)和加载地址(LMA)。这里虚拟地址和我们平常说的虚拟地址是一样的,即section[*]在目标文件加载后所在的虚拟地址。例如在一个可执行的ELF文件中,.text section的VMA是0x08048310,即.text section的基地址位于虚拟地址空间的0x08048310处。加载地址指section被加载到内存中的地址,对于应用程序来说它通常和VMA相同,但对于内核来说,LMA是指section被加载到的物理地址。例如内核.text的VMA是0xc1001000,则LMA是0x01001000。很明显,这就是我们所熟知的内核虚拟地址 = 物理地址 + 0xC0000000(3G)的identify mapping关系。
[*]前面提到目标文件的加载是若干segment被加载到内存中的过程,这和section的加载并不冲突。实际上,当我们不指定segment的LMA和VMA时,这个两个值取segment中第一个section的LMA、VMA。加载segment也就是将其包含的各个section加载到内存中的过程。
好了,下面我们来看看内核链接脚本是怎么干的。
除去一些文件包含和宏定义,内核链接脚本以下面内容开始:
OUTPUT_FORMAT("elf32-i386", "elf32-i386", "elf32-i386")
OUTPUT_ARCH(i386)
ENTRY(phys_startup_32)
jiffies = jiffies_64;
PHDRS {
text PT_LOAD FLAGS(5); /* R_E */
data PT_LOAD FLAGS(7); /* RWE */
note PT_NOTE FLAGS(0); /* ___ */
}
OUTPUT_FORMAT("elf32-i386", "elf32-i386", "elf32-i386") OUTPUT_ARCH(i386) ENTRY(phys_startup_32) jiffies = jiffies_64;
PHDRS { text PT_LOAD FLAGS(5); /* R_E */ data PT_LOAD FLAGS(7); /* RWE */ note PT_NOTE FLAGS(0); /* ___ */ } |
OUTPUT_FORMAT和OUTPUT都是链接脚本的关键字,它们指定了目标文件的格式和所运行平台的架构,这些公式化的东西我们不关心它,具体内容详见参考文献1。ENTRY指定了整个目标文件的入口点(或入口函数),这里phys_startup_32是个地址,从名字我们就可以看出它是startup_32()函数的物理地址,在后面会看到该地址是如何计算得到的。jiffies = jiffes_64的魔术与本文无关,感兴趣的朋友可以参见ULK3的6.2.1.2节。
下面进入正题。PHDRS关键字描述了3个segment:text、data和note,它们分别具有PT_LOAD和PT_NOTE类型,并指定了每个segment的属性。PT_LOAD类型表示该segment是从文件加载入内存的,在这个上下文中文件应该指最后生成的内核image。FLAG关键字指定segment的属性,如注释所示,text segment为可读可执行、data segment为可读可写可执行,note段留到后面再说。至此,PHDRS定义了内核image的大体框架,它包含两个最主要的segment —— text和data,并确定了它们的属性,后面的代码就是向两个segment填充section了。
链接脚本知识:
PHDRS关键字的完整格式如下:
PHDRS{ name type [ FILEHDR ] [ PHDRS ] [ AT ( address ) ] [ FLAGS ( flags ) ] ;}其中name表示segment的名字,它位于单独的名字空间,不会和后面的section name冲突。type即segment的类型,如上的PT_LOAD,详细列表参见参考文献1。FILEHDR和PHDRS指定是否要包含ELF文件头和ELF程序头。AT指定segment的加载地址,FLAGS指定segment的属性。