第一篇博客:
最近在研究uboot,红色部分为我加上的注解
转载地址:
lds文件与scatter文件相似都是决定一个可执行程序的各个段的存储位置,以及入口地址,这也是链接定位的作用。U-boot的lds文件说明如下:
SECTIONS{
...
secname start BLOCK(align)(NOLOAD):AT(ldadr)
{contents}>region:phdr = fill
...
}
secname和contents是必须的,前者用来命名这个段,后者用来确定代码中的什么部分放在这个段,以下是这个描述中的一些关键字的解释。
1、secname :段名
2、contents :决定哪些内容放在本段,可以使整个目标文件,也可以是目标文件中的某段(代码段,数据段等)
3、start:是段的重定位地址,本段连接(运行)的地址, 如果代码中有位置无关指令,程序运行时这个段必须放在这个地址上,start可以用任意一种描述地址的符号来描述。
4、AT(ldar):定义本段存储(加载)的地址,如果不使用这个选项,则加载地址等于运行地址通过这个选项可以控制各个分段分别保存于输出文件中的不同位置
例如:
/*nand.lds*/
SECTIONS {
first 0x00000000:{head.o init.o}
second 0x30000000:AT(4096) {main.o}
}
以上,head.o放在0x00000000地址开始处,init.o放在head.o后面他的运行地址也是0x00000000,即连接地址和存储地址相同 (没有AT指定);main.o放在4096(0x1000是AT指定的存储地址)开始处,但他的运行地址在0x30000000,运行之前需要从0x1000(加载地址)复制到0x30000000(运行地址处),此过程也就需要读取flash,把程序拷贝到相应位置才能运行。这就是存储地址和运行的不同,称为加载时域和运行时域可以再.lds连接脚本文件中分别制定。
编写好的.lds文件,在用arm-linux-ld连接命令时带-Tfilename来调用执行,如 arm-linux-ld -Tnand.lds x.o y.o -o xy.o。也可用-Ttext参数直接指定连接地址如:
arm-linux-ld -Ttext 0x30000000 x.o y.o -o xy.o
既然程序有了 两种地址,就涉及到一些跳转指令的区别。
下面1、2这一部分可以看韦东山的书,看看具体如何跳转的
1)b step: b跳转指令是相对跳转,依赖当前PC的值,偏移量是通过该指令本身的bit[23:0]算出来的,这使得使用b指令的不依赖于要跳到的代码的位置,只看指令本身。
2)ldr pc,=step;该指令是一个伪指令编译后会生成以下代码:
ldr pc,0x30008000
<0x30008000>
step
是从内存中的某个位置(step)读出数据并赋给PC,同样依赖于当前PC的值,但是偏移量是step的连接地址(运行时的地址),所以可以用它实现从Flash到RAM的程序跳转。
(3)此外,有必要回味一下adr伪指令,U-boot中那段relocate代码就是通过adr实现当前程序是在RAM中还是在Flash中:
relocate: /*把u-boot重新定位到RAM*/
adr r0,_start r0是代码的当前位置
adr指令:ADR伪指令--- 小范围的地址读取ADR伪指令将基于PC相对偏移的地址值或基于寄存器相对偏移的地址值读取到寄存器中。在汇编编译器编译源程序时,ADR伪指令被编译器替换成一条合适的指令。通常,编译器用一条ADD指令或SUB指令来实现该ADR伪指令的功能,若不能用一条指令实现,则产生错误,编译失败。
ADR伪指令格式 :ADR{cond} register, expr
地址表达式expr的取值范围:
当地址值是字节对齐时,其取指范围为: +255 ~ 255B;
当地址值是字对齐时,其取指范围为: -1020 ~ 1020B;
是根据pc地址与相对偏移地址计算出来的,例如:现在是在片内ram中执行时,这时的r0的值为0x00000000,如果被从flash中load到外置的sdram(假设便宜地址为0x3000 0000)中,则此时r0的值为0x3000 0000
/*adr伪指令,汇编器会自动通过当前PC的值得算出这条指令中“_start" 的值,执行到start时PC的值放到r0中:
当此段在flash中执行时 r0=_start=0;当此段在RAM中执行时_start=_TEXT_BASE(在board/smdk2410/config.mk)中指定的值为0x33F80000,即U-Boot在把代码拷贝到RAM中去执行的代码段的开始*/
ldr r1,_TEXT_BASE /*测试判断从Flash启动,还是从RAM启动此句执行的结果r1始终是0x33FF80000,因为此值是链接指定的*/
cmp r0,r1 /*比较r0和r1,调试的时候不要执行重定位*/
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm");指定输出可执行文件elf格式,32位ARM指令,小端模式
/*OUTPUT_FORMAT("elf32-arm", "elf32-arm", "elf32-arm")*/
OUTPUT_ARCH(arm) 指定输入平台为ARM
ENTRY(_start) 指定输出可执行文件的起始代码为_start
SECTIONS
{
. = 0x00000000; 定位当前地址为0地址
/*OUTPUT_FORMAT("elf32-arm", "elf32-arm", "elf32-arm")*/
OUTPUT_ARCH(arm) 指定输入平台为ARM
ENTRY(_start) 指定输出可执行文件的起始代码为_start
SECTIONS
{
. = 0x00000000; 定位当前地址为0地址
. = ALIGN(4); 代码以四字节对齐
.text : 指定代码段
{
cpu/arm920t/start.o (.text) 代码的第一个代码段
*(.text) 其他代码段
}
.text : 指定代码段
{
cpu/arm920t/start.o (.text) 代码的第一个代码段
*(.text) 其他代码段
}
. = ALIGN(4);
.rodata : { *(.rodata) } 指定只读数据段
.rodata : { *(.rodata) } 指定只读数据段
. = ALIGN(4);
.data : { *(.data) } 指定读写数据段
.data : { *(.data) } 指定读写数据段
. = ALIGN(4);
.got : { *(.got) } 指定got段,got段式是uboot自定义的一个段,非标准段
.got : { *(.got) } 指定got段,got段式是uboot自定义的一个段,非标准段
__u_boot_cmd_start = .;其赋值为当前位置,即起始位置
.u_boot_cmd : { *(.u_boot_cmd) } u_boot_cmd段,Uboot把所有的uboot命令放在该段。
__u_boot_cmd_end = .;把其赋值为当前位置,即结束位置
. = ALIGN(4);
__bss_start = .; 把__bss_start赋值为当前位置,即bss段的开始位置
.bss : { *(.bss) } ;指定bss段
_end = .;;把_end赋值为当前位置,即bss段的结束位置
}
__bss_start = .; 把__bss_start赋值为当前位置,即bss段的开始位置
.bss : { *(.bss) } ;指定bss段
_end = .;;把_end赋值为当前位置,即bss段的结束位置
}
本文来自CSDN博客,转载请标明出处: http://blog.csdn.net/catamout/archive/2010/02/08/5298449.aspx
先要讲讲这个问题是怎么来的。(咱们在分析一个技术的时候,先要考虑它是想解决什么问题,或者学习新知识的时候,要清楚这个知识的目的是什么)。
- 我在编译内核的时候,发现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,这又是什么东西?
好吧,希望上面的问题勾起你的好奇心。下面我们来扫盲,最后会给一个链接地址,各看官可以去那深造。
一 section是什么?
好吧,我们需要解释一下平时编译链接生成的二进制可执行程序(比如说ELF,EXE也行),so或者dll,内核(非压缩的,参加本系列第一节内容、vmlinux),或者ko是怎么组织的。
其实,大家或多或少都知道这些二进制中包括有什么text/bss/data节(也叫section)。text节存储的是代码、data存储的是已经初始化的静态变量、bss节存储的是未初始化的什么东西...
上面的东西我就不细究了。反正一点,一个二进制,最终会包含很多section。那么,为什么section叫text/bss/data,能叫别的名字吗?
OK,可以。但是你得告诉ld,那么这些内容就通过-T选项指定一个linker script就行了。这些内容我们放到后面的实例中来介绍。
(再三强调,咱们在理论上只是抛砖引玉,希望有兴趣的看官自己研究,注意和我们分享你的成果就行了。)
二 link script基础知识介绍
linker script中的语法是linker command language(很简单的language,大家不用害怕...)。那么LS的目的是什么呢?
- 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选项可以查看。
三 简单例子
下面来一个简单例子,
SECTIONS
{. = 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段构成。
你看,我们从这个LC文件中学到了什么?
恩,我们可以任意设置各个段的LMA值。当然,绝大部分情况,我们不需要有自己的LS来控制输出文件的内存布局。不过LK(linux kernel)可不一样了......
四 霸王硬上弓---vmlinux.lds.S分析
OK,有了上面的基础知识,下面我们霸王硬上弓,直接分析arch/arm/kernel/vmlinux.lds.S.虽然最终链接用的是vmlinux.lds,但是那个文件
由vmlinux.lds.S(这是一个汇编文件)得到,
arm-linux-gcc -E -Wp,-MD,arch/arm/kernel/.vmlinux.lds.d -nostdinc ...... -D__KERNEL__ -mlittle-endian ......
-DTEXT_OFFSET=0x00008000 -P -C -Uarm -D__ASSEMBLY__ -o arch/arm/kernel/vmlinux.lds arch/arm/kernel/vmlinux.lds.S
所以,我们直接分析vmlinux.lds好了。
/*
一堆注释,这里就不再贴上了,另外,增加//号做为注释标识
* Convert a physical address to a Page Frame Number and back
*/
一堆注释,这里就不再贴上了,另外,增加//号做为注释标识
* Convert a physical address to a Page Frame Number and back
*/
//OUTPUT_ARCH是LS语法中的COMMAND,用来指定输出文件的machine arch。objdump -f可查询所有支持的machine。另外
//这些东西涉及到一种叫BFD的。各位看官可以自己搜索下BFD的内容。
//下面这 表示输出文件基于ARM架构
OUTPUT_ARCH(arm)
OUTPUT_ARCH(arm)
//ENTRY也是一个command,用来设置入口点。这里表示入口点是stext 。根据LD的描述,入口点的意思就是程序运行的第一条指令。内核是一个模块,大家把他想象
//成一个运行在硬件上的大程序就可以了。而我们的程序又是运行在内核至上的。比较下Java虚拟机以及运行在其上的Java程序吧......
ENTRY(stext)
ENTRY(stext)
//设置jiffies为jiffies_64
jiffies = jiffies_64;
jiffies = jiffies_64;
//定义输出文件的段
SECTIONS
{
SECTIONS
{
//设置location count为0xc0008000,这���好理解吧?内核运行的地址全在C0000000以上
. = 0xC0000000 + 0x00008000;
. = 0xC0000000 + 0x00008000;
//定义一个.text.head段,由输入文件中所有.text.head段组成
/*
LS语法中,关于seciton的定义如下:
section [address] [(type)] :
[AT(lma)] [ALIGN(section_align)]
[SUBALIGN(subsection_align)]
[constraint]
{
output-section-command
output-section-command
...
} [>region] [AT>lma_region] [:phdr :phdr ...] [=fillexp]
[AT(lma)] [ALIGN(section_align)]
[SUBALIGN(subsection_align)]
[constraint]
{
output-section-command
output-section-command
...
} [>region] [AT>lma_region] [:phdr :phdr ...] [=fillexp]
其中,address为VMA,而AT命令中的为LMA。一般情况,address不会设置,所以它默认等于当前的location counter
*/
.text.head : {
.text.head : {
/*这个非常关键,咱们在内核代码中经常能看到一些变量声明,例如extern int __stext,但是却找不到在哪定义的
其实这些都是在lds文件中定义的。这里得说一下编译链接相关的小知识。咱们这知道大概即可,具体内容可以自己深入研究
假设C代码中定义一个变量 int x = 0;那么
1 编译器首先会分配一块内存,用来存储该变量的值
2 编译器在程序的symbol表中,创建一项,用来存储这个变量的地址
例如,上面的 int x = 0,就在symbol表中创建一x项,这个x项指向一块内存,sizeof(int)大小,存储的值为0。当有地方使用这个x的时候,编译器会生成相应的代码,
首先指向这个x的内存,然后读取内存中的值。
上面的内容是C中一个变量的定义。但是Linker script中也可以定义变量,这时候只会生成一个symbol项,但是没有分配内存。。例如_stext=0x100,那么会
创建一个symbol项,指向0x100的内存,但该内存中没有存储value。所以,我们在C中使用LS中定义的变量的话,只能取它的地址。下面是一个例子:
<span style="color: rgb(0, 0, 255);"><span style="font-size: 18px;">start_of_ROM = .ROM; end_of_ROM = .ROM + sizeof (.ROM) - 1; start_of_FLASH = .FLASH;</span></span>
<span style="color: rgb(255, 0, 0);"><span style="font-size: 18px;">上面三个变量是在LS中定义的,分别指向.ROM段的开始和结尾,以及FLASH段的开始。现在在C代码中想把ROM段的内容拷贝到FLASH段中,下面是C代码:</span></span>
<span style="font-size: 18px;"></span><pre class="smallexample" name="code"><span style="color: rgb(0, 0, 255);"><span style="font-size: 18px;">extern char start_of_ROM, end_of_ROM, start_of_FLASH; memcpy (& start_of_FLASH, & start_of_ROM, & end_of_ROM - & start_of_ROM);</span></span>
注意其中的取地址符号&。C代码中只能通过这种方式来使用LS中定义的变量. start_of_ROM这个值本身是没有意义的,只有它的地址才有意义。因为它的值没有初始化。
地址就指向.ROM段的开头。
说白了,LS中定义的变量其实就是地址,即_stext=0x100就是C代码中的一个地址 int *_stext=0x100。明白了?
最终的ld中会分配一个slot,然后存储x的地址。也就是说,ld知道这些勾当。那么当然我们在LS中
也可以定义一个变量,然后在C中使用了。所以下面这句话实际上定义了一个_stext变量。在C中通过extern就可以引用了。但是这里有一个
比较关键的问题。C中定义的x=0,其值被初始化为0了。也就是slot...待补充
*/
_stext = .;.
_sinittext = .;
*(.text.head)
}
_stext = .;.
_sinittext = .;
*(.text.head)
}
//定义.init段,由所有的.init.text/.cpuinit.text/.meminit.text组成
//这时的LC的值为.init的开始
.init : { /* Init code and data */
*(.init.text) *(.cpuinit.text) *(.meminit.text)
.init : { /* Init code and data */
*(.init.text) *(.cpuinit.text) *(.meminit.text)
//定义一个变量 _einitext,它的值为当前的LC,即.init的初值+*(.init.text) *(.cpuinit.text) *(.meminit.text)的大小。也就是说变量
//_einitext标示一个结尾。
_einittext = .;
_einittext = .;
//下面这个变量 __proc_info_begin标示一个开头
__proc_info_begin = .;
*(.proc.info.init) //所有.proc.info.init段内容在这
__proc_info_end = .;//下面这个变量 __proc_info_end标示结尾,它和__proc_info_begin变量牢牢得把输出文件.proc.info.init的内容卡住了。
__proc_info_begin = .;
*(.proc.info.init) //所有.proc.info.init段内容在这
__proc_info_end = .;//下面这个变量 __proc_info_end标示结尾,它和__proc_info_begin变量牢牢得把输出文件.proc.info.init的内容卡住了。
//有了上面begin和end的介绍,后面就简单了,大部分都是一个begin+end来卡住一段内容。根据前面的介绍,begin和end又可以在C程序中引用
//也就是我们通过Begin+end,就可以获得卡住的内容了。例如我们把一些初始化的函数指针放到一个begin和end中。然后通过一个循环,不就是
//可以调用这些函数了么。最后我们就来个例子介绍下。
__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)
__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)
__early_initcall_end = .;
*(.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 = .;
}
*(.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 = .;
}
//DISACARD是一个特殊的section,表示符合这个条件的输入段都不会写到输出段中,也就是输出文件中不包含下列段
/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)
}
//省略部分内容
/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)
}
//省略部分内容
//ADDR为内置函数,用来返回VMA的
/*
这里举个小例子,大家看看VMA和LMA到底有什么作用
SECTIONS
{
.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 = . ;}
}
{
.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 = . ;}
}
看到了么?.mdata段运行的时候在0x2000,但是数据load地址却在.text段后,所以运行的时候需要把.mdata段内容拷贝过去。
extern char _etext, _data, _edata, _bstart, _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; //初始化数据区域
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)
}
......//省略部分内容
.rodata : AT(ADDR(.rodata) - 0) {
__start_rodata = .;
*(.rodata) *(.rodata.*) *(__vermagic) *(__markers_strings) *(__tracepoints_strings)
}
.rodata1 : AT(ADDR(.rodata1) - 0) {
*(.rodata1)
}
......//省略部分内容
_edata_loc = __data_loc + SIZEOF(.data);
.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")
.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")
五 内核代码中使用LS中定义的变量
咱们看一个小例子
[-->init/main.c]
extern initcall_t __initcall_start[], __initcall_end[], __early_initcall_end[]; //这几个值在LS中定义。大家可以在上面搜索下
static void __init do_initcalls(void)
{
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();
}
{
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();
}
六 总结
关于LS的详细文档,见下面的网址:
上面文档写得比较粗,但大家知道两点即可:
- LK源码中那些找不到来源的变量是怎么来的---》在LS定义。
- VMA和LMA是怎么回事。
Linux Kernel系列 相关阅读:http://www.linuxidc.com/search.aspx?Where=Nkey&Keyword=Linux+Kernel%e7%b3%bb%e5%88%97