多任务的切换的关键在于:保存当前任务的数据 + 切换到其他任务。这两个条件的目的是为了能够使程序看起来像是连贯的进行,就像是同时进行一样。至于保存当前程序的状态,什么反映了当前程序的状态?大家恐怕是比较疑惑这里的,实际上,程序得以运行是依靠ARM架构下的寄存器的,寄存器包括哪些?这里有传送门
还有思考过在创建线程时,需要开启一个栈,栈大小为512,而且还同时申请了一个线程的TCB控制块,TCB控制块中包含线程的全部信息,当时就思考过是不是把TCB线程控制块的信息存储到开起的栈中,实际上并不是存储到这个栈中的。这个申请的512的栈用来存储的是ARM寄存器里的数据,一个寄存器是32位,一共16个寄存器,32*16=512。之前我还傻傻得开启1024,现在想想还的确是讽刺呢。
查看上次的线程创建的内容,今天会注意到一个函数
/* init thread stack */
/*初始化线程栈,并且返回线程栈指针*/
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);
rt_hw_stack_init()函数的原型是这样的
rt_uint8_t *rt_hw_stack_init(void *tentry,
void *parameter,
rt_uint8_t *stack_addr, //传入时候 -4,可能是为了后面容易修改吧
void *texit)
{
struct stack_frame *stack_frame;
rt_uint8_t *stk;
unsigned long i;
stk = stack_addr + sizeof(rt_uint32_t); //sizeof(rt_uint32_t)的大小就是4,
//加上传入的 -4,正负相消。 另外这里的stack_addr是栈顶指针,不是创建时的栈底指针
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 */
#if USE_FPU
stack_frame->flag = 0;
#endif /* USE_FPU */
/* return task's current stack address */
return stk;
}
我们都知道PC(R15)内的数据是程序的跳转地址,这里指线程的入口地址。R0约定是线程形参,状态寄存器xPSR的第24位是1.
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
{
#if USE_FPU
rt_uint32_t flag;
#endif /* USE_FPU */
/* 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;
};
texit是什么呢,大家可能会疑问
void rt_thread_exit(void)
{
struct rt_thread *thread;
register rt_base_t level;
/* get current thread */
thread = rt_current_thread;
/* disable interrupt */
level = rt_hw_interrupt_disable();
/* remove from schedule */
rt_schedule_remove_thread(thread);
/* change stat */
thread->stat = RT_THREAD_CLOSE;
/* remove it from timer list */
rt_timer_detach(&thread->thread_timer);
if ((rt_object_is_systemobject((rt_object_t)thread) == RT_TRUE) &&
thread->cleanup == RT_NULL)
{
rt_object_detach((rt_object_t)thread);
}
else
{
/* insert to defunct thread list */
rt_list_insert_after(&rt_thread_defunct, &(thread->tlist));
}
/* enable interrupt */
rt_hw_interrupt_enable(level);
/* switch to next task */
rt_schedule();
}
野火哥的教程里这里写得是LR(R14)的值写得是0,可以理解,以为野火哥这里只是讲解原理。
经过rt_hw_stack_init函数,系统调度需要的基础条件已经有了。程序知道一旦需要自己执行时,需要向ARM的寄存器里填入什么数据。那最后一步了,开始调度。
调度器也需要初始化
/**
* @ingroup SystemInit
* This function will initialize the system scheduler
*/
void rt_system_scheduler_init(void)
{
register rt_base_t offset;
rt_scheduler_lock_nest = 0;
RT_DEBUG_LOG(RT_DEBUG_SCHEDULER, ("start scheduler: max priority 0x%02x\n",
RT_THREAD_PRIORITY_MAX));
for (offset = 0; offset < RT_THREAD_PRIORITY_MAX; offset ++)
{
rt_list_init(&rt_thread_priority_table[offset]);
}
rt_current_priority = RT_THREAD_PRIORITY_MAX - 1;
rt_current_thread = RT_NULL;
/* initialize ready priority group */
rt_thread_ready_priority_group = 0;
#if RT_THREAD_PRIORITY_MAX > 32
/* initialize ready table */
rt_memset(rt_thread_ready_table, 0, sizeof(rt_thread_ready_table));
#endif
/* initialize thread defunct */
rt_list_init(&rt_thread_defunct);
}
/**
* @ingroup SystemInit
* This function will startup scheduler. It will select one thread
* with the highest priority level, then switch to it.
*/
void rt_system_scheduler_start(void)
{
register struct rt_thread *to_thread;
register rt_ubase_t highest_ready_priority;
#if RT_THREAD_PRIORITY_MAX > 32
register rt_ubase_t number;
number = __rt_ffs(rt_thread_ready_priority_group) - 1;
highest_ready_priority = (number << 3) + __rt_ffs(rt_thread_ready_table[number]) - 1;
#else
highest_ready_priority = __rt_ffs(rt_thread_ready_priority_group) - 1;
#endif
/* get switch to thread */
to_thread = rt_list_entry(rt_thread_priority_table[highest_ready_priority].next,
struct rt_thread,
tlist);
rt_current_thread = to_thread;
/* switch to new thread */
rt_hw_context_switch_to((rt_uint32_t)&to_thread->sp);
/* never come back */
}
上面的代码,有一部分还是看不太懂的。比如说__rt_ffs()函数,这个就是RTT的位图优先级调度算法的内容。这个后面等理解了这个算法,再单独列出来。今天的内容不考虑优先级的问题。
/**
* @brief get the struct for this entry
* @param node the entry point
* @param type the type of structure
* @param member the name of list in structure
*/
#define rt_list_entry(node, type, member) \
rt_container_of(node, type, member)
/**
* rt_container_of - return the member address of ptr, if the type of ptr is the
* struct type.
*/
// 返回member的ptr地址,如果这个类型的变量是结构体类型;这里的menber指得是参数member,而不是广义的成员
//已知一个结构体里面的成员的地址,反推出该结构体的首地址;
#define rt_container_of(ptr, type, member) \
((type *)((char *)(ptr) - (unsigned long)(&((type *)0)->member)))
没有见过这种骚操作,可以学习。我的理解是,将0强制转化为rt_thread的结构体类型,查找rt_thread->tlist的位置,这样就知道对于从0地址开始的rt_thread类型的成员tlist的地址,大概是有多大。用ptr的地址减去这个偏移量就可以得到rt_thread的首地址,应该就是这个意思,而且也是符合这个函数的注释意思:“给一个结构体内的成员地址,返回结构体首地址”。
/* switch to new thread */
rt_hw_context_switch_to((rt_uint32_t)&to_thread->sp);
从这个函数开始,就进入了调度的程序。启用PendSV中断,进行线程间得切换。注意这个位置是传入sp,在RT-Thread的TCB控制块中,sp的概念,就是前期在申请rt_thread struct结构体时同时申请的stack[512],当然,sp里存放得就是stack的首地址。在调度程序里,上文保存(中断现场的保护),下文切换(中断现场的恢复)就是将ARM处理器下的寄存器组织数据存放在其中。
/*
* 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
/*
* void rt_hw_context_switch(rt_uint32 from, rt_uint32 to);
* r0 --> from
* r1 --> to
*/
rt_hw_context_switch
/*
*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