RT-Thread源码阅读(2)——任务启动与调度
线程初始化
线程初始化,相关解释见注释
static rt_err_t _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;// 初始化栈空间大小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,(rt_uint8_t *)((char *)thread->stack_addr + thread->stack_size - sizeof(rt_ubase_t)), (void *)_thread_exit);// 优先级初始化thread->current_priority = priority;// 分配可运行的时间片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, _thread_timeout, thread, 0, RT_TIMER_FLAG_ONE_SHOT);// 线程初始化回调函数RT_OBJECT_HOOK_CALL(rt_thread_inited_hook, (thread));return RT_EOK;
}
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;
};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;// 计算出SPstk = 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;
}
第一眼看这个代码,可能会有一个疑问,调用rt_hw_stack_init
函数时stack_addr
入参为stack_addr + stack_size - sizeof(rt_ubase_t)
函数开始又 stack_addr + sizeof(rt_uint32_t)
,为何不直接入参stack_addr + stack_size
,加4减4既不是多此一举?
向下增长的栈 - sizeof(rt_ubase_t)
是对应着当前栈顶,例如栈空间buff[100],不减的话栈指向buff[100],访问就会溢出
thread.c
作为内核文件,为了保持参数含义统一性,向上/下增长的栈入参都为栈顶/底位置,没毛病
对于cpuport.c
不同的单片机会有不同的内容,架构可能不一样,主要区别如下:
-
当SP指针指向的地址空间没有存放有效数据,则称之为空堆栈
-
当SP指针指向的地址空间存放有有效数据,则称之为满堆栈
因此针对满堆栈,写入数据的流程为先移动SP指针再填写有效数据;而对于空堆栈则是先填写有效数据再移动堆栈指针
由满堆栈、空堆栈与向上增长堆栈、向下增长堆栈,共可组成四种组合:
- 向上递增满堆栈(满增)
- 向下递增满堆栈(满减)
- 向上递增空堆栈(空增)
- 向下递增空堆栈(空减)
The stack must also conform to the following constraint at a public interface:
• SP mod 8 = 0. The stack must be double-word aligned.
Cortex-M是满减堆栈,RV32也是满减堆栈,AAPCS中还要求栈作为调用入口时保持8字节对齐
所以不难理解如下内容,当然8字节对齐可能造成4字节空间浪费
stk = stack_addr + sizeof(rt_uint32_t);
stk = (rt_uint8_t *)RT_ALIGN_DOWN((rt_uint32_t)stk, 8);
stk -= sizeof(struct stack_frame);
观察后续代码可知,R4-R11在地址小的空间,也会被先出栈,此时返回的栈指针stk指向的就是R4,返回给thread->sp
这里有几个寄存器非常重要
- r0:第一个参数,即
thread->parameter
- pc:函数入口,即
thread->entry
- lr:线程return后的入口,即
_thread_exit
,用来回收资源
启动线程——加入调度
rt_err_t rt_thread_startup(rt_thread_t thread)
{thread->number_mask = 1L << thread->current_priority;/* 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();}return RT_EOK;
}rt_err_t rt_thread_resume(rt_thread_t thread)
{rt_base_t level;if ((thread->stat & RT_THREAD_STAT_MASK) != RT_THREAD_SUSPEND){return -RT_ERROR;}// 如果在suspend列表中则移除rt_list_remove(&(thread->tlist));// 定时器相关,后续介绍rt_timer_stop(&thread->thread_timer);// 加入调度列表rt_schedule_insert_thread(thread);RT_OBJECT_HOOK_CALL(rt_thread_resume_hook, (thread));return RT_EOK;
}void rt_schedule_insert_thread(struct rt_thread *thread)
{rt_base_t level;/* it's current thread, it should be RUNNING thread */if (thread == rt_current_thread){thread->stat = RT_THREAD_RUNNING | (thread->stat & ~RT_THREAD_STAT_MASK);goto __exit;}/* READY thread, insert to ready queue */thread->stat = RT_THREAD_READY | (thread->stat & ~RT_THREAD_STAT_MASK);// 加入到调度链表/* there is no time slices left(YIELD), inserting thread before ready list*/if((thread->stat & RT_THREAD_STAT_YIELD_MASK) != 0){rt_list_insert_before(&(rt_thread_priority_table[thread->current_priority]),&(thread->tlist));}/* there are some time slices left, inserting thread after ready list to schedule it firstly at next time*/else{rt_list_insert_after(&(rt_thread_priority_table[thread->current_priority]),&(thread->tlist));}// 说明对应的优先级有线程rt_thread_ready_priority_group |= thread->number_mask;
}
启动第一个线程
rt_system_scheduler_start
调用rt_hw_context_switch_to启用第一个线程
void rt_system_scheduler_start(void)
{// ......// 此时只注册了main和IDLE线程,获取到的高优先级线程必然是main线程to_thread = _scheduler_get_highest_priority_thread(&highest_ready_priority);rt_current_thread = to_thread;rt_hw_context_switch_to((rt_ubase_t)&to_thread->sp);
}
- MRS 指令: 对状态寄存器CPSR和SPSR进行读操作
- MSR指令: 对状态寄存器CPSR和SPSR进行写操作
rt_hw_context_switch_to
/** void rt_hw_context_switch_to(rt_uint32 to);* R0 --> to*/.global rt_hw_context_switch_to.type rt_hw_context_switch_to, %function
rt_hw_context_switch_to:/* rt_interrupt_to_thread = (rt_ubase_t)&to_thread->sp */LDR R1, =rt_interrupt_to_thread ; R1 = &rt_interrupt_to_threadSTR R0, [R1] ; *R1 = R0 /* rt_interrupt_from_thread = 0 */LDR R1, =rt_interrupt_from_thread ; R1 = &rt_interrupt_to_thread MOV R0, #0 ; R0 = 0 STR R0, [R1] ; *R1 = R0 /* rt_thread_switch_interrupt_flag = 1 */LDR R1, =rt_thread_switch_interrupt_flagMOV R0, #1STR R0, [R1]/* SHPR3[23:16]是控制PendSV的优先级, SHPR3[31:24]是控制SysTick的优先级 *//* 这里将其全置为1 */LDR R0, =SHPR3 ; R0 = 0xE000ED20 LDR R1, =PENDSV_PRI_LOWEST ; R1 = 0xFFFF0000LDR.W R2, [R0,#0] ; R2 = *R0 ORR R1, R1, R2 ; R1 = R1 | R2 STR R1, [R0] ; *R0 = R1 /* ICSR的bit28 写 1 以悬起 PendSV */LDR R0, =ICSR ; R0 = 0xE000ED04 LDR R1, =PENDSVSET_BIT ; R1 = 0x10000000 STR R1, [R0] ; *R0 = R1 /* restore MSP */LDR r0, =SCB_VTOR ; SCB_VTOR存储向量表起始地址 LDR r0, [r0] ; r0 = *SCB_VTOR 获取向量表的位置LDR r0, [r0] ; r0 = MSP 向量表第一个字为初始MSP值NOPMSR msp, r0/* 开中断后应该跳转到PendSV_Handler */CPSIE F ; Enable 异常 CPSIE I ; Enable 中断 /* 内存屏障 保证寄存器写入生效 */DSBISB
PendSV_Handler
线程的切换在这里进行
.global PendSV_Handler.type PendSV_Handler, %function
PendSV_Handler:/* 保存中断状态到R2,然后关闭中断 */MRS R2, PRIMASKCPSID I/* 如果 rt_thread_switch_interrupt_flag == 0 则跳转到pendsv_exit */LDR R0, =rt_thread_switch_interrupt_flagLDR R1, [R0]CBZ R1, pendsv_exit /* clear rt_thread_switch_interrupt_flag to 0 */MOV R1, #0STR R1, [R0]/* rt_interrupt_from_thread 可能为0 直接转到switch_to_thread */LDR R0, =rt_interrupt_from_threadLDR R1, [R0]CBZ R1, switch_to_thread MRS R1, PSP ; R1 = PSPSTMFD R1!, {R4 - R11} ; 将 R4-R11 压入堆栈(保存寄存器)LDR R0, [R0] ; R0 = rt_interrupt_from_threadSTR R1, [R0] ; *rt_interrupt_from_thread = R1,即入栈后的SPswitch_to_thread:LDR R1, =rt_interrupt_to_thread ; R1 = &rt_interrupt_to_threadLDR R1, [R1] ; R1 = rt_interrupt_to_thread = (rt_ubase_t)&to_thread->spLDR R1, [R1] ; R1 = to_thread->spLDMFD R1!, {R4 - R11} ; 从堆栈弹出 R4-R11(恢复寄存器)MSR PSP, R1 ; PSP = R1pendsv_exit:/* 恢复中断状态 */MSR PRIMASK, R2; 在进入异常服务程序后,LR的值被自动更新为特殊的EXC_RETURN,所以只需要在异常中将LR的bit2置1就可以切换PSP了ORR LR, LR, #0x04BX LR; 返回会系统会从PSP自动弹出PC等寄存器,这些寄存器在创建线程时赋值 或者 进入中断之前自动保存了
线程调度
线程调度的核心就是rt_schedule
,可以知道怎么调度,至于为什么调度,什么时候调度,请后续了解
void rt_schedule(void)
{rt_base_t level;struct rt_thread *to_thread;struct rt_thread *from_thread;/* disable interrupt */level = rt_hw_interrupt_disable();// rt_enter_critical和rt_exit_critical会增减rt_scheduler_lock_nestif (rt_scheduler_lock_nest == 0){rt_ubase_t highest_ready_priority;// rt_thread_ready_priority_group为0表示没有就绪线程if (rt_thread_ready_priority_group != 0){/* need_insert_from_thread: need to insert from_thread to ready queue */int need_insert_from_thread = 0;// 获取当前就绪线程中优先级最高的线程to_thread = _scheduler_get_highest_priority_thread(&highest_ready_priority);// 如果当前线程正在运行态if ((rt_current_thread->stat & RT_THREAD_STAT_MASK) == RT_THREAD_RUNNING){// 当前优先级高if (rt_current_thread->current_priority < highest_ready_priority){to_thread = rt_current_thread;}// 优先级相同需要判断YIELD标志,在线程tick减到0或者主动YIELD时才会置位else if (rt_current_thread->current_priority == highest_ready_priority &&(rt_current_thread->stat & RT_THREAD_STAT_YIELD_MASK) == 0){to_thread = rt_current_thread;}else{need_insert_from_thread = 1;}rt_current_thread->stat &= ~RT_THREAD_STAT_YIELD_MASK;}if (to_thread != rt_current_thread){// 如果目标线程与当前线程不同rt_current_priority = (rt_uint8_t)highest_ready_priority;from_thread = rt_current_thread;rt_current_thread = to_thread;RT_OBJECT_HOOK_CALL(rt_scheduler_hook, (from_thread, to_thread));if (need_insert_from_thread){// 将原线程插入就绪列表rt_schedule_insert_thread(from_thread);}// 将目标线程从就绪列表中删除rt_schedule_remove_thread(to_thread);to_thread->stat = RT_THREAD_RUNNING | (to_thread->stat & ~RT_THREAD_STAT_MASK);// rt_interrupt_nest为0表示当前线程不在中断上下文中if (rt_interrupt_nest == 0){// 在Cortex-M3中rt_hw_context_switch和rt_hw_context_switch_interrupt一样rt_hw_context_switch((rt_ubase_t)&from_thread->sp, (rt_ubase_t)&to_thread->sp);/* enable interrupt */rt_hw_interrupt_enable(level);goto __exit;}else{RT_DEBUG_LOG(RT_DEBUG_SCHEDULER, ("switch in interrupt\n"));rt_hw_context_switch_interrupt((rt_ubase_t)&from_thread->sp, (rt_ubase_t)&to_thread->sp);}}else{// 如果目标线程与当前线程相同rt_schedule_remove_thread(rt_current_thread);rt_current_thread->stat = RT_THREAD_RUNNING | (rt_current_thread->stat & ~RT_THREAD_STAT_MASK);}}}/* enable interrupt */rt_hw_interrupt_enable(level);__exit:return;
}
/** void rt_hw_context_switch(rt_uint32 from, rt_uint32 to);* R0 --> from* R1 --> to*/.global rt_hw_context_switch_interrupt.type rt_hw_context_switch_interrupt, %function.global rt_hw_context_switch.type rt_hw_context_switch, %function
rt_hw_context_switch_interrupt:
rt_hw_context_switch:/* set rt_thread_switch_interrupt_flag to 1 */LDR R2, =rt_thread_switch_interrupt_flagLDR R3, [R2]CMP R3, #1 ; 若R3==1则Z=0,Z 标志位通常由 CMP、SUBS、ADDS 等指令设置BEQ _reswitch ; 若 Z == 1跳转MOV R3, #1STR R3, [R2] ; rt_thread_switch_interrupt_flag = 1LDR R2, =rt_interrupt_from_thread /* set rt_interrupt_from_thread */STR R0, [R2]_reswitch:LDR R2, =rt_interrupt_to_thread /* set rt_interrupt_to_thread */STR R1, [R2]LDR R0, =ICSR ; 使能PendSV中断LDR R1, =PENDSVSET_BITSTR R1, [R0]BX LR