【移动应用开发技术】Linux Kernel编译和链接中的linker script语法是怎样的_第1页
【移动应用开发技术】Linux Kernel编译和链接中的linker script语法是怎样的_第2页
【移动应用开发技术】Linux Kernel编译和链接中的linker script语法是怎样的_第3页
【移动应用开发技术】Linux Kernel编译和链接中的linker script语法是怎样的_第4页
【移动应用开发技术】Linux Kernel编译和链接中的linker script语法是怎样的_第5页
已阅读5页,还剩4页未读 继续免费阅读

付费下载

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

【移动应用开发技术】LinuxKernel编译和链接中的linkerscript语法是怎样的

这篇文章将为大家详细讲解有关LinuxKernel编译和链接中的linkerscript语法是怎样的,文章内容质量较高,因此在下分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。先要讲讲这个问题是怎么来的。我在编译内核的时候,发现arch/arm/kernel目录下有一个这样的文件:vmlinux.lds.S。第一眼看上去,想想是不是汇编文件呢?打开一看,好像不是。那它是干嘛的?而且前面已经说过,make

V=1的时候,发现这个文件的用处在ld命令中,即ld-Tvmlinux.lds.S,好像是链接命令用的,如下所示如arm-linux-ld-EL-p--no-undefined-X--build-id-ovmlinux-Tarch/arm/kernel/vmlinux.lds。manld,得到-T的意思是:为ld指定一个Linkerscript,意思是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选项指定一个linkerscript就行了。这些内容我们放到后面的实例中来介绍。(再三强调,咱们在理论上只是抛砖引玉,希望有兴趣的看官自己研究,注意和我们分享你的成果就行了。)二linkscript基础知识介绍linkerscript中的语法是linkercommandlanguage(很简单的language,大家不用害怕...)。那么LS的目的是什么呢?LS描述输入文件(也就是gcc

-c命令产生的.o文件即object文件)中的section最终如何对应到一个输出文件。这个其实好理解,例如一个elf由三个.o文件构成,每个.o文件都有text/data/bss段,但最终的那一个elf就会将三个输入的.o文件的段合并到一起。好了,下面我们介绍一些基本知识:ld的功能是将input文件组装成一个output文件。这些文件内部的都有特殊的组织结构,这种结构被叫做objectfileformat。每一个文件叫做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,即Loadmemory

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;其中的.非常关键,它代表locationcounter(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(linuxkernel)可不一样了四霸王硬上弓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__-oarch/arm/kernel/vmlinux.lds

arch/arm/kernel/vmlinux.lds.S所以,我们直接分析vmlinux.lds好了。/*

一堆注释,这里就不再贴上了,另外,增加//号做为注释标识

*ConvertaphysicaladdresstoaPageFrameNumberandback

*///OUTPUT_ARCH是LS语法中的COMMAND,用来指定输出文件的machinearch。objdump-f可查询所有支持的machine。另外//这些东西涉及到一种叫BFD的。各位看官可以自己搜索下BFD的内容。//下面这表示输出文件基于ARM架构OUTPUT_ARCH(arm)

//ENTRY也是一个command,用来设置入口点。这里表示入口点是stext

。根据LD的描述,入口点的意思就是程序运行的第一条指令。内核是一个模块,大家把他想象//成一个运行在硬件上的大程序就可以了。而我们的程序又是运行在内核至上的。比较下Java虚拟机以及运行在其上的Java程序吧ENTRY(stext)//设置jiffies为jiffies_64jiffies=jiffies_64;//定义输出文件的段SECTIONS{//设置locationcount为0xc0008000,这个好理解吧?内核运行的地址全在C0000000以上

.=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]其中,address为VMA,而AT命令中的为LMA。一般情况,address不会设置,所以它默认等于当前的locationcounter*/

.text.head:{/*这个非常关键,咱们在内核代码中经常能看到一些变量声明,例如externint__stext,但是却找不到在哪定义的其实这些都是在lds文件中定义的。这里得说一下编译链接相关的小知识。咱们这知道大概即可,具体内容可以自己深入研究假设C代码中定义一个变量intx=0;那么1编译器首先会分配一块内存,用来存储该变量的值2编译器在程序的symbol表中,创建一项,用来存储这个变量的地址例如,上面的intx=0,就在symbol表中创建一x项,这个x项指向一块内存,sizeof(int)大小,存储的值为0。当有地方使用这个x的时候,编译器会生成相应的代码,首先指向这个x的内存,然后读取内存中的值。上面的内容是C中一个变量的定义。但是Linkerscript中也可以定义变量,这时候只会生成一个symbol项,但是没有分配内存。。例如_stext=0x100,那么会创建一个symbol项,指向0x100的内存,但该内存中没有存储value。所以,我们在C中使用LS中定义的变量的话,只能取它的地址。下面是一个例子: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);注意其中的取地址符号&。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)

}//定义.init段,由所有的.init.text/.cpuinit.text/.meminit.text组成//这时的LC的值为.init的开始

.init:{/*Initcodeanddata

*/

*(.init.text)*(.cpuinit.text)*(.meminit.text)//定义一个变量_einitext,它的值为当前的LC,即.init的初值+*(.init.text)*(.cpuinit.text)*(.meminit.text)的大小。也就是说变量//_einitext标示一个结尾。

_einittext=.;//下面这个变量__proc_info_begin标示一个开头

__proc_info_begin=.;

*(..init)

//所有..init段内容在这

__proc_info_end=.;//下面这个变量__proc_info_end标示结尾,它和__proc_info_begin变量牢牢得把输出文件..init的内容卡住了。//有了上面begin和end的介绍,后面就简单了,大部分都是一个begin+end来卡住一段内容。根据前面的介绍,begin和end又可以在C程序中引用//也就是我们通过Begin+end,就可以获得卡住的内容了。例如我们把一些初始化的函数指针放到一个begin和end中。然后通过一个循环,不就是//可以调用这些函数了么。最后我们就来个例子介绍下。

__arch_info_begin=.;

*(..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,表示对齐,即这里的LocationCounter的位置必须按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/:{/*Exitcodeanddata

*/

*(.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

{

.text0x1000:{*(.text)_etext=.;}

/.text段的VMA为0x1000,而且LMA=VMA

.mdata0x2000://.mdata段的VMA为0x2000,但是它的LMA却在.text段的结尾

AT(ADDR(.text)+SIZEOF(.text))

{_data=.;*(.data);_edata=.;}

.bss0x3000:

{_bstart=.;*(.bss)*(COMMON);_bend=.;}

}看到了么?.mdata段运行的时候在0x2000,但是数据load地址却在.text段后,所以运行的时候需要把.mdata段内容拷贝过去。

externchar_etext,_data,_edata,_bstart,_bend;

char*src=&_etext;

//_etext为.text端的末尾VMA地址,但同时也是.mdata段LMA的开始,有LS种的AT指定

char*dst=&_data;

//_data为mdata段的VMA,现在需要把LMA地址开始的内容拷贝到VMA开始的地方

/*ROMhasdataatendoftext;copyit.*/

while(dst<&_edata)

*dst++=*src++;

//拷贝明白了?不明白的好好琢磨

/*Zerobss.*/

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)

}

//省略部分内容

_edata_loc=__data_loc+SIZEOF(.data);

.bss:{

__bss_start=.;/*BSS

*/

*(.bss)

*(COMMON)

_end=.;

}

/*Stabsdebuggingse

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论