众所周知RT-Thread 操作系统的线程当中,一共有两种–静态线程和是动态线程,而RT-Thread 中的线程一般由三部分组成:线程代码(函数)、 线程控制块、 线程堆栈
那线程的创建又是怎么实现的呢?
首先咱们先看一下静态线程的创建,代码是这样的:
线程的初始化
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);
函数参数分别为
首先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_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 */
/* light weight process if present */
#ifdef RT_USING_LWP
void *lwp;
#endif
rt_uint32_t user_data; /**< private user data beyond this thread */
};
typedef struct rt_thread *rt_thread_t;
除了上述参数以外,需要说明的是结构体中有rt_list_t list; /**< the object list */和 rt_list_t tlist; /**< the thread list */
两个链表节点,它们就好像是线程控制块里面的一个钩子,可以把线程控制块挂在各种链表中。
以rt_list_t tlist;
为例,代表线程链表节点, tlist 的数据类型是是 rt_list_t,该数据类型在 rtdef.h中定义。
1 struct rt_list_node
2 {
3 struct rt_list_node *next; /* 指向后一个节点 */
4 struct rt_list_node *prev; /* 指向前一个节点 */
5 };
6 typedef struct rt_list_node rt_list_t;
从中可以看出是一个双向链表,可以理解为每有一个线程创建,就会被插入到线程链表,会作用在在线程调度的时候。
而其它参数中明显看到我们要先定义一个线程控制块 这个还是很好理解的,毕竟凡是使用一个东西都有自己的名字和属性嘛 —>线程控制块
然后就是线程栈了,栈是个神奇的东西,它编译器自动分配释放 ,存放函数的参数值,一般为局部变量的值等。毕竟函数都需要储存地方嘛,而且也要有参数储存的地方,但不理解的是为什么每个线程都需要自己的堆栈呢??为什么不能好几个线程共用一个呢,只要把空间划分好就可以了啊?
其实在RT-Thread中 栈的主要作用是:**当进行线程切换时,会将当前线程的上下文存在栈中,当线程要恢复运行时,再从栈中读取上下文信息,进行恢复。**也就是说只要能实现此功能,你几个线程用一个栈都不重要了,但在大佬的帖子中发现多个线程使用一个栈是有问题的,(帖子请点此处)是不是被大佬的逻辑征服了啊?反正我是被大佬点醒了,弄懂了这两部分,我们下面看一下创建线程代码:
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)
{
/* thread check */
RT_ASSERT(thread != RT_NULL);
RT_ASSERT(stack_start != RT_NULL);
/* init thread object */
rt_object_init((rt_object_t)thread, RT_Object_Class_Thread, name);
return _rt_thread_init(thread,
name,
entry,
parameter,
stack_start,
stack_size,
priority,
tick);
}
代码中可以看出,其实只有rt_object_init
和_rt_thread_init
,首先看rt_object_init
void rt_object_init(struct rt_object *object,
enum rt_object_class_type type,
const char *name)
{
register rt_base_t temp;
struct rt_object_information *information;
#ifdef RT_USING_MODULE
struct rt_dlmodule *module = dlmodule_self();
#endif
/* get object information 获得对象信息 */
information = rt_object_get_information(type);
RT_ASSERT(information != RT_NULL);
/* initialize object's parameters 初始化对象的参数*/
/* set object type to static 设置为静态的对象类型*/
object->type = type | RT_Object_Class_Static;
/* copy name */
rt_strncpy(object->name, name, RT_NAME_MAX);
RT_OBJECT_HOOK_CALL(rt_object_attach_hook, (object));
/* lock interrupt */
temp = rt_hw_interrupt_disable();
#ifdef RT_USING_MODULE
if (module)
{
rt_list_insert_after(&(module->object_list), &(object->list));
object->module_id = (void *)module;
}
else
#endif
{
/* insert object into information object list 将对象插入系统对象列表*/
rt_list_insert_after(&(information->object_list), &(object->list));
}
/* unlock interrupt */
rt_hw_interrupt_enable(temp);
}
对于我这中英语菜鸡来说,翻译后终于知道是干吗的了,原来rt_object_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;
}
这个函数前面主要是把线程函数的后几个参数和线程结构体进行赋值(可能用词不准确,但意思就是那个意思),主要是把线程栈的入口函数地址、参数地址、栈的大小明确一下,其中有一个与线程堆栈有关的函数rt_hw_stack_init
,这个先跳过等会儿说,后面还有thread->cleanup
和thread->user_data
其中cleanup
会在线程退出时,被空闲线程回调一次以执行用户设置的清理现场等工作,user_data
可由用户挂接一些数据信息到线程控制块中,以提供类似线程私有数据的实现。rt_timer_init
是内置线程定时器主要控制时间片功能,RT_OBJECT_HOOK_CALL
是一个钩子函数,空闲钩子的作用官网下面有讲到
下面看一下rt_hw_stack_init
:
rt_uint8_t *rt_hw_stack_init(void *tentry,
void *parameter,
rt_uint8_t *stack_addr,
void *texit)
{
struct stack_frame *stack_frame;
rt_uint8_t *stk;
unsigned long i;
stk = stack_addr + sizeof(rt_uint32_t);
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 */
/* return task's current stack address */
return stk;
}
现在看完这个代码是不是理解了,这个函数实现了给函数堆栈指针thread->sp赋予地址,也就是静态申请内存的首地址。
现在捋一下静态初始化过程:
首先 rt_object_init
把线程对象名称赋给线程对象,并且把线程对象插入到系统对象列表中。
然后 _rt_thread_init
rt_hw_stack_init
函数把线程栈的入口函数地址、参数地址、函数堆栈指针、栈的大小等明确。
最后 thread->cleanup
和thread->user_data
清理现场工作和用户挂接一些数据信息工作。
这样就初始化了一个完整的线程。
线程的启动
那初始化完,线程是怎么启动的呢?
在我们初始化或者创建完线程以后,我们都会使用例如这样的rt_thread_startup(&thread1);
形式来启动,我们具体来看一下是怎么实现的,
在这个函数里除了,检测参数,设置状态以外还有rt_schedule()
函数,而这个函数就是把线程挂到自己该有的位置,从而供系统调度的。此函数会调用
rt_list_insert_before(&(rt_thread_priority_table[thread->current_priority]),
&(thread->tlist));
来将线程插入到就序列表,就绪列表的下标对应的是线程的优先级,从而确定插入的位置,同时函数中包括以下两个指针:
/* 用于存储上一个线程的栈的 sp 的指针 */
rt_uint32_t rt_interrupt_from_thread;
/* 用于存储下一个将要运行的线程的栈的 sp 的指针 */
rt_uint32_t rt_interrupt_to_thread;
从而实现系统的调度,进一步实现线程的启动,大致的实现差不多可以理解为这样,具体系统调度以及线程的切换,以及第一次线程的切换要比这复杂得多,想了解的可以去深入了解一下。
静态线程理解之后,动态线程就很好搞了,毕竟大差不差嘛,主要区别是动态线程是系统动态分配栈和线程控制块。
/*分配栈*/
thread = (struct rt_thread *)rt_object_allocate(RT_Object_Class_Thread,
name);
/*分配线程控制块*/
stack_start = (void *)RT_KERNEL_MALLOC(stack_size); //系统分配
由此也可以看出动态线程和静态线程的区别,静态线程是先申请空间再执行,由于事先不知道需要运用多少空间的情况下,多少会浪费,但由于事先申请好了,所以过程中不必要申请,会省一部分时间,动态线程则相反,相对来说节省内存而稍微浪费一点时间,具体去何线程,应论实际情况而定。