先要讲讲这个问题是怎么来的。(咱们在分析一个技术的时候,先要考虑它是想解决什么问题,或者学习新知识的时候,要清楚这个知识的目的是什么)。
我在编译内核的时候,发现arch/arm/kernel目录下有一个这样的文 件:vmlinux.lds.S。第一眼看上去,想想是不是汇编文件呢?打开一看,好像不是。那它是干嘛的?而且前面已经说过,make V=1的时候,发现这个文件的用处在ld命令中,即ld -T vmlinux.lds.S,好像是链接命令用的,如下所示
如arm-linux-ld -EL -p --no-undefined -X --build-id -o vmlinux -T arch/arm/kernel/vmlinux.lds。man ld,得到-T的意思是:为ld指定一个Linker script,意思是ld根据这个文件的内容来生成最终的二进制。
也许上面这个问题,你从没关注过,但是在研究内核代码的时候,常常有地方说“ __init宏会在最后的模块中生成一个特定的section,然后kernel加载的时候,寻找这个section中的函数”,说白了,上面这句话就是 说最后生成的模块中,有一个特定的section,这又是什么东西?
- LS描述输入文件(也就是gcc -c命令产生的.o文件即object文件)中的section最终如何对应到一个输出文件。这个其实好理解,例如一个elf由三个.o文件构成,每 个.o文件都有text/data/bss段,但最终的那一个elf就会将三个输入的.o文件的段合并到一起。
- ld的功能是将input文件组装成一个output文件。这些文件内部的都有特殊 的组织结构,这种结构被叫做object file format。每一个文件叫做object file(这可能就是.o文件的来历吧。哈哈),输出文件也叫可执行文件(an executable),但是对于ld来说,它也是一种object文件。那么Object文件有什么特殊的地方呢?恩,它内部组织是按照 section(段、或者节,以后不再区分二者)来组织的。一句话,object文件内部包含段......
- 每个段都有名字和size。另外,段内部还包含一些数据, 这些数据叫做section contents,以后称段内容。每个段有不同的属性。例如text段标志为可加载(loadable),表示该段内的contents在运行时候(当然 指输出文件执行的时候)需要加载到内存中。另外一些段中没有contents,那么这些段标示为allocatable,即需要分配一些内存(有时候这些 内存会被初始化成0,这里说的应该是BSS段。BSS段在二进制文件中没有占据空间,即磁盘上二进制文件的大小比较小,但是加载到内存后,需要为BSS段 分配内存空间。),还有一些段属于debug的,这里包含一些debug信息。
- 既然需要加载到内存中,那么加载到内存的地址是什么 呢?loadable和allocable的段都有两个地址,VMA:虚拟地址,即程序运行时候的地址,例如把text段的VMA首地址设置为 0x800000000,那么运行时候的首地址就是这个了。另外还有一个LMA,即Load memory address。这个地址是section加载时的地址。晕了吧?二者有啥区别?一般情况下,VMA=LMA。但也有例外。例如设置某数据段的LMA在 ROM中(即加载的时候拷贝到ROM中),运行的时候拷贝到RAM中,这样LMA和VMA就不同了。---------》很难搞懂不是?这种方法用于初始 化一些全局变量,基于那种ROM based system。(问一个问题,run的时候,怎么根据section中的VMA进行相应设置啊??以后可能需要研究下内核中关于execve实现方面的内 容了)。关于VMA和LMA,大家通过objdump -h选项可以查看。
. = 0x10000;
.text : { *(.text) }
. = 0x8000000;
.data : { *(.data) }
.bss : { *(.bss) }
}
- SECTIONS是LS语法中的关键command,它用来描述输出文件的内存布局。例如上例中就含text/data/bss三个部分(实际上text/data/bss才是段,但是SECTIONS这个词在LS中是一个command,希望各位看官要明白)。
- .=0x10000; 其中的.非常关键,它代表location counter(LC)。意思是.text段的开始设置在0x10000处。这个LC应该指的是LMA,但大多数情况下VMA=LMA。
- .text:{*(.text)},这个表示输出文件的.text段内容由所有输入文件(*)的.text段组成。组成顺序就是ld命令中输入文件的顺序,例如1.obj,2.obj......
- 此后,由来了一个.=0x800000000;。如果没有 这个赋值的,那么LC应该等于0x10000+sizeof(text段),即LC如果不强制指定的话,它默认就是上一次的LC+中间section的长 度。还好,这里强制指定LC=0X800000000.表明后面的.data段的开始位于这个地址。
- .data和后面的.bss表示分别有输入文件的.data和.bss段构成。
一堆注释,这里就不再贴上了,另外,增加//号做为注释标识
* Convert a physical address to a Page Frame Number and back
*/
OUTPUT_ARCH(arm)
ENTRY(stext)
jiffies = jiffies_64;
SECTIONS
{
. = 0xC0000000 + 0x00008000;
[AT(lma)] [ALIGN(section_align)]
[SUBALIGN(subsection_align)]
[constraint]
{
output-section-command
output-section-command
...
} [>region] [AT>lma_region] [:phdr :phdr ...] [=fillexp]
.text.head : {
start_of_ROM = .ROM; end_of_ROM = .ROM + sizeof (.ROM) - 1; start_of_FLASH = .FLASH;
上面三个变量是在LS中定义的,分别指向.ROM段的开始和结尾,以及FLASH段的开始。现在在C代码中想把ROM段的内容拷贝到FLASH段中,下面是C代码:
extern char start_of_ROM, end_of_ROM, start_of_FLASH; memcpy (& start_of_FLASH, & start_of_ROM, & end_of_ROM - & start_of_ROM);
_stext = .;.
_sinittext = .;
*(.text.head)
}
.init : { /* Init code and data */
*(.init.text) *(.cpuinit.text) *(.meminit.text)
_einittext = .;
__proc_info_begin = .;
*(.proc.info.init) //所有.proc.info.init段内容在这
__proc_info_end = .;//下面这个变量 __proc_info_end标示结尾,它和__proc_info_begin变量牢牢得把输出文件.proc.info.init的内容卡住了。
__arch_info_begin = .;
*(.arch.info.init)
__arch_info_end = .;
__tagtable_begin = .;
*(.taglist.init)
__tagtable_end = .;
. = ALIGN(16);
__setup_start = .;
*(.init.setup)
__setup_end = .;
__early_begin = .;
*(.early_param.init)
__early_end = .;
__initcall_start = .;
*(.initcallearly.init)
*(.initcall0.init) *(.initcall0s.init) *(.initcall1.init) *(.initcall1s.init) *(.initcall2.init) *(.initcall2s.init) *(.initcall3.init) *(.initcall3s.init) *(.initcall4.init) *(.initcall4s.init) *(.initcall5.init) *(.initcall5s.init) *(.initcallrootfs.init) *(.initcall6.init) *(.initcall6s.init) *(.initcall7.init) *(.initcall7s.init)
__initcall_end = .;
__con_initcall_start = .;
*(.con_initcall.init)
__con_initcall_end = .;
__security_initcall_start = .;
*(.security_initcall.init)
__security_initcall_end = .;
. = ALIGN(32);//ALIGN,表示对齐,即这里的Location Counter的位置必须按32对齐
__initramfs_start = .; //ramfs的位置
usr/built-in.o(.init.ramfs)
__initramfs_end = .;
. = ALIGN(4096); //4K对齐
__per_cpu_load = .;
__per_cpu_start = .;
*(.data.percpu.page_aligned)
*(.data.percpu)
*(.data.percpu.shared_aligned)
__per_cpu_end = .;
__init_begin = _stext;
*(.init.data) *(.cpuinit.data) *(.cpuinit.rodata) *(.meminit.data) *(.meminit.rodata)
. = ALIGN(4096);
__init_end = .;
}
/DISCARD/ : { /* Exit code and data */
*(.exit.text) *(.cpuexit.text) *(.memexit.text)
*(.exit.data) *(.cpuexit.data) *(.cpuexit.rodata) *(.memexit.data) *(.memexit.rodata)
*(.exitcall.exit)
*(.ARM.exidx.exit.text)
*(.ARM.extab.exit.text)
}
//省略部分内容
{
.text 0x1000 : { *(.text) _etext = . ; } /.text段的VMA为0x1000,而且LMA=VMA
.mdata 0x2000 : //.mdata段的VMA为0x2000,但是它的LMA却在.text段的结尾
AT ( ADDR (.text) + SIZEOF (.text) )
{ _data = . ; *(.data); _edata = . ; }
.bss 0x3000 :
{ _bstart = . ; *(.bss) *(COMMON) ; _bend = . ;}
}
char *src = &_etext; //_etext为.text端的末尾 VMA地址,但同时也是.mdata段LMA的开始,有LS种的AT指定
char *dst = &_data; //_data为mdata段的VMA,现在需要把LMA地址开始的内容拷贝到VMA开始的地方
/* ROM has data at end of text; copy it. */
while (dst < &_edata)
*dst++ = *src++; //拷贝....明白了?不明白的好好琢磨
/* Zero bss. */
for (dst = &_bstart; dst< &_bend; dst++)
*dst = 0; //初始化数据区域
.rodata : AT(ADDR(.rodata) - 0) {
__start_rodata = .;
*(.rodata) *(.rodata.*) *(__vermagic) *(__markers_strings) *(__tracepoints_strings)
}
.rodata1 : AT(ADDR(.rodata1) - 0) {
*(.rodata1)
}
......//省略部分内容
.bss : {
__bss_start = .; /* BSS */
*(.bss)
*(COMMON)
_end = .;
}
/* Stabs debugging sections. */
.stab 0 : { *(.stab) }
.stabstr 0 : { *(.stabstr) }
.stab.excl 0 : { *(.stab.excl) }
.stab.exclstr 0 : { *(.stab.exclstr) }
.stab.index 0 : { *(.stab.index) }
.stab.indexstr 0 : { *(.stab.indexstr) }
.comment 0 : { *(.comment) }
}
//ASSERT是命令,如果第一个参数为0,则打印第二个参数的信息(也就是错误信息),然后ld命令退出。
ASSERT((__proc_info_end - __proc_info_begin), "missing CPU support")
ASSERT((__arch_info_end - __arch_info_begin), "no machine record defined")
{
initcall_t *call;
//上面已经定义成数组了,所以下面这些变量直接取的就是指针,和上面例子中使用&一个意思,反正不能用value
for (call = __early_initcall_end; call < __initcall_end; call++)
do_one_initcall(*call);
/* Make sure there is no pending stuff from the initcall sequence */
flush_scheduled_work();
}
- LK源码中那些找不到来源的变量是怎么来的---》在LS定义。
- VMA和LMA是怎么回事。