(学习日记)2024.03.03:UCOSIII第五节:常用汇编指令+OS初始化+启动任务+任务切换
- 互联网
- 2025-08-03 03:21:02

写在前面: 由于时间的不足与学习的碎片化,写博客变得有些奢侈。 但是对于记录学习(忘了以后能快速复习)的渴望一天天变得强烈。 既然如此 不如以天为单位,以时间为顺序,仅仅将博客当做一个知识学习的目录,记录笔者认为最通俗、最有帮助的资料,并尽量总结几句话指明本质,以便于日后搜索起来更加容易。 标题的结构如下:“类型”:“知识点”——“简短的解释” 部分内容由于保密协议无法上传。 点击此处进入学习日记的总目录
2024.03.03 十、UCOSIII:常用汇编指令十一、UCOSIII:OS系统初始化十二、UCOSIII:启动系统十三、UCOSIII:任务切换1、PendSV异常2、PendSV异常服务函数 十、UCOSIII:常用汇编指令 指令名称作用EQU给数字常量取一个符号名,相当于C语言中的defineAREA汇编一个新的代码段或者数据段SPACE分配内存空间PRESERVE8当前文件栈需按照8字节对齐EXPORT声明一个标号具有全局属性,可被外部的文件使用DCD以字为单位分配内存,要求4字节对齐,并要求初始化这些内存PROC定义子程序,与ENDP成对使用,表示子程序结束WEAK弱定义,如果外部文件声明了一个标号,则优先使用外部文件定义的标号,如果外部文件没有定义也不出错。要注意的是:这个不是ARM的指令,是编译器的,这里放在一起只是为了方便。IMPORT声明标号来自外部文件,跟C语言中的EXTERN关键字类似B跳转到一个标号ALIGN编译器对指令或者数据的存放地址进行对齐,一般需要跟一个立即数,缺省表示4字节对齐。要注意的是:这个不是ARM的指令,是编译器的,这里放在一起只是为了方便。END到达文件的末尾,文件结束IF,ELSE,ENDIF汇编条件分支语句,跟C语言的if else类似MRS加载特殊功能寄存器的值到通用寄存器MSR存储通用寄存器的值到特殊功能寄存器CBZ比较,如果结果为0 就转移CBNZ比较,如果结果非0 就转移LDR从存储器中加载字到一个寄存器中LDR[伪指令]加一个立即数或者一个地址值到一个寄存器。举例:LDRLDRH从存储器中加载半字到一个寄存器中LDRB从存储器中加载字节到一个寄存器中STR把一个寄存器按字存储到存储器中STRH把一个寄存器存器的低半字存储到存储器中STRB把一个寄存器的低字节存储到存储器中LDMIA加载多个字,并且在加载后自增基址寄存器STMIA存储多个字,并且在存储后自增基址寄存器ORR按位或BX直接跳转到由寄存器给定的地址BL跳转到标号对应的地址,并且把跳转前的下条指令地址保存到LRBLX跳转到由寄存器REG给出的的地址,并根据REG的LSB切换处理器状态,还要把转移前的下条指令地址保存到LR。ARM(LSB=0),Thumb(LSB=1)。CM3 只在Thumb中运行,就必须保证reg 的LSB=1,否则一个fault 打过来为了快速地开关中断, CM3 专门设置了一条 CPS 指令,有 4 种用法 PRIMASK和FAULTMAST是CM3里面三个中断屏蔽寄存器中的两个,还有一个是BASEPRI, 有关这三个寄存器的详细用法见表
名字功能描述PRIMASK这是个只有单一比特的寄存器。在它被置1后,就关掉所有可屏蔽的异常,只剩下NMI和硬FAULT可以响应。它的缺省值是0,表示没有关中断。FAULTMASK这是个只有1个位的寄存器。当它置1时,只有NMI才能响应,所有其他的异常,甚至是硬FAULT,也通通闭嘴。它的缺省值也是0,表示没有关异常。BASEPRI这个寄存器最多有9位(由表达优先级的位数决定)。它定义了被屏蔽优先级的阈值。当它被设成 某个值后,所有优先级号大于等于此值的中断都被关(优先级号越大,优先级越低)。但若被设成0,则不关闭任何中断,0也是缺省值。 十一、UCOSIII:OS系统初始化OS系统初始化一般是在硬件初始化完成之后来做的,主要做的工作就是初始化μC/OS-III中定义的全局变量。 OSInit()函数在文件 os_core.c中定义
/* RTOS初始化 ** 初始化全局变量 */ void OSInit (OS_ERR *p_err) { OSRunning = OS_STATE_OS_STOPPED; OSTCBCurPtr = (OS_TCB *)0; OSTCBHighRdyPtr = (OS_TCB *)0; OS_RdyListInit(); *p_err = OS_ERR_NONE; } OSRunning = OS_STATE_OS_STOPPED; 系统用一个全局变量OSRunning来指示系统的运行状态, 刚开始系统初始化的时候,默认为停止状态,即OS_STATE_OS_STOPPED。 OSTCBCurPtr = (OS_TCB *)0; 全局变量OSTCBCurPtr是系统用于指向当前正在运行的任务的TCB指针,在任务切换的时候用得到。OSTCBHighRdyPtr = (OS_TCB *)0; 全局变量OSTCBHighRdyPtr用于指向就绪任务中优先级最高的任务的TCB,在任务切换的时候用得到。 此处暂时不支持优先级,则用于指向第一个运行的任务的TCB。OS_RdyListInit(); OS_RdyListInit()用于初始化全局变量OSRdyList[],即初始化就绪列表。 OS_RdyListInit()在os_core.c文件中定义*p_err = OS_ERR_NONE; 代码运行到这里表示没有错误,即OS_ERR_NONE。全局变量OSTCBCurPtr和OSTCBHighRdyPtr均在os.h中定义 十二、UCOSIII:启动系统任务创建好,系统初始化完毕之后,就可以开始启动系统了。系统启动函数OSStart()在os_core.c中定义
if ( OSRunning == OS_STATE_OS_STOPPED ) 系统是第一次启动的话,if 肯定为真,则继续往下运行。
OSTCBHighRdyPtr = OSRdyList[0].HeadPtr; OSTCBHIghRdyPtr 指向第一个要运行的任务的TCB。 因为暂时不支持优先级,所以系统启动时先手动指定第一个要运行的任务。
OSStartHighRdy(); OSStartHighRdy()用于启动任务切换,即配置PendSV的优先级为最低,然后触发PendSV异常, 在PendSV异常服务函数中进行任务切换。该函数不再返回,在文件os_cpu_a.s中定义,由汇编语言编写
LDR R0, = NVIC_SYSPRI14 配置PendSV的优先级为0XFF,即最低。在μC/OS-III中, 上下文切换是在PendSV异常服务程序中执行的,配置PendSV的优先级为最低,从而消灭了在中断服务程序中执行上下文切换的可能。
MOVS R0, #0 设置PSP的值为0,开始第一个任务切换。在任务中, 使用的栈指针都是PSP,后面如果判断出PSP为0,则表示第一次任务切换。
LDR R0, =NVIC_INT_CTRL 触发PendSV异常,如果中断启用且有编写PendSV异常服务函数的话, 则内核会响应PendSV异常,去执行PendSV异常服务函数。
CPSIE I 开中断,因为有些用户在main()函数开始会先关掉中断, 等全部初始化完成后,在启动OS的时候才开中断。 汇编常用指令作用如下
十三、UCOSIII:任务切换 1、PendSV异常当调用OSStartHighRdy()函数,触发PendSV异常后,就需要编写PendSV异常服务函数,然后在里面进行任务切换。
PendSV异常服务函数名称必须与启动文件里面向量表中PendSV的向量名一致, 如果不一致则内核是响应不了用户编写的PendSV异常服务函数的,只响应启动文件里面默认的PendSV异常服务函数。
启动文件里面为每个异常都编写好默认的异常服务函数,函数体都是一个死循环,当你发现代码跳转到这些启动文件里面默认的异常服务函数的时候, 就要检查下异常函数名称是否写错了,没有跟向量表里面的一致。
PendSV异常服务中主要完成两个工作, 一是保存上文,即保存当前正在运行的任务的环境参数; 二是切换下文, 即把下一个需要运行的任务的环境参数从任务栈中加载到CPU寄存器,从而实现任务的切换。PendSV异常服务中用到了OSTCBCurPtr和OSTCBHighRdyPtr这两个全局变量,这两个全局变量在os.h中定义, 要想在汇编文件os_cpu_a.s中使用,必须将这两个全局变量导入到os_cpu_a.s中
IMPORT OSTCBCurPtr IMPORT OSTCBHighRdyPtr 使用IMPORT关键字将os.h中的OSTCBCurPtr和OSTCBHighRdyPtr这两个全局变量导入到该汇编文件, 从而该汇编文件可以使用这两个变量。如果是函数也可以使用IMPORT导入的方法。
EXPORT OSStartHighRdy EXPORT PendSV_Handler 使用EXPORT关键字导出该汇编文件里面的OSStartHighRdy和PendSV_Handler这两个函数, 让外部文件可见。除了使用EXPORT导出外,还要在 某个C的头文件里面声明下这两个函数(在μC/OS-III中是在os_cpu.h中声明),这样才可以在C文件里面调用这两个函数。
2、PendSV异常服务函数 ;******************************************************************************************************** ; PendSVHandler异常 ;******************************************************************************************************** PendSV_Handler ; 任务的保存,即把CPU寄存器的值存储到任务的堆栈中 CPSID I ; 关中断,NMI和HardFault除外,防止上下文切换被中断 MRS R0, PSP ; 将psp的值加载到R0 CBZ R0, OS_CPU_PendSVHandler_nosave ; 判断R0,如果值为0则跳转到OS_CPU_PendSVHandler_nosave ; 进行第一次任务切换的时候,R0肯定为0 ;-----------------------一、保存上文----------------------------- ; 任务的切换,即把下一个要运行的任务的栈内容加载到CPU寄存器中 ; 在进入PendSV异常的时候,当前CPU的xPSR,PC(任务入口地址),R14,R12,R3,R2,R1,R0会自动存储到当前任务堆栈,同时递减PSP的值 ;-------------------------------------------------------------- STMDB R0!, {R4-R11} ; 手动存储CPU寄存器R4-R11的值到当前任务的堆栈 LDR R1, = OSTCBCurPtr ; 加载 OSTCBCurPtr 指针的地址到R1,这里LDR属于伪指令 LDR R1, [R1] ; 加载 OSTCBCurPtr 指针到R1,这里LDR属于ARM指令 STR R0, [R1] ; 存储R0的值到 OSTCBCurPtr->OSTCBStkPtr,这个时候R0存的是任务空闲栈的栈顶 ;-----------------------二、切换下文----------------------------- ; 实现 OSTCBCurPtr = OSTCBHighRdyPtr ; 把下一个要运行的任务的栈内容加载到CPU寄存器中 ; 任务的切换,即把下一个要运行的任务的堆栈内容加载到CPU寄存器中 ;-------------------------------------------------------------- OS_CPU_PendSVHandler_nosave ; OSTCBCurPtr = OSTCBHighRdyPtr; LDR R0, = OSTCBCurPtr ; 加载 OSTCBCurPtr 指针的地址到R0,这里LDR属于伪指令 LDR R1, = OSTCBHighRdyPtr ; 加载 OSTCBHighRdyPtr 指针的地址到R1,这里LDR属于伪指令 LDR R2, [R1] ; 加载 OSTCBHighRdyPtr 指针到R2,这里LDR属于ARM指令 STR R2, [R0] ; 存储 OSTCBHighRdyPtr 到 OSTCBCurPtr LDR R0, [R2] ; 加载 OSTCBHighRdyPtr 到 R0 LDMIA R0!, {R4-R11} ; 加载需要手动保存的信息到CPU寄存器R4-R11 MSR PSP, R0 ; 更新PSP的值,这个时候PSP指向下一个要执行的任务的堆栈的栈底(这个栈底已经加上刚刚手动加载到CPU寄存器R4-R11的偏移) ORR LR, LR, #0x04 ; 确保异常返回使用的堆栈指针是PSP,即LR寄存器的位2要为1 CPSIE I ; 开中断 BX LR ; 异常返回,这个时候任务堆栈中的剩下内容将会自动加载到xPSR,PC(任务入口地址),R14,R12,R3,R2,R1,R0(任务的形参) ; 同时PSP的值也将更新,即指向任务堆栈的栈顶。在STM32中,堆栈是由高地址向低地址生长的。 NOP ; 为了汇编指令对齐,不然会有警告 END ; 汇编文件结束CPSID I 关中断,NMI和HardFault除外,防止上下文切换被中断。 在上下文切换完毕之后,会重新开中断。
MRS R0, PSP 将PSP的值加载到R0寄存器。MRS是ARM 32位数据加载指令, 功能是加载特殊功能寄存器的值到通用寄存器。
CBZ R0, OS_CPU_PendSVHandler_nosave 判断R0,如果值为0则跳转到OS_CPU_PendSVHandler_nosave。进行第一次任务切换的时候, PSP在OSStartHighRdy初始化为0,所以这个时候R0肯定为0,则跳转到OS_CPU_PendSVHandler_nosave。 CBZ是ARM16位转移指令,用于比较,结果为0则跳转。
STMDB R0!, {R4-R11} 手动存储CPU寄存器R4-R11的值到当前任务的栈。 当异常发生,进入PendSV异常服务函数的时候, 当前CPU寄存器xPSR,PC(任务入口地址),R14,R12,R3,R2,R1,R0会自动存储到当前的任务栈,同时递减PSP的值, 这个时候当前任务的栈空间分布具体见 进入PendSV异常时当前任务的栈空间分布图。
当执行STMDB R0!, {R4-R11}代码后, 当前任务的栈空间分布具体见 当前任务执行完上文保存时的栈空间分布图
STR R0, [R1] 存储R0的值到OSTCBCurPtr->OSTCBStkPtr, 这个时候R0存的是任务空闲栈的栈顶。到了这里,上文的保存就总算完成。 这个时候当前任务的栈空间分布和栈指针指向具体见 当前任务执行完上文保存时的栈空间分布和StkPtr指向图
OS_CPU_PendSVHandler_nosave 当第一次任务切换的时候,会跳转到这里运行。当执行过一次任务切换之后, 则顺序执行到这里。这个标号以后的内容属于下文切换。
LDR R0, = OSTCBCurPtr 加载 OSTCBCurPtr 指针的地址到R0。在ARM汇编中,操作变量都属于间接操作, 即要先获取到这个变量的地址。这里LDR属于伪指令,不是ARM指令。举例:LDR Rd, = label,如果label是立即数, 那Rd等于立即数,如果label是一个标识符,比如指针,那存到Rd的就是label这个标识符的地址。
STR R2, [R0] 存储 OSTCBHighRdyPtr 到 OSTCBCurPtr, 实现下一个要运行的任务的TCB存储到OSTCBCurPtr。
LDR R0, [R2] 加载 OSTCBHighRdyPtr 到 R0。TCB中第一个成员是栈指针StkPtr, 所以这个时候R0等于StkPtr,后续操作任务栈都是通过操作R0来实现,不需要操作StkPtr。
LDMIA R0!, {R4-R11} LDMIA中的I是increase的缩写,A是after的缩小, R0后面的感叹号“!”表示会自动调节R0里面存的指针。 将任务栈中需要手动加载的内容加载到CPU寄存器R4-R11,同时会递增R0, 让R0指向空闲栈的栈顶,栈空间的分布情况具体见 任务创建成功后栈空间的分布图。
当任务被创建的时候,任务的栈会被初始化, 初始化的流程是: 先让栈指针StkPtr指向栈顶, 然后从栈顶开始依次存储 异常退出时会自动加载到CPU寄存器 的值和 需要手动加载到CPU寄存器 的值, 具体代码实现见OSTaskStkInit()函数。 当把需要手动加载到CPU的栈内容加载完毕之后,栈空间的分布图和栈指针指向具体见图 手动加载栈内容到CPU寄存器后的栈空间分布图 , 注意这个时候StkPtr不变,变的是R0。
MSR PSP, R0 更新PSP的值,这个时候PSP与图3‑4中R0的指向一致。
ORR LR, LR, #0x04 设置LR寄存器的位2为1,确保异常退出时使用的栈指针是PSP。 当异常退出后,就切换到就绪任务中优先级最高的任务继续运行。
CPSIE I 开中断。上下文切换已经完成了四分之三,剩下的就是异常退出时自动保存的部分。
BX LR 异常返回,这个时候任务栈中的剩下内容将会自动加载到xPSR,PC(任务入口地址), R14,R12,R3,R2,R1,R0(任务的形参)这些寄存器。同时PSP的值也将更新,即指向任务栈的栈顶。 这样就切换到了新的任务。 这个时候栈空间的分布具体见 刚切换完成即将运行的任务的栈空间分布和栈指针指向图
(学习日记)2024.03.03:UCOSIII第五节:常用汇编指令+OS初始化+启动任务+任务切换由讯客互联互联网栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“(学习日记)2024.03.03:UCOSIII第五节:常用汇编指令+OS初始化+启动任务+任务切换”
上一篇
初学者学习51还是STM32