RISC-V 32架构实践专题二(青稞V4F启动代码分析)

一、链接文件分析

//设置代码的入口点,链接器在进行链接时,将此标签作为入口地址
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系统。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: RISC-V架构手册是一本介绍RISC-V指令集体系结构的重要参考资料。RISC-V是一种开放的指令集架构,具有可扩展性、灵活性和高效性的特点。 首先,RISC-V架构手册详细介绍了RISC-V指令集中的所有指令,包括常用的算术指令、逻辑指令、存取指令、分支指令等。它不仅列出了每个指令的编码格式和操作码,还详细说明了每个指令的功能和使用方法。这样的信息对于开发RISC-V处理器的工程师来说至关重要。 其次,RISC-V架构手册还介绍了RISC-V处理器的基本结构和中断处理机制。它详细解释了数据通路、控制单元和存储器等组件的连接方式和工作原理。同时,手册还讨论了RISC-V处理器的中断处理流程,包括中断的触发条件、中断处理程序的执行和中断返回等。这对于编写操作系统或者驱动程序的开发者来说非常有帮助。 此外,RISC-V架构手册还包含了关于扩展特性和可选模块的说明。它介绍了如何在RISC-V架构上扩展自定义指令或者功能,以及如何添加可选的模块,如浮点单元、虚拟内存管理等。这样的灵活性使得RISC-V架构非常适合于各种应用领域,从嵌入式系统到超级计算机。 总的来说,RISC-V架构手册是开发人员和研究人员掌握RISC-V指令集和架构设计的重要参考资料。它全面介绍了RISC-V指令集的各个方面,并提供了实现RISC-V处理器的指导。通过学习和理解这本手册,人们可以更好地应用和推广RISC-V架构,推动开源指令集的发展。 ### 回答2: RISC-V(精简指令集计算机-五)架构手册是一份完整的指南,用于描述和解释RISC-V计算机架构的细节和规范。这个手册为开发者和研究人员提供了一个详细的参考,以了解和标准化他们在RISC-V处理器设计和实现方面的工作。 RISC-V架构手册包含多个章节和附录,涵盖了RISC-V指令集的不同方面。它首先介绍了RISC-V的设计原理和目标,比如简洁性、可扩展性和定制化能力。然后,手册详细说明了RISC-V指令集的不同指令格式和编码规则,包括指令解码过程和操作码的定义。 这个手册还涵盖了RISC-V的寄存器和寄存器文件,描述了它们的使用方法和特殊规则。此外,手册还提供了关于异常处理机制和中断处理机制的解释,以及RISC-V中的特权级别和特权模式的详细信息。 RISC-V架构手册还包含了有关内存管理单元(MMU)和虚拟内存系统的信息,说明了RISC-V支持的不同内存访问方式和存储体系结构的细节。此外,手册还提供了有关浮点运算和向量指令集的详细说明以及其使用方法。 除了这些主要内容外,RISC-V架构手册还提供了一些附录,包括指令集的变种和扩展,以及示例代码和编程实例。这些附录为开发者提供了实际应用和开发RISC-V处理器的指导支持。 总之,RISC-V架构手册是一个重要的参考资料,用于理解和使用RISC-V计算机架构。它的详细说明和规范为开发者提供了标准化和统一化的参考,以便设计、实现和优化RISC-V处理器。 ### 回答3: RISC-V架构手册是一本详细介绍RISC-V指令集架构的重要参考资料。RISC-V是一种新兴的开源指令集架构,其优势在于简洁、可扩展和高度灵活。RISC-V架构手册系统地介绍了RISC-V指令集的各种特性和用法。 首先,RISC-V架构手册提供了RISC-V指令集的全面介绍。它详细解释了RISC-V的指令编码方式、寄存器组织、内存管理机制等基本概念。通过学习手册,人们可以了解到RISC-V指令的格式和操作方式,从而能够编写符合RISC-V架构的程序。 其次,RISC-V架构手册系统介绍了RISC-V的扩展指令集。RISC-V提供了一种模块化的设计理念,允许用户根据不同的应用需求选择使用不同的指令集扩展。手册详细介绍了RISC-V的各种扩展,如乘法/除法扩展、向量扩展等,并提供了使用这些扩展的示例和指导。 另外,RISC-V架构手册还介绍了RISC-V的异常处理和中断机制。这些机制对于系统安全和稳定运行非常重要。手册详细解释了异常和中断的分类、处理流程以及相关的指令和寄存器。通过学习手册,人们可以了解到如何在RISC-V架构中设计有效的异常处理和中断控制机制。 最后,RISC-V架构手册还介绍了RISC-V的特殊指令和特殊寄存器。这些特殊指令和寄存器常用于系统级编程和性能优化。手册提供了这些指令和寄存器的详细说明和使用方法,帮助人们充分发挥RISC-V架构的优势。 综上所述,RISC-V架构手册是学习和理解RISC-V指令集架构的重要工具。通过研读手册,人们可以掌握RISC-V的基本概念、指令格式和操作方式,进而能够灵活应用RISC-V的各种扩展和特殊功能,为不同的应用场景设计高效的RISC-V架构系统。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值