目录
void rt_hw_context_switch_to(rt_uint32_t to)
void rt_hw_context_switch(rt_uint32_t from, rt_uint32_t to):
提出问题:什么叫线程?思考这个问题之前,先想想怎么切换线程?怎么保存线程?
想要回答以上的问题,需要了解一些微机原理和ARM架构及汇编的知识
ARM架构
ARM芯片属于精简指令集计算机(RISC:Reduced Instruction Set Computor),它所用的指令比较简单,有如下特点:
- 对内存只有读、写指令
- 对于数据的运算是在CPU内部实现
- 使用RISC指令的CPU复杂度小一点,易于设计
比如,对于a=a+b这样的算式,需要经过下面4个步骤来实现:
细看这个几个步骤,有些疑问:
1、读a,那么a的值读出后保存在CPU里面哪里?
2、读b,那么b的值读出后保存在CPU里面哪里?
3、a+b的结果又保存在哪里?
这需要深入ARM处理器的内部,简单概括如下,我们先忽略各种CPU模式(系统模式、用户模式等)
CPU运行时,先去取指令,再执行指令:
- 把内存a的值读入CPU寄存器R0
- 把内存b的值读入CPU寄存器R1
- 把R0、R1累加存入R0
- 把R0的值写入内存a
CPU内部有r0到r15寄存器,这些寄存器有如下别名:
通用寄存器R0-R12:用于传送和暂存数据,也可参与算术逻辑运算和保存运算结果。
堆栈指针寄存器R13(SP):在ARM处理器中共有两个堆栈指针:主堆栈指针(MSP)和进程堆栈指针(PSP)。若用户用到其中一个,另一个必须用特殊指令(MRS、MSR指令来访问),因此任一时刻只能使用其中的一个。主堆栈指针是复位后默认使用的堆栈指针。
链接寄存器R14(LR):当调用一个子程序时,由R14存储返回地址。不像大多数处理器,ARM为了减少访问内存的次数,把返回地址存进CPU内部寄存器中,这样无须访问堆栈空间,,从而提高子程序的调用效率。
程序计数器寄存器R15(PC):PC是程序计数器(PC),其内容为当前正在执行的指令的地址。如果修改它的值,就能改变程序的执行流程。PC值的变化反应了程序的真实流程。
特殊功能寄存器:ARM内核中有一组特殊功能寄存器,包括程序状态字寄存器(xPSR)、中断屏蔽寄存器(PRIMASK)和控制寄存器(CONTROL)。
程序状态字寄存器(xPSR):程序状态字寄存器在内部分为APSR、 IPSR和EPSR。
几条汇编指令
需要掌握的汇编指令并不多,只需要掌握几条:
- 读内存指令:LDR,即Load之意
- 写内存指令:STR,即Store之意
- 加减指令:ADD SUB
- 跳转指令:BL,即Branch And Link
- 入栈指令:PUSH
- 出栈指令:POP
函数运行的本质:
如下是一个简单的程序,主函数里调用add_val函数:
int add_val(int *pa,int *pb)
{
volatile int tmp;
tmp = *pa;
tmp = tmp+ *pb;
*pa = tmp;
}
int main( void )
{
int a =1;
int b =2;
add_val(&a,&b);
return 0;
}
对应的汇编代码:
0x080023D0 B50C PUSH {r2-r3,lr} //进入main函数,寄存器r3、lr的值都存入内存的栈中(lr保存程序返回地址)
15: int a =1; //将a的值存入栈中sp+4,局部变量是放在栈里的
0x080023D2 2001 MOVS r0,#0x01
0x080023D4 9001 STR r0,[sp,#0x04]
16: int b =2;
17:
0x080023D6 2002 MOVS r0,#0x02 //将b的值存入栈中sp+0,局部变量放在栈里
0x080023D8 9000 STR r0,[sp,#0x00]
18: add_val(&a,&b);
19:
0x080023DA 4669 MOV r1,sp
0x080023DC A801 ADD r0,sp,#0x04
0x080023DE F7FEFCC6 BL.W add_val (0x08000D6E) //跳转到0x08000D6E处
20: return 0;
21:
add_val函数的汇编代码:
0x08000D6E B508 PUSH {r3,lr} //进入函数,寄存器r3、lr的值都存入内存的栈中
0x08000D70 4602 MOV r2,r0
6: tmp = *pa;
0x08000D72 6810 LDR r0,[r2,#0x00] //将寄存器r2的值存放到r0,其中r2是函数的第一个参数
0x08000D74 9000 STR r0,[sp,#0x00] //将寄存器r0的值存储到sp指向的内存
7: tmp = tmp+ *pb;
0x08000D76 6808 LDR r0,[r1,#0x00] //将寄存器r1的值存放到r0,其中r1是函数的第二个参数
0x08000D78 9B00 LDR r3,[sp,#0x00] //将寄存器sp指向内存的值存放到r3
0x08000D7A 4418 ADD r0,r0,r3 //将寄存器r0和r3相加,保存到r0
0x08000D7C 9000 STR r0,[sp,#0x00] //将寄存器r0的值存储到sp指向的内存
8: *pa = tmp;
9:
0x08000D7E 9800 LDR r0,[sp,#0x00] //将寄存器sp指向内存的值存放到r0
0x08000D80 6010 STR r0,[r2,#0x00] //将寄存器r0的值存放到r2中,其中r2将作为函数的返回值
0x080023E2 2000 MOVS r0,#0x00
22: }
0x080023E4 BD0C POP {r2-r3,pc} //退出函数,获取内存的栈中的数据放入r2 r3 pc
什么叫线程?怎么保存线程?
什么叫线程:运行中的函数,被暂停运行的函数
怎么保存线程:把暂停瞬间的CPU寄存器的值,保存进栈里
创建线程
我个人认为线程主要就3要素:线程栈、线程控制块、线程的入口函数
线程结构体:
struct rt_thread
{
/* rt object */
char name[RT_NAME_MAX]; /**< 线程名字 */
rt_uint8_t type; /**< 对象类型 */
rt_uint8_t flags; /**< 对象标志 */
#ifdef RT_USING_MODULE
void *module_id; /**< id of application module */
#endif
rt_list_t list; /**< 对象容器的链表 */
rt_list_t tlist; /**< 优先级就绪链表 */
/* stack point and entry */
void *sp; /**< 栈顶指针 */
void *entry; /**< 函数指针*/
void *parameter; /**< 入口函数参数 */
void *stack_addr; /**< 栈的起始地址 */
rt_uint32_t stack_size; /**< 栈大小 */
/* error code */
rt_err_t error; /**< error code */
rt_uint8_t stat; /**< thread status */
/* priority */
rt_uint8_t current_priority; /**< 当前优先级*/
rt_uint8_t init_priority; /**< 初始优先级 */
#if RT_THREAD_PRIORITY_MAX > 32
rt_uint8_t number;
rt_uint8_t high_mask;
#endif
rt_uint32_t number_mask;
#if defined(RT_USING_EVENT)
/* thread event */
rt_uint32_t event_set;
rt_uint8_t event_info;
#endif
#if defined(RT_USING_SIGNALS)
rt_sigset_t sig_pending; /**< 信号挂起 */
rt_sigset_t sig_mask; /**< 信号屏蔽 */
void *sig_ret; /**< the return stack pointer from signal */
rt_sighandler_t *sig_vectors; /**< vectors of signal handler */
void *si_list; /**< the signal infor list */
#endif
rt_ubase_t init_tick; /**< 一次能运行多少个tick */
rt_ubase_t remaining_tick; /**< 当前运行还剩多少个tick */
struct rt_timer thread_timer; /**< built-in thread timer */
void (*cleanup)(struct rt_thread *tid); /**< cleanup function when thread exit */
/* light weight process if present */
#ifdef RT_USING_LWP
void *lwp;
#endif
rt_uint32_t user_data; /**< 用户私有数据 */
};
创建线程的过程,就是线程的三要素的创建以及构造栈的过程。
函数接口如下:根据线程栈和线程控制块的内存是动态分配还是静态分配可以分为静态创建线程和动态创建线程两种方式,对应有脱离线程和删除线程两种方式,脱离线程只是从内核对象容器中脱离,内存不会被释放, 删除线程则会从对象容器中删除,内存也会相应的释放
动态创建线程:
rt_thread_t rt_thread_create(const char *name,
void (*entry)(void *parameter),
void *parameter,
rt_uint32_t stack_size,
rt_uint8_t priority,
rt_uint32_t tick)
{
struct rt_thread *thread;
void *stack_start;
/*1、分配对象,分配线程结构体*/
thread = (struct rt_thread *)rt_object_allocate(RT_Object_Class_Thread,
name);
if (thread == RT_NULL)
return RT_NULL;
/*2、分配栈*/
stack_start = (void *)RT_KERNEL_MALLOC(stack_size);
if (stack_start == RT_NULL)
{
/* allocate stack failure */
rt_object_delete((rt_object_t)thread);
return RT_NULL;
}
/*3、初始化栈,即构造栈的内容*/
_rt_thread_init(thread,
name,
entry,
parameter,
stack_start,
stack_size,
priority,
tick);
return thread;
}
静态创建线程:
rt_err_t rt_thread_init(struct rt_thread *thread,
const char *name,
void (*entry)(void *parameter),
void *parameter,
void *stack_start,
rt_uint32_t stack_size,
rt_uint8_t priority,
rt_uint32_t tick)
{
/* thread check */
RT_ASSERT(thread != RT_NULL);
RT_ASSERT(stack_start != RT_NULL);
/* 初始化对象 */
rt_object_init((rt_object_t)thread, RT_Object_Class_Thread, name);
return _rt_thread_init(thread,
name,
entry,
parameter,
stack_start,
stack_size,
priority,
tick);
}
从上面两个创建线程的关键来看,区别就是动态创建线程是动态分配内存给线程控制块和线程栈,静态则是用户事先分配好了线程控制块和线程栈,二者关键都有调用内核的函数_rt_thread_init(...)函数,该函数主要是构造栈的内容。
static rt_err_t _rt_thread_init(struct rt_thread *thread,
const char *name,
void (*entry)(void *parameter),
void *parameter,
void *stack_start,
rt_uint32_t stack_size,
rt_uint8_t priority,
rt_uint32_t tick)
{
/* 把用户设定的参数,进行参数赋值 */
rt_list_init(&(thread->tlist));
thread->entry = (void *)entry;
thread->parameter = parameter;
/* stack init */
thread->stack_addr = stack_start;
thread->stack_size = stack_size;
/* 关键初始化堆栈 */
rt_memset(thread->stack_addr, '#', thread->stack_size);
thread->sp = (void *)rt_hw_stack_init(thread->entry, thread->parameter,
(void *)((char *)thread->stack_addr + thread->stack_size - 4),
(void *)rt_thread_exit);
/* priority init */
RT_ASSERT(priority < RT_THREAD_PRIORITY_MAX);
thread->init_priority = priority;
thread->current_priority = priority;
thread->number_mask = 0;
#if RT_THREAD_PRIORITY_MAX > 32
thread->number = 0;
thread->high_mask = 0;
#endif
/* tick init */
thread->init_tick = tick;
thread->remaining_tick = tick;
/* error and flags */
thread->error = RT_EOK;
thread->stat = RT_THREAD_INIT;
/* initialize cleanup function and user data */
thread->cleanup = 0;
thread->user_data = 0;
/*每个线程都内置一个定时器 */
rt_timer_init(&(thread->thread_timer),
thread->name,
rt_thread_timeout,
thread,
0,
RT_TIMER_FLAG_ONE_SHOT);
/* initialize signal */
#ifdef RT_USING_SIGNALS
thread->sig_mask = 0x00;
thread->sig_pending = 0x00;
thread->sig_ret = RT_NULL;
thread->sig_vectors = RT_NULL;
thread->si_list = RT_NULL;
#endif
#ifdef RT_USING_LWP
thread->lwp = RT_NULL;
#endif
RT_OBJECT_HOOK_CALL(rt_thread_inited_hook, (thread));
return RT_EOK;
}
上面函数主要是把用户创建线程传入的参数进行赋值,以及最为关键的构造栈的内容。这里问个问题,创建线程之后,系统怎么知道去哪里执行入口函数entry,前面说到的CPU寄存器中的PC寄存器就是反映了程序的执行流程,只要把PC寄存器设为入口函数的地址即可,所以这里必须构造栈的内容。
构造栈
rt_uint8_t *rt_hw_stack_init(void *tentry,
void *parameter,
rt_uint8_t *stack_addr,
void *texit)
{
struct stack_frame *stack_frame;
rt_uint8_t *stk;
unsigned long i;
stk = stack_addr + sizeof(rt_uint32_t);
stk = (rt_uint8_t *)RT_ALIGN_DOWN((rt_uint32_t)stk, 8);
stk -= sizeof(struct stack_frame);
stack_frame = (struct stack_frame *)stk;
/* init all register */
for (i = 0; i < sizeof(struct stack_frame) / sizeof(rt_uint32_t); i ++)
{
((rt_uint32_t *)stack_frame)[i] = 0xdeadbeef;
}
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 = (unsigned long)texit; /* lr */
stack_frame->exception_stack_frame.pc = (unsigned long)tentry; /* entry point, pc */
stack_frame->exception_stack_frame.psr = 0x01000000L; /* PSR */
/* return task's current stack address */
return stk;
}
这里可以看到一些熟悉的CPU寄存器,结构体stack_freame有以下内容组成:r4-r11(8个)、以及另外8个(除去SP,因为SP在线程结构体中定义了,这个函数返回值就是SP)。
struct 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;
};
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;
};
这里为什么要放分成两个结构体,不直接把16个寄存器放入一个结构体中,而且函数感觉只给(r0、r1、r2一部分寄存器赋值)?通过网上查找资料,在Cortex M3和M4权威指南中有解答,栈的初始化会有两部分,一部分是软件初始化和硬件初始化
这里为什么要放分成两个结构体,不直接把16个寄存器放入一个结构体中,而且函数感觉只给(r0、r1、r2一部分寄存器赋值)?通过网上查找资料,在Cortex M3和M4权威指南中有解答,栈的初始化会有两部分,exception_stack_frame 部分是ARM的硬件压栈部分, stack_frame 的R4-R11则是需要自行实现的压栈部分。
每个线程都有自己独自的线程栈,其中创建线程最为关键的就是构造栈(关键是PC构造为入口函数参数以便系统知道去哪里执行线程的入口函数以及R0存放入口函数参数),其他寄存器不重要但是也要构造出来。
以上就是整个创建线程的过程。
RT-Thread的启动流程:
系统是怎么个执行流程。可以分为4个步骤:
1)硬件初始化
2)RTOS系统初始化
3)创建各种线程
4)启动RTOS调度器
创建各种线程有两种方式,一种是每一个线程都创建,最后启动RTOS调度器,后一种是RT-Thread和FreeRTOS默认使用的,先创建一个启动线程,再启动线程里创建其他各种应用线程,最后启动线程把自己删除后调用RTOS的调度器。
其实,可以进行单步运行程序,可以看到,在_main函数里会进入components.c中如下位置:
这里$Sub$$main和$Super$$main配对使用,是MDK扩展了main函数,在执行main函数之前,会先进入这个函数,如果你想让你的函数也做一些扩展,其实可以只需把main换成function即可,$Sub$$fuction。$Sub$$main函数调用了rtthread_startip()函数:
int rtthread_startup(void)
{
rt_hw_interrupt_disable();
/* board level initalization
* NOTE: please initialize heap inside board initialization.
*/
rt_hw_board_init();
/* show RT-Thread version */
rt_show_version();
/* timer system initialization */
rt_system_timer_init();
/* scheduler system initialization */
rt_system_scheduler_init();
#ifdef RT_USING_SIGNALS
/* signal system initialization */
rt_system_signal_init();
#endif
/* create init_thread */
rt_application_init();
/* timer thread initialization */
rt_system_timer_thread_init();
/* idle thread initialization */
rt_thread_idle_init();
/* start scheduler */
rt_system_scheduler_start();
/* never reach here */
return 0;
}
从上面可以看到这函数包括了硬件初始化rt_hw_board_init()和系统的各种初始化,定时器初始化,调度器Scheduler初始化和启动调度器等,我们最为关心的就是 rt_application_init():创建了main启动线程(优先级为32/3,为10)。
void rt_application_init(void)
{
rt_thread_t tid;
#ifdef RT_USING_HEAP
tid = rt_thread_create("main", main_thread_entry, RT_NULL,
RT_MAIN_THREAD_STACK_SIZE, RT_MAIN_THREAD_PRIORITY, 20);
RT_ASSERT(tid != RT_NULL);
#else
rt_err_t result;
tid = &main_thread;
result = rt_thread_init(tid, "main", main_thread_entry, RT_NULL,
main_stack, sizeof(main_stack), RT_MAIN_THREAD_PRIORITY, 20);
RT_ASSERT(result == RT_EOK);
/* if not define RT_USING_HEAP, using to eliminate the warning */
(void)result;
#endif
rt_thread_startup(tid);
}
上面这些分析是针对以往的main里创建线程,用的例程是在tshell里创建线程,涉及到RT-Thread的自动初始化机制。
线程切换流程:核心就是恢复和保存现场(上下文)。
线程切换主要就是保存当前线程的上下文以及恢复要切换线程的上下文。
创建完线程之后,都会启动线程:把线程添加到相应的就绪列表中,开启调度。
rt_err_t rt_thread_startup(rt_thread_t thread)
/* change thread stat */
thread->stat = RT_THREAD_SUSPEND;
/* then resume it */
rt_thread_resume(thread);
if (rt_thread_self() != RT_NULL)
{
/* do a scheduling */
rt_schedule();
}
/* insert to schedule ready list */
rt_schedule_insert_thread(thread);
任务切换实现:
1、void rt_hw_context_switch(rt_uint32_t from, rt_uint32_t to);
2、void rt_hw_context_switch_to(rt_uint32_t to);
3、void PendSV_Handler(void);
void rt_hw_context_switch_to(rt_uint32_t to)
函数原型如下:该函数用于第一次线程启动,在rt_system_scheduler_start函数中被调用
;/*
; * void rt_hw_context_switch_to(rt_uint32 to);
; * r0 --> to
; * this fucntion is used to perform the first thread switch
; */
rt_hw_context_switch_to PROC
; 导出rt_hw_context_switch_to,让其具有全局属性,可以在C文件中调用
EXPORT rt_hw_context_switch_to
; 设置rt_interrupt_to_thread的值
LDR r1, =rt_interrupt_to_thread ;将rt_interrupt_to_thread的地址加载到r1
STR r0, [r1] ;将r0的值存储到rt_interrupt_to_thread
; 设置rt_interrupt_from_thread的值为0,表示启动第一次线程切换
LDR r1, =rt_interrupt_from_thread ;将rt_interrupt_from_thread的地址加载到r1
MOV r0, #0x0 ;配置r0等于0
STR r0, [r1] ;将r0的值存储到rt_interrupt_from_thread
; 设置中断标志位rt_thread_switch_interrupt_flag的值为1
LDR r1, =rt_thread_switch_interrupt_flag ;将rt_thread_switch_interrupt_flag的地址加载到r1
MOV r0, #1 ;配置r0等于1
STR r0, [r1] ;将r0的值存储到rt_thread_switch_interrupt_flag
; 设置PendSV异常的优先级
LDR r0, =NVIC_SYSPRI2
LDR r1, =NVIC_PENDSV_PRI
LDR.W r2, [r0,#0x00] ; 读
ORR r1,r1,r2 ; 改
STR r1, [r0] ; 写
; 触发PendSV异常(产生上下文切换)
LDR r0, =NVIC_INT_CTRL
LDR r1, =NVIC_PENDSVSET
STR r1, [r0]
; restore MSP
LDR r0, =SCB_VTOR
LDR r0, [r0]
LDR r0, [r0]
MSR msp, r0
; 开中断
CPSIE F
CPSIE I
; 永远不会到达这里
ENDP
void rt_hw_context_switch(rt_uint32_t from, rt_uint32_t to):
函数原型如下
;/*
; * void rt_hw_context_switch(rt_uint32 from, rt_uint32 to);
; * r0 --> from
; * r1 --> to
; */
rt_hw_context_switch_interrupt
EXPORT rt_hw_context_switch_interrupt
rt_hw_context_switch PROC
EXPORT rt_hw_context_switch
; 设置中断标志位 rt_thread_switch_interrupt_flag 为1
LDR r2, =rt_thread_switch_interrupt_flag ;加载rt_thread_switch_interrupt_flag的地址到r2
LDR r3, [r2] ;加载rt_thread_switch_interrupt_flag的值到r3
CMP r3, #1 ;r3与1比较,相等执行BEQ指令,否则不执行
BEQ _reswitch
MOV r3, #1 ; 设置r3的值为1
STR r3, [r2] ; 将r3的值存储到rt_thread_switch_interrupt_flag,即置1
;设置rt_interrupt_from_thread的值
LDR r2, =rt_interrupt_from_thread ; 加载rt_interrupt_from_thread的地址到r2
STR r0, [r2] ; 存储r0的值到rt_interrupt_from_thread,即上一个线程栈指针SP的指针
_reswitch
;设置rt_interrupt_to_thread的值
LDR r2, =rt_interrupt_to_thread ; 加载rt_interrupt_from_thread的地址到r2
STR r1, [r2] ; 存储r0的值到rt_interrupt_from_thread,即上一个线程栈指针SP的指针
LDR r0, =NVIC_INT_CTRL ; 触发PendSV异常,实现上下文切换
LDR r1, =NVIC_PENDSVSET
STR r1, [r0]
BX LR ; 子程序返回
ENDP
PendSV_Handler
函数原型如下:
; 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
; 失能中断,为了保护上下文切换不被中断
MRS r2, PRIMASK
CPSID I
; 获取中断标志位,看看是否为0
LDR r0, =rt_thread_switch_interrupt_flag ; 加载rt_thread_switch_interrupt_flag的地址到r0
LDR r1, [r0] ; 加载rt_thread_switch_interrupt_flag的值到r1
CBZ r1, pendsv_exit ; 判断r1是否为0,为0则跳转到pendsv_exit
; r1不为0则清零
MOV r1, #0x00
STR r1, [r0]
; 判断rt_interrupt_from_thread的值是否为0
LDR r0, =rt_interrupt_from_thread ;加载rt_interrupt_from_thread的地址到r0
LDR r1, [r0] ; 加载rt_interrupt_from_thread的值到r1
CBZ r1, switch_to_thread ; 判断r1是否为0,为0则跳转到switch_to_thread
; 第一次线程切换rt_interrupt_from_thread肯定为0,则跳转到switch_to_thread
; ========================== 上文保存 ==============================
; 当进入PendSVC Handler时,上一个线程运行的环境即:
; xPSR,PC(线程入口地址),LR,R12,R3,R2,R1,R0(线程的形参)
; 这些CPU寄存器的值会自动保存到线程的栈中,剩下的r4~r11需要手动保存
MRS r1, psp ; 获取线程栈指针到r1
STMFD r1!, {r4 - r11} ; 将CPU寄存器r4-r11的值存储到r1指向的地址(每操作一次地址将递减1)
LDR r0, [r0] ; 加载r0指向值到r0,即r0=rt_interrupt_from_thread
STR r1, [r0] ;将r1的值存储到r0,即更新线程栈sp
; ========================== 下文切换 ==============================
switch_to_thread
LDR r1, =rt_interrupt_to_thread ;加载rt_interrupt_to_thread的地址到r1
LDR r1, [r1] ;加载rt_interrupt_to_thread的值到r1,即sp
LDR r1, [r1] ; 加载rt_interrupt_to_thread的值到r1,即sp
LDMFD r1!, {r4 - r11} ; 将线程栈指针r1(操作之前先递减)指向的内容加载到CPU寄存器r4-r11
MSR psp, r1 ; 将线程栈指针更新到psp
pendsv_exit
;恢复中断
MSR PRIMASK, r2
ORR lr, lr, #0x04 ;确保异常返回使用的堆栈指针是PSP,即LR寄存器的位2要为1
BX lr ;异常返回,这个时候任务堆栈中的剩下内容将会自动加载到xPSR,PC(任务入口地址),R14,R12,R3,R2,R1,R0(任务的形参)
; 同时PSP的值也将更新,即指向任务堆栈的栈顶。在ARMC3中,堆是由高地址向低地址生长的。
ENDP
本文分析了线程创建和启动流程以及线程切换的大致过程,其中的代码细节还有诸多值得分析推敲的地方,比如:创建任务伪造现场的过程中,传参时stack_addr减去4,后面在函数内部又加回4?
回答参考:RT-Thread-关于栈地址的转换不能理解RT-Thread问答社区 - RT-Thread
thread->sp = (void *)rt_hw_stack_init(thread->entry, thread->parameter,
(void *)((char *)thread->stack_addr + thread->stack_size - 4),
(void *)rt_thread_exit);
stk = stack_addr + sizeof(rt_uint32_t);
stk = (rt_uint8_t *)RT_ALIGN_DOWN((rt_uint32_t)stk, 8);
stk -= sizeof(struct stack_frame);