文章目录
- 4. 杂项
- 4.1 检查是否否是2的幂
- 5. 预编译命令
- void类型和rt_noreturn类型的区别
- 6.map文件分析
- 7.汇编.s文件
- 7.1 汇编指令
- 7.1.1 BX
- 7.1.2 LR链接寄存器
- 7.1.4 []的作用
- 7.1.4 简单的指令
- 7.2 MSR
- 7.3 PRIMASK寄存器
- 7.4.中断启用禁用
- 7.3 HardFault_Handler
- 15 ARM指针寄存器
- 16 IDLE线程
- 16.1 defunct流程
https://github.com/wdfk-prog/RT-Thread-Study
4. 杂项
4.1 检查是否否是2的幂
- 检查sz_blk是否是2的幂。原理如下:
如果一个数是2的幂,那么它的二进制表示中只有一个位是1,其余都是0。例如,2(10),4(100),8(1000)等。
当我们从这个数中减去1时,所有从最右边的1开始到最左边的所有位都会翻转。例如,4(100)减去1变成3(011)。
因此,如果一个数是2的幂,那么这个数与它自己减去1的结果进行位与运算,会得到0。因为没有位同时在两个数中都是1。
反之,如果一个数不是2的幂,那么它至少有一个位不是1,这样减去1之后,至少有一个位在两个数中都是1,位与运算的结果不为0。
这个技巧在编程中经常被用来快速检查一个数是否是2的幂,因为它比循环或递归方法更高效。
#define IS_POWER_OF_TWO(x) (((x) & ((x) - 1)) == 0)
5. 预编译命令
#define __RT_STRINGIFY(x...) #x
#define RT_STRINGIFY(x...) __RT_STRINGIFY(x)
#define rt_section(x) __attribute__((section(x)))
#define rt_used __attribute__((used))
#define rt_align(n) __attribute__((aligned(n)))
#define rt_weak __attribute__((weak))
#define rt_typeof __typeof__
#define rt_noreturn __attribute__ ((noreturn))
#define rt_inline static __inline
#define rt_always_inline static inline __attribute__((always_inline))
-__RT_STRINGIFY(x...)
和 RT_STRINGIFY(x...)
:这两个宏用于将参数转换为字符串。__RT_STRINGIFY
直接将参数转换为字符串,而 RT_STRINGIFY
则通过 __RT_STRINGIFY
间接完成转换,以确保参数先被宏展开再转换为字符串。
-rt_section(x)
:这个宏用于将特定的函数或变量放入指定的段(section)中。
-rt_noreturn
:这个宏用于指示函数不会返回。这对于像 exit()
或 abort()
这样的函数很有用。
rt_noreturn
是一个函数属性,用于告诉编译器这个函数不会返回到调用者。这个属性可以帮助编译器进行优化。
在C语言中,大多数函数在完成它们的工作后都会返回到调用者。然而,有些函数,如 exit()
或 abort()
,在被调用后不会返回。这是因为它们会终止程序的执行,或者跳转到其他的执行流程,而不是返回到原来的位置。当编译器看到一个函数被声明为 noreturn
,它就知道这个函数不会返回到调用者。这样,编译器就可以省略一些针对函数返回的代码生成和优化。例如,编译器可能不需要保存寄存器的值,或者不需要在函数调用后生成一些可能永远不会执行的代码。
void类型和rt_noreturn类型的区别
void
类型的函数和带有 rt_noreturn
属性的函数之间的主要区别在于它们的行为,而不仅仅是它们的返回值。
void
类型的函数确实没有返回值,但这并不意味着它们不会返回到调用者。当 void
函数完成其工作后,控制权会返回到调用该函数的代码。
然而,带有 rt_noreturn
属性的函数永远不会返回到调用者。这意味着一旦调用了这样的函数,程序的控制流就不会回到原来的位置。这对于像 exit()
或 abort()
这样的函数来说是非常有用的,因为这些函数在被调用后会终止程序的执行,或者跳转到其他的执行流程。
所以,rt_noreturn
并不是关于函数的返回值的,而是关于函数是否会返回到调用者。这个属性可以帮助编译器进行更好的优化,因为编译器知道一旦调用了 rt_noreturn
函数,就不需要生成任何后续的代码。希望这个解释对你有所帮助!
6.map文件分析
Image$$ER_IROM1$$Base 0x90000000 Number 0 anon$$obj.o ABSOLUTE
__Vectors 0x90000000 Data 4 startup_stm32h750xx.o(RESET)
__Vectors_End 0x90000298 Data 0 startup_stm32h750xx.o(RESET)
__main 0x90000299 Thumb Code 0 entry.o(.ARM.Collect$$$$00000000)
_main_stk 0x90000299 Thumb Code 0 entry2.o(.ARM.Collect$$$$00000001)
_main_scatterload 0x9000029d Thumb Code 0 entry5.o(.ARM.Collect$$$$00000004)
__main_after_scatterload 0x900002a1 Thumb Code 0 entry5.o(.ARM.Collect$$$$00000004)
_main_clock 0x900002a1 Thumb Code 0 entry7b.o(.ARM.Collect$$$$00000008)
_main_cpp_init 0x900002a1 Thumb Code 0 entry8b.o(.ARM.Collect$$$$0000000A)
_main_init 0x900002a1 Thumb Code 0 entry9a.o(.ARM.Collect$$$$0000000B)
__rt_final_cpp 0x900002a9 Thumb Code 0 entry10a.o(.ARM.Collect$$$$0000000D)
__rt_final_exit 0x900002a9 Thumb Code 0 entry11a.o(.ARM.Collect$$$$0000000F)
rt_hw_interrupt_disable 0x900002ad Thumb Code 8 context_rvds.o(.text)
rt_hw_interrupt_enable 0x900002b5 Thumb Code 6 context_rvds.o(.text)
rt_hw_context_switch 0x900002bb Thumb Code 32 context_rvds.o(.text)
rt_hw_context_switch_interrupt 0x900002bb Thumb Code 0 context_rvds.o(.text)
PendSV_Handler 0x900002db Thumb Code 108 context_rvds.o(.text)
rt_hw_context_switch_to 0x90000347 Thumb Code 76 context_rvds.o(.text)
rt_hw_interrupt_thread_switch 0x90000393 Thumb Code 2 context_rvds.o(.text)
HardFault_Handler 0x90000395 Thumb Code 56 context_rvds.o(.text)
MemManage_Handler 0x90000395 Thumb Code 0 context_rvds.o(.text)
rt_memcpy 0x900003e9 Thumb Code 0 rt_memcpy_rvds.o(.text)
Reset_Handler 0x9000060d Thumb Code 8 startup_stm32h750xx.o(.text)
- Reset_Handler 根据链接脚本设置
ENTRY(Reset_Handler)
;应为RAM首地址位置;实际并不是
原因:
- 在汇编启动文件中,首先设置了向量表,再设置复位函数
; Vector Table Mapped to Address 0 at Reset
AREA RESET, DATA, READONLY
EXPORT __Vectors
EXPORT __Vectors_End
EXPORT __Vectors_Size
__Vectors DCD __initial_sp ; Top of Stack
DCD Reset_Handler ; Reset Handler
-Reset_Handler
编写中调用 SystemInit
与 __main
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT SystemInit
IMPORT __main
LDR R0, =SystemInit
BLX R0
LDR R0, =__main
BX R0
ENDP
-__main
中执行rtt初始化:https://www.rt-thread.org/document/api/group___system_init.html#details
7.汇编.s文件
https://zhuanlan.zhihu.com/p/98888285
7.1 汇编指令
7.1.1 BX
- BX指令:在ARM汇编语言中,BX指令用于跳转到指令中所指定的目标地址。这个目标地址可以是ARM指令,也可以是Thumb指令。BX指令的格式为:
BX {条件} 目标地址
。这个指令的特点是它可以改变处理器的状态,从ARM状态切换到Thumb状态,或者从Thumb状态切换到ARM状态。这种状态切换的功能使得BX指令在实现子程序调用和处理器工作状态切换时非常有用。
7.1.2 LR链接寄存器
-
LR链接寄存器:在ARM架构中,链接寄存器(Link Register,简称LR)通常用于存储子程序返回地址。当执行BL(带返回的跳转指令)或BLX(带返回和状态切换的跳转指令)时,处理器会将下一条指令的地址保存到LR中。然后,当子程序执行完毕后,可以通过将LR的内容加载到程序计数器(PC)中,从而返回到调用者。这种机制使得子程序的调用和返回变得非常方便和高效。
-
在用户模式下,LR(或R14)用作链接寄存器,用于存储子程序调用时的返回地址。如果返回地址存储在堆栈上,它也可以用作通用寄存器。
在异常处理模式中,LR 保存异常的返回地址,或者如果在异常内执行子例程调用,则保存子例程返回地址。如果返回地址存储在堆栈中,LR 可以用作通用寄存器。
7.1.4 []的作用
-
在这段ARM汇编代码中,LDR r3, [r2] 是一条加载指令。这条指令的作用是从内存中加载数据。
-
r3 和 r2 是寄存器,它们是CPU中用于临时存储数据的小存储区。在这个上下文中,r3 和 r2 只是寄存器的名称,它们没有特殊的含义,只是用于标识这些寄存器。
[r2] 的含义是:使用 r2 寄存器中的值作为内存地址,从该地址加载数据。[] 的作用是表示间接寻址,也就是说,我们不是直接使用 r2 的值,而是使用 r2 中的值作为一个内存地址,从这个地址中获取数据。
所以,LDR r3, [r2] 的整体含义是:从内存中的 r2 所指向的地址加载数据,然后将这些数据存储到 r3 寄存器中。这就是这条指令的作用。
7.1.4 简单的指令
- LDR: 将地址加载到寄存器中。
- CMP: 比较两个操作数的值。
- BEQ
7.2 MSR
- MSR指令[1][2][3][4]:在ARM汇编语言中,MSR(Move to Status Register)指令用于将操作数的内容传送到程序状态寄存器的特定域中。其中,操作数可以为通用寄存器或立即数。MSR指令通常用于恢复或改变程序状态寄存器的内容。例如,当需要修改状态寄存器的内容时,可以通过“读取-修改-写回”指令序列完成。这种操作通常用于切换处理器模式、或者允许/禁止IRQ/FIQ中断等。
7.3 PRIMASK寄存器
- PRIMASK寄存器[5][6][7]:在ARM Cortex-M处理器中,PRIMASK寄存器用于控制中断的优先级,允许屏蔽(禁止)特定优先级的中断。PRIMASK寄存器是一个单比特(bit)的寄存器,只有两个有效的取值:0和1。当PRIMASK寄存器的值为0时,表示所有中断都可以触发。当PRIMASK寄存器的值为1时,会禁止所有可屏蔽的中断。这意味着通过设置 PRIMASK 寄存器为 1,可以禁用所有中断,从而实现临界区的保护或者实现禁止中断的功能。
7.4.中断启用禁用
/*
* rt_base_t rt_hw_interrupt_disable();
*/
.global rt_hw_interrupt_disable
.type rt_hw_interrupt_disable, %function
rt_hw_interrupt_disable:
//将PRIMASK写入RO寄存器
MRS r0, PRIMASK
//设置CPSID为I,用于禁用中断
CPSID I
BX LR
/*
* void rt_hw_interrupt_enable(rt_base_t level);
*/
.global rt_hw_interrupt_enable
.type rt_hw_interrupt_enable, %function
rt_hw_interrupt_enable:
//从RO取回PRIMASK
MSR PRIMASK, r0
BX LR
- 由于将PRIMASK的值暂存在r0中,执行临界段代码时r0值会不会改变?
https://club.rt-thread.org/ask/question/d5156cdf3abb63a1.html
7.3 HardFault_Handler
.global HardFault_Handler
.type HardFault_Handler, %function
HardFault_Handler:
/* 获取当前上下文 */
MRS r0, msp /* 从处理程序获取故障上下文 */
TST lr, #0x04 /* 如果!EXC_RETURN[2] */
BEQ _get_sp_done
MRS r0, psp /* 从线程获取故障上下文 */
_get_sp_done:
STMFD r0!, {r4 - r11} /* 压入r4 - r11寄存器 */
#if defined (__VFP_FP__) && !defined(__SOFTFP__)
STMFD r0!, {lr} /* 压入标志的占位符 */
#endif
STMFD r0!, {lr} /* 压入exec_return寄存器 */
TST lr, #0x04 /* 如果!EXC_RETURN[2] */
BEQ _update_msp
MSR psp, r0 /* 更新堆栈指针到PSP */
B _update_done
_update_msp:
MSR msp, r0 /* 更新堆栈指针到MSP */
_update_done:
PUSH {LR}
BL rt_hw_hard_fault_exception /* 调用硬件故障异常处理函数 */
POP {LR}
ORR lr, lr, #0x04
BX lr /* 返回 */
15 ARM指针寄存器
https://blog.csdn.net/zhuguanlin121/article/details/120883025
-堆栈指针r13 SP:每一种异常模式都有其自己独立的r13,它通常指向异常模式所专用的堆栈,也就是说五种异常模式、非异常模式(用户模式和系统模式),都有各自独立的堆栈,用不同的堆栈指针来索引。这样当ARM进入异常模式的时候,程序就可以把一般通用寄存器压入堆栈,返回时再出栈,保证了各种模式下程序的状态的完整性。
栈顶指针(Stack Pointer)是寄存器页的核心,用以指向系统栈的栈顶位置,某些情况下也可以作为通用寄存器来使用,例如,在 ARM Cortex M 内核中,SP 可以作为 R13 来使用。由于栈是函数式语言的核心,在操作系统中 SP 的地位举足轻重,以 RT-Thread 为例,每个用户任务都有独享的栈,任务的切换几乎就是栈的切换,也就是栈顶指针的切换,我们可以毫不夸张的说:栈顶指针就是每个任务的生命线。
-连接寄存器r14 LR:每种模式下r14都有自身版组,它有两个特殊功能。
(1)保存子程序返回地址。使用BL或BLX时,跳转指令自动把返回地址放入r14中;子程序通过把r14复制到PC来实现返回,通常用下列指令之一:
(2)当异常发生时,异常模式的r14用来保存异常返回地址,将r14如栈可以处理嵌套中断。
(3) LR 本质上相当于一个深度为 1 的硬件栈,支持且仅支持 1 级函数调用。
PC 指针(Program Counter)和 LR 指针(Link Return)是寄存器页的核心,用于实现流水线的执
行和分支,详细内容我们在本章的开头已经详细讨论过。LR 寄存器在某些情况下也可以作为通用寄存
器来使用,例如,在 ARM Cortex M 内核中,LR 可以作为 R14 来使用。
-程序计数器r15 PC:PC是有读写限制的。当没有超过读取限制的时候,读取的值是指令的地址加上8个字节,由于ARM指令总是以字对齐的,故bit[1:0]总是00。当用str或stm存储PC的时候,偏移量有可能是8或12等其它值。在V3及以下版本中,写入bit[1:0]的值将被忽略,而在V4及以上版本写入r15的bit[1:0]必须为00,否则后果不可预测。
IF 阶段从什么地址读取指令是由 PC 指针控制的,修改其值就可以实现程序的分支。
16 IDLE线程
- cleanup 会在线程退出时,被空闲线程回调一次以执行用户设置的清理现场等工作。
16.1 defunct流程
- rt_thread_defunct_enqueue 将退出线程和分离线程插入到defunct链表中
- IDLE线程会在空闲时,执行defunct链表中的线程,将线程节点从链表中移除
- 从对象容器中移除线程对象
- 执行线程清除函数,释放线程控制块