千家信息网

Linux Kernel编译和链接中的linker script语法是怎样的

发表于:2026-10-31 作者:千家信息网编辑
千家信息网最后更新 2026年10月31日,这篇文章将为大家详细讲解有关Linux Kernel编译和链接中的linker script语法是怎样的,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。
千家信息网最后更新 2026年10月31日Linux Kernel编译和链接中的linker script语法是怎样的

这篇文章将为大家详细讲解有关Linux Kernel编译和链接中的linker script语法是怎样的,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。

先要讲讲这个问题是怎么来的。

我在编译内核的时候,发现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
*/

//OUTPUT_ARCH是LS语法中的COMMAND,用来指定输出文件的machine arch。objdump -f可查询所有支持的machine。另外

//这些东西涉及到一种叫BFD的。各位看官可以自己搜索下BFD的内容。

//下面这 表示输出文件基于ARM架构
OUTPUT_ARCH(arm)

//ENTRY也是一个command,用来设置入口点。这里表示入口点是stext 。根据LD的描述,入口点的意思就是程序运行的第一条指令。内核是一个模块,大家把他想象

//成一个运行在硬件上的大程序就可以了。而我们的程序又是运行在内核至上的。比较下Java虚拟机以及运行在其上的Java程序吧......
ENTRY(stext)

//设置jiffies为jiffies_64
jiffies = jiffies_64;

//定义输出文件的段
SECTIONS
{

//设置location count为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不会设置,所以它默认等于当前的location counter

*/
.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中定义的变量的话,只能取它的地址。下面是一个例子:

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 : { /* Init code and data */
*(.init.text) *(.cpuinit.text) *(.meminit.text)

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

//_einitext标示一个结尾。
_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的内容卡住了。

//有了上面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)

__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 = .;
}

//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)
}
//省略部分内容

//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 = . ;}
}

看到了么?.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; //初始化数据区域

*/
.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")

五 内核代码中使用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();
}

关于Linux Kernel编译和链接中的linker script语法是怎样的就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。

文件 地址 变量 内容 内存 时候 输出 运行 代码 就是 内核 编译 程序 存储 链接 也就是 例子 命令 输入 语法 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 鸠鸠互联网科技 互联网科技开发公司 杭州互联网科技公司 中国互联网科技发展 鑫亿盛互联网科技 壹账通互联网科技 互联网金融监管科技 顺网科技2021年互联网排名 互联网科技公司的市场部门 天速互联网络科技有限公司 银川新华互联网科技职业技术培训学校图片 互联网科技有哪些公司 开一家科技互联网公司需要多少钱 迪庆互联网科技招生 互联网科技和法律行业 互联网科技创新的路径 皮皮虾互联网科技有限公司 上海创道互联网络科技有限公司 风投北京互联网科技 互联网科技机房 上海睿民互联网科技有限公司 广西互联网科技公司 团飞科技与伯堂互联网服务平台 成都铂石互联网科技做什么的 移动互联网年度科技创新 美国互联网科技实力 互联网科技年终奖 九思连强互联网电子科技有限公司 银川新华互联网科技学校原画 深圳互联网科技园地址
0