内存管理01——链接脚本

新年的第一篇博文,先祝我工作顺利,万事如意!祝福大家的话就不赘述了。
在开始操作系统的内存管理相关内容前,首先来关注链接脚本,因为动态内存即堆区的地址是在链接脚本中分配的,知道了堆的起始地址和长度才能进行内存的分配和管理。

1. 链接脚本的作用是什么?

在这里插入图片描述在这里插入图片描述在这里插入图片描述
链接的作用就是把编译生成的多个目标文件(.o)合并起来,生成最后的可执行文件(.elf)。如上图中间的就是.o目标文件,最右的则是链接生成的.elf文件。除此之外,链接脚本还关注一个问题,就是生成的各个段被加载在内存的什么位置。
举个例子很容易就明白,下面是一个RISC-V代码的链接脚本:

OUTPUT_ARCH( "riscv" )	/* 代码采用的是RISC-V架构*/
ENTRY( _start )		/*代码入口符号是_start,就是汇编启动函数的符号*/
MEMORY
{
	/* 定义了一段起始地址为0x80000000,长度为128MB的内存区域,取名叫ram*/
	ram   (wxa!ri) : ORIGIN = 0x80000000, LENGTH = 128M
}
SECTIONS
{
	/* 所有输入文件中的.text段、.text.*段都合在一起,组成输出elf文件中的.text段;
	* 此外,定义了两个符号_text_start和_text_end ,注意符号'.'代表的是当前地址;
	* 生成的.text段被放在了ram这个内存区域中。
	*/
	.text : {
		PROVIDE(_text_start = .);
		*(.text .text.*)
		PROVIDE(_text_end = .);
	} >ram

	.rodata : {
		PROVIDE(_rodata_start = .);
		*(.rodata .rodata.*)
		PROVIDE(_rodata_end = .);
	} >ram

	.data : {
		. = ALIGN(4096);
		PROVIDE(_data_start = .);
		*(.sdata .sdata.*)
		*(.data .data.*)
		PROVIDE(_data_end = .);
	} >ram

	.bss :{
		PROVIDE(_bss_start = .);
		*(.sbss .sbss.*)
		*(.bss .bss.*)
		*(COMMON)
		PROVIDE(_bss_end = .);
	} >ram
	PROVIDE(_memory_start = ORIGIN(ram));
	PROVIDE(_memory_end = ORIGIN(ram) + LENGTH(ram));
	PROVIDE(_heap_start = _bss_end);
	PROVIDE(_heap_size = _memory_end - _heap_start);
}

从上面这个简单的链接脚本中可以看出,MEMORY语法和SECTIONS语法是链接脚本里最关键的两个。该链接脚本依次将.text、.rodata、.data等段按顺序放在了ram内存区域内。并通过定义_heap_start和_heap_size把分配以后剩下的全部空间都分给了堆区(该例子中没有给栈分配空间,因为栈也可以start.S中分配在代码空间里,就是上面的.text空间里包含了)。
表面上看,我们的问题解决了,堆的起始地址和长度都知道了,但是问题出现了,在链接脚本中定义的符号可以在C代码中直接使用吗?请看第二小节。

2. 在C语言中使用链接脚本中定义的符号

符号就是名字,变量名和函数名都是符号。在目标文件中有一个专门的"符号表"段用来放符号。比如我们定义一个全局变量:
int g_foo = 100; 显然在data段会有四字节的空间存放100这个值,假设是0x00001000~0x00001003这四个字节。而g_foo这个符号或者说变量名字,则跟0x00001000这个地址一起存放在符号表里。

typedef struct
{
	Elf32_Word  st_name;          /* Symbol name (string tbl index) */字符串表中的字节偏移,指向符号的以null结尾的字符串名称。
	Elf32_Addr   st_value;           /* Symbol value */符号的地址,是距定义目标的节的起始位置的偏移,对于可执行目标文件来说,该值是一个绝对运行时地址。
	Elf32_Word  st_size;             /* Symbol size */对象的长度,例如一个指针的长度或struct中包含的字节数,如果长度未知,其值可以设置为0。
	unsigned char      st_info;     /* Symbol type and binding */ 一个符号的确切用途由st_info定义,它分为两部分。
	unsigned char      st_other;          /* Symbol visibility */未使用
	Elf32_Section       st_shndx;        /* Section index */保存一个节的索引(在节头表中),符号将绑定到该节,该符号通常定义在此节的代码中。
} Elf32_Sym;

符号的结构体定义如上所示。对于上述g_foo这个变量来说,其符号结构体中st_name间接表示"g_foo"这个字符串,st_value值就是0x00001000,st_size代表的则是4。在C语言中,当我们使用&g_foo就可以得到g_foo变量的地址,即st_value。
但是,在链接脚本中定义的符号与变量不同,链接脚本中的符号我们可以认为就是一个地址的别名,目标文件中不会为这个符号开辟存储空间。举个例子,一个符号_heap_start = 0x00002000,其符号结构体中,st_value值就是0x00002000。当我们在C语言中想使用_heap_start,可以通过下面程序实现:

extern int _heap_start ;
int val = &_heap_start ;

val的值就是0x00002000。
顺着思路继续,我们可以在汇编代码中定义一个真正的变量,其值等于符号值,这样在C语言中使用的时候就不用取地址来用了。
在汇编中写下面的代码,.word即为HEAP_START开辟一个word大小的内存空间,值为_heap_start:

.global HEAP_START
HEAP_START: .word _heap_start

然后,就可以在C语言中做extern声明后直接使用HEAP_START,其值就是0x00002000。
参考:https://blog.csdn.net/dropping_1979/article/details/42555349

3. 利用链接脚本把变量指定存放的物理位置
MEMORY
{undefined
   ps7_ddr_0_S_AXI_BASEADDR : ORIGIN = 0x00100000, LENGTH = 0x3FF00000
   ps7_ram_0_S_AXI_BASEADDR : ORIGIN = 0x00000000, LENGTH = 0x00030000
   ps7_ram_1_S_AXI_BASEADDR : ORIGIN = 0xFFFF0000, LENGTH = 0x0000FE00
   ps7_ddr_0_A_AXI_MATRIX  : ORIGIN = 0x20000000, LENGTH = 0x100000
}
.matirx : {undefined
   __matrix_start = .;
   *(.matrix)
   *(.matrix.*)
   __matrix_end = .;
} > ps7_ddr_0_A_AXI_MATRIX
_end = .;
}

首先在链接脚本中定义一段单独的内存区域和单独的section。
然后在C语言编程时,采用下面语句指定全局变量的存储位置就好了。在Xilinx SDK编程中亲测可用。

int matrix[16][16384] __attribute__((section(".matrix")));
  • 4
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值