多线程的表象就是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),其实讲道理,我只是听说这样可以增加读取的速度和节约存放的空间。讲道理还是对编译过程不熟悉,不过,我现在的水平也就是这样,揠苗助长确实不妥,还是一步步来吧,先记着。
在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文件文件中。疑惑?
双向链表和单向链表的定义
/**
* 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. */
/**
* @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;
}
链表已经有功能了,初始化,增加,删除。功能可以说比较完备了,链表的作用在这里是有准备就绪列表的作用。
就绪列表的样子并不是我一开始理解得一个双向链表,而是一个优先级一组就绪列表。也很好理解,因为放在一个就绪列表里,的确分不清优先级,放在不同的就绪列表里就不会出现这种情况了。
这就是从线程创建到插入就绪链表的全部过程,嗯,插入就绪列表有一个函数
/*
*在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]就行。