linker list文件位置
include\linker_lists.h
linker_list文件中的宏
宏名 | 含义 |
---|---|
llsym(_type, _name, _list) | 访问链接器生成的_lsit数组中类型为_type的名称为name的元素 |
ll_entry_declare(_type, _name, _list) | 在_list数组中声明一个链接器产生的_type类型命名为name的元素 |
ll_entry_declare_list(_type, _name, _list) | 在_list数组中声明一个链接器产生的_type类型命名为name数组的数组 |
ll_entry_start(_type, _list) | 声明一个指向_type类型的_list数组中第一个元素的类型为_type指针 |
ll_entry_end(_type, _list) | 声明一个指向_type类型的_list数组中最后一个元素的末尾地址的下一个地址的类型为_type指针 |
ll_entry_count(_type, _list) | 返回_type类型的_list数组中元素个数 |
ll_entry_get(_type, _name, _list) | 获取_type类型_list数组中命名为name的元素 |
宏llsym
- #define llsym(_type, _name, _list) \
- ((_type *)&_u_boot_list_2_##_list##_2_##_name)
-
宏的作用
用于访问由_u_boot_list_2_##_list##2##_name组成的(_type *)类型变量的访问,其中##_list和##name就是将宏中参数_list和_name连接起来,如llsym(struct driver, usb_generic_hub, driver)就是(struct driver *)&u_boot_list_2 driver_2 usb_generic_hub
-
参数
_type:数据类型:如int,long,结构体,函数等等
_name:数组元素名称
_list: 数组名称
- 使用案列
就等价于llsym(struct driver, usb_generic_hub, driver)
,意思是将全局变量(struct driver *)&_u_boot_list_2_ driver_2_ usb_generic_hub
强制转化为struct driver类型进行使用。_u_boot_list_2_ driver_2_ usb_generic_hub
如何被定义以及如何初始化为struct driver类型将在宏_u_boot_list_2_ driver_2_ usb_generic_hub
中讲解ll_entry_declare(_type, _name, _list)
宏ll_entry_declare
- #define ll_entry_declare(_type, _name, _list) \
- _type _u_boot_list_2_##_list##_2_##_name __aligned(4) \
- __attribute__((unused, \
- section(".u_boot_list_2_"#_list"_2_"#_name)))
-
宏的作用
声明一个_type类型的变量,该变量四字节对齐,并且将该变量放置在_u_boot_list_2_##_list##2##_name段下,使用此宏声明的变量必须在编译时初始化
-
参数
_type:数据类型:如int,long,结构体,函数等等
_name:数组元素名称
_list: 数组名称
__aligned(4):变量进行4字节对齐
attribute:给变量增加属性
unused:变量的一个属性,如果该变量未使用则不产生警告
section(“Demo”):变量的一个属性,将变量放到Demo段下
-
使用注意
–参数_type不能包含”static”关键字
–如果声明了一个包含一些数组元素的段,并且声明了该段的一个子段并包含了一些元素,那么这些元素必须是同一类型的。
–如果声明了一个包含一些数组元素的外部段,并且声明了该段的一个内部子段并包含了一些元素,那么在遍历外部段时,甚至内部段的元素也出现在数组中。
- 使用案例
1. struct env_clbk_tbl {
2. const char *name; /* Callback name */
3. int (*callback)(const char *name, const char *value, enum env_op op,
4. int flags);
5. };
6. #define U_BOOT_ENV_CALLBACK(name, callback) \
7. ll_entry_declare(struct env_clbk_tbl, name, env_clbk) = \
8. {#name, callback}
9. #endif
这是一个环境变量在创建、删除或修改时进行一个回调操作,struct env_clbk_tbl就是为环境变量回调操作定义的一个结构体,结构体第一个元素name就是环境变量名称,结构体第二个元素就是一个指向环境变量回调函数的函数指针callback。

这里最终目的就是创建了的变量,这个变量的类型为struct env_clbk_tbl类型,变量名为_u_boot_list_2_ env_clbk 2 ipaddr,__aligned(4)让变量进行4字节对齐,也就是说如果整个结构体所占内存没有4字节对齐时进行填充,例如整个结构体占10个字节,那就在结构体后面填充2个空字节,补充到12字节,实现4字节对齐。
attribute((unused, section(.u_boot_list_2_ env_clbk 2 ipaddr)))给这个变量添加了两个属性
-
unused属性
这个变量如果没有被使用,编译器不会报“变量从未使用“的警告
-
section属性
section(.u_boot_list_2_ env_clbk_2_ipaddr)属性:将这个变量放置到.u_boot_list_2_env_clbk_2_ipaddr字段下,与.text段或.data等其他段独立出来,从uboot.map文件中可以查看
u-boot的linker list源码分析
可见在.u_boot_list_2_env_clbk_2_ipaddr字段下保存有一个变量_u_boot_list_2_env_clbk_2_ipaddr
由此可见通过宏U_BOOT_ENV_CALLBACK(name, callback)就可以创建一个struct env_clbk_tbl 类型,命名为_u_boot_list_2_env_clbk_2_##_name的变量,name就是环境变量名,而且根据struct env_clbk_tbl 类型,此环境变量还做了初始化,
_u_boot_list_2_env_clbk_2_##_name->name=name
_u_boot_list_2_env_clbk_2_##_name->callback=callback
因此环境变量name在创建,删除或修改时就可以使用_u_boot_list_2_env_clbk_2_##_name->callback函数做回调操作。
宏ll_entry_declare_list
- #define ll_entry_declare_list(_type, _name, _list) \
- _type _u_boot_list_2_##_list##_2_##_name[] __aligned(4) \
- __attribute__((unused, \
- section(".u_boot_list_2_"#_list"_2_"#_name)))
与ll_entry_declare功能类似,只是用于声明一个变量数组,这个宏直接参考ll_entry_declare,只是它在定义时为数组,调用时直接为
u_boot_list_2##_list##2##_name[0]
u_boot_list_2##_list##2##_name[1]
ll宏_entry_start和ll_entry_end
为了计算数组中元素的个数定义了ll_entry_start和ll_entry_end两个宏,在link_list数据结构中段的命名都是以.u_boot_list_2_开始,
.u_boot_list_2_"#_list"_2_"#_nam
e中_list为数组项名称,_name数组项下的元素名称,下面整理了u_boot部分_list,_name
.u_boot_list_2_"#_list"_2_"#_name
在u-boot.lds中.u_boot_list_*段进行排序
因此以
.u_boot_list_
开头的段都会按照字符顺序进行排序,如
_u_boot_list_3*
、
_u_boot_list_2*
、
_u_boot_list_1*
,则链接器链接的时候就以
_u_boot_list_1*
、
_u_boot_list_2*
、
_u_boot_list_3*
的顺序进行链接。
所以在所有
_u_boot_list_2*
段前面插入一个
_u_boot_list
_
1
段,在
_u_boot_list_2*
段后面插入一个
_u_boot_list_3
段,这两个段不分配任何内存,让
_u_boot_list_1
指向
_u_boot_list_2*
开始的起始地址,让
_u_boot_list_3
指向最后
_u_boot_list_2*
最后一个地址的下一个地址,这样
{(_u_boot_list_3)-(_u_boot_list_1)=(_u_boot_list_2*所占内存大小)}
,在这里使用一个空数组
start[0]
进行定位,因为数组长度为0,所以不分配内存,但是它指向的地址时当前所在位置,他赋予属性,将他放在
_u_boot_list_1
段,这样
start[0]
就可以指向下一个段的起始地址,下个段为
_u_boot_list_2*
。
同理在
.u_boot_list_2*
末尾插入
.u_boot_list_3
段,使用一个空数组end[0],并且将它放到
.u_boot_list_3
段,这样end[0]就指向
.u_boot_list_3
后面的第一个地址。如果start和end强制为char型指针,那
(end-start)
就是
_u_boot_list_2*
所占内存大小,如果时_type类型,那么
(_u_boot_list_2*所占内存大小)=sizeof(_type)*(start-end)
同理也可以计算
_u_boot_list_2_##_list##_2*
的内存大小或数组元素个数,为什么将这个
_list
列表称之为数组,是因为在ll_entry_declare宏定义时就要求相同的_list下的元素的数据类型必须一致,例如
_list
名为cmd的数据类型就为
struct cmd_tbl_s
结构体,driver的数据类型为struct driver结构体,因为连接脚本中对
.u_boot_list_*
段进行了排序,例如所有的会
.u_boot_list_2_cmd_2*
段会排放在一起,自然而然这些段中的变
_u_boot_list_2_ cmd _2_*
就会被放在连续的地址空间,数据类型又是相同的,所以和数组的属性一致,因此将之称之为数组。同上在
.u_boot_list_2_cmd_2*
段的起始地址插一个
.u_boot_list_2_cmd_1
段,并且存放一个空数组
start[0]
,让指向
_u_boot_list_2_ cmd _2_*
数组列第一个变量,在
_u_boot_list_2_ cmd _2_*
数组列结束后插入一个
.u_boot_list_2_cmd_3
段,存放一个空数组
end[0]
,end指向
_u_boot_list_2_ cmd _2_*
数组列结束后的第一个地址,然后将end和start强制转化为struct cmd_tbl_s结构体指针,这样
(end-start)=(_u_boot_list_2_ cmd _2_*)数组元素的个数
在map文件中可查看:
可见.u_boot_list_2_cmd_1的地址和.u_boot_list_2_cmd_2_adc段的地址相同,而且每个段都按.u_boot_list_2_cmd_2_*都按照顺序排列,变量_u_boot_list_2_cmd_2_*地址是连续的,每个变量_u_boot_list_2_cmd_2_*大小都是一致的。
在末尾.u_boot_list_2_cmd_3段指向下一个list变了的首地址,下一个是driver的list,同样在driver list首地址插入.u_boot_list_2_driver_1段,指向driver list 段的第一个变量_u_boot_list_2_driver_2_clk_fixed_factor,其他list参考cmd list。
根据u-boot.map文件整理的list存储列表如下