RT-Thread内核实现 --线程调度的实现(一)

多任务的切换的关键在于:保存当前任务的数据 + 切换到其他任务。这两个条件的目的是为了能够使程序看起来像是连贯的进行,就像是同时进行一样。至于保存当前程序的状态,什么反映了当前程序的状态?大家恐怕是比较疑惑这里的,实际上,程序得以运行是依靠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)))

RT-Thread内核实现 --线程调度的实现(一)_第1张图片

 没有见过这种骚操作,可以学习。我的理解是,将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


 

你可能感兴趣的:(RT-Thread)