今天實現事件控制塊,存儲管理以及定時器。
1 事件控制塊
本節代碼位於12_event中
什麼是事件控制塊呢?
可以這樣理解,前面學習我們已經知道,創建一個任務需要給這個任務分配一個任務控制塊,這個任務控制塊存儲着關於這個任務的重要信息。那麼,事件控制塊就好比任務裏的任務控制塊。它存儲着這個事件的重要信息,我們說創建一個事件(信號,郵箱,消息隊列),其本質的過程就是初始化這個事件控制塊。
一個任務或者中斷服務子程序可以通過事件控制塊ECB(Event Control Blocks)來向另外的任務發信號。這裏,所有的信號都被看成是事件(Event)。一個任務還可以等待另一個任務或中斷服務子程序給它發送信號。這裏要注意的是,只有任務可以等待事件發生,中斷服務子程序是不能這樣做的。
對於處於等待狀態的任務,還可以給它指定一個最長等待時間,以此來防止因爲等待的事件沒有發生而無限期地等下去。
多個任務可以同時等待同一個事件的發生。在這種情況下,當該事件發生後,所有等待該事件的任務中,優先級最高的任務得到了該事件並進入就緒狀態,準備執行。上面講到的事件,可以是信號量、郵箱或者消息隊列等。 引用自http://blog.csdn.net/h32dong809/article/details/7082490
如下圖是一個典型是事件過程。
最開始任務是task1,task1請求事件event0,此時event0沒有事件發生,因此將task1加入event0的等待隊列。然後運行的任務是task2,task2頁請求event0,此時event0還是沒有事件發生,因此task2加入到event0的等待隊列。過了一段時間,task0發生event0事件,此時event0控制塊有事件發生,將處於等待隊列的task1喚醒,觸發一次任務調度,當前任務轉換爲task1,此時task2還是依然處於事件控制塊等待隊列。
代碼實現
接口定義
首先看一下事件控制塊的定義,以及操作接口。當前沒有一種具體的時間控制塊,所以event_type_e只有一種UNKNOWN類型的事件控制塊。
- event_t就是事件控制塊結構定義,只包含兩個字段,一個是事件控制塊的類型event_type_e,另一個就是事件控制塊的任務等待隊列wait_list。
- event_init初始化事件控制塊。
- event_wait將任務task等待事件event,state爲任務等待某一種事件時的狀態,這裏暫時用不到,填0即可,timeout爲任務task等待事件event的超時時間。 msg也是特定事件控制塊所用的,這裏也暫時用不上,填0即可。
- event_wakeup觸發一次event事件,將它等待隊列中的任務按一定規則喚醒。
- event_remove_task移除任務task中所有等待的事件。
- event_remove_all移除event中所有等待的事件。
- event_wait_count返回該事件中等待任務的個數。
event.h
1 #ifndef EVENT_H
2 #define EVENT_H
3
4 #include <stdint.h>
5 #include "os_stdio.h"
6 #include "lib.h"
7 #include "task.h"
8
9 typedef enum event_type_tag {
10 EVENT_TYPE_UNKNOWN = 0,
11 }event_type_e;
12
13 typedef struct event_tag {
14 event_type_e type;
15 list_t wait_list;
16 }event_t;
17
18 extern void event_init(event_t *event, event_type_e type);
19 extern void event_wait(event_t *event, task_t *task, void *msg, uint32_t state, uint32_t timeout);
20 extern void event_wakeup(event_t *event, void *msg, uint32_t result);
21 extern void event_remove_task(task_t *task, void *msg, uint32_t result);
22 extern uint32_t event_remove_all(event_t *event, void *msg, uint32_t result);
23 extern uint32_t event_wait_count(event_t *event);
24
25 #endif /*EVENT_H*/
實現
- 修改task.h, 之前任務狀態只有就緒,延時和掛起,現在新增一種等待狀態OS_TASK_WAIT_MASK。在task_t中增加任務控制塊相關的字段,wait_event是該任務等待的事件的指針,一般來說,一個任務同一時間點只能等待一個事件。event_msg是任務像事件獲取消息時的緩衝區,wait_event_result記錄了任務等待事件的結果,是正常等待呢還是超時等待。
task.h
13 #define OS_TASK_WAIT_MASK (0xFF << 16)
...
38 /*Event control block*/
39 struct event_tag *wait_event;
40 void *event_msg;
41 uint32_t wait_event_result;
42
43 }task_t;
- 在task.c中對上述幾個字段進行初始化
task.c
63 /*Event control block*/
64 task->wait_event = (event_t *)NULL;
65 task->event_msg = (void *)0;
66 task->wait_event_result = NO_ERROR;
67
68 }
- 實現event_init接口,代碼很簡單,初始化event的類型和等待鏈表即可。
event.c
4 void event_init(event_t *event, event_type_e type)
5 {
6 event->type = type;
7 list_init(&event->wait_list);
8 }
- 實現event_wait接口。1.首先將task的任務控制塊相關的字段根據傳入的參數相應的賦值。2.將任務task從就緒表移除,把它加入event的等待隊列尾部。3.如果設置了超時,那麼將該任務還要加入到延時隊列中,這樣當延時時間到時,能將任務從延時隊列中喚醒,並且加入到就緒表。
event.c
10 void event_wait(event_t *event, task_t *task, void *msg, uint32_t state, uint32_t timeout)
11 {
12 uint32_t status = task_enter_critical();
13
14 task->state |= state;
15 task->wait_event = event;
16 task->wait_event_result = NO_ERROR;
17
18 task_unready(task);
19 list_append_last(&event->wait_list, &task->prio_list_node);
20 if (timeout != 0) {
21 task_delay_wait(task, timeout);
22 }
23
24 task_exit_critical(status);
25 }
所以在task_system_tick_handler要做一些改動,加入對任務控制塊的處理。如下代碼, 當任務延時時間到,並且有在等待某一個事件的話,不僅要把任務從延時隊列中拿到,還需要把它從相應任務控制塊中的等待隊列拿掉,加入到就緒表中。
task.c
123 void task_system_tick_handler(void)
124 {
....
133 while (temp_node != head) {
134 task = container_of(temp_node, task_t, delay_node);
135 temp_node = temp_node->next;
136 if (--task->delay_ticks == 0) {
137
138 if (task->wait_event != (event_t *)NULL) {
139 event_remove_task(task, (void *)0, ERROR_TIMEOUT);
140 }
...
149 }
150 }
151
- 實現 event_wakeup接口。該函數邏輯很簡單,就是將任務控制塊event等待隊列中的第一個任務拿出來,然後把相應的msg和result填到任務task相應字段, 清除任務的等待狀態標誌。如果任務設置了等待超時,那麼這個時候還需要把任務從等待隊列中移除,加入到就緒表中,觸發一次任務調度。
event.c
27 task_t *event_wakeup(event_t *event, void *msg, uint32_t result)
28 {
29 list_node_t *node = (list_node_t *)NULL;
30 task_t *task = (task_t *)NULL;
31
32 uint32_t status = task_enter_critical();
33
34 if ((node = list_remove_first(&event->wait_list)) != (list_node_t *)NULL) {
35 task = (task_t *)container_of(node, task_t, prio_list_node);
36 task->wait_event = (event_t *)NULL;
37 task->event_msg = msg;
38 task->state &= ~OS_TASK_WAIT_MASK;
39
40 if (task->delay_ticks != 0) {
41 task_delay_wakeup(task);
42 }
43 task_ready(task);
44 }
45
46 task_exit_critical(status);
47 return task;
48}
- 實現event_remove_task接口。 該接口就是將任務從自己等待的事件的等待隊列中移除而已。
event.c
49 void event_remove_task(task_t *task, void *msg, uint32_t result)
50 {
51 uint32_t status = task_enter_critical();
52
53 list_remove(&task->wait_event->wait_list, &task->prio_list_node);
54 task->wait_event = (event_t *)NULL;
55 task->event_msg = msg;
56 task->wait_event_result = result;
57 task->state &= ~OS_TASK_WAIT_MASK;
58
59 task_exit_critical(status);
60 }
- 實現event_remove_all接口。 這個接口跟event_wakeup實現很類似,只是event_wakeup只喚醒一個任務,而這個接口會把所有處於等待隊列中的任務都喚醒。代碼上實現就是遍歷event的等待隊列,將其移除並加入到就緒表中。
event.h
62 uint32_t event_remove_all(event_t *event, void *msg, uint32_t result)
63 {
64 list_node_t *node = (list_node_t *)NULL;
65 uint32_t count = 0;
66 uint32_t status = task_enter_critical();
67
68 DEBUG("%s:\n", __func__);
69 count = list_count(&event->wait_list);
70 while ((node = list_remove_first(&event->wait_list)) != (list_node_t *)NULL) {
71 DEBUG("########\n");
72 task_t *task = (task_t *)container_of(node, task_t, prio_list_node);
73 task->wait_event = (event_t *)NULL;
74 task->event_msg = msg;
75 task->wait_event_result = result;
76 task->state &= ~OS_TASK_WAIT_MASK;
77 if (task->delay_ticks != 0) {
78 task_delay_wakeup(task);
79 }
80 task_ready(task);
81 }
82
83 task_exit_critical(status);
84 return count;
85 }
測試
task3和task2分別等待event_wait_normal事件,task1每次都會移除所有等待的事件。所以task2和task3總是在task1調用完event_remove_all纔得到運行。
main.c
event_t event_wait_timeout;
event_t event_wait_normal;
void task1_entry(void *param)
{
init_systick(10);
event_init(&event_wait_timeout, EVENT_TYPE_UNKNOWN);
for(;;) {
printk("%s\n", __func__);
uint32_t wakeup_count = event_remove_all(&event_wait_normal, (void *)0, 0);
if (wakeup_count > 0) {
task_sched();
printk("wakeup_count:%d\n", wakeup_count);
printk("count:%d\n", event_wait_count(&event_wait_normal));
}
task_sched();
task_delay_s(1);
}
}
void delay(uint32_t delay)
{
while(delay--);
}
void task2_entry(void *param)
{
for(;;) {
event_wait(&event_wait_normal, g_current_task, (void *)0, 0, 0);
task_sched();
printk("%s\n", __func__);
task_delay_s(1);
}
}
void task3_entry(void *param)
{
event_init(&event_wait_normal, EVENT_TYPE_UNKNOWN);
for(;;) {
event_wait(&event_wait_normal, g_current_task, (void *)0, 0, 0);
task_sched();
printk("%s\n", __func__);
task_delay_s(1);
}
}
2 信號量
本節代碼位於13_semaphore中
相信本文的讀者一定是對信號量的概念以及操作的是非常瞭解的,本文也不是寫書,就不詳細解釋信號量的概念和操作了。如果實在是小白入門,可以上網查信號量,資料非常多,我想重新寫一遍也未必解釋的比之前的好。
信號量概述
信號量(Semaphore),有時被稱爲信號燈,是在多線程環境下使用的一種設施,是可以用來保證兩個或多個關鍵代碼段不被併發調用。在進入一個關鍵代碼段之前,線程必須獲取一個信號量;一旦該關鍵代碼段完成了,那麼該線程必須釋放信號量。其它想進入該關鍵代碼段的線程必須等待直到第一個線程釋放信號量。爲了完成這個過程,需要創建一個信號量VI,然後將Acquire Semaphore VI以及Release Semaphore VI分別放置在每個關鍵代碼段的首末端。確認這些信號量VI引用的是初始創建的信號量。
特性
抽象的來講,信號量的特性如下:信號量是一個非負整數(車位數),所有通過它的線程/進程(車輛)都會將該整數減一(通過它當然是爲了使用資源),當該整數值爲零時,所有試圖通過它的線程都將處於等待狀態。在信號量上我們定義兩種操作: Wait(等待) 和 Release(釋放)。當一個線程調用Wait操作時,它要麼得到資源然後將信號量減一,要麼一直等下去(指放入阻塞隊列),直到信號量大於等於一時。Release(釋放)實際上是在信號量上執行加操作,對應於車輛離開停車場,該操作之所以叫做“釋放”是因爲釋放了由信號量守護的資源
操作方式
對信號量有4種操作(#include
代碼實現
接口定義
信號量無非就是PV操作,P對信號量減一,V對信號量加1。上文已經信號量操作簡單解釋了一下,我們來看看我們的系統中信號量的相關定義。
- sem_t 定義了信號量的結構,信號量是一種特殊的事件控制塊,信號量可以理解爲一種事件存在,所以它的結構中包含了一個event_t成員,如果對讀者熟悉OOP,可以把信號量sem_t繼承事件控制塊event_t。其他的字段都是這種特殊的事件控制塊獨有的字段,count是信號量當前的計數值,而max_count是信號量可以操作最大的計數值。
- sem_info_t 定義了信號量的查詢的結構,類似於任務查詢之類的東西,並不是關鍵的數據結構。
- sem_init 初始化信號量sem, count是信號量初始值,max_count爲信號量最大值。
- sem_acquire就是信號量的P操作,其作用就是將信號量減1,如果信號量小於0了,那麼獲取該信號量的任務就要進入信號量的任務等待隊列,即進入到事件控制塊的等待隊列。wait_ticks設置等待信號量超時時間。
- sem_acquire_no_wait也是信號量的P操作,它和sem_acquire的區別是sem_acquire_no_wait也是信號量的V操作並不會進行任務的等待。如果信號量已經沒有了資源,即信號量小於1,那麼任務也不會等待該信號量。
- sem_release是信號量的P操作,即對信號量+1,當信號量的等待隊列中存在任務時,喚醒等待隊列中的第一個任務,否則就是對信號量+1,當然信號量計數值不能超過信號量的最大計數值。
- sem_get_info獲取信號量sem在某一時刻的關鍵信息並保存到info裏。
- sem_destory 銷燬信號量sem。
sem.h
1 #ifndef SEM_H
2 #define SEM_H
3
4 #include "event.h"
5 #include "config.h"
6
7 typedef struct sem_tag {
8
9 event_t event;
10 uint32_t count;
11 uint32_t max_count;
12 }sem_t;
13
14 typedef struct sem_info_tag {
15 uint32_t count;
16 uint32_t max_count;
17 uint32_t task_count;
18 }sem_info_t;
19
20 extern void sem_init(sem_t *sem, uint32_t count, uint32_t max_count);
21 extern uint32_t sem_acquire(sem_t *sem, uint32_t wait_ticks);
22 extern uint32_t sem_acquire_no_wait(sem_t *sem);
23 extern void sem_release(sem_t *sem);
24 extern void sem_get_info(sem_t *sem, sem_info_t *info);
25 extern uint32_t sem_destory(sem_t *sem);
26
27 #endif /*SEM_H*/
實現
- 首先需要修改一下event.h,在event_type_e中加入信號量類型EVENT_TYPE_SEM
event.h
9 typedef enum event_type_tag {
10 EVENT_TYPE_UNKNOWN = 0,
11 EVENT_TYPE_SEM = 1,
12 }event_type_e;
13
- sem_init函數其實現與接口描述一樣,首先初始化信號量sem中的事件控制塊event成員,將其type設置爲EVENT_TYPE_SEM。接着就是設置sem_init的count和max_count值。
sem.c
5 void sem_init(sem_t *sem, uint32_t count, uint32_t max_count)
6 {
7 event_init(&sem->event, EVENT_TYPE_SEM);
8
9 if (max_count == 0) {
10 sem->count = count;
11 } else {
12 sem->count = count > max_count ? max_count : count;
13 }
14 }
- sem_acquire函數實現。從代碼中看到如果sem的count大於0,那麼就是對信號量的計數值count-1即可。如果信號量的計數值已經小於0了,那麼將任務加入到信號量的等待隊列中。這裏的操作是調用了event_wait函數,而等待的事件控制塊就是sem內部的event成員,將任務加入到了事件控制塊的等待隊列後,需要觸發一次任務調度。
從這個函數中我們就可以看到信號量其實就是對事件控制塊的一種特殊的封裝,其等待及喚醒的操作都是通過調用信號量sem內部事件控制塊的函數來完成的。而信號量值負責對計數值的操作。
sem.c
16 uint32_t sem_acquire(sem_t *sem, uint32_t wait_ticks)
17 {
18 uint32_t status = task_enter_critical();
19
20 if (sem->count > 0) {
21 --sem->count;
22 task_exit_critical(status);
23 return NO_ERROR;
24 } else {
25 event_wait(&sem->event, g_current_task, (void *)0, EVENT_TYPE_SEM, wait_ticks);
26 task_exit_critical(status);
27 task_sched();
28 return g_current_task->wait_event_result;
29 }
30
31 }
- sem_acquire_no_wait實現與sem_acquire類似,只是sem_acquire_no_wait在信號量小於1的時候直接返回即可,並不加入到信號量的等待隊列中。
sem.c
33 uint32_t sem_acquire_no_wait(sem_t *sem)
34 {
35 uint32_t status = task_enter_critical();
36
37 if (sem->count > 0) {
38 --sem->count;
39 task_exit_critical(status);
40 return NO_ERROR;
41 } else {
42 task_exit_critical(status);
43 return g_current_task->wait_event_result;
44 }
45 }
- sem_release正好與sem_acquire實現相反的操作。首先判斷信號量等待隊列中是否有任務在等待,如果有任務等待,那麼將等待隊列的第一個任務喚醒即可,並且相應地觸發一次任務調度。如果等待隊列中不存在任務,那麼就把信號量+1,然後返回即可。
sem.c
47 void sem_release(sem_t *sem)
48 {
49 uint32_t status = task_enter_critical();
50 if (event_wait_count(&sem->event) > 0) {
51 task_t *task = event_wakeup(&sem->event, (void *)0, NO_ERROR);
52 if (task->prio < g_current_task->prio) {
53 task_sched();
54 }
55 } else {
56 sem->count++;
57 if ((sem->max_count != 0) && (sem->count > sem->max_count)) {
58 sem->count = sem->max_count;
59 }
60 }
61 task_exit_critical(status);
62 }
- sem_get_info函數實現,很簡單,無須解釋。
sem.c
64 void sem_get_info(sem_t *sem, sem_info_t *info)
65 {
66 uint32_t status = task_enter_critical();
67 info->count = sem->count;
68 info->max_count = sem->max_count;
69 info->task_count = event_wait_count(&sem->event);
70 task_exit_critical(status);
71 }
- sem_destory將信號銷燬。其操作就是將信號量等待等待隊列中所有的任務移除,並且將信號量置爲0。如果信號量等待隊列中存在任務的話,在移除之後就觸發一次任務調度。
sem.c
73 uint32_t sem_destory(sem_t *sem)
74 {
75 uint32_t status = task_enter_critical();
76
77 uint32_t count = event_remove_all(&sem->event, (void *)0, ERROR_DEL);
78 sem->count = 0;
79 task_exit_critical(status);
80
81 if (count > 0) {
82 task_sched();
83 }
84 return count;
85 }
測試
測試代碼中定義了兩個信號量sem1和sem2。task1初始化信號量爲0,所以task1一旦調用sem_acquire它就會等待sem1發生,而task2中調用sem_release來釋放信號量,此時會將等待中task1喚醒。task1的打印在sem_acquire後面。所以即使task1的打印會在task2的打印之後打印。
task3初始化信號量sem2爲0,此時調用sem_acquire會進行到等待狀態,它填寫了超時時間,所以即可沒有其他的任務來釋放sem2,當超時時間一旦到達,task3會放棄等待sem2,繼續運行。所以從log來看task2先打印,然後task1。而task3與task1和task2無關。
main.c
sem_t sem1;
sem_t sem2;
void task1_entry(void *param)
{
init_systick(10);
sem_init(&sem1, 0, 10);
for(;;) {
sem_acquire(&sem1, 0);
printk("%s\n", __func__);
task_delay_s(1);
}
}
void delay(uint32_t delay)
{
while(delay--);
}
void task2_entry(void *param)
{
for(;;) {
printk("%s\n", __func__);
task_delay_s(1);
sem_release(&sem1);
}
}
void task3_entry(void *param)
{
sem_init(&sem2, 0, 10);
for(;;) {
sem_acquire(&sem2, 500);
printk("%s\n", __func__);
task_delay_s(1);
}
}
3 郵箱
在多任務操作系統中,通常需要在任務與任務之間傳遞一個數據(這種數據叫做消息)的方式來進行通信。爲了達到這個目的,可以在內存中創建一個存儲空間作爲該數據的緩衝區。如果把這個緩衝區叫做消息緩衝區,那麼在任務之間傳遞數據的一個最簡單的辦法就是傳遞消息緩衝區的指針。因此,用來傳遞消息緩衝區指針的數據結構就叫做消息郵箱。 引用自《嵌入式實時操作系統uc/OS-II 原理及應用(第四版)》
下圖就是一次郵箱發送與獲取的過程。初試狀態下,郵箱裏並沒有數據可以讀,而task1申請讀取郵箱消息,所以task1進入了郵箱的等待隊列。過了一會task0往郵箱裏發送了一個消息,這個時候郵箱的write指針向後移一位,並且將task1從等待隊列中喚醒,同時task1把數據從郵箱中讀出來。
代碼實現
接口定義
mbox_t定義了郵箱結構。同樣郵箱是一種特殊的事件控制塊,所以mbox_t和信號量一樣包含了一個event_t成員。 count是郵箱當前消息的數量,read是郵箱緩衝區的讀索引,write是郵箱緩衝區的寫索引,max_count是郵箱緩衝區的長度,也就是郵箱最大允許容納的消息數量。
mbox_init初始化郵箱mbox,msg_buffer事傳給郵箱緩衝區的地址。
mbox_get從郵箱中讀數據,msg是待讀取消息的地址,wait_ticks同樣是等到超時的參數。
mbox_get_no_wait從郵箱讀取消息,但當郵箱沒有消息時,任務不會進行等待。
mbox_send往郵箱mbox發消息,msg是消息的地址,notify_opition是發送消息的選項,後面實現會講到是幹什麼的。
mbox_flush清空郵箱mbox中所有消息。
mbox_destory銷燬郵箱mbox。
mbox_get_info查詢郵箱某一時刻的信息。
mailbox.h
2 #define MAILBOX_H
3
4 #include "event.h"
5 #include "config.h"
6
7 #define MBOX_SEND_FRONT 0x12345678
8 #define MBOX_SEND_NORMAL 0
9 typedef struct mbox_tag {
10
11 event_t event;
12 uint32_t count;
13 uint32_t read;
14 uint32_t write;
15 uint32_t max_count;
16 void **msg_buffer;
17
18 }mbox_t;
19
20 typedef struct mbox_info_tag {
21 uint32_t count;
22 uint32_t max_count;
23 uint32_t task_count;
24 }mbox_info_t;
25
26 extern void mbox_init(mbox_t *mbox, void **msg_buffer, uint32_t max_count);
27 extern uint32_t mbox_get(mbox_t *mbox, void **msg, uint32_t wait_ticks);
28 extern uint32_t mbox_get_no_wait(mbox_t *mbox, void **msg);
29 extern uint32_t mbox_send(mbox_t *mbox, void *msg, uint32_t notify_opition);
30 extern void mbox_flush(mbox_t *mbox);
31 extern uint32_t mbox_destory(mbox_t *mbox);
32 extern void mbox_get_info(mbox_t *mbox, mbox_info_t *info);
實現
- mbox_init實現:初始化郵箱的事件控制塊event,並根據傳入的參數填寫相應的字段。
mbox.c
5 void mbox_init(mbox_t *mbox, void **msg_buffer, uint32_t max_count)
6 {
7 event_init(&mbox->event, EVENT_TYPE_MAILBOX);
8 mbox->msg_buffer = msg_buffer;
9 mbox->max_count = max_count;
10 mbox->read = 0;
11 mbox->write = 0;
12 mbox->count = 0;
13 }
- mbox_get實現:從郵箱mbox中獲取消息,消息保存到msg所指向的地址中。當郵箱中有消息的時候,將郵箱消息數量count-1, 並從郵箱中消息緩衝區msg_buffer讀出一個消息給msg,並且將郵箱的讀指針read自增1,當read大於郵箱的最大長度時,read會指向0。也就是說,郵箱中的消息緩衝區是一個長度爲max_count的循環緩衝區。
當郵箱中沒有消息的時候,系統將當前任務加入到郵箱的等待隊列中,安排一次任務調度。當調度再次回到這個任務的時候,會將事件控制塊中的消息賦值給msg指針並且返回。
mbox.c
15 uint32_t mbox_get(mbox_t *mbox, void **msg, uint32_t wait_ticks)
16 {
17 uint32_t status = task_enter_critical();
18
19 if (mbox->count > 0) {
20
21 mbox->count--;
22 *msg = mbox->msg_buffer[mbox->read++];
23 if (mbox->read >= mbox->max_count) {
24 mbox->read = 0;
25 }
26 task_exit_critical(status);
27 return NO_ERROR;
28 } else {
29 event_wait(&mbox->event, g_current_task, (void *)0, EVENT_TYPE_MAILBOX, wait_ticks);
30 task_exit_critical(status);
31 task_sched();
32
33 *msg = g_current_task->event_msg;
34 return g_current_task->wait_event_result;
35 }
36 }
- mbox_get_no_wait實現:該函數與mbox_get差不多,只不過在郵箱沒有消息的時候不會等待任務。
mbox.c
38 uint32_t mbox_get_no_wait(mbox_t *mbox, void **msg)
39 {
40 uint32_t status = task_enter_critical();
41
42 if (mbox->count > 0) {
43
44 mbox->count--;
45 *msg = mbox->msg_buffer[mbox->read++];
46 if (mbox->read >= mbox->max_count) {
47 mbox->read = 0;
48 }
49 task_exit_critical(status);
50 return NO_ERROR;
51 } else {
52 task_exit_critical(status);
53 return g_current_task->wait_event_result;
54 }
55 }
- mbox_send實現稍微有點長,我們一點一點來看,首先判斷郵箱中是否有任務在等待啊。
如果有,那就喚醒等待中的第一個任務,然後直接把傳遞的消息賦值給喚醒任務控制塊中的消息指針,不對郵箱的讀寫指針改動。
如果沒有等待的任務,首先判斷郵箱內的消息已經滿了,如果郵箱滿了,那麼就不能再往郵箱裏寫消息,並返回ERROR_RESOURCE_FULL。如果郵箱沒滿,那麼就往郵箱裏寫一個數據。這裏也分兩種情況,如果notify_opition是MBOX_SEND_FRONT,那麼數據往前寫,然後把讀指針read往前挪一個。否則就是正常寫,把寫指針write往後挪一個。寫完後把郵箱的消息計數count+1。
mbox.c
uint32_t mbox_send(mbox_t *mbox, void *msg, uint32_t notify_opition)
{
uint32_t status = task_enter_critical();
task_t *task = (task_t *)NULL;
if (event_wait_count(&mbox->event) > 0) {
task = event_wakeup(&mbox->event, (void *)msg, NO_ERROR);
if (task->prio < g_current_task->prio) {
task_sched();
}
} else {
if (mbox->count >= mbox->max_count) {
task_exit_critical(status);
return ERROR_RESOURCE_FULL;
}
if (notify_opition & MBOX_SEND_FRONT) {
if (mbox->read <= 0) {
mbox->read = mbox->max_count - 1;
} else {
mbox->read--;
}
mbox->msg_buffer[mbox->read] = msg;
} else {
mbox->msg_buffer[mbox->write++] = msg;
if (mbox->write >= mbox->max_count) {
mbox->write = 0;
}
}
mbox->count++;
}
task_exit_critical(status);
return NO_ERROR;
}
- mbox_flush 代碼很簡單,如果郵箱內沒有等待的任務,那麼把郵箱內的各個字段清0即可。
mbox.c
93 void mbox_flush(mbox_t *mbox)
94 {
95 uint32_t status = task_enter_critical();
96
97 if (event_wait_count(&mbox->event) == 0) {
98 mbox->read = 0;
99 mbox->write = 0;
100 mbox->count = 0;
101 }
102
103 task_exit_critical(status);
104 }
- mbox_destory移除郵箱等待隊列中的所有任務,並且觸發一次任務調度。
mbox.c
106 uint32_t mbox_destory(mbox_t *mbox)
107 {
108 uint32_t status = task_enter_critical();
109
110 uint32_t count = event_remove_all(&mbox->event, (void *)0, ERROR_DEL);
111
112 task_exit_critical(status);
113 if (count > 0) {
114 task_sched();
115 }
116
117 return count;
118 }
- mbox_get_info實現代碼如下:
mbox.c
120 void mbox_get_info(mbox_t *mbox, mbox_info_t *info)
121 {
122 uint32_t status = task_enter_critical();
123 info->count = mbox->count;
124 info->max_count = mbox->max_count;
125 info->task_count = event_wait_count(&mbox->event);
126 task_exit_critical(status);
127 }
測試
測試代碼中定義了一個郵箱mbox1,task1第一次往郵箱中以MBOX_SEND_NORMAL寫入數據,然後延時20s,然後task1以MBOX_SEND_FRONT方式寫入數據,然後延時20s。
而task2一次性讀取了20個數據,因此在task1延時的20s內,task2處於等待郵箱狀態,也不會執行。所以只有等到task1再次往郵箱裏寫數據的時候,task2才被喚醒並讀取數據。log上看不出來task2延時的效率。讀者只需要make run一下就能看到運行的時序是如何的。
mbox.c
mbox_t mbox1;
void *mbox1_msg_buffer[20];
uint32_t msg[20];
void task1_entry(void *param)
{
uint32_t i = 0;
init_systick(10);
mbox_init(&mbox1, mbox1_msg_buffer, 20);
for(;;) {
printk("%s:send mbox\n", __func__);
for (i = 0; i < 20; i++) {
msg[i] = i;
mbox_send(&mbox1, &msg[i], MBOX_SEND_NORMAL);
}
task_delay_s(20);
printk("%s:send mbox front\n", __func__);
for (i = 0; i < 20; i++) {
msg[i] = i;
mbox_send(&mbox1, &msg[i], MBOX_SEND_FRONT);
}
task_delay_s(20);
task_delay_s(1);
}
}
void task2_entry(void *param)
{
void *msg;
uint32_t err = 0;
for(;;) {
err = mbox_get(&mbox1, &msg, 10);
if (err == NO_ERROR) {
uint32_t value = *(uint32_t *)msg;
printk("%s:value:%d\n", __func__, value);
}
}
}
4 內存分區
本節代碼位於15_mem_block中
應用程序在運行中爲了某種特殊需要,經常需要臨時獲得一些內存空間,因此作爲一個比較完善的操作系統,必須具有動態分配內存的能力。對於RTOS來說,還需要保證系統在動態分配內存時,其執行時間是固定的。本文中的存儲塊分配與uCosII類似,採用固定大小的內存塊。這樣一來簡化了代碼實現,二來能使內存管理能夠在O(1)內完成。
存儲塊進行兩級管理,即把一個連續的內存空間分爲若干個分區,每個分區又分爲若干個大小的內存塊。RTOS以分區爲單位來管理動態內存,而任務以內存塊爲單位來獲取和釋放內存。內存分區及內存塊的使用情況由內存控制塊來記錄。所以在代碼中定義一個內存分區及其內存塊的方法非常簡單,只需要定義二維數組即可。例如:
uint32_t mem_buf[10][10]; /*定義了1個內存分區,每個內存分區有10個內存塊,每個內存塊大小爲10個word*/
其分配釋放過程如下圖。
1. 首先圖中有一個只有一個內存塊的內存分區,task1申請獲得一個內存塊,此時內存分區內不再有內存塊。
2. 過了一段時間task0申請內存塊,而此時內存分區內並沒有內存塊可以分配,task0進入內存塊的等待隊列。
3. task1釋放內存塊,此時RTOS將喚醒task0,並且將內存塊分配給task0。
相信讀者從上面可以看出來存儲塊分配釋放也是一種事件,因此存儲塊的實現也可以依賴於事件控制塊來實現。
代碼實現
結構定義
首先我們看內存分區的結構定義,
- 內存分區mem_block_t中包含了一個event_t字段,用來處理內存分區事件。
- start定義了內存分區的起始地址
- block_size定義了內存分區每一個內存快的大小。
- max_count定義了內存分區中最多有幾個內存塊
- block_list是內存分區中內存塊鏈表。當內存塊不被使用的使用,它就被插入到block_list中,當內存被任務申請時,它就從block_list中移除。
memblock.h
11 typedef struct mem_block_tag {
12 event_t event;
13 void *start;
14 uint32_t block_size;
15 uint32_t max_count;
16 list_t block_list;
17 }mem_block_t;
18
接口實現
mem_block_init函數初始化內存分區。
- start:內存分區的起始地址
- block_size:每一個內存塊的大小
- block_cnt:內存塊個數。
- 初始化過程很簡單,首先根據傳入的參數初始化各個字段。
- 然後初始化block_list
- 將內存塊一個一個鏈接到block_list上,這裏很巧妙的是,當內存塊不使用的時候,將內存塊強制轉換成list_node_t結構,鏈接到該鏈表上,當內存塊需要被使用的時候,根據相應的類型強制轉換一下即可。這樣可以省下一個list_node_t的空間。
根據上述的描述,可以看到內存分區與內存塊的關係如下圖。
memblock.c
4 void mem_block_init(mem_block_t *mem_block, uint8_t *start, uint32_t block_size, uint32_t block_cnt)
5 {
6 uint8_t *mem_block_start = (uint8_t *)start;
7 uint8_t *mem_block_end = mem_block_start + block_size * block_cnt;
8
9 if (block_size < sizeof(list_node_t)) {
10 goto cleanup;
11 }
12
13 event_init(&mem_block->event, EVENT_TYPE_MEM_BLOCK);
14
15 mem_block->start = start;
16 mem_block->block_size = block_size;
17 mem_block->max_count = block_cnt;
18 list_init(&mem_block->block_list);
19
20
21 while (mem_block_start < mem_block_end) {
22 list_node_init((list_node_t *)mem_block_start);
23 list_append_last(&mem_block->block_list, (list_node_t *)mem_block_start);
24 mem_block_start += block_size;
25 }
26 cleanup:
27 return;
28
29 }
mem_block_alloc從mem_block中分配一個內存塊到mem。
- mem:分配的內存塊的地址。
- wait_ticks:分配超時時間,與其他事件控制塊類似。
- 分配邏輯很簡單,如果內存分區還有內存塊,那麼直接從內存塊鏈表中拿出第一個內存塊給任務並返回。
- 如果內存塊鏈表中不存在內存塊,那麼將當前任務插入到內存塊等待鏈表尾部,並且觸發一次任務調度。當任務被喚醒時,從event_msg中拿到釋放的內存塊地址返回。
memblock.c
31 uint32_t mem_block_alloc(mem_block_t *mem_block, uint8_t **mem, uint32_t wait_ticks)
32 {
33 uint32_t status = task_enter_critical();
34
35 if (list_count(&mem_block->block_list) > 0) {
36 *mem = (uint8_t *)list_remove_first(&mem_block->block_list);
37 task_exit_critical(status);
38 return NO_ERROR;
39 } else {
40 event_wait(&mem_block->event, g_current_task, (void *)0, EVENT_TYPE_MEM_BLOCK, wait_ticks);
41 task_exit_critical(status);
42 task_sched();
43 *mem = g_current_task->event_msg;
44 return g_current_task->wait_event_result;
45 }
46 }
mem_block_alloc_no_wait與mem_block_alloc唯一差異是mem_block_alloc_no_wait當沒有可用的內存塊時,不會阻塞任務的運行。
memblock.c
48 uint32_t mem_block_alloc_no_wait(mem_block_t *mem_block, uint8_t **mem)
49 {
50 uint32_t status = task_enter_critical();
51
52 if (list_count(&mem_block->block_list) > 0) {
53 *mem = (uint8_t *)list_remove_first(&mem_block->block_list);
54 task_exit_critical(status);
55 return NO_ERROR;
56 } else {
57 task_exit_critical(status);
58 return ERROR_RESOURCE_FULL;
59 }
60
61 }
mem_block_free釋放地址爲mem的內存塊。內部實現很簡單
- 當內存分區等待隊列中存在等待任務時,喚醒等待隊列中的第一個任務,然後將該內存塊分配給喚醒的任務。如果喚醒的任務優先級高於當前任務,那麼就觸發一次任務調度。
- 當內存分區等待隊列中沒有任務等待時,直接將該內存塊插入到內存塊鏈表的尾部。
memblock.c
63 void mem_block_free(mem_block_t *mem_block, uint8_t *mem)
64 {
65 uint32_t status = task_enter_critical();
66 if (event_wait_count(&mem_block->event) > 0) {
67 task_t *task = event_wakeup(&mem_block->event, (void *)mem, NO_ERROR);
68 if (task->prio > g_current_task->prio) {
69 task_sched();
70 }
71 } else {
72 list_append_last(&mem_block->block_list, (list_node_t *)mem);
73 }
74
75 task_exit_critical(status);
76 }
mem_block_destory銷燬內存塊,將內存塊中等待隊列全部喚醒。
memblock.c
88 uint32_t mem_block_destory(mem_block_t *mem_block)
89 {
90 uint32_t status = task_enter_critical();
91
92 uint32_t count = event_remove_all(&mem_block->event, (void *)0, ERROR_DEL);
93
94 task_exit_critical(status);
95
96 if (count > 0) {
97 task_sched();
98 }
99 return count;
100 }
測試
- task1初始化了一個內存分區mem1[20][100]。然後申請二十個內存塊,然後釋放。延時5s後再次分配,可以看到每次分配的內存確實就是內存分區中內存塊。
main.c
29 /*20 block of size 100 bytes*/
30 uint8_t mem1[20][100];
31 mem_block_t mem_block1;
32 typedef uint8_t (*block_t)[100];
33
34 void task1_entry(void *param)
35 {
36 uint8_t i;
37 block_t block[20];
38 init_systick(10);
39
40 mem_block_init(&mem_block1, (uint8_t *)mem1, 100, 20);
41 for(;;) {
42 printk("%s\n", __func__);
43 for (i = 0; i < 20; i++) {
44 mem_block_alloc(&mem_block1, (uint8_t **)&block[i], 0);
45 printk("block:%x, mem[i]:%x\n", block[i], &mem1[i][0]);
46 }
47
48 for (i = 0; i < 20; i++) {
49 mem_block_free(&mem_block1, (uint8_t *)block[i]);
50 }
51 task_delay_s(5);
52 }
53 }
54
5 定時器
考慮平臺硬件定時器個數限制的,RTOS 通過一個定時器任務管理軟定時器組,滿足用戶定時需求。定時器任務會在其執行期間檢查用戶啓動的時間週期溢出的定時器,並調用其回調函數。本文RTOS同時支持一個硬定時器組,硬定時器組是直接systick對定時器組計數,而軟定時器是在定時器任務中對定時器進行操作。
定時器狀態
定時器一共有5種狀態,定義如下。很好理解,一共是創建,啓動,運行,停止和銷燬。
timer.h
6 typedef enum timer_state{
7 TIMER_CREATED,
8 TIMER_STARTED,
9 TIMER_RUNNING,
10 TIMER_STOPPED,
11 TIMER_DESTORYED,
12 }timer_state_e;
其狀態之間的關係如下圖:
定時器運行原理
下面以軟定時器組爲例,來闡述定時器的運行原理。硬定時器組原理一樣,只是計數的地方不一樣而已。 定時器任務有一個定時器鏈表,下面掛了三個定時器,timer0,timer1和timer2。發生一次systick中斷,就會定時器鏈表中所有的定時器的計數器減1,當某一個定時器定時時間到達之後,會調用掛載定時器下的定時器回調函數。如下圖所示,timer0定時時間到,會調用timer0的func0,打印hello。
代碼實現
定時器定義
timer_t定義了定時器的結構
- link_node用於鏈接到定時器鏈表中。
- duration_ticks 週期定時時的週期tick數
- start_delay_ticks 初次啓動延後的ticks數
- delay_ticks 當前定時遞減計數值
- timer_func 定時回調函數
- arg 傳遞給回調函數的參數
- config 定時器配置參數
- state 定時器狀態
TIMER_CONFIG_TYPE_HARD和TIMER_CONFIG_TYPE_SOFT用於設置定時器是軟定時器組還是硬定時器組。
timer.h
14 typedef struct timer_tag {
15
16 list_node_t link_node;
17 uint32_t duration_ticks;
18 uint32_t start_delay_ticks;
19 uint32_t delay_ticks;
20 void (*timer_func)(void *arg);
21 void *arg;
22 uint32_t config;
23
24 timer_state_e state;
25 }timer_t;
26
27 #define TIMER_CONFIG_TYPE_HARD (1 << 0)
28 #define TIMER_CONFIG_TYPE_SOFT (0 << 0)
接口實現
timer_init根據傳入的參數初始化定時器timer,並將timer的狀態設置成TIMER_CREATED。
timer.c
10 void timer_init(timer_t *timer, uint32_t delay_ticks, uint32_t duration_ticks,
11 void(*timer_func)(void *arg), void *arg, uint32_t config)
12 {
13 list_node_init(&timer->link_node);
14 timer->start_delay_ticks = delay_ticks;
15 timer->duration_ticks = duration_ticks;
16 timer->timer_func = timer_func;
17 timer->arg = arg;
18 timer->config = config;
19
20 if (delay_ticks == 0) {
21 timer->delay_ticks = duration_ticks;
22 } else {
23 timer->delay_ticks = timer->start_delay_ticks;
24 }
25
26 timer->state = TIMER_CREATED;
27 }
timer_module_init初始化定時器組模塊,代碼很簡單,初始化硬定時器鏈表和軟定時器鏈表,並且初始化軟定時器任務timer_soft_task,timer_soft_task的優先級爲1,屬於較高優先級的任務。
timer.c
29 static task_t g_timer_task;
30 static task_stack_t g_timer_task_stack[OS_TIMERTASK_STACK_SIZE];
103 static void timer_soft_task(void *param)
104 {
105 for(;;) {
106 sem_acquire(&g_timer_tick_sem, 0);
107 sem_acquire(&g_timer_protect_sem, 0);
108
109 timer_call_func_list(&g_timer_soft_list);
110 sem_release(&g_timer_protect_sem);
111 }
112 }
123 void timer_module_init()
124 {
125 list_init(&g_timer_hard_list);
126 list_init(&g_timer_soft_list);
127 sem_init(&g_timer_protect_sem, 1, 1);
128 sem_init(&g_timer_tick_sem, 0, 0);
129
130 task_init(&g_timer_task, &timer_soft_task, (void *)0,
131 OS_TIMERTASK_PRIO, &g_timer_task_stack[OS_TIMERTASK_STACK_SIZE]);
132
133 }
timer_start啓動定時器timer,只有當定時器處於TIMER_CREATED和TIMER_STOPPED的時候才能夠啓動定時器。啓動定時器分爲以下步驟:
1. 設置定時器的delay_ticks如果定時器沒有起始定時時間,那麼之間將週期定時時間賦給定時器的delay_ticks。
2. 將定時器的狀態設置爲TIMER_STARTED
3. 根據定時器的配置,將定時器插入到不同的定時器鏈表中。當配置爲軟定時器組時,將定時器插入到g_timer_hard_list中,反之,則插入到g_timer_soft_list中。
timer.c
33 void timer_start(timer_t *timer)
34 {
35 switch(timer->state) {
36 case TIMER_CREATED:
37 case TIMER_STOPPED:
38 timer->delay_ticks = timer->start_delay_ticks ? timer->start_delay_ticks : timer->duration_ticks;
39 timer->state = TIMER_STARTED;
40
41 if (timer->config & TIMER_CONFIG_TYPE_HARD) {
42 uint32_t status = task_enter_critical();
43 list_append_last(&g_timer_hard_list, &timer->link_node);
44 task_exit_critical(status);
45 } else {
46 sem_acquire(&g_timer_protect_sem, 0);
47 list_append_last(&g_timer_soft_list, &timer->link_node);
48 sem_release(&g_timer_protect_sem);
49 }
50 break;
51 default:
52 break;
53 }
54 }
timer_stop停止定時器timer。其操作很簡單,正好與timer_start反操作,直接將定時器從相應的定時器鏈表中移除即可。
timer.c
56 void timer_stop(timer_t *timer)
57 {
58 switch(timer->state) {
59 case TIMER_STARTED:
60 case TIMER_RUNNING:
61 if (timer->config & TIMER_CONFIG_TYPE_HARD) {
62 uint32_t status = task_enter_critical();
63 list_remove(&g_timer_hard_list, &timer->link_node);
64 task_exit_critical(status);
65 } else {
66 sem_acquire(&g_timer_protect_sem, 0);
67 list_remove(&g_timer_soft_list, &timer->link_node);
68 sem_release(&g_timer_protect_sem);
69 }
70 timer->state = TIMER_STOPPED;
71 break;
72 default:
73 break;
74 }
75 }
timer_destory銷燬定時器timer,停止定時器並且將定時器的狀態設置爲TIMER_DESTORYED。
timer.c
97 void timer_destory(timer_t *timer)
98 {
99 timer_stop(timer);
100 timer->state = TIMER_DESTORYED;
101 }
從上文中可以看到軟定時器任務timer_soft_task只做了一件事,就是調用timer_call_func_list這個函數。這個函數就是
1. 將定時器的狀態設置爲TIMER_RUNNING。
2. 根據傳入了傳入的定時器鏈表,遍歷該定時器鏈表,然後調用定時器回調函數timer->timer_func(timer->arg);。
3. 根據定時器的週期定時數來判斷定時器是否是週期定時器。如果duration_ticks是0,那麼該定時器是一次性的定時器,在一次定時完成後,將停止定時器。如果duration_ticks不爲0,那麼將duration_ticks重新賦給delay_ticks,那麼定時器又開始重新計數了。
timer.c
77 static void timer_call_func_list(list_t *timer_list)
78 {
79 list_node_t *node;
80 timer_t *timer;
81
82 for (node = timer_list->head.next; node != &(timer_list->head); node = node->next) {
83 timer = container_of(node, timer_t, link_node);
84 if ((timer->delay_ticks == 0) || (--timer->delay_ticks == 0)) {
85 timer->state = TIMER_RUNNING;
86 timer->timer_func(timer->arg);
87 timer->state = TIMER_STARTED;
88 if (timer->duration_ticks > 0) {
89 timer->delay_ticks = timer->duration_ticks;
90 } else {
91 timer_stop(timer);
92 }
93 }
94 }
95 }
timer_module_tick_notify用於硬定時器組。 該函數在task_system_tick_handle中調用。這樣硬定時器組就是由systick計數,而非軟定時器任務。這樣硬定時器的優先級的最高的,因此硬定時器組的定時器總能在較快的時間內進行相應。
timer.c
114 void timer_module_tick_notify(void)
115 {
116 uint32_t status = task_enter_critical();
117
118 timer_call_func_list(&g_timer_hard_list);
119 task_exit_critical(status);
120 sem_release(&g_timer_tick_sem);
121 }
task.c
125 void task_system_tick_handler(void)
126 {
......
166 timer_module_tick_notify();
167 task_exit_critical(status);
168
169 task_sched();
170 }
測試
task1中使用了3個定時器timer1,timer2和timer3。
- timer1 起始延時爲100個tick(即1s),週期延時1s,即每1s回調一次timer_func1,參數爲0,將其配置爲硬定時器組。
- timer2 起始延時爲200個tick(即2s),週期延時2s,即每1s回調一次timer_func2,參數爲0,將其配置爲軟定時器組。
- timer3 起始延時爲300個tick(即3s),沒有周期延時,即只一次性調用timer_func3, 參數爲0,將其配置爲軟定時器組。
task1延時10s後,停止timer2。
然後再延時10s,銷燬timer1,此時timer1再也不會運行了。同時啓動timer2。
隨後延時10s,銷燬timer2。至此任務中所有定時器都不再執行。
main.c
30 timer_t timer1;
31 timer_t timer2;
32 timer_t timer3;
33
34 static void timer_func1(void *arg)
35 {
36 printk("timer_func1\n");
37 }
38
39 static void timer_func2(void *arg)
40 {
41 printk("timer_func2\n");
42 }
43
44 static void timer_func3(void *arg)
45 {
46 printk("timer_func3\n");
47 }
54 void task1_entry(void *param)
55 {
56 uint32_t stopped = 0;
57 init_systick(10);
58
59 timer_init(&timer1, 100, 100, timer_func1, (void *)0, TIMER_CONFIG_TYPE_HARD);
60 timer_start(&timer1);
61 timer_init(&timer2, 200, 200, timer_func2, (void *)0, TIMER_CONFIG_TYPE_SOFT);
62 timer_start(&timer2);
63
64 timer_init(&timer3, 300, 0, timer_func3, (void *)0, TIMER_CONFIG_TYPE_HARD);
65 timer_start(&timer3);
66
67 for(;;) {
68 if (stopped == 0) {
69 task_delay(1000);
70 timer_stop(&timer2);
71 task_delay(1000);
72 timer_start(&timer2);
73 timer_destory(&timer1);
74 task_delay(1000);
75 timer_destory(&timer2);
76 stopped = 1;
77 }
78 }
79 }