第3章 进程 - RTOS 进程切换

 

1 参考文档

 6. 线程的定义与线程切换的实现 — [野火]RT-Thread内核实现与应用开发实战——基于STM32 文档

RTOS的线程,是一个独立的、无限循环且不能返回的函数。

 

 

2 定义线程栈

RTOS中存在多线程和线程切换,没有进程的概念,也不需要进程的概念;

每个线程都有线程栈,通常是预先定义好的全局数组,存在RAM中。

比如定义两个线程栈:

/* 定义线程栈*/
rt_uint8_t rt_flag1_thread_stack[512];
rt_uint8_t rt_flag2_thread_stack[512];

线程栈里面应该放什么?后面会讲到。

 

 

3 定义线程控制块

struct rt_thread,类似Linux中的进程描述符struct_task;

struct rt_thread//    (1)
{void        *sp;              /* 线程栈指针 *//* 线程函数名,表示入口地址* 线程是一个独立的、无限循环且不能返回的函数*/void        *entry;void        *parameter;       /* 线程形参 *//* 线程栈的起始地址* 就是刚才分配的rt_flag1_thread_stack和rt_flag2_thread_stack*/void        *stack_addr;rt_uint32_t stack_size;       /* 线程栈大小,单位为字节 */rt_list_t   tlist;            /* 线程链表节点 */
};/* 定义线程控制块 */
struct rt_thread rt_flag1_thread;
struct rt_thread rt_flag2_thread;

 

 

4 实现线程创建函数

4.1 rt_thread_init()

rt_err_t rt_thread_init(struct rt_thread *thread,//          (1)void (*entry)(void *parameter),//    (2)void             *parameter,//       (3)void             *stack_start,//     (4)rt_uint32_t       stack_size)//      (5)
{rt_list_init(&(thread->tlist));//                         (6)thread->entry ==== (void *)entry;//                       (7)thread->parameter ==== parameter;//                       (8)thread->stack_addr ==== stack_start;//                    (9)thread->stack_size ==== stack_size;//                     (10)/* 初始化线程栈,并返回线程栈指针 */thread->sp ==== (void *)rt_hw_stack_init( thread->entry,thread->parameter, (void *)((char *)thread->stack_addr + thread->stack_size) );return RT_EOK;
}/* 线程就绪列表*/
rt_list_t rt_thread_priority_table[RT_THREAD_PRIORITY_MAX];/* 调用 */
/* 初始化线程1 */
rt_thread_init(&rt_flag1_thread,                 /* 线程控制块 */flag1_thread_entry,               /* 线程入口地址 */RT_NULL,                          /* 线程形参 */&rt_flag1_thread_stack[0],        /* 线程栈起始地址 */sizeof(rt_flag1_thread_stack) );  /* 线程栈大小,单位为字节 *//* 将线程插入到就绪列表 */
rt_list_insert_before( &(rt_thread_priority_table[0]), &(rt_flag1_thread.tlist) );/* 初始化线程2 */
rt_thread_init(&rt_flag2_thread,                 /* 线程控制块 */flag2_thread_entry,               /* 线程入口地址 */RT_NULL,                          /* 线程形参 */&rt_flag2_thread_stack[0],        /* 线程栈起始地址 */sizeof(rt_flag2_thread_stack) );  /* 线程栈大小,单位为字节 *//* 将线程插入到就绪列表 */
rt_list_insert_before( &(rt_thread_priority_table[1]), &(rt_flag2_thread.tlist) );

 

4.2 rt_hw_stack_init()

① 相关结构体

struct stack_frame
{/* r4 ~ r11 register异常发生时需手动保存的寄存器 */rt_uint32_t r4;rt_uint32_t r5;rt_uint32_t r6;rt_uint32_t r7;rt_uint32_t r8;rt_uint32_t r9;rt_uint32_t r10;rt_uint32_t r11;struct exception_stack_frame exception_stack_frame;/* 异常发生时自动保存的寄存器 */rt_uint32_t r0;rt_uint32_t r1;rt_uint32_t r2;rt_uint32_t r3;rt_uint32_t r12;rt_uint32_t lr;rt_uint32_t pc;rt_uint32_t psr;
};

 ② 实现

/* 线程栈初始化 */
rt_uint8_t *rt_hw_stack_init(void       *tentry,//                  (1)void       *parameter,//                 (2)rt_uint8_t *stack_addr)//                (3)
{struct stack_frame *stack_frame;//                               (4)rt_uint8_t         *stk;unsigned long       i;/* 获取栈顶指针 */stk  ==== stack_addr;/* stk指针继续向下移动sizeof(struct stack_frame)个偏移 */stk -= sizeof(struct stack_frame);//                             (7)/* 将stk指针强制转化为stack_frame类型后存到stack_frame */stack_frame ==== (struct stack_frame *)stk;//                       (8)/* 以stack_frame为起始地址,将栈空间里面的sizeof(struct stack_frame)个内存初始化为0xdeadbeef */for (i = 0; i < sizeof(struct stack_frame) / sizeof(rt_uint32_t); i ++)//   (9){((rt_uint32_t *)stack_frame)[i] = 0xdeadbeef;}/* 初始化异常发生时自动保存的寄存器 —— 在哪里自动保存?*///                            (10)stack_frame->exception_stack_frame.r0  ==== (unsigned long)parameter; /* r0 : argument */stack_frame->exception_stack_frame.r1  ==== 0;                        /* r1 */stack_frame->exception_stack_frame.r2  ==== 0;                        /* r2 */stack_frame->exception_stack_frame.r3  ==== 0;                        /* r3 */stack_frame->exception_stack_frame.r12 ==== 0;                        /* r12 */stack_frame->exception_stack_frame.lr  ==== 0;                        /* lr */stack_frame->exception_stack_frame.pc  ==== (unsigned long)tentry;    /* entry point, pc */stack_frame->exception_stack_frame.psr ==== 0x01000000L;              /* PSR *//* 返回线程栈指针,指向stack_frame,或者说,指向r4 */return stk;//                                                    (11)
}
  • 初始化线程栈,其实就是初始化线程栈中靠近栈顶位置的stack_frame结构体
  • stack_frame->expection_stack_frame.r0、pc、psr分别是入参、线程函数指针和PSR值
  • stack_frame的r4~r10暂时不用保存

③ 示意图

  •  线程栈地址是ThreadStk[0],也就是rt_flag1_thread_stack数组的地址
  • 线程栈地址不是线程栈指针,后者也就是线程控制块中的sp,指的是存放r4~psr的一块区域的地址,也就是指向r4
  • 从线程栈地址找到线程栈指针,需要从线程栈栈顶往下偏移来找

 

 

5 实现调度器

5.1 调度器初始化函数

/* 初始化系统调度器 */
void rt_system_scheduler_init(void)
{register rt_base_t offset;//                                        (1)/* 线程就绪列表初始化 */for (offset ==== 0; offset < RT_THREAD_PRIORITY_MAX; offset ++)//    (2){rt_list_init(&rt_thread_priority_table[offset]);}/* 初始化当前线程控制块指针 */rt_current_thread ==== RT_NULL;//                                        (3)
}

 

5.2 调度器初始化

int main(void)
{/* 硬件初始化 *//* 将硬件相关的初始化放在这里,如果是软件仿真则没有相关初始化代码 *//* 调度器初始化 */rt_system_scheduler_init();/* 初始化线程 */rt_thread_init( &rt_flag1_thread,                 /* 线程控制块 */flag1_thread_entry,               /* 线程入口地址,也就是线程函数 */RT_NULL,                          /* 线程形参 */&rt_flag1_thread_stack[0],        /* 线程栈起始地址 */sizeof(rt_flag1_thread_stack) );  /* 线程栈大小,单位为字节 *//* 将线程插入到就绪列表 */rt_list_insert_before( &(rt_thread_priority_table[0]),&(rt_flag1_thread.tlist) );/* 初始化线程 */rt_thread_init( &rt_flag2_thread,                 /* 线程控制块 */flag2_thread_entry,               /* 线程入口地址,也就是线程函数 */RT_NULL,                          /* 线程形参 */&rt_flag2_thread_stack[0],        /* 线程栈起始地址 */sizeof(rt_flag2_thread_stack) );  /* 线程栈大小,单位为字节 *//* 将线程插入到就绪列表 */rt_list_insert_before( &(rt_thread_priority_table[1]),&(rt_flag2_thread.tlist) );/* 启动系统调度器 */rt_system_scheduler_start();
}

 

5.3 启动调度器

/* 启动系统调度器 */
void rt_system_scheduler_start(void)
{register struct rt_thread *to_thread;/* 手动指定第一个运行的线程 *///                                     (1)to_thread ==== rt_list_entry(rt_thread_priority_table[0].next,struct rt_thread,tlist);rt_current_thread ==== to_thread;//                                   (2)/* 切换到第一个线程,该函数在context_rvds.S中实现,在rthw.h声明,用于实现第一次任务切换。* 当一个汇编函数在C文件中调用的时候,如果有形参,则执行的时候会将形参传入到CPU寄存器r0。* 注意,这里传入的参数是线程栈指针的指针!*/rt_hw_context_switch_to((rt_uint32_t)&to_thread->sp); //           (3)
}

 

6 线程切换

6.1 rt_hw_context_switch_to()

;*************************************************************************
;                                 全局变量
; IMPORT用于导入全局变量
; 在该函数中需要修改这些全局变量,并在其他地方要用到
;*************************************************************************IMPORT rt_thread_switch_interrupt_flag        ; PendSV中断服务函数执行标志IMPORT rt_interrupt_from_thread               ; 用于存储上一个线程的栈的sp的指针IMPORT rt_interrupt_to_thread                 ; 用于存储下一个将要运行的线程的栈的sp的指针;*************************************************************************
;                                 常量                                 (5)
;*************************************************************************
;-------------------------------------------------------------------------
;有关内核外设寄存器定义可参考官方文档:STM32F10xxx Cortex-M3 programming manual
;系统控制块外设SCB地址范围:0xE000ED00-0xE000ED3F
;-------------------------------------------------------------------------
SCB_VTOR        EQU     0xE000ED08     ; 向量表偏移寄存器
NVIC_INT_CTRL   EQU     0xE000ED04     ; 中断控制状态寄存器
NVIC_SYSPRI2    EQU     0xE000ED20     ; 系统优先级寄存器(2)
NVIC_PENDSV_PRI EQU     0x00FF0000     ; PendSV 优先级值 (lowest)
NVIC_PENDSVSET  EQU     0x10000000     ; 触发PendSV exception的值;*************************************************************************
;                              代码产生指令
; AERA表示汇编一个新的数据段或者代码段
; .text表示段名字,如果段名不是以字母开头,而是以其它符号开头则需要在段名两边加上‘|’
; CODE表示为代码
; READONLY表示只读
; ALIGN=2,表示当前文件指令要2^2字节对齐
; THUMB表示THUMB指令代码
; REQUIRE8和PRESERVE8均表示当前文件的栈按照8字节对齐
;*************************************************************************AREA |.text|, CODE, READONLY, ALIGN=2THUMBREQUIRE8PRESERVE8;/*
; *-----------------------------------------------------------------------
; * 函数原型:void rt_hw_context_switch_to(rt_uint32 to);
; * r0 --> to
; * 该函数用于开启第一次线程切换
; *-----------------------------------------------------------------------
; */rt_hw_context_switch_to    PROC    ; PROC表示函数开始; 导出rt_hw_context_switch_to,让其具有全局属性,可以在C文件调用EXPORT rt_hw_context_switch_to;*************************************************************************
; 当一个汇编函数在C文件 中调用的时候:
; 如果有一个形参,则执行的时候会将这个形参传入到CPU寄存器r0;
; 如果有两个形参,第二个则传入到r1;
; 这里r0保存的是rt_hw_context_switch_to()的第一个参数(也就是唯一参数)to_thread->sp
;*************************************************************************; 设置rt_interrupt_to_thread的值                                    (8);将rt_interrupt_to_thread的地址加载到r1LDR     r1, =rt_interrupt_to_thread                                (8)-1;将r0的值存储到rt_interrupt_to_threadSTR     r0, [r1]                                                   (8)-2; 设置rt_interrupt_from_thread的值为0,表示启动第一次线程切换         (9);将rt_interrupt_from_thread的地址加载到r1LDR     r1, =rt_interrupt_from_thread                              (9)-1;配置r0等于0MOV     r0, #0x0                                                   (9)-2;将r0的值存储到rt_interrupt_from_threadSTR     r0, [r1]                                                   (9)-3; 设置中断标志位rt_thread_switch_interrupt_flag的值为1               (10);将rt_thread_switch_interrupt_flag的地址加载到r1LDR     r1, =rt_thread_switch_interrupt_flag                       (10)-1;配置r0等于1MOV     r0, #1                                                     (10)-2;将r0的值存储到rt_thread_switch_interrupt_flagSTR     r0, [r1]                                                   (10)-3; 设置 PendSV 异常的优先级LDR     r0, =NVIC_SYSPRI2LDR     r1, =NVIC_PENDSV_PRILDR.W   r2, [r0,#0x00]       ; 读ORR     r1,r1,r2             ; 改STR     r1, [r0]             ; 写; 触发 PendSV 异常 (产生上下文切换)                                  (12)LDR     r0, =NVIC_INT_CTRLLDR     r1, =NVIC_PENDSVSETSTR     r1, [r0]; 开中断CPSIE   F                                                          (13)CPSIE   I; 永远不会到达这里ENDP        ; 表示函数结束,和PROC成对使用ALIGN   4                                                          (3)END                                                                (2)

利用(PendSV)中断来实现线程切换,中断中还可以访问线程上下文,在中断中把线程上下文替换了,并把前线程放入到就绪队列,应该是这个逻辑。 

注意:

这里没有改变rt_interrupt_from_thread和rt_interrupt_to_thread是线程栈sp(指针)的指针的属性;

也就是触发中断时,中断获取的这两个参数都是双重指针。

 

 

6.2 PendSV_Handler()中断函数

;/*
; *-----------------------------------------------------------------------
; * void PendSV_Handler(void);
; * r0 --> switch from thread stack
; * r1 --> switch to thread stack
; * psr, pc, lr, r12, r3, r2, r1, r0 are pushed into [from] stack
; *-----------------------------------------------------------------------
; */PendSV_Handler   PROC
EXPORT PendSV_Handler; 失能中断,为了保护上下文切换不被中断                              (1)
MRS     r2, PRIMASK
CPSID   I; *-----------------------------------------------------------------------
; 获取中断标志位,看看是否为0,正常情况不应该为0,为0则退出          (2)
; 可以看下汇编获取全局变量的值,都需要两步:先把地址存放到寄存器,然后从寄存器拿到值,存放到另一个寄存器
; *-----------------------------------------------------------------------
; 加载rt_thread_switch_interrupt_flag的地址到r0
LDR     r0, ====rt_thread_switch_interrupt_flag                     (2)-1
; 加载rt_thread_switch_interrupt_flag的值到r1
LDR     r1, [r0]                                                 (2)-2
; 判断r1是否为0,为0则跳转到pendsv_exit
CBZ     r1, pendsv_exit                                          (2)-3; r1不为0则清0                                                    (3)
MOV     r1, #0x00
; 将r1的值存储到rt_thread_switch_interrupt_flag,即清0
STR     r1, [r0]; 判断rt_interrupt_from_thread的值是否为0                         (4)
; 加载rt_interrupt_from_thread的地址到r0
LDR     r0, ====rt_interrupt_from_thread                            (4)-1
; 加载rt_interrupt_from_thread的值到r1
LDR     r1, [r0]                                                 (4)-2
; 判断r1是否为0,为0则跳转到switch_to_thread
; 第一次线程切换时rt_interrupt_from_thread肯定为0,则跳转到switch_to_thread
CBZ     r1, switch_to_thread                                     (4)-3; ======================================================================================================== 上文保存 ================================================================================================    (6)
; 当进入PendSVC Handler时,上一个线程运行的环境即:
; xPSR,PC(线程入口地址),R14,R12,R3,R2,R1,R0(线程的形参)
; 这些CPU寄存器的值会自动保存到线程的栈中,剩下的r4~r11需要手动保存
; 获取线程栈指针到r1
MRS     r1, psp                                                  (6)-1
;将CPU寄存器r4~r11的值存储到r1指向的地址
;从r11开始操作(存到寄存器中),每操作一次地址将递减一次
;最后r1指向有效数据最低位
;类似memcpy(r1, [r4, r11], sizeof([r4, r11])
STMFD   r1!, {r4 - r11}                                          (6)-2; ======================================================================================================== 当前线程栈指针 -> r0 ================================================================================================    (6)
; 加载r0指向值到r0,即r0====rt_interrupt_from_thread,这是上一步加载过的
LDR     r0, [r0]                                                 (6)-3
; 将r1的值存储到r0,即更新当前线程栈sp
STR     r1, [r0]                                                 (6)-4; ======================================================================================================== 目标线程栈指针 -> r1 ========================================================================================================  (5)
switch_to_thread
; 加载rt_interrupt_to_thread的地址到r1
; rt_interrupt_to_thread是一个全局变量,里面存的是线程栈指针SP的指针
LDR     r1, ====rt_interrupt_to_thread                              (5)-1
; 加载rt_interrupt_to_thread的值到r1,即sp指针的指针
LDR     r1, [r1]                                                 (5)-2
; 加载rt_interrupt_to_thread的值到r1,即sp
LDR     r1, [r1]                                                 (5)-3; ======================================================================================================== 下文加载 ========================================================================================================  (5)
;将线程栈指针r1(操作之前先递减)指向的内容加载到CPU寄存器r4~r11
; 线程切换的核心,就是psp指针的替换,以及cpu寄存器内容的替换
LDMFD   r1!, {r4 - r11}                                          (5)-4; ======================================================================================================== psp替换 ========================================================================================================  (5)
;将线程栈指针更新到PSP
MSR     psp, r1                                                  (5)-5pendsv_exit
; 恢复中断
MSR     PRIMASK, r2                                              (7); 确保异常返回使用的栈指针是PSP,即LR寄存器的位2要为1
ORR     lr, lr, #0x04                                            (8)
; 异常返回,这个时候栈中的剩下内容将会自动加载到CPU寄存器:
; xPSR,PC(线程入口地址),R14,R12,R3,R2,R1,R0(线程的形参)
; 同时PSP的值也将更新,即指向线程栈的栈顶
BX      lr                                                       (9); PendSV_Handler 子程序结束
ENDP                                                             (10)

当进入PendSVC Handler时,上一个线程运行的环境即:xPSR,PC(线程入口地址),R14,R12,R3,R2,R1,R0(线程的形参)这些CPU寄存器的值会自动保存到线程的栈中,并更新PSP的值,剩下的r4~r11需要手动保存。

中断完成之后,CPU寄存器的r4-r11,xPSR,PSP已经改变;通过psp线程栈指针,可以很方便的找到线程控制块及其成员,至此CPU完成线程切换。

 

 

7 系统调度

7.1 rt_schedule()

/* 系统调度 */
void rt_schedule(void)
{struct rt_thread *to_thread;struct rt_thread *from_thread;/* 两个线程轮流切换 *///                                    (1)if( rt_current_thread ======== rt_list_entry( rt_thread_priority_table[0].next,struct rt_thread,tlist) ){from_thread ==== rt_current_thread;to_thread ==== rt_list_entry( rt_thread_priority_table[1].next,struct rt_thread,tlist);rt_current_thread ==== to_thread;}else//                                                    (2){from_thread ==== rt_current_thread;to_thread ==== rt_list_entry( rt_thread_priority_table[0].next,struct rt_thread,tlist);rt_current_thread ==== to_thread;}//                                                       (3)/* 产生上下文切换 */rt_hw_context_switch((rt_uint32_t)&from_thread->sp,(rt_uint32_t)&to_thread->sp);
}

 

7.2 rt_hw_contex_switch()

;/*
; *----------------------------------------------------------------------
; * void rt_hw_context_switch(rt_uint32 from, rt_uint32 to);
; * r0 --> from
; * r1 --> to
; *----------------------------------------------------------------------
; */
rt_hw_context_switch    PROC
EXPORT rt_hw_context_switch; 设置中断标志位rt_thread_switch_interrupt_flag为1          (1)
; 加载rt_thread_switch_interrupt_flag的地址到r2
LDR     r2, ====rt_thread_switch_interrupt_flag               (1)-1
; 加载rt_thread_switch_interrupt_flag的值到r3
LDR     r3, [r2]                                           (1)-2
;r3与1比较,相等则执行BEQ指令,否则不执行
CMP     r3, #1                                             (1)-3
BEQ     _reswitch
; 设置r3的值为1
MOV     r3, #1                                             (1)-4
; 将r3的值存储到rt_thread_switch_interrupt_flag,即置1
STR     r3, [r2]                                           (1)-5; 设置rt_interrupt_from_thread的值                          (2)
; 加载rt_interrupt_from_thread的地址到r2
LDR     r2, ====rt_interrupt_from_thread                      (2)-1
; 存储r0的值到rt_interrupt_from_thread,即上一个线程栈指针sp的指针
STR     r0, [r2]                                           (2)-2_reswitch
; 设置rt_interrupt_to_thread的值                            (3)
; 加载rt_interrupt_to_thread的地址到r2
LDR     r2, ====rt_interrupt_to_thread                        (3)-1
; 存储r1的值到rt_interrupt_from_thread,即下一个线程栈指针sp的指针
STR     r1, [r2]                                           (3)-2; 触发PendSV异常,实现上下文切换                             (4)
LDR     r0, ====NVIC_INT_CTRL
LDR     r1, ====NVIC_PENDSVSET
STR     r1, [r0]
; 子程序返回
BX      LR                                                 (5)
; 子程序结束
ENDP                                                       (6)
  •  和rt_hw_context_switch_to()逻辑雷同,只不过少了from_thread为0和开PendSV中断(只需要做一次)的操作

 

8 main()

/*
*************************************************************************
*                             包含的头文件
*************************************************************************
*/
#include <rtthread.h>
#include "ARMCM4.h"/*
*************************************************************************
*                              全局变量
*************************************************************************
*/
rt_uint8_t flag1;
rt_uint8_t flag2;/* 每个线程都有一个优先级列表 */
extern rt_list_t rt_thread_priority_table[RT_THREAD_PRIORITY_MAX];/*
*************************************************************************
*                      线程控制块 & STACK & 线程声明
*************************************************************************
*/
/* 定义线程控制块 */
struct rt_thread rt_flag1_thread;
struct rt_thread rt_flag2_thread;ALIGN(RT_ALIGN_SIZE)
/* 定义线程栈 */
rt_uint8_t rt_flag1_thread_stack[512];
rt_uint8_t rt_flag2_thread_stack[512];/* 线程声明 */
void flag1_thread_entry(void *p_arg);
void flag2_thread_entry(void *p_arg);/*
*************************************************************************
*                               函数声明
*************************************************************************
*/
void delay(uint32_t count);/************************************************************************
* @brief  main函数
* @param  无
* @retval 无
*
* @attention
***********************************************************************
*/
int main(void)
{/* 硬件初始化 *//* 将硬件相关的初始化放在这里,如果是软件仿真则没有相关初始化代码 *//* 调度器初始化 */rt_system_scheduler_init();/* 初始化线程 */rt_thread_init( &rt_flag1_thread,                 /* 线程控制块 */flag1_thread_entry,               /* 线程入口地址 */RT_NULL,                          /* 线程形参 */&rt_flag1_thread_stack[0],        /* 线程栈起始地址 */sizeof(rt_flag1_thread_stack) );  /* 线程栈大小,单位为字节 *//* 将线程插入到就绪列表 */rt_list_insert_before( &(rt_thread_priority_table[0]),&(rt_flag1_thread.tlist) );/* 初始化线程 */rt_thread_init( &rt_flag2_thread,                 /* 线程控制块 */flag2_thread_entry,               /* 线程入口地址 */RT_NULL,                          /* 线程形参 */&rt_flag2_thread_stack[0],        /* 线程栈起始地址 */sizeof(rt_flag2_thread_stack) );  /* 线程栈大小,单位为字节 *//* 将线程插入到就绪列表 */rt_list_insert_before( &(rt_thread_priority_table[1]),&(rt_flag2_thread.tlist) );/* 启动系统调度器 */rt_system_scheduler_start();
}/*
*************************************************************************
*                               函数实现
*************************************************************************
*/
/* 软件延时 */
void delay (uint32_t count)
{for(; count!====0; count--);
}/* 线程1 */
void flag1_thread_entry( void *p_arg )
{for( ;; ){flag1 ==== 1;delay( 100 );flag1 ==== 0;delay( 100 );/* 线程切换,这里是手动切换 */rt_schedule();//                                (注意)}
}/* 线程2 */
void flag2_thread_entry( void *p_arg )
{for( ;; ){flag2 ==== 1;delay( 100 );flag2 ==== 0;delay( 100 );/* 线程切换,这里是手动切换 */rt_schedule();//                                (注意)}
}

 

 

 9 汇编翻译

; MOV: 直接赋值
; r1 = 0x00
MOV     r1, #0x00; LDR: 取址
; r0 = &rt_interrupt_from_thread
LDR     r0, ====rt_interrupt_from_thread
; r0 = *r0
LDR r0, [r0]; 修改全局变量的方法固定:① 将全局变量地址加载到寄存器r1:LDR r1, = rt_xxx
;                         ② 设置好寄存器r0的值:MOV r0, #0x0
;                         ③ 将r0的值赋值给全局变量:r0, [r1]; STR: 通过地址赋值
; *r0 = r1
STR     r1, [r0]; LDR和STR组合
LDR r0, ====xxx
str r1, [r0]; CBZ:跳转
; if(r1) goto switch_to_thread
CBZ     r1, switch_to_thread

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/878761.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【vscode配置】一种基于Beta版UTF-8解决VSCode中C/C++/Python等的中文乱码问题的简便方法

在VSCode或其他编码场景下输入输出中文时会出现乱码,常用的解决方式是通过GBK编码重新打开或保存文件后再使用 但又是来回切换保存会导致代码中的中文变成乱码难以恢复 而且个人觉得来回切换很麻烦 下面给出一种了解到的解决方法:控制面板——时钟和区域——区域——管理——…

Graphviz-画数据结构图的利器

1.Win系统上的安装 安装Graphviz的方法有很多,以下是手动安装的方式: 1.访问 https://graphviz.org/download/,选择你想要下载的版本,手动安装选择ZIP安装包;2.解压下载的zip压缩包到你选择的文件目录,例如:D:\Graphviz-12.2.1-win64; 3.将Graphviz的安装目录(如:D:\…

4G优先级切换

本文来自博客园,作者:{IceSparks},转载请注明原文链接:https://www.cnblogs.com/IceSparks/p/18698508

4G与5GSIB对比

本文来自博客园,作者:{IceSparks},转载请注明原文链接:https://www.cnblogs.com/IceSparks/p/18698503

AI赋能软件测试:未来已来,你准备好了吗?

ps:文末有福利领取哦引言 在数字化转型的浪潮中,软件测试作为保障产品质量的关键环节,正面临着前所未有的挑战。 传统的测试方法已难以满足快速迭代和复杂场景的需求,而人工智能(AI)的引入,则为软件测试带来了革命性的变化。 本文将从现状、优势、挑战及未来趋势四个方面…

C语言 输出回文数

回文数,既把数值的每个数 颠倒输出(从右往左输出),再合起来的数如果和原来的值相等 例:输出0 至 1993 的回文数#include <stdio.h>int main() { int i ,x ,d ,a ; for(i=0;i<=1993;i++) {a = i; x = 0; while (a != 0) { d = a%10; //每次都取a值的最后一位 x = …

洛谷 P3842 [TJOI2007] 线段 题解

题目大意 原题链接 给你一个大小为 \(n \times n\) 的矩阵,上面有 \(n\) 条线段(一行一条)。你只能向右,向下,向左走。每一条线段都必须完整的经过。请问从 \((1,1)\) 到 \((n,n)\) 最短路径长度是多少? 解法 这道题可以使用最短路或者DP来做。我觉得DP本题更简单一点所以我…

数据降维技术研究:Karhunen-Love展开与快速傅里叶变换的理论基础及应用

在现代科学计算和数据分析领域,数据降维与压缩技术对于处理高维数据具有重要意义。本文主要探讨两种基础而重要的数学工具:Karhunen-Love展开(KLE)和快速傅里叶变换(FFT)。通过分析这两种方法的理论基础和应用特点,阐述它们在数据降维中的优势和适用场景。 Karhunen-Lov…

一个Python GC的小Bug

这个代码是在看了“码农高天”的视频照模仿的。虽然 GC 能完成大部分内存管理的工作,但是基于性能计数器的GC确实存在小 Bug,这一点还是需要通过del和gc.collect手动管理。 from typing import Optionalclass Object:def __init__(self, name: str) -> None:self.obj: Opt…

2月做题记录

还有一个月。2月做题记录✩ trick ✯ 会大部分,要\(tj\)提示 ✬ 会小部分/完全没想到,看了\(tj\)才会 ◈ 脑电波 ✡ 有某一算法的神秘通用性质 ⊗ 待补目录2月做题记录字符串CF1827CCF1913FP10716P11150 ✩✯树[NOI2021] 轻重边做法1 ✩做法2做法3 ✡《简单树剖练习题》[QOJ9…

第六节 特征提取和无监督学习

没有标签y依旧提取出特征你的模型朝着哪个方向压缩特征,由你的标签来定自监督学习:从x中提取一部分作为自己的标签y,然后用x预测y 在自监督学习中,需要根据数据的特点设计一些自监督任务,这些任务通常基于数据的一些内在属性或关系来构建。例如,在图像数据中,可以设计图像…