RT-Thread内核实现 --线程的创建直到就绪态

多线程的表象就是CPU来回在多个线程中切换,造成这几个线程同时都在响应的感觉。所以多线程的核心是调度,而使调度有意义就需要告诉系统,当前可以调度那些线程。

在RT-Thread中,线程创建——>加入就绪态——>线程运行。

就绪态的实现,是因为引入了一个双向链表,将线程插入链表则表示就绪态,删除则表示脱离就绪态。


栈,一个神奇的地方。在我之前转的一个文章里已经对这个有了一定程序的叙述。

栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。

在线程创建这一栏中,栈扮演着什么作用?在我之前刚刚学习RT-Thread的时候,写过线程创建(非内核实现),线程创建刚开始就定义了一个数组空间和一个结构体线程块-TCB线程控制块

ALIGN(RT_ALIGN_SIZE)
static rt_uint8_t threadx_statck[512];
static struct rt_thread threadx;

线程结构体中包含很多信息,而恰巧先前创建的threadx_statck[512]的地址和大小就会被记录其中。当然这个是完全版的,包含很多信息。不过还好注释很翔实,这对于往后的学习非常有帮助。

不过,还是有一些疑惑。静态创建的有threadx_statck[]一段数组作为栈空间,然而又定义了一个rt_thread结构体来从而记录TCB数据块的数据。虽然后续也有对threadx_stack[]的操作,但是线程运行的数据到底是存放在结构体那个位置,还是放在数组这个栈空间位置了?是怎么实现的?PS:在rt_hw_statck_init()函数里,相当明显得显示了threadx_statck[]里的数据是ARM寄存器的数据,和TCB数据块里的数据并不是同一类数据。具体的内容下一节在中断保存和恢复中分析。

线程的动态创建方式摒弃了创建threadx_stack[]数组,那么他的数据是不是完全存放在堆中而非栈中?PS:刚才翻看了creat函数,的确是存储在堆空间里的。

/**
 * Thread structure
 */
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_uint32_t stack_size;                             /**< stack size */

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

    rt_uint8_t  stat;                                   /**< thread status */

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

#if defined(RT_USING_SIGNALS)
    rt_sigset_t     sig_pending;                        /**< the pending signals */
    rt_sigset_t     sig_mask;                           /**< the mask bits of signal */

    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;                              /**< 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 */
};
typedef struct rt_thread *rt_thread_t;

这里还有_rt_thread_init函数,不是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)
{
    /* init thread list */
    rt_list_init(&(thread->tlist));

    thread->entry = (void *)entry;
    thread->parameter = parameter;

    /* stack init */
    thread->stack_addr = stack_start;
    thread->stack_size = stack_size;

    /* 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);

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

    /* init thread timer */
    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;
}

在另外的帖子里,有一个关于为什么RTOS系统总会让一个线程用一个线程栈而不共用同一个线程栈的意义。<传送门>在RTOS中,栈大小就有了很大的意义,尤其是对于静态创建而言。通过查阅.map文件是理论上是可以分析栈空间大小是多少。不过,仍然是有疑问的,就是启动文件中可以查看到Statck_size,以及Heap_size的大小定义,可是在编程使用时候,不太明白这个大小对于程序的意义,或者说感觉空间太小了不够用但是程序貌似还是正常的。在日后的学习中,如果搞明白了,我再写一篇说明。

如果你现在回头看,我在第一个代码块的位置,也装模作样的写了一个ALIGN(RT_ALIGN_SIZE),其实讲道理,我只是听说这样可以增加读取的速度和节约存放的空间。讲道理还是对编译过程不熟悉,不过,我现在的水平也就是这样,揠苗助长确实不妥,还是一步步来吧,先记着。

1.创建一个线程

在RTOS中,线程的调度基础:首先得有一个线程。当然这是一句废话,那不是废话的部分就是,线程的创建的实现。

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)
{
    /* init thread list */
    rt_list_init(&(thread->tlist));

    thread->entry = (void *)entry;
    thread->parameter = parameter;

    /* stack init */
    thread->stack_addr = stack_start;
    thread->stack_size = stack_size;

    /* 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);

    return RT_EOK;
}

这里仍然是_rt_thread_init函数,去掉一些暂时还看不懂的内容,是不是轻快多了,至少我感觉是这样的。输入的参数,大部分被struct rt_thread *thread给存储到TCB控制块中,然后,初始化一个双向链表rt_list_init();这个函数的定义在rtservice.h函数中,嗯嗯~~~这种在.h写函数貌似就没有报错,编译出来没有错误,也不清楚为什么不写到.c文件文件中。疑惑?

2.就绪列表的实现

双向链表和单向链表的定义

/**
 * Double List structure
 */
struct rt_list_node
{
    struct rt_list_node *next;                          /**< point to next node. */
    struct rt_list_node *prev;                          /**< point to prev node. */
};
typedef struct rt_list_node rt_list_t;                  /**< Type for lists. */

/**
 * Single List structure
 */
struct rt_slist_node
{
    struct rt_slist_node *next;                         /**< point to next node. */
};
typedef struct rt_slist_node rt_slist_t;                /**< Type for single list. */

rt_list_init()函数

/**
 * @brief initialize a list
 *
 * @param l list to be initialized
 */
rt_inline void rt_list_init(rt_list_t *l)
{
    l->next = l->prev = l;
}

双向链表的插入函数,删除函数

/**
 * @brief insert a node after a list
 *
 * @param l list to insert it
 * @param n new node to be inserted
 *///选定的节点后添加
rt_inline void rt_list_insert_after(rt_list_t *l, rt_list_t *n)
{
    l->next->prev = n;
    n->next = l->next;

    l->next = n;
    n->prev = l;
}

/**
 * @brief insert a node before a list
 *
 * @param n new node to be inserted
 * @param l list to insert it
 *///选定的节点前添加
rt_inline void rt_list_insert_before(rt_list_t *l, rt_list_t *n)
{
    l->prev->next = n;
    n->prev = l->prev;

    l->prev = n;
    n->next = l;
}

/**
 * @brief remove node from list.
 * @param n the node to remove from the list.
 *///删除一个节点
rt_inline void rt_list_remove(rt_list_t *n)
{
    n->next->prev = n->prev;
    n->prev->next = n->next;

    n->next = n->prev = n;
}

RT-Thread内核实现 --线程的创建直到就绪态_第1张图片RT-Thread内核实现 --线程的创建直到就绪态_第2张图片

链表已经有功能了,初始化,增加,删除。功能可以说比较完备了,链表的作用在这里是有准备就绪列表的作用。

2.就绪列表的样子

就绪列表的样子并不是我一开始理解得一个双向链表,而是一个优先级一组就绪列表。也很好理解,因为放在一个就绪列表里,的确分不清优先级,放在不同的就绪列表里就不会出现这种情况了。

RT-Thread内核实现 --线程的创建直到就绪态_第3张图片

 这就是从线程创建到插入就绪链表的全部过程,嗯,插入就绪列表有一个函数

/*
*在rtconfig.h
*/
#define RT_THREAD_PRIORITY_MAX 32

/*
*在scheduler.c
*/
rt_list_t rt_thread_priority_table[RT_THREAD_PRIORITY_MAX];

/*
*将线程插入就绪列表中
*/
rt_list_insert_before(&(rt_thread_priority_table[0],&(thread1.tlist));

嗯,不要以为rt_thread_priority_table[0]的*next是rt_thread_priority_table[1]就行。

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