记一次rt-thread的进程无法切换问题

rt-thread源码解析

今天开发过程遇到一个很奇怪的问题,有一个对喇叭进行声音播放的进程里每次进入这个进程后其他任务都无法工作了,因为这个播放的任务优先级调到了比其他任务都低的级别,本质是为了能在播放音乐时不影响其他任务继续工作,所以在播放的任务是一个大约2分钟的传输数据,奇怪在于这两分钟内其他任务既然无法工作了,只有在这个任务结束后一切才正常,其实我很快就定位到问题,但在解决这个问题后还是将rt-thread的源码过了一遍。这个问题的主要原因是调用了sys_tick_delay()这个函数,因为这个函数的延时也是用sys_tick中断来写的,与rt-thread的中断发生了冲突才导致的,本文使用的事最新得2.1版本的rt-thread,硬件平台为nuc130这款mcu,接下来看下启动流程

启动过程

start up里面的就不看了,大同小异,前面我也写过这部分,有兴趣可以去看下,我们直接看main里面的内容

int main(void)
{
    /* disable interrupt first */
    rt_hw_interrupt_disable();

    /* startup RT-Thread RTOS */
    rtthread_startup();

    return 0;
}

这里先关掉硬件中断,将总中断关掉,主要的初始化工作rtthread_startup()这里完成

void rtthread_startup(void)
{
    /* init board */
    rt_hw_board_init();

    /* show version */
    rt_show_version();

    /* init timer system */
    rt_system_timer_init();

#ifdef RT_USING_HEAP
    rt_system_heap_init((void*)M05X_SRAM_BEGIN, (void*)M05X_SRAM_END);
#endif

    /* init scheduler system */
    rt_system_scheduler_init();

    /* init application */
    rt_application_init();

    /* init timer thread */
    rt_system_timer_thread_init();

    /* init idle thread */
    rt_thread_idle_init();

    /* start scheduler */
    rt_system_scheduler_start();

    /* never reach here */
    return ;
}

我们一个个函数都看下

void rt_hw_board_init()
{
    /* NVIC Configuration */
    NVIC_Configuration();

    /* Configure the system clock */
    rt_hw_system_init();
    /* Configure the SysTick */
    SysTick_Config(SystemCoreClock / RT_TICK_PER_SECOND);

    /* Initial usart driver, and set console device */
    rt_hw_usart_init();
#ifdef RT_USING_CONSOLE
    rt_console_set_device(RT_CONSOLE_DEVICE_NAME);
#endif

    /* Call components board initial (use INIT_BOARD_EXPORT()) */

#ifdef RT_USING_COMPONENTS_INIT
    rt_components_board_init();
#endif

}

这里先对NVIC控制器做操作,我这边没做操作使用默认的系统设置,之后进行系统时钟的一些初始化,设置时钟源和频率,在设置sys_tick也就是这个系统切换的间隔,一般10ms一次就够了,具体切换间隔看应用需求,之后就是串口的初始化,这个我们细看下,应用到linux里面很经典的device-driver的建构,实现设备和驱动的分离。

void rt_hw_usart_init(void)
{
#ifdef RT_USING_UART0
    struct serial_configure config = RT_SERIAL_CONFIG_DEFAULT;
    config.baud_rate = BAUD_RATE_115200;
    serial0.ops    = &nuc130_uart_ops;
    serial0.config = config;


    /* register UART0 device */
    rt_hw_serial_register(&serial0, "uart0",
                          RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_INT_RX,
                          UART0);
#endif /* RT_USING_UART0 */

}

这里定义了一个全局的serial0的数据结构,我们看下这个数据

struct rt_device
{
    struct rt_object          parent;                   /**< inherit from rt_object */

    enum rt_device_class_type type;                     /**< device type */
    rt_uint16_t               flag;                     /**< device flag */
    rt_uint16_t               open_flag;                /**< device open flag */

    rt_uint8_t                ref_count;                /**< reference count */
    rt_uint8_t                device_id;                /**< 0 - 255 */

    /* device call back */
    rt_err_t (*rx_indicate)(rt_device_t dev, rt_size_t size);
    rt_err_t (*tx_complete)(rt_device_t dev, void *buffer);

    /* common device interface */
    rt_err_t  (*init)   (rt_device_t dev);
    rt_err_t  (*open)   (rt_device_t dev, rt_uint16_t oflag);
    rt_err_t  (*close)  (rt_device_t dev);
    rt_size_t (*read)   (rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size);
    rt_size_t (*write)  (rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size);
    rt_err_t  (*control)(rt_device_t dev, rt_uint8_t cmd, void *args);

    void                     *user_data;                /**< device private data */
};

struct rt_uart_ops
{
    rt_err_t (*configure)(struct rt_serial_device *serial, struct serial_configure *cfg);
    rt_err_t (*control)(struct rt_serial_device *serial, int cmd, void *arg);

    int (*putc)(struct rt_serial_device *serial, char c);
    int (*getc)(struct rt_serial_device *serial);

    rt_size_t (*dma_transmit)(struct rt_serial_device *serial, const rt_uint8_t *buf, rt_size_t size, int direction);
};

struct serial_configure
{
    rt_uint32_t baud_rate;

    rt_uint32_t data_bits               :4;
    rt_uint32_t stop_bits               :2;
    rt_uint32_t parity                  :2;
    rt_uint32_t bit_order               :1;
    rt_uint32_t invert                  :1;
    rt_uint32_t bufsz                   :16;
    rt_uint32_t reserved                :4;
};

struct rt_serial_device
{
    struct rt_device          parent;

    const struct rt_uart_ops *ops;
    struct serial_configure   config;

    void *serial_rx;
    void *serial_tx;
};

上面我截取了用到的所以数据结构,我们看下rt_serial_device的第一个数据结构是rt_device,这个是整个架构的核心,每个设备都包含这个device,在注册时将每个device都放入双向链表中实现快速遍历,rt_device的第一数据结构是rt_object可以理解为每个设备都包含的通用信息,会被每个每个设备所包含,其他的比如type表示设备类型,这里很linux几乎一样,rt-thread里面也是分为字符设备,网络设备,块设备,还有一些比如引用次数,设备号等等,里面还提供了一组标准的POSIX的结果便于以后直接移植linux程序,想法很超前,虽然是10级年轻就开发的软件,我们看下rt_object最后一个数据结构,user_data这个变量很有用,他可以将设备的信息穿个他,比如我们需要将串口的地址传入以备各个接口调用,如果需要传入的数据比较大,比如网络设备可以利用结构体封装之后传入结构体地址,这个一定程度上起到封装作用,C语言在语言上的缺陷被这样实现起来反而看的是如此精巧。
rt_uart_ops和serial_configure从上面可以看出来就是操作串口的实际函数和初始化需要用到的数据,如读写,配置、串口的波特率,控制位等等,为了封装本质上ops的函数是更底层的函数,因为串口的需要的接比较少,所以用户只需调用 void *serial_rx和void *serial_tx这两个接口函数即可。
我们在看下如何将这些信息初始化,接口函数,配置信息填充进去

    rt_hw_serial_register(&serial0, "uart0",
                          RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_INT_RX,
                          UART0);

这个注册函数就是做这些操作

rt_err_t rt_hw_serial_register(struct rt_serial_device *serial,
                               const char              *name,
                               rt_uint32_t              flag,
                               void                    *data)
{
    struct rt_device *device;
    RT_ASSERT(serial != RT_NULL);

    device = &(serial->parent);

    device->type        = RT_Device_Class_Char;
    device->rx_indicate = RT_NULL;
    device->tx_complete = RT_NULL;

    device->init        = rt_serial_init;
    device->open        = rt_serial_open;
    device->close       = rt_serial_close;
    device->read        = rt_serial_read;
    device->write       = rt_serial_write;
    device->control     = rt_serial_control;
    device->user_data   = data;

    /* register a character device */
    return rt_device_register(device, name, flag);
}

注册函数主要操作就是对device的各个接口进行赋值我们看下rt_serial_init吧,其他都一样,都是对之前的ops接口函数进行调用

static rt_err_t rt_serial_init(struct rt_device *dev)
{
    rt_err_t result = RT_EOK;
    struct rt_serial_device *serial;

    RT_ASSERT(dev != RT_NULL);
    serial = (struct rt_serial_device *)dev;

    /* initialize rx/tx */
    serial->serial_rx = RT_NULL;
    serial->serial_tx = RT_NULL;

    /* apply configuration */
    if (serial->ops->configure)
        result = serial->ops->configure(serial, &serial->config);

    return result;
}

初始化后判断serial->ops->configure是否已经初始化后就直接调用配置函数,这个初始化在前面已经完成了serial0.ops = &nuc130_uart_ops,所以这个ops函数是需要自己填写的,主要就是初始化有,读写接口,因为所有的函数最终都是调用这个接口。

rt_err_t rt_device_register(rt_device_t dev,
                            const char *name,
                            rt_uint16_t flags)
{
    if (dev == RT_NULL)
        return -RT_ERROR;

    if (rt_device_find(name) != RT_NULL)
        return -RT_ERROR;

    rt_object_init(&(dev->parent), RT_Object_Class_Device, name);
    dev->flag = flags;
    dev->ref_count = 0;

    return RT_EOK;
}

最后注册device,之后如果我们应用程需要调用串口这个设备就可以通过rt_device_find这个函数通过串口名字找到函数,后调用标准的POSIX接口,看着像小型化的linux内核,简单,优雅!初始化讲完了,我们在看面的函数
rt_system_timer_init();

void rt_system_timer_init(void)
{
    int i;

    for (i = 0; i < sizeof(rt_timer_list)/sizeof(rt_timer_list[0]); i++)
    {
        rt_list_init(rt_timer_list+i);
    }
}

这里主要是对定时器进行初始化,不过这里面的命名可能会引起误解,因为rt-thread还有个rt_soft_timer_list这个定时器可能让人误以为上面那个是硬件定时器,其实本质都是软件的定时器,只是rt_timer_list的定时器是用在线程调度用的timeout函数回调上的,rt-thread的系统休眠回调函数在时间消耗完后会调用这和list的回调函数,这部分我们讲到下面的时候在仔细分析代码,rt-thread使用的双向链表,我们看下初始化函数

rt_inline void rt_list_init(rt_list_t *l)
{
    l->next = l->prev = l;
}

本质就是next指向prev。对于链表不熟悉推荐百度下,这里不做深入讲解
rt_system_heap_init()函数是对堆得大小、起始、等信息做初始化,我们再看下一个函数

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);
}

调度器初始化,这部分挺大的,篇幅原因讲个大概,这里先初始化系统设定的线程数的链表,我这边为32,所以会初始化32个链表,在之后每次注册一个线程就会被添加到相应优先级的链表中去,之后初始化一些全局变量在调用rt_list_init(&rt_thread_defunct)主要是用于idle线程调用。
接下来看下如何启动一个线程

int rt_application_init()
{
    rt_thread_t init_thread;

#if (RT_THREAD_PRIORITY_MAX == 32)
    init_thread = rt_thread_create("init",
                                   rt_init_thread_entry, RT_NULL,
                                   512, 8, 20);
#else
    init_thread = rt_thread_create("init",
                                   rt_init_thread_entry, RT_NULL,
                                   512, 80, 20);
#endif
    if(init_thread != RT_NULL)
        rt_thread_startup(init_thread);

    return 0;
}

rt_application_init主要是完成线程调用,看下rt_thread_create函数

{
    struct rt_thread *thread;
    void *stack_start;

    thread = (struct rt_thread *)rt_object_allocate(RT_Object_Class_Thread,
                                                    name);
    if (thread == RT_NULL)
        return RT_NULL;

    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;
    }

    _rt_thread_init(thread,
                    name,
                    entry,
                    parameter,
                    stack_start,
                    stack_size,
                    priority,
                    tick);

    return thread;
}

看到线程初始化代码先是分配rt_thread和stack_start这两个结构体,在调用_rt_thread_init初始化函数,初始化函数就是对rt-thread这个数据进行填充,我们看在这个结构体

struct rt_thread
{
    /* rt object */
    char        name[RT_NAME_MAX];                      /**< the name of thread */
    rt_uint8_t  type;                                   /**< type of object */
    rt_uint8_t  flags;                                  /**< thread's flags */

#ifdef RT_USING_MODULE
    void       *module_id;                              /**< id of application module */
#endif

    rt_list_t   list;                                   /**< the object list */
    rt_list_t   tlist;                                  /**< the thread list */

    /* stack point and entry */
    void       *sp;                                     /**< stack point */
    void       *entry;                                  /**< entry */
    void       *parameter;                              /**< parameter */
    void       *stack_addr;                             /**< stack address */
    rt_uint16_t stack_size;                             /**< stack size */

    /* error code */
    rt_err_t    error;                                  /**< error code */

    rt_uint8_t  stat;                                   /**< thread stat */

    /* priority */
    rt_uint8_t  current_priority;                       /**< current priority */
    rt_uint8_t  init_priority;                          /**< initialized 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

    rt_ubase_t  init_tick;                              /**< thread's initialized tick */
    rt_ubase_t  remaining_tick;                         /**< remaining tick */

    struct rt_timer thread_timer;                       /**< built-in thread timer */

    void (*cleanup)(struct rt_thread *tid);             /**< cleanup function when thread exit */

    rt_uint32_t user_data;                              /**< private user data beyond this thread */
};

在结合代码

{
    /* init thread list */
    rt_list_init(&(thread->tlist));
    回调函数地址传入entry
    thread->entry = (void *)entry;
    参数地址传入
    thread->parameter = parameter;

    /* stack init */
    栈地址
    thread->stack_addr = stack_start;
    栈大小
    thread->stack_size = (rt_uint16_t)stack_size;

    /* init thread stack */
    初始化栈数据    
    rt_memset(thread->stack_addr, '#', thread->stack_size);
    sp指针赋值
    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;

    /* tick init */
    用于同一任务时间片轮换使用
    thread->init_tick      = tick;
    thread->remaining_tick = tick;

    /* error and flags */
    异常处理,语言要是有异常处理和RAII就完美了
    thread->error = RT_EOK;
    thread->stat  = RT_THREAD_INIT;

    /* initialize cleanup function and user data */
    清理线程和用户数据
    thread->cleanup   = 0;
    thread->user_data = 0;

    /* init thread timer */
    rt_timer_init(&(thread->thread_timer),
                  thread->name,
                  rt_thread_timeout,
                  thread,
                  0,
                  RT_TIMER_FLAG_ONE_SHOT);

    return RT_EOK;
}

上面各个数据的作用大致注释了下tick init哪里,因为thread_time里面也有tick这个变量,这里是完全不同的作用,线程里的主要是用于同一优先级的时间片轮转使用进行切换任务,thread_time里面是用于调用rt_thread_timeout这个函数时当前线程重新切换到ready状态。我们看下rt_timer_init函数

struct rt_timer
{
    struct rt_object parent;                            /**< inherit from rt_object */

    rt_list_t        row[RT_TIMER_SKIP_LIST_LEVEL];

    void (*timeout_func)(void *parameter);              /**< timeout function */
    void            *parameter;                         /**< timeout function's parameter */

    rt_tick_t        init_tick;                         /**< timer timeout tick */
    rt_tick_t        timeout_tick;                      /**< timeout tick */
};

{
    /* timer check */
    RT_ASSERT(timer != RT_NULL);

    /* timer object initialization */
    rt_object_init((rt_object_t)timer, RT_Object_Class_Timer, name);

    _rt_timer_init(timer, timeout, parameter, time, flag);
}

static void _rt_timer_init(rt_timer_t timer,
                           void (*timeout)(void *parameter),
                           void      *parameter,
                           rt_tick_t  time,
                           rt_uint8_t flag)
{
    int i;

    /* set flag */
    timer->parent.flag  = flag;

    /* set deactivated */
    timer->parent.flag &= ~RT_TIMER_FLAG_ACTIVATED;

    timer->timeout_func = timeout;
    timer->parameter    = parameter;

    timer->timeout_tick = 0;
    timer->init_tick    = time;

    /* initialize timer list */
    for (i = 0; i < RT_TIMER_SKIP_LIST_LEVEL; i++)
    {
        rt_list_init(&(timer->row[i]));
    }

这里做了定时器的初始化,应该看了就很清晰,这里主要tick的所以操作最终会在sys_tick_handle的这个中断函数做处理,我们这边来看下可能会疑惑的地方,这里的定时器都只是做了初始化却没有见timer插入链表中,这个其实是在其他地方做初始化,在最开头初始会定时器时初始化了一个全局的定时器链表,我们看下static rt_list_t rt_timer_list[RT_TIMER_SKIP_LIST_LEVEL],这个链表是所有线程定时器的最初的初始表,这个表头初始化在rt_timer_start中完成

rt_err_t rt_timer_start(rt_timer_t timer)
{
    unsigned int row_lvl;
    rt_list_t *timer_list;
    register rt_base_t level;
    rt_list_t *row_head[RT_TIMER_SKIP_LIST_LEVEL];
    unsigned int tst_nr;
    static unsigned int random_nr;

    /* timer check */
    RT_ASSERT(timer != RT_NULL);

    /* stop timer firstly */
    level = rt_hw_interrupt_disable();
    /* remove timer from list */
    _rt_timer_remove(timer);
    /* change status of timer */
    timer->parent.flag &= ~RT_TIMER_FLAG_ACTIVATED;
    rt_hw_interrupt_enable(level);

    RT_OBJECT_HOOK_CALL(rt_object_take_hook, (&(timer->parent)));

    /*
     * get timeout tick,
     * the max timeout tick shall not great than RT_TICK_MAX/2
     */
    RT_ASSERT(timer->init_tick < RT_TICK_MAX / 2);
    timer->timeout_tick = rt_tick_get() + timer->init_tick;

    /* disable interrupt */
    level = rt_hw_interrupt_disable();

#ifdef RT_USING_TIMER_SOFT
    if (timer->parent.flag & RT_TIMER_FLAG_SOFT_TIMER)
    {
        /* insert timer to soft timer list */
        timer_list = rt_soft_timer_list;
    }
    else
#endif
    {
        /* insert timer to system timer list */
        timer_list = rt_timer_list;
    }

    row_head[0]  = &timer_list[0];
    for (row_lvl = 0; row_lvl < RT_TIMER_SKIP_LIST_LEVEL; row_lvl++)
    {
        for (;row_head[row_lvl] != timer_list[row_lvl].prev;
             row_head[row_lvl]  = row_head[row_lvl]->next)
        {
            struct rt_timer *t;
            rt_list_t *p = row_head[row_lvl]->next;

            /* fix up the entry pointer */
            t = rt_list_entry(p, struct rt_timer, row[row_lvl]);

            /* If we have two timers that timeout at the same time, it's
             * preferred that the timer inserted early get called early.
             * So insert the new timer to the end the the some-timeout timer
             * list.
             */
            if ((t->timeout_tick - timer->timeout_tick) == 0)
            {
                continue;
            }
            else if ((t->timeout_tick - timer->timeout_tick) < RT_TICK_MAX / 2)
            {
                break;
            }
        }
        if (row_lvl != RT_TIMER_SKIP_LIST_LEVEL - 1)
            row_head[row_lvl+1] = row_head[row_lvl]+1;
    }

    /* Interestingly, this super simple timer insert counter works very very
     * well on distributing the list height uniformly. By means of "very very
     * well", I mean it beats the randomness of timer->timeout_tick very easily
     * (actually, the timeout_tick is not random and easy to be attacked). */
    random_nr++;
    tst_nr = random_nr;

    rt_list_insert_after(row_head[RT_TIMER_SKIP_LIST_LEVEL-1],
                         &(timer->row[RT_TIMER_SKIP_LIST_LEVEL-1]));
    for (row_lvl = 2; row_lvl <= RT_TIMER_SKIP_LIST_LEVEL; row_lvl++)
    {
        if (!(tst_nr & RT_TIMER_SKIP_LIST_MASK))
            rt_list_insert_after(row_head[RT_TIMER_SKIP_LIST_LEVEL - row_lvl],
                                 &(timer->row[RT_TIMER_SKIP_LIST_LEVEL - row_lvl]));
        else
            break;
        /* Shift over the bits we have tested. Works well with 1 bit and 2
         * bits. */
        tst_nr >>= (RT_TIMER_SKIP_LIST_MASK+1)>>1;
    }

    timer->parent.flag |= RT_TIMER_FLAG_ACTIVATED;

    /* enable interrupt */
    rt_hw_interrupt_enable(level);

#ifdef RT_USING_TIMER_SOFT
    if (timer->parent.flag & RT_TIMER_FLAG_SOFT_TIMER)
    {
        /* check whether timer thread is ready */
        if (timer_thread.stat != RT_THREAD_READY)
        {
            /* resume timer thread to check soft timer */
            rt_thread_resume(&timer_thread);
            rt_schedule();
        }
    }
#endif

    return -RT_EOK;
}

这里可以看到对于传入的system线程定时器最终都会被插入到全区表头里面,而这个函数是在rt_thread_sleep里面调用的,所有只有程序调用了使函数休眠的操作才会有插入链表的操作,一定程度是减低了系统消耗、我们回过头来继续看下面的程序

rt_err_t rt_thread_startup(rt_thread_t thread)
{
    /* thread check */
    RT_ASSERT(thread != RT_NULL);
    RT_ASSERT(thread->stat == RT_THREAD_INIT);

    /* set current priority to init priority */
    thread->current_priority = thread->init_priority;

    /* calculate priority attribute */
#if RT_THREAD_PRIORITY_MAX > 32
    thread->number      = thread->current_priority >> 3;            /* 5bit */
    thread->number_mask = 1L << thread->number;
    thread->high_mask   = 1L << (thread->current_priority & 0x07);  /* 3bit */
#else
    thread->number_mask = 1L << thread->current_priority;
#endif

    RT_DEBUG_LOG(RT_DEBUG_THREAD, ("startup a thread:%s with priority:%d\n",
                                   thread->name, thread->init_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_thread_resume主要是将线程放入ready链表里面,状态转为ready状态。后面的 rt_system_timer_thread_init(),rt_thread_idle_init()基本一样,都是开启一个线程,其中rt_system_timer_thread_init主要是软件定时器,会一直轮询软件定时器链表。我们最后看下rt_system_scheduler_start,

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 */
}

highest_ready_priority = __rt_ffs(rt_thread_ready_priority_group) - 1;主要是根rt_thread_ready_priority_group的值。通过__rt_ffs就算出就绪链表中的最高优先级线程,最后切换进程。
到这里基本流程已经很清晰了,我们最后看下sys_tick看下如何进行线程切换

void SysTick_Handler(void)
{
    /* enter interrupt */
    rt_interrupt_enter();

    rt_tick_increase();

    /* leave interrupt */
    rt_interrupt_leave();
}
主要看下rt_tick_increase
void rt_tick_increase(void)
{
    struct rt_thread *thread;

    /* increase the global tick */
    ++ rt_tick;

    /* check time slice */
    thread = rt_thread_self();

    -- thread->remaining_tick;
    if (thread->remaining_tick == 0)
    {
        /* change to initialized tick */
        thread->remaining_tick = thread->init_tick;

        /* yield */
        rt_thread_yield();
    }

    /* check timer */
    rt_timer_check();
}

– thread->remaining_tick;就是对当前进程的时钟进行递减,为0时间自己调用rt_thread_yield,这里会先当前程序移除当前链表,在插到链表尾端,最后调用调度函数开始调度

rt_err_t rt_thread_yield(void)
{
    register rt_base_t level;
    struct rt_thread *thread;

    /* disable interrupt */
    level = rt_hw_interrupt_disable();

    /* set to current thread */
    thread = rt_current_thread;

    /* if the thread stat is READY and on ready queue list */
    if (thread->stat == RT_THREAD_READY &&
        thread->tlist.next != thread->tlist.prev)
    {
        /* remove thread from thread list */
        rt_list_remove(&(thread->tlist));

        /* put thread to end of ready queue */
        rt_list_insert_before(&(rt_thread_priority_table[thread->current_priority]),
                              &(thread->tlist));

        /* enable interrupt */
        rt_hw_interrupt_enable(level);

        rt_schedule();

        return RT_EOK;
    }

    /* enable interrupt */
    rt_hw_interrupt_enable(level);

    return RT_EOK;
}

我们在看下调度函数

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();

    /* check the scheduler is enabled or not */
    if (rt_scheduler_lock_nest == 0)
    {
        register rt_ubase_t highest_ready_priority;

#if RT_THREAD_PRIORITY_MAX <= 32
        highest_ready_priority = __rt_ffs(rt_thread_ready_priority_group) - 1;
#else
        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;
#endif

        /* get switch to thread */
        to_thread = rt_list_entry(rt_thread_priority_table[highest_ready_priority].next,
                                  struct rt_thread,
                                  tlist);

        /* if the destination thread is not the same as current thread */
        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));

            /* switch to new thread */
            RT_DEBUG_LOG(RT_DEBUG_SCHEDULER,
                         ("[%d]switch to priority#%d "
                          "thread:%.*s(sp:0x%p), "
                          "from thread:%.*s(sp: 0x%p)\n",
                          rt_interrupt_nest, highest_ready_priority,
                          RT_NAME_MAX, to_thread->name, to_thread->sp,
                          RT_NAME_MAX, from_thread->name, from_thread->sp));

#ifdef RT_USING_OVERFLOW_CHECK
            _rt_scheduler_stack_check(to_thread);
#endif

            if (rt_interrupt_nest == 0)
            {
                rt_hw_context_switch((rt_uint32_t)&from_thread->sp,
                                     (rt_uint32_t)&to_thread->sp);
            }
            else
            {
                RT_DEBUG_LOG(RT_DEBUG_SCHEDULER, ("switch in interrupt\n"));

                rt_hw_context_switch_interrupt((rt_uint32_t)&from_thread->sp,
                                               (rt_uint32_t)&to_thread->sp);
            }
        }
    }

    /* enable interrupt */
    rt_hw_interrupt_enable(level);
}

和之前开始切换的函数大同小异,做些判断后就开始寻找最高优先级的线程在进行切换,同一优先级的看完我们在看下对最后一个函数rt_timer_check();

void rt_timer_check(void)
{
    struct rt_timer *t;
    rt_tick_t current_tick;
    register rt_base_t level;

    RT_DEBUG_LOG(RT_DEBUG_TIMER, ("timer check enter\n"));

    current_tick = rt_tick_get();

    /* disable interrupt */
    level = rt_hw_interrupt_disable();

    while (!rt_list_isempty(&rt_timer_list[RT_TIMER_SKIP_LIST_LEVEL-1]))
    {
        rt_kprintf("entry timer check .....\r\n");
        t = rt_list_entry(rt_timer_list[RT_TIMER_SKIP_LIST_LEVEL - 1].next,
                          struct rt_timer, row[RT_TIMER_SKIP_LIST_LEVEL - 1]);

        /*
         * It supposes that the new tick shall less than the half duration of
         * tick max.
         */
        if ((current_tick - t->timeout_tick) < RT_TICK_MAX/2)
        {
            RT_OBJECT_HOOK_CALL(rt_timer_timeout_hook, (t));

            /* remove timer from timer list firstly */
            _rt_timer_remove(t);

            /* call timeout function */
            t->timeout_func(t->parameter);

            /* re-get tick */
            current_tick = rt_tick_get();

            RT_DEBUG_LOG(RT_DEBUG_TIMER, ("current tick: %d\n", current_tick));

            if ((t->parent.flag & RT_TIMER_FLAG_PERIODIC) &&
                (t->parent.flag & RT_TIMER_FLAG_ACTIVATED))
            {
                /* start it */
                t->parent.flag &= ~RT_TIMER_FLAG_ACTIVATED;
                rt_timer_start(t);
            }
            else
            {
                /* stop timer */
                t->parent.flag &= ~RT_TIMER_FLAG_ACTIVATED;
            }
        }
        else
            break;
    }

    /* enable interrupt */
    rt_hw_interrupt_enable(level);

    RT_DEBUG_LOG(RT_DEBUG_TIMER, ("timer check leave\n"));
}

这里面就是对线程的定时器操作函数,判断是否时间到,看下!rt_list_isempty(&rt_timer_list[RT_TIMER_SKIP_LIST_LEVEL-1])
这个函数在有发送休眠的相应函数调用下,定时器就会被添加进去,上面我已经分析过了,判断到(current_tick - t->timeout_tick) < RT_TICK_MAX/2满足是说明定时时间到了,开始调用回调函数,将线程重新加入就绪状态,插入就绪链表中去,我解释下(current_tick - t->timeout_tick) < RT_TICK_MAX/2是如何判断的,timeout_tick的值是timer->timeout_tick = rt_tick_get() + timer->init_tick;也就是会大于current_tick的值的,而这两个值都是unsigned类型的,最终在current_tick 未到达 timeout_tick之前都是一个负数的补码,将会大于 RT_TICK_MAX/2,反之者成立并执行下面语句。
到这里基本流程算是一段,最终代码会放在我的github,有兴趣或者发现哪里分析有误,忘大家指出

总结

写的篇幅有点大了,但是还是有许多没有讲到,比如线程池的实现,任务调度的实现,任务的位图处理怎么计算最高优先级,线程切换的汇编代码,还有一些IPC,通信的东西都没讲,如果时间充裕后续会在写一些详细的分析文章,毕竟上班时间也比较忙。
SO Enjoy it
https://github.com/zwxf/ESP8266_RTOS_SDK

你可能感兴趣的:(cortex,M0)