Raw-OS操作系统内核代码汇编部分cpu.asm分析

文章目录

  • 1.和汇编文件相关的头文件部分:
  • 2.raw_start_first_task()函数:
  • 3.raw_task_create(...)任务创建部分:
  • 4.PendSV_Handler中断部分:
  • 注释版代码:

1.和汇编文件相关的头文件部分:

#ifndef RAW_CPU_H
#define RAW_CPU_H
                 
unsigned int OS_CPU_SR_Save(void);
//上述函数是把PRIMASK的值读到R0中,并通过返回值将PRIMASK赋值给cpu_sr
//关于PRIMASK,PRIMASK是只有1个位的寄存器,用于屏蔽中断

void OS_CPU_SR_Restore(unsigned int sr);
//该函数是通过形参传递把cpu_sr的值给R0,然后把R0中的值给PRIMASK
                                                              
#define  RAW_SR_ALLOC()                 unsigned int  cpu_sr = 0
//RAW_SR_ALLOC说明:调用临界区这两个接口时必须要定义一个cpu_sr 变量,
//这个变量是用来存放关中断前的状态寄存器的,看懂代码意思知道这个局部变量只需定义,至于赋值0与否不重要	

#define	 USER_CPU_INT_DISABLE()			{cpu_sr = OS_CPU_SR_Save();}
//上述宏实现将PRIMASK的值通过R0赋值给cpu_sr

#define  USER_CPU_INT_ENABLE()			{OS_CPU_SR_Restore(cpu_sr);}
//上述宏实现将cpu_sr的值通过R0赋值给PRIMASK

#endif

下面是汇编文件中,实现上述头文件中进入临界区和退出临界区的部分:

OS_CPU_SR_Save
    MRS     R0, PRIMASK      ;读取BASEPRI寄存器到R0中。MRS: 状态寄存器到通用寄存器的传送指令
    CPSID   I	;屏蔽所有中断,只剩下NMI、复位中断、硬件中断无法屏蔽,也屏蔽了PendSV
    BX      LR	;跳转到调用该函数的函数中去。LR存的是调用此函数的地址

OS_CPU_SR_Restore
    MSR     PRIMASK, R0		;MSR: 通用寄存器到状态寄存器的传送指令。形参的值通过R0传递,现在将cpu_sr赋值给PRIMASK
    BX      LR	;跳转到调用该函数的函数中去。LR存的是调用此函数的地址

如果对于以上几句没看懂,可以看我的另一篇文章中的详细介绍:
https://blog.csdn.net/wcc243588569/article/details/117825965

2.raw_start_first_task()函数:

在raw_os_start()中,调用了该函数启动操作系统:

NVIC_INT_CTRL   EQU     0xE000ED04  ; Interrupt control state register,写这个地址的第28位可以悬起PENDSV中断,
;参考M3权威指南第131页表8.5

NVIC_SYSPRI14   EQU     0xE000ED22; System priority register (priority 14)。控制PendSV的优先级
;参考M3权威指南第128页表8.3B

NVIC_PENDSV_PRI EQU           0xFF; PendSV priority value (lowest)。设置PendSV的优先级为最低
;把PendSV配置最低优先级,那么如果同时有多个异常被触发,它会在其他异常执行完毕后再执行,而且任何异常都可以中断它

NVIC_PENDSVSET  EQU     0x10000000  ; Value to trigger PendSV exception.将第28位置1,用以悬起PendSV中断

raw_start_first_task;这个函数的作用是设置PendSV的异常中断优先级,并把优先级设置为最低
;设置了PendSV的中断优先级以后,当检测到其他的中断被systick抢占,就会触发PendSV异常,

    LDR     R0, =NVIC_SYSPRI14                   
    LDR     R1, =NVIC_PENDSV_PRI
    STRB    R1, [R0]	;设置PendSV的优先级

    MOVS    R0, #0  ;把R0设置为0                                         
    MSR     PSP, R0	;将PSP任务堆栈指针指向0。MSR: 通用寄存器到状态寄存器的传送指令。把R0的值复制给PSP
;之所以要把PSP指向0,不是要让程序的堆栈从0地址开始,而是要在PendSV_Handler中CBZ R0,OS_CPU_PendSVHandler_nosave检测是否操作系统第一次执行任务

;align msp to 8 byte,8字节对齐
;	MRS 	R0, MSP		;把MSR的值复制给R0
;	LSRS    R0, R0, #3	
;	LSLS    R0, R0, #3		
;	MSR     MSP, R0		;感觉这2句没用,移位后又移回来,没有任何作用,可以去掉,功能不变,没发现有任何影响

    LDR     R0, =NVIC_INT_CTRL                                  
    LDR     R1, =NVIC_PENDSVSET	
    STR     R1, [R0];这一句就是往ICSR第28位写1,触发PendSV异常,若是当前没有高优先级中断产生,那么程序将会进入PendSV handler,可以参考M3权威指南第131页的表进行理解
	;STR是吧第1个数的值复制到第二个数里面

    CPSIE   I   ;开启中断,开启中断后,如果没有其他外部中断,就会响应PendSV handler。否则就会执行下面的OSStartHang                                               

OSStartHang
    B       OSStartHang	;should never get here,一般情况下不会执行到这里,但初始化时可能会执行到这里一下  
;B是跳转的意思

关于以上配置中,为什么要将PendSV配置为最低优先级,可以看我的这篇文章:
https://blog.csdn.net/wcc243588569/article/details/117792602

3.raw_task_create(…)任务创建部分:

本来按顺序应该讲PendSV_Handler中断部分,但是必须要先知道任务创建时干了什么,才能知道任务切换时做了什么。
在raw_task_create函数的形参中,第一个是

RAW_TASK_OBJ  *task_obj

而RAW_TASK_OBJ 的第一个成员是RAW_VOID *task_stack;用户堆栈指针。因此task_obj地址也就是用户用户堆栈的二级指针。
在raw_task_create函数中调用了以下函数

raw_memset(task_obj, 0, sizeof(RAW_TASK_OBJ));

上述函数的作用是把任务控制块结构体的所有内容都初始设置为0

在raw_task_create函数中调用了以下函数

task_obj->task_stack = port_stack_init(task_stack_base, stack_size, task_arg, task_entry);

这个函数的作用就很有意思了,因此我们将该函数内容放到下面:其中去掉了FPU的部分,减少篇幅

RAW_VOID  *port_stack_init(PORT_STACK  *p_stk_base, RAW_U32 stk_size,  RAW_VOID   *p_arg, RAW_TASK_ENTRY p_task)
{
	PORT_STACK *stk;
	RAW_U32 temp = (RAW_U32)(p_stk_base + stk_size);

	temp &= 0xfffffff8;
	
	stk = (PORT_STACK  *)temp;
	                                       
	*(--stk)  =  (RAW_U32)0x01000000L;             /* xPSR                                               */
	*(--stk)  = (RAW_U32)p_task;                    /* Entry Point                                        */
	*(--stk)  = (RAW_U32)0xFFFFFFFEL;             /* R14 (LR) (init value will cause fault if ever used)*/
	*(--stk)  = (RAW_U32)0x12121212L;             /* R12                                                */
	*(--stk)  = (RAW_U32)0x03030303L;             /* R3                                                 */
	*(--stk)  = (RAW_U32)0x02020202L;             /* R2                                                 */
	*(--stk)  = (RAW_U32)0x01010101L;             /* R1                                                 */
	*(--stk)  = (RAW_U32)p_arg;                   /* R0 : argument                                      */
	                                 		
	*(--stk)  = (RAW_U32)0x11111111L;             /* R11                                                */
	*(--stk)  = (RAW_U32)0x10101010L;             /* R10                                                */
	*(--stk)  = (RAW_U32)0x09090909L;             /* R9                                                 */
	*(--stk)  = (RAW_U32)0x08080808L;             /* R8                                                 */
	*(--stk)  = (RAW_U32)0x07070707L;             /* R7                                                 */
	*(--stk)  = (RAW_U32)0x06060606L;             /* R6                                                 */
	*(--stk)  = (RAW_U32)0x05050505L;             /* R5                                                 */
	*(--stk)  = (RAW_U32)0x04040404L;             /* R4                                                 */
	 return stk;
}

上述函数实现的功能是在用户的堆栈中预留出32个字节用于存放寄存器R4-R11数据,再预留出32个字节用于存放R0-R3、R12、R14、任务入口函数地址、xPSR数据。
然后返回的stk是用户开辟的用户堆栈区数据的栈顶地址减去64个字节后的地址,赋值给ask_obj->task_stack。
则ask_obj->task_stack地址递增方向是存放CPU寄存器的地址,往减方向才是用户任务中用到的临时变量等内容存放的用户堆栈区。
任务创建函数中用到了raw_list_entry(node, RAW_TASK_OBJ, task_list)函数,不明白这个函数什么意思的同学可以看我的这一篇文章:
https://blog.csdn.net/wcc243588569/article/details/117754931

4.PendSV_Handler中断部分:

相信看了我https://blog.csdn.net/wcc243588569/article/details/117792602这篇文章的,都知道为什么要在systick中断里面做系统心跳,在PendSV_Handler中做任务切换了。
PendSV_Handler任务切换部分实现过程如下:

PendSV_Handler
    CPSID   I 		;关中断                                 
    MRS     R0, PSP               ;把PSP指针的值赋给R0,PSP指针是双堆栈指针中的一个,
    ;可以参考https://blog.csdn.net/hanchaoman/article/details/103727155                   
    ;也就是说,PSP指针存放的是进PendSV_Handler中断前,用户任务的堆栈区地址。
    ;可以通过切换PSP指针来指向不同任务的堆栈区
    
	CBZ     R0, OS_CPU_PendSVHandler_nosave	;为0则跳转
	;如果程序堆栈指针为0(也就是如果PSP指针是0),说明程序是首次运行,可以直接跳转到OS_CPU_PendSVHandler_nosave,
	;因为是第一次运行,因此不需要不保存任务的R4-R11寄存器的值,

执行到上面后,会因为第一次执行,PSP为0而跳转到下面代码中:

OS_CPU_PendSVHandler_nosave
    LDR     R0, =raw_task_active   ;将目前活跃任务的任务控制块地址放到R0里。因为每1个任务在创建时都执行了port_stack_init函数,
	;这个函数将每个任务的task_stack指向了每个任务栈的栈顶减去用来存放寄存器R0-R12、R14、任务入口地址、xPSP数据的共16*4=64个字节,
	
    LDR     R1, =high_ready_obj		;将目前最高优先级的任务控制块地址放到R1里。
	;因为*task_stack在任务控制块结构体的第一个,任务控制块的起始地址存放的数据也就是*task_stack的值
	;raw_task_active是一个结构体指针,结构体指针指向的地址就是结构体中第一个成员的地址,也就是内部指针*task_stack的地址
	
    LDR     R2, [R1]				;将存储器地址为R1的32位数据读入寄存器R2。也就是high_ready_obj指向的结构体的地址
    STR     R2, [R0]				;刚好与LDR相反,将R2寄存器中的32位数据放到内存地址为寄存器地址R0值的地址中去。
	;通过以上语句,将high_ready_obj指向的结构体所在地址复制给了raw_task_active,
	;raw_task_active指向的结构体是high_ready_obj指向的结构体
	;以上2句并没有改变R1和R0的值
	
	LDR     R0, [R2]                ;R2目前存的high_ready_obj指针指向的结构体地址,[R2]表示的是high_ready_obj指向的结构体中
	;task_stack的内容
	;以上一句,R0目前存的是high_ready_obj指向的结构体中task_stack的内容
	;以上5句,raw_task_active指向了原本high_ready_obj指向的结构体
    LDM     R0, {R4-R11} ;LDM的方向与LDR相反,该指令是把R0指向的地址中的内容复制到R4-R11中,因R4-R11为32位寄存器,共拷贝了4*8=32个字节             
    ;以上语句,把high_ready_obj指向的结构体中task_stack指向的任务堆栈中内容拷贝到了R4-R11寄存器中。其中R0地址+
	ADDS    R0, R0, #0x20	;R0+=32,跳过了用户堆栈中用来存放r3-r11数据的32个字节,再往上就是FPU数据或者R0数据,具体看port_stack_init这个函数
	

	IF		{FPU} != "SoftVFP"
	;只有M4内核有,M3内核没有MPU
	;如果有FPU,则r0+=32,空出32个字节
	VLDMFD	r0!, {d8 - d15} 		; pop FPU register s16~s31。LDMFD是事前递增方式,也就是R0每赋一个值给D8,R0都递增一下
	ENDIF
		
	;经过以上递增后,task_stack再往上就是R0-R3、R12、R14、函数入口、PSR数据了
		
    MSR     PSP, R0 ;Load PSP with new process SP。MSR和MRS是用于在状态寄存器和通用寄存器之间传送数据。
	;MRS: 状态寄存器到通用寄存器的传送指令。MSR: 通用寄存器到状态寄存器的传送指令。
	;把R0的地址给PSP,PSP也就是用户堆栈
	
    ORR     LR, LR, #0x04   ;指令用于在两个操作数上进行逻辑或运算,并把结果放置到目的寄存器中。
	;上句是将LR的低3位拉高。LR是连接寄存器,
    CPSIE   I	;开中断,从PendSV_Handler一进入就关闭中断,到这开中断之间的代码是临界区代码,也就是不可被中断的操作。
    BX      LR	;异常返回。如果是从异常状态返回到线程状态,则使用新的PSP指针作为栈顶指针
	NOP
    END

当第二次再执行时,因为PSP指针不为0了,会在执行

	CBZ     R0, OS_CPU_PendSVHandler_nosave	;0则跳转

判断时,会转而执行:

	IF		{FPU} != "SoftVFP"
	VSTMFD	r0!, {d8 - d15} 		; push FPU register s16~s31
	ENDIF

    SUBS    R0, R0, #0x20 ;R0保存的地址(SP)减去0x20(32),地址减去32,是因为PSP是堆栈栈顶指针,而根据port_stack_init函数,
	;栈顶往下32个字节保存的是R0-R3、R12、R14、程序入口等信息。
	;在响应中断时,ARM架构会自动的保存R0-R3、R12、R14、程序入口等信息,则此时到了R3-R11的R11处。
	;减去32个字节后,R0就指向了存放R3所在的地址,进而执行下面指令,地址递增,将R3-R11共32个字节数据保存在用户堆栈中           
    STM     R0, {R4-R11}	;8个寄存器的值装进R0减去32个字节的地方。

    LDR     R1, =raw_task_active              
    LDR     R1, [R1]
    STR     R0, [R1]                      

	PUSH    {R14}
	bl      raw_stack_check
	POP     {R14}

自此,整个汇编文件我们讲解完毕。
*如果感觉对你有帮助,还希望帮忙点个赞、收藏一下哦^~^!!!

注释版代码:

我把带详细注释的程序下载链接放到了下面,是不要积分不要C币免费下载的。有下载不了的可以联系我,留下邮箱
https://download.csdn.net/download/wcc243588569/19576360

你可能感兴趣的:(嵌入式软件开发)