一、链接文件分析
//设置代码的入口点,链接器在进行链接时,将此标签作为入口地址
ENTRY( _start )
//设置栈空间大小为2KB
__stack_size = 2048;
//定义一个符号,并且指定_stack_size符号的初值为2KB,最后在链接的过程中,链接器会为此符号分配内存空间并将其与此内存空间地址关联起来;
//经过PROVIDE定义的符号,可以在代码中引用。
PROVIDE( _stack_size = __stack_size );
//声明内存空间的布局。FLASH和RAM是空间的名称;(rx)和(xrw)是空间的权限;ORIGIN和LENGTH是空间的起始地址与长度。
MEMORY
{
FLASH (rx) : ORIGIN = 0x00000000, LENGTH = 256K
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 64K
}
//段描述
SECTIONS
{
//init段,此段基本存放的是初始代码
.init :
{
_sinit = .;
//将此处地址进行4字节对齐
. = ALIGN(4);
//KEEP的作用是保留.init段中的所有内容,SORT_NONE的作用则是不对此段进行优化排序;使得.init段按照源代码中的顺序保持不变。
KEEP(*(SORT_NONE(.init)))
. = ALIGN(4);
_einit = .;
} >FLASH AT>FLASH
//‘AT>’运算符指定了段在可执行文件或者可加载文件中的加载地址,也就是它们在存储器中的位置;
//‘>’运算符则指定了段在程序运行时的实际内存地址,也就是程序在运行时会被加载到的内存地址。
.vector :
{
//向量段,一般是用于存放中断向量表的段
*(.vector);
. = ALIGN(64);
} >FLASH AT>FLASH
//此段则为代码段,存放代码的位置
.text :
{
. = ALIGN(4);
*(.text)
*(.text.*)
*(.rodata)
*(.rodata*)
*(.gnu.linkonce.t.*)
/* section information for finsh shell */
. = ALIGN(4);
__fsymtab_start = .;
KEEP(*(FSymTab))
__fsymtab_end = .;
. = ALIGN(4);
__vsymtab_start = .;
KEEP(*(VSymTab))
__vsymtab_end = .;
. = ALIGN(4);
/* section information for initial. */
//此处需要注意一下,在rt-thread代码中,此段用于存放指向一系列初始化函数的向量表。
. = ALIGN(4);
__rt_init_start = .;
//这里与上面的SORT_NONE不同,这里的作用是保留所有以‘.rti_fn’开头的符号,并按名称排序;这样做的好处在于,代码中可以将此段内容看作一个数组进行轮询。
KEEP(*(SORT(.rti_fn*)))
__rt_init_end = .;
. = ALIGN(4);
/* section information for modules */
. = ALIGN(4);
__rtmsymtab_start = .;
KEEP(*(RTMSymTab))
__rtmsymtab_end = .;
. = ALIGN(4);
} >FLASH AT>FLASH
.fini :
{
KEEP(*(SORT_NONE(.fini)))
. = ALIGN(4);
} >FLASH AT>FLASH
PROVIDE( _etext = . );
PROVIDE( _eitcm = . );
.preinit_array :
{
PROVIDE_HIDDEN (__preinit_array_start = .);
KEEP (*(.preinit_array))
PROVIDE_HIDDEN (__preinit_array_end = .);
} >FLASH AT>FLASH
.init_array :
{
PROVIDE_HIDDEN (__init_array_start = .);
KEEP (*(SORT_BY_INIT_PRIORITY(.init_array.*) SORT_BY_INIT_PRIORITY(.ctors.*)))
KEEP (*(.init_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .ctors))
PROVIDE_HIDDEN (__init_array_end = .);
} >FLASH AT>FLASH
.fini_array :
{
PROVIDE_HIDDEN (__fini_array_start = .);
KEEP (*(SORT_BY_INIT_PRIORITY(.fini_array.*) SORT_BY_INIT_PRIORITY(.dtors.*)))
KEEP (*(.fini_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .dtors))
PROVIDE_HIDDEN (__fini_array_end = .);
} >FLASH AT>FLASH
.ctors :
{
/* gcc uses crtbegin.o to find the start of
the constructors, so we make sure it is
first. Because this is a wildcard, it
doesn't matter if the user does not
actually link against crtbegin.o; the
linker won't look for a file to match a
wildcard. The wildcard also means that it
doesn't matter which directory crtbegin.o
is in. */
KEEP (*crtbegin.o(.ctors))
KEEP (*crtbegin?.o(.ctors))
/* We don't want to include the .ctor section from
the crtend.o file until after the sorted ctors.
The .ctor section from the crtend file contains the
end of ctors marker and it must be last */
KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .ctors))
KEEP (*(SORT(.ctors.*)))
KEEP (*(.ctors))
} >FLASH AT>FLASH
.dtors :
{
KEEP (*crtbegin.o(.dtors))
KEEP (*crtbegin?.o(.dtors))
KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .dtors))
KEEP (*(SORT(.dtors.*)))
KEEP (*(.dtors))
} >FLASH AT>FLASH
//对齐段,下面的两段用于定义_data_vma和_data_lma两个符号,并进行赋值;
.dalign :
{
//这里将此处的运行地址赋值给_data_vma符号
. = ALIGN(4);
PROVIDE(_data_vma = .);
} >RAM AT>FLASH
.dlalign :
{
//这里将此处的加载地址赋值给_data_lma符号
. = ALIGN(4);
PROVIDE(_data_lma = .);
} >FLASH AT>FLASH
//数据段,用于存放数据
.data :
{
*(.gnu.linkonce.r.*)
*(.data .data.*)
*(.gnu.linkonce.d.*)
. = ALIGN(8);
PROVIDE( __global_pointer$ = . + 0x800 );
*(.sdata .sdata.*)
*(.sdata2.*)
*(.gnu.linkonce.s.*)
. = ALIGN(8);
*(.srodata.cst16)
*(.srodata.cst8)
*(.srodata.cst4)
*(.srodata.cst2)
*(.srodata .srodata.*)
. = ALIGN(4);
//这里将此处的运行地址赋值给_edata符号
PROVIDE( _edata = .);
} >RAM AT>FLASH
//在启动汇编代码中,将出现对数据段的搬运操作;而搬运的地址与大小,就在这3段描述中;
//定义bss段
.bss :
{
. = ALIGN(4);
PROVIDE( _sbss = .);
*(.sbss*)
*(.gnu.linkonce.sb.*)
*(.bss*)
*(.gnu.linkonce.b.*)
*(COMMON*)
. = ALIGN(4);
PROVIDE( _ebss = .);
} >RAM AT>FLASH
PROVIDE( _end = _ebss);
PROVIDE( end = . );
//最后进行栈的划分;将内存的最后2KB大小空间,划分出来作为栈使用。
.stack ORIGIN(RAM) + LENGTH(RAM) - __stack_size :
{
//并且栈的起始处,其实就是堆空间的结束处。
PROVIDE( _heap_end = . );
. = ALIGN(4);
PROVIDE(_susrstack = . );
. = . + __stack_size;
PROVIDE( _eusrstack = .);
} >RAM
}
二、启动汇编分析
//声明此代码链接时的位置在 .init 段。
.section .init,"ax",@progbits
//声明全局符号 _start ,在链接脚本中使用了此符号,用于申明代码入口。
.global _start
//进行2^1对齐。
.align 1
_start:
//跳转到 handle_reset 标签处执行;j,跳转,并且不会存储返回地址。
j handle_reset
.word 0x00000013
.word 0x00000013
.word 0x00000013
.word 0x00000013
.word 0x00000013
.word 0x00000013
.word 0x00000013
.word 0x00000013
.word 0x00000013
.word 0x00000013
.word 0x00000013
.word 0x00000013
.word 0x00100073
//声明下面的段为 .vector 段,用于存储中断向量表的。
.section .vector,"ax",@progbits
.align 1
_vector_base:
.option norvc;
// .word 用于声明一个字(word)的存储空间,也就是4字节;其中的每一项存储的都是对应异常处理函数的基地址。
.word _start
.word 0
.word NMI_Handler /* NMI */
.word HardFault_Handler /* Hard Fault */
.word 0
.word Ecall_M_Mode_Handler /* Ecall M Mode */
.word 0
.word 0
.word Ecall_U_Mode_Handler /* Ecall U Mode */
.word Break_Point_Handler /* Break Point */
.word 0
.word 0
.word SysTick_Handler /* SysTick */
.word 0
.word SW_Handler /* SW */
.word 0
/* External Interrupts */
.word WWDG_IRQHandler /* Window Watchdog */
...... //忽略此部分中断定义
.word DMA2_Channel11_IRQHandler /* DMA2 Channel 11 */
.option rvc;
.section .text.vector_handler, "ax", @progbits
//这里非常重要,.weak声明了一个弱符号,使得代码中可以对此符号有多个定义,然后在链接时选择其中某一个定义。
.weak NMI_Handler /* NMI */
.weak HardFault_Handler /* Hard Fault */
.weak Ecall_M_Mode_Handler /* Ecall M Mode */
.weak Ecall_U_Mode_Handler /* Ecall U Mode */
.weak Break_Point_Handler /* Break Point */
.weak SysTick_Handler /* SysTick */
.weak SW_Handler /* SW */
.weak WWDG_IRQHandler /* Window Watchdog */
...... //忽略此部分中断申明
.weak DMA2_Channel11_IRQHandler /* DMA2 Channel 11 */
//这里就是对上述的弱符号进行一个默认定义,也就是实现了一个死循环;当对应的中断函数在c代码中实现后,代码在编译链接时,将链接到c代码中实现的中断函数。
NMI_Handler: 1: j 1b
HardFault_Handler: 1: j 1b
Ecall_M_Mode_Handler: 1: j 1b
Ecall_U_Mode_Handler: 1: j 1b
Break_Point_Handler: 1: j 1b
SysTick_Handler: 1: j 1b
SW_Handler: 1: j 1b
WWDG_IRQHandler: 1: j 1b
...... //忽略此部分中断定义
DMA2_Channel11_IRQHandler: 1: j 1b
.section .text.handle_reset,"ax",@progbits
.weak handle_reset
.align 1
handle_reset:
.option push
.option norelax
csrw mepc, t0
la gp, __global_pointer$
.option pop
1:
//设置栈指针,_eusrstack 在链接文件中进行了定义和赋值
la sp, _eusrstack
2:
//这里就是将数据段从flash中拷贝到ram中去,结合第一章链接文件的描述即可理解
/* Load data section from flash to RAM */
la a0, _data_lma
la a1, _data_vma
la a2, _edata
//比较a1和a2,如果a1大于等于a2时,则无需进行拷贝数据段
bgeu a1, a2, 2f
1:
//进行数据段的拷贝
lw t0, (a0)
sw t0, (a1)
addi a0, a0, 4
addi a1, a1, 4
bltu a1, a2, 1b
2:
//将bss段进行清零处理
/* Clear bss section */
la a0, _sbss
la a1, _ebss
bgeu a0, a1, 2f
1:
sw zero, (a0)
addi a0, a0, 4
bltu a0, a1, 1b
2:
//配置微处理器流水线、指令预测等相关特性
li t0, 0x1f
csrw 0xbc0, t0
//使能硬件压栈功能和中断嵌套功能
/* Enable nested and hardware stack */
li t0, 0x1f
csrw 0x804, t0
# csrw 0x804, zero
//使能浮点运行,并且将MPP设置为机器模式;也就是说当进行mret返回时,也会回到机器模式中来。
/* Enable floating point and interrupt */
li t0, 0x7800
csrs mstatus, t0
//最后进行中断向量表的基地址设置;并且设置中断采用绝对地址跳转(即中断向量表中存放的就是异常处理函数的地址,而不是跳转到异常处理函数的指令),以及采用向量模式而非统一入口地址模式
//这里可以详细描述一下关于向量模式以及统一入口模式;对于统一入口地址模式来说,当发生异常时,不管是什么种类异常,都只有一个入口地址,也就是跳转到同一个基地址;而向量模式则当发生异常时,将根据异常或中断的编号*4再加上基地址进行偏移跳转。
la t0, _vector_base
ori t0, t0, 3
csrw mtvec, t0
//跳转到c代码中,进行时钟的初始配置
jal SystemInit
//设置entry函数地址到mepc中,当mret返回时,将跳转到mepc中的地址执行。
la t0, entry
csrw mepc, t0
mret
以上基本完成了青稞V4F的启动代码分析,当执行完mret指令后,将进入到entry函数中;而entry函数为一个rtos的入口函数,下一部分则将进行讲解在riscv架构下,运行的一个rtos系统。