衆所周知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); //系統分配
由此也可以看出動態線程和靜態線程的區別,靜態線程是先申請空間再執行,由於事先不知道需要運用多少空間的情況下,多少會浪費,但由於事先申請好了,所以過程中不必要申請,會省一部分時間,動態線程則相反,相對來說節省內存而稍微浪費一點時間,具體去何線程,應論實際情況而定。