在操作系统中,线程是实现任务的载体。RT-Thread中的线程调度是基于抢占式的。
它是操作系统用于管理线程的数据结构,存放线程的基本信息,比如优先级、线程名称、状态等,也包含线程与线程之间链接用的链表结构、线程等待事件集合等。
(1)线程栈
RT-Thread线程具有独立的栈,当进行线程切换时,会将当前线程上下文保存在栈中。还存放函数中的局部变量。
(2)线程状态
线程包含5种状态:初始、就绪、运行、挂起、关闭。
(3)线程优先级
RT-Thead最大支持256个线程优先级0-255,数值越小的优先级越高,0为最高优先级。
(4)时间片
每个线程都有时间片参数,但时间片仅对优先级相同的就绪状态线程有效。
(5)线程的入口函数
线程控制块的entry是线程的入口函数,它是线程实现预期功能的函数,有两种代码模式:
①无限循环模式:
在实时系统中,线程通常是被动式的,等待外界事件的发生,而后进行相应的服务。
void thread_entry(void* paramenter)
{
while (1)
{
/* 等待事件的发生 */
/* 对事件进行服务、进行处理 */
}
}
线程不能陷入死循环,必须要有让出CPU使用权的操作,比如在循环中调用延时函数或主动挂起。
②顺序执行或有限次循环模式:
一次性线程,在执行完毕后,线程将被系统自动删除。
static void thread_entry(void* parameter)
{
/* 处理事务#1 */
...
/* 处理事务#2 */
...
/* 处理事务#3 */
}
(6)线程错误码
为每一个线程都配备了一个变量,用于保存错误码。错误码有以下几种:
#define RT_EOK 0 /* 无错误*/
#define RT_ERROR 1 /* 普通错误*/
#define RT_ETIMEOUT 2 /* 超时错误*/
#define RT_EFULL 3 /* 资源已满*/
#define RT_EEMPTY 4 /* 无资源*/
#define RT_ENOMEM 5 /* 无内存*/
#define RT_ENOSYS 6 /* 系统不支持*/
#define RT_EBUSY 7 /* 系统忙*/
#define RT_EIO 8 /* IO错误*/
#define RT_EINTR 9 /* 中断系统调用*/
#define RT_EINVAL 10 /* 非法参数*/
注意,RT-Thread中,实际上线程并不存在运行状态,就绪状态和运行状态是等同的。
可以通过下面这个函数创建动态线程,前提是先在rtconfig,h中使能动态堆(#define RT_USING_HEAP ):
rt_thread_t rt_thread_create(const char *name,
void (*entry)(void *parameter),
void *parameter,
rt_uint32_t stack_size,
rt_uint8_t priority,
rt_uint32_t tick)
使用下面这个函数接口创建静态线程:
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);
对于一些使用rt_thread_create()创建的线程,可以使用rt_thread_delete()这个函数删除线程,但它仅仅是把相应的线程状态更改为RT_THREAD_CLOSE状态,然后放入rt_thread_defunct队列中;真正删除是由空闲线程删除的。
rt_thread_detach()操作的是rt_thread_init()初始化的线程控制块,而rt_thread_delete()操作的是rt_thread_create()创建的句柄。
rt_thread_startuo将创建或初始化后的线程调入相应优先级的就绪队列。
一段相同的代码可能会被多个线程执行,在执行的时候可以通过rt_thread_self(void)函数获得当前执行的线程句柄。
rt_thread_yield() 和 rt_schedule(),前者执行后,当前线程换出,相同优先级的将被执行;后者是执行后,当前线程并不一定被换出,即使被换出,也不会放到就绪线程链表的尾部,而是选取优先级最高的线程执行。
线程睡眠可以使用下面这三个函数接口:
rt_err_t rt_thread_sleep(rt_tick_t tick);
rt_err_t rt_thread_delay(rt_tick_t tick);
rt_err_t rt_thread_mdelay(rt_int32_t ms);
调用它们可以使当前线程挂起一段指定的时间,当这个时间过后,线程会被唤醒并再次进入就绪状态。
如果想控制线程的话,比如动态更改线程的优先级,用下面这个接口:
rt_err_t rt_thread_control(rt_thread_t thread, rt_uint8_t cmd, void* arg);
rt_err_t表示Type for error number。
空闲钩子函数是空闲线程的钩子函数,如果设置了空闲钩子函数,就可以在系统执行空闲线程时自动执行空闲钩子函数来做一些其他事情,比如系统指示灯。设置和删除空闲钩子的接口如下:
rt_err_t rt_thread_idle_sethook(void (*hook)(void));
rt_err_t rt_thread_idle_delhook(void (*hook)(void));
需要注意的是,空闲线程是状态永远为就绪状态,设置的钩子函数必须保证空闲线程在任何时刻都不会处于挂起状态,例如rt_thread_delay()
(延时)、rt_sem_take()
(获取信号量)等可以导致线程挂起的函数都不能使用。
如果想知道在某个时刻发生了什么样的线程切换,可以通过调用下面的函数接口设置一个相应的钩子函数:
void rt_scheduler_sethook(void (*hook)(struct rt_thread*from, struct rt_thread* to));
在线程切换时,这个钩子将会被调用。但请注意在这个钩子函数中,基本上不允许调用系统API,更不应该导致当前运行的上下文挂起。