RISC-V MCU 堆棧機制


RISC-V MCU堆棧機制

1、什么是堆棧?

在嵌入式的世界里,堆棧通常指的是,嚴格來說,堆棧分為堆(Heap)棧(Stack)

  • 棧(Stack): 一種順序數據結構,滿足后進先出(Last-In / First-Out)的原則,由編譯器自動分配和釋放。使用一級緩存,調用完立即釋放。
  • 堆(Heap):類似於鏈表結構,可對任意位置進行操作,通常由程序員手動分配,使用完需及時釋放(free),不然容易造成內存泄漏。使用二級緩存。

image

2、堆棧的作用

  • 函數調用時,如果函數參數和局部變量很多,寄存器放不下,需要開辟棧空間存儲。
  • 中斷發生時,棧空間用於存放當前執行程序的現場數據(下一條指令地址、各種緩存數據),以便中斷結束后恢復現場。

3、堆棧大小定義

RISC-V MCU的堆棧大小通常在ld鏈接腳本中定義,關於ld鏈接腳本可查看該文:RISC-V MCU ld鏈接腳本說明

ENTRY( _start ) /* 入口地址 */ 
 
__stack_size = 2048; /* 定義棧大小 */
PROVIDE( _stack_size = __stack_size );/* 定義_stack_size符號,類似於全局變量 */

MEMORY
{
    FLASH (rx) : ORIGIN = 0x00000000 , LENGTH = 0x10000
    RAM (xrw) : ORIGIN = 0x20000000 , LENGTH = 0x5000
}
/* 
...
中間省略 
...
*/

.stack ORIGIN(RAM) + LENGTH(RAM) - __stack_size :  /* 分配棧空間0x20004800 ~ 0x20005000,共2KB */
{
    . = ALIGN(4);
    PROVIDE(_susrstack = . );
    . = . + __stack_size;
    PROVIDE( _eusrstack = .);
} >RAM 

以RISC-V MCU CH32V103為例,在其ld鏈接腳本中,定義了_stack_size符號,值為 2048 Byte,后面使用該值在.stack段中分配棧空間,可更改此值調整棧空間大小。

CH32V103 的RAM共20KB,除去程序用到的databss段,剩下空間即為動態數據段,供堆棧的動態使用。

ld鏈接腳本中,沒有明確定義heap堆的大小,按照其定義,動態數據段,除了stack占用的,剩下的都可用於heap,通過malloc進行動態管理。

4、壓棧出棧過程

以CH32V103 printf函數調用為例,其反匯編代碼如下:

000007a4 <iprintf>:
     7a4:	7139                	addi  sp,sp,-64         # 調整堆棧指針sp,分配64字節的棧空間
     7a6:	da3e                	sw	a5,52(sp)       # 壓棧,保存a5寄存器的值
     7a8:	d22e                	sw	a1,36(sp)       # 壓棧,按需保存相應的寄存器
     7aa:	d432                	sw	a2,40(sp)
     7ac:	d636                	sw	a3,44(sp)
     7ae:	d83a                	sw	a4,48(sp)
     7b0:	dc42                	sw	a6,56(sp)
     7b2:	de46                	sw	a7,60(sp)
     7b4:	80818793          	addi	a5,gp,-2040 # 20000078 <_impure_ptr>
     7b8:	cc22                	sw	s0,24(sp)       # 壓棧,保存幀指針fp(s0)
     7ba:	4380                	lw	s0,0(a5)
     7bc:	ca26                	sw	s1,20(sp)
     7be:	ce06                	sw	ra,28(sp)       # 壓棧,保存返回地址(ra寄存器)
     7c0:	84aa                	mv	s1,a0
     7c2:	c409                	beqz	s0,7cc <iprintf+0x28>
     7c4:	4c1c                	lw	a5,24(s0)
     7c6:	e399                	bnez	a5,7cc <iprintf+0x28>
     7c8:	8522                	mv	a0,s0
     7ca:	2315                	jal	cee <__sinit>
     7cc:	440c                	lw	a1,8(s0)
     7ce:	1054                	addi	a3,sp,36
     7d0:	8626                	mv	a2,s1
     7d2:	8522                	mv	a0,s0
     7d4:	c636                	sw	a3,12(sp)
     7d6:	167000ef          	jal	ra,113c <_vfiprintf_r>
     7da:	40f2                	lw	ra,28(sp)      # 出棧,恢復返回地址(ra寄存器)
     7dc:	4462                	lw	s0,24(sp)      # 出棧,恢復幀指針fp(s0)
     7de:	44d2                	lw	s1,20(sp)      # 出棧,按需恢復相應的寄存器
     7e0:	6121                	addi	sp,sp,64       # 釋放棧空間
     7e2:	8082                	ret                    # 函數返回,根據ra寄存器地址返回

5、malloc使用注意事項

CH32V103默認工程中,heap只有起始地址,沒有結束地址約束,這樣最終會導致malloc永遠都不會返回NULL。

如果使用malloc時,需進行如下操作:

  1. 重寫_sbrk函數,代碼如下,放在工程任意位置,推薦放在debug.c 文件中。

    _sbrk代碼原型:https://github.com/riscv/riscv-newlib/blob/riscv-newlib-3.1.0/libgloss/libnosys/sbrk.c

    void *_sbrk(ptrdiff_t incr)
    {
    	extern char _end[];
    	extern char _heap_end[];
    	static char *curbrk = _end;
    
    	if ((curbrk + incr < _end) || (curbrk + incr > _heap_end))
    	return NULL - 1;
    
    	curbrk += incr;
    	return curbrk - incr;
    }
    
  2. 修改ld鏈接腳本,定義heap大小

    • 默認RAM中除去data、bss、stack等剩余的都為heap空間

      增加PROVIDE( _heap_end = . ); 定義,位置如下:

          PROVIDE( _end = _ebss);
          PROVIDE( end = . );  /* 定義heap起始位置 */
        
          .stack ORIGIN(RAM) + LENGTH(RAM) - __stack_size :
            {
                 PROVIDE( _heap_end = . );   /* 定義heap結束位置,默認到棧底結束 */ 
            
                . = ALIGN(4);
                PROVIDE(_susrstack = . );
                /*ASSERT ((. > 0x20005000),"ERROR:No room left for the stack");*/
                . = . + __stack_size;
                PROVIDE( _eusrstack = .);
            } >RAM 
    
    • 指定heap大小的修改方式如下:

      增加 PROVIDE( _heap_end = . + 0x400); 定義,位置如下:

      PROVIDE( _end = _ebss);
      PROVIDE( end = . );  /* 定義heap起始位置 */
      PROVIDE( _heap_end = . + 0x400);   /* 定義heap結束位置,長度為1KB */ 
    
      .stack ORIGIN(RAM) + LENGTH(RAM) - __stack_size :
        {
            . = ALIGN(4);
            PROVIDE(_susrstack = . );
            /*ASSERT ((. > 0x20005000),"ERROR:No room left for the stack");*/
            . = . + __stack_size;
            PROVIDE( _eusrstack = .);
        } >RAM 
    

參考:
https://github.com/riscv/riscv-gnu-toolchain/issues/571
https://github.com/lowRISC/ibex/issues/1415


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM