一、什么是BSS段(ZI段)
: x( h( _+ z+ p- R7 B4 ~bss段(bss segment)通常是指用来存放程序中未初始化的全局变量的一块内存区域。
% Q5 z$ F" o! b S) j4 Ebss是英文Block Started by Symbol的简称。
* P; ]5 ]( A7 t& [bss段属于静态内存分配。
1 u$ a/ N: S+ O
: c+ x, J0 u/ K) P: B2 D而在Keil里,BSS段被称为ZI段
+ o( Q9 {# l: `6 H1 l" v; F8 Z9 l6 w; P1 A: u; Q1 v" F7 s
二、为什么要复制data段- v* ]! V0 o) v6 _% H/ O; _
这是因为对于在STM32F103这类资源紧缺的单片机芯片中,数据段只是暂时先保存在Flash上,在使用前被复制到内存里,而复制到内存这个过程是需要我们自己实现的。实际上在keil中,*(InRoot$$Sections)这一段就是keil帮我们做好的一段代码,当我们调用main函数时,keil会帮我们自动添加。但是为了学习,我们来自己实现一下,那么就需要知道数据段的加载地址(在Flash上的位置)和链接地址(需要放在内存中的位置),在keil中获取这些地址的方法如下:; v2 a6 d: \5 |3 Y4 E3 c8 o
; R7 [+ b! g' ~
- 数据段的加载地址: Load$RW_IRAM1$Base, }7 B% A+ n7 h' B
- 数据段的链接地址: Image$RW_IRAM1$Base
, ~2 B2 s# H1 _2 H- b! Q - 数据段的长度: Image$RW_IRAM1$Length
% u' f* E# q3 [; L, {- A, l - ZI段的链接地址: Image$RW_IRAM1$ZI$Base' ]/ W' @- P. a- n
- ZI段的长度: Image$RW_IRAM1$ZI$Length
复制代码
. ~, q; W% A4 Z; c, v5 T三、编写代码实现
2 l, A& t/ z+ B将start.s修改为如下所示8 U( D' E0 I8 Y+ P/ }
! i+ f0 u; R6 l) G2 Z; |) Y
- Stack_Size EQU 0x00000500 ;定义堆栈大小为1024byte) [ r! D9 ~6 g. k$ k) ^8 w
- AREA STACK, NOINIT, READWRITE, ALIGN=3 ;定义一个数据段,标记为STACK,即栈,不写入初始值初,对RAM来说,即初始化为0,8字节对齐2 N" A1 p% ^. q
- Stack_Mem SPACE Stack_Size ;保留Stack_Size大小的栈空间
6 T7 x8 X- F% ^# C/ b( W& g - __initial_sp ;标号,代表堆栈顶部地址,后面有用
9 y; z& ?- }' z - 2 {+ X: f6 I) D1 k% m0 j5 h
- PRESERVE8 ;指示编译器8字节对齐
! k4 ]$ } ?" a$ B: ^ - THUMB ;指示编译器以后的指令为THUMB指令 0 D( ?- c3 h( B4 H7 K
5 k( g% S. ^/ q9 @( }# n: r
: ?3 |; n# ]! _( ?- ; Vector Table Mapped to Address 0 at Reset6 _3 \; X6 B. u) u7 N1 w
- AREA RESET, CODE, READONLY ;定义只读数据段,标记为RESET,其实放在CODE区,位于0地址
# A# I6 z' K3 s; u3 s - EXPORT __Vectors ;在程序中声明一个全局的标号__Vectors,该标号可在其他的文件中引用9 k8 N6 L) ~9 v% o+ d0 Y
- * I' M3 B' k- B9 A, I1 O( O+ D
- __Vectors DCD __initial_sp ;当前地址写入一个字(32bit)数据,值应该为栈顶地址1 Q4 K6 m7 h& y D) l' P6 I1 U/ x
- DCD Reset_Handler ;当前地址写入一个字(32bit)数据,值为Reset_Handler指向的地址值,即程序入口地址
, x/ P7 i; n2 O1 E+ {' f5 m& a
/ s" t$ }, c o% q- AREA |.text|, CODE, READONLY ;定义代码段,标记为.text
1 `$ {- H4 J4 E- }# e. W - 2 i5 G; a% v$ }4 C" N
- ; Reset handler ;利用PROC、ENDP这一对伪指令把程序段分为若干个过程,使程序的结构加清晰& L8 U: }/ u0 j7 u
- Reset_Handler PROC ;过程的开始 . B6 _- F$ F [2 y
- EXPORT Reset_Handler [WEAK] ;[WEAK] 弱定义,意思是如果在别处也定义该标号(函数),在链接时用别处的地址。$ c# K* Q; x& M; H
- 0 L* Z9 c+ y! G+ _
- IMPORT |Image$RW_IRAM1$Base| ;从别处导入data段的链接地址1 B( R4 U( {0 O( l' U) `5 D
- IMPORT |Image$RW_IRAM1$Length| ;从别处导入data段的长度% b a( ?$ d" I
- IMPORT |Load$RW_IRAM1$Base| ;从别处导入data段的加载地址
C5 L0 i2 G" \( u/ ], M; n/ | - IMPORT |Image$RW_IRAM1$ZI$Base| ;从别处导入ZI段的链接地址
7 L: s5 V5 E% N/ D - IMPORT |Image$RW_IRAM1$ZI$Length|;从别处导入ZI段的长度7 `6 E7 d, M `0 T
# U( p4 f) y0 |& e7 ^6 A# \- ; 复制数据段
6 c8 \& B3 t! {5 T- ~& D - LDR R0, = |Load$RW_IRAM1$Base| ;将data段的加载地址存入R0寄存器4 j8 @! u; E; V; b- P- H- ~
- LDR R1, = |Image$RW_IRAM1$Base| ;将data段的链接地址存入R1寄存器
2 p8 V p5 f$ Q0 y; Z5 _/ |: E - LDR R2, = |Image$RW_IRAM1$Length| ;将data段的长度存入R2寄存器
' P& e. m& O3 t! P5 o) x - CopyData
; a6 t# }4 s8 k/ i0 o/ R - SUB R2, R2, #4 ;每次复制4个字节的data段数据0 j6 C J9 }3 E" k
- LDR R3, [R0, R2] ;把加载地址处的值取出到R3寄存器
. U! p3 x1 n) K6 C- C+ h1 K( W - STR R3, [R1, R2] ;把取出的值从R3寄存器存入到链接地址 0 H6 C# w/ G: L) A) ]/ ?7 ]& ?6 N' J
- CMP R2, #0 ;将计数和0相比较
1 @) E. z0 z' U+ o3 ] - BNE CopyData ;如果不相等,跳转到CopyData标签处,相等则往下执行
q' r% S D! |4 a
! P! ?: ]# [* O3 R8 s8 u- ; 清除BSS段
5 Q6 {. t- s) o - LDR R0, = |Image$RW_IRAM1$ZI$Base| ;将bss段的链接地址存入R1寄存器
! Y9 H' t( ^' w- { - LDR R1, = |Image$RW_IRAM1$ZI$Length| ;将bss段的长度存入R2寄存器- {/ p+ j& N9 G% L2 U
- CleanBss
& c; S; ~- `% h( f* T! g - SUB R1, R1, #4 ;每次清除4个字节的bss段数据
0 l% A; A3 J9 f# h( @4 P7 n E - MOV R3, #0 ;将0存入r3寄存器8 P) h' M& s* F1 m4 ^6 U) X3 n
- STR R3, [R0, R1] ;把R3寄存器存入到链接地址
" J4 g0 Y9 T5 B2 ]2 }1 ], }, E' j: w* @ - CMP R1, #0 ;将计数和0相比较, S+ s0 ]) b0 J$ L; ]7 O! p
- BNE CleanBss ;如果不相等,跳转到CleanBss标签处,相等则往下执行
# ^+ x. s' M8 a6 \1 U( E - 7 r! m5 o6 I8 _+ A* u( o6 M0 V
- ; x3 r5 P9 n! a! O0 P$ |
- IMPORT mymain ;通知编译器要使用的标号在其他文件/ m; y' n) U8 R' t5 I) r) p) c
- BL mymain ;跳转去执行main函数7 o Y$ L9 s, \; @% z6 W( F4 V
- B . ;原地跳转,即处于循环状态
! H2 j n) S2 ^' R1 Z9 R _* N - ENDP# Y& v/ C- o# ?0 h2 o3 c& D
- 2 P0 s9 ]1 h, d; j' U* G# I
- ALIGN ;填充字节使地址对齐' J+ ^- x0 p- }3 P+ [# |
- END ;整个汇编文件结束
! k: N6 @6 d4 a. o) U5 k0 b
复制代码
' X" \4 @, i& |' w6 b四、验证" u# z8 ^, W1 v' C9 w, N; F
其他文件不做修改,编译烧录运行,可以看到所有变量的值都正确了
6 f) H6 I( E' \' T+ a% |/ D" k8 H# b/ c5 e# ^ x
9 e5 q% n: l( e( w
. z2 S c, t4 _) t8 ^ I% D五、注意3 c2 `( W. F2 L8 d) A; ~% `
我们之前看过编译出来的hex文件,发现myzero和my变量也存在,这就意味着这两个本该在bss段的变量也会在Flash中,这就很奇怪了,而事实是:# a3 F6 [/ F, e |4 q# n ?
2 Q; S) L2 w$ g) b6 O7 G对于keil来说,一个本该放到BSS段的变量,如果它所占据的空间小于等于8字节自己,keil仍然会把它放在data段里。只有当它所占据的空间大于8字节时,才会放到BSS段。
. j; P( i5 s' b3 s/ C( Y; O, d1 k' o; N$ l7 `% c, N4 ]
那么我们将myzero修改为一个数组2 R9 ?3 @- L V4 s, H
5 ]% \: y" R/ t% R6 o3 V* [% w/ \- y o3 U1 E6 W* d/ ]
这样查看hex文件,可以看到,只有my变量存在了1 D. f& e w: I- |+ f5 [/ H
% H9 l8 T, l/ R% z# k$ W
- Z' `- _" h% l' F; k
, o" O8 {5 R. S& u
% Q/ U3 G: L! w. D: ~ |