第二十二章 邮箱(第二部分)
五、邮箱控制块
1 struct rt_mailbox {
2 struct rt_ipc_object parent; (1)
**:邮箱属于内核对象,也会在自身结构体里面包含一个内核对象类
型的成员,通过这个成员可以将邮箱挂到系统对象容器里面。 **
3
4 rt_uint32_t *msg_pool; (2) **邮箱缓冲区的开始地址。 **
5
6 rt_uint16_t size; (3) **邮箱缓冲区的大小**
7
8 rt_uint16_t entry; (4) **邮箱中当前邮件的数目**
9 rt_uint16_t in_offset; (5) **邮箱邮件的进偏移指针,指向空的邮件**
10 rt_uint16_t out_offset; (6)
**邮箱邮件的出偏移指针,如果邮箱中有邮件,则指向先进来的邮件。 **
11
12 rt_list_t suspend_sender_thread; (7) **发送线程的挂起等待链表**
13 };
14 typedef struct rt_mailbox *rt_mailbox_t;
六:常用函数
1、 邮箱创建函数 rt_mb_create()
1 rt_mailbox_t rt_mb_create(const char *name, (1) **name 邮箱名称。**
2 rt_size_t size, (2) **:size 邮箱容量**
3 rt_uint8_t flag) (3) **flag 用于设置邮箱的阻塞唤醒模式**
4 {
5 rt_mailbox_t mb;
6
7 RT_DEBUG_NOT_IN_INTERRUPT;
8
9 /* 分配邮箱对象 */
10 mb = (rt_mailbox_t)rt_object_allocate(RT_Object_Class_MailBox, name);
11 if (mb == RT_NULL) (4) **分配邮箱对象,**
12 return mb;
13
14 /* 设置接收线程等待模式 */
15 mb->parent.parent.flag = flag; (5) **设置邮箱的阻塞唤醒模式**
16
17 /* 初始化邮箱对象 */
18 rt_ipc_object_init(&(mb->parent)); (6) **初始化邮箱内核对象**
19
20 /* 初始化邮箱 */
21 mb->size = size; (7) **初始化邮箱,设置邮箱的大小**
22 mb->msg_pool = RT_KERNEL_MALLOC(mb->size * sizeof(rt_uint32_t));
23 if (mb->msg_pool == RT_NULL) { (8)
**申请邮箱内存,其内存大小为邮箱容量乘以 4 个字节,因为每封邮件的大小为 4 个字节。**
24 /* 删除邮箱对象 */
25 rt_object_delete(&(mb->parent.parent)); (9) **如果内存申请失败,则需要删除邮箱对象。**
26
27 return RT_NULL;
28 }
29 mb->entry = 0; (10) **申请内存成功,则初始化相关信息**
30 mb->in_offset = 0;
31 mb->out_offset = 0;
32
33 /* 初始化发送邮件挂起线程的链表 */
34 rt_list_init(&(mb->suspend_sender_thread)); (11) **初始化发送邮件挂起线程的链表。 **
35
36 return mb;
37 }
38 RTM_EXPORT(rt_mb_create);
(5):设置邮箱的阻塞唤醒模式
如果创建成功则返回创建的邮箱句柄,如果是返回 RT_NULL,则表示失败
例子:
1 /* 定义邮箱控制块 */
2 static rt_mailbox_t test_mail = RT_NULL;
3
4 /* 创建一个邮箱 */
5 test_mail = rt_mb_create("test_mail", /* 消息队列名字 */
6 10, /* 邮箱大小 */
7 RT_IPC_FLAG_FIFO);/* 信号量模式 FIFO(0x00)*/
8 if (test_mail != RT_NULL)
9 rt_kprintf("邮箱创建成功!\n\n");
2、 邮箱创建函数 rt_mb_create()
1 rt_err_t rt_mb_delete(rt_mailbox_t mb) (1) **mb 是我们自己定义的邮箱句柄**
2 {
3 RT_DEBUG_NOT_IN_INTERRUPT;
4
5 /* 邮箱句柄检查 */
6 RT_ASSERT(mb != RT_NULL); (2)
**检查邮箱句柄 mb 是否有效,如果它是未定义或者未创建的邮箱句
柄,那么是无法进行删除操作的。 **
7
8 /* 恢复所有阻塞在接收邮件的线程 */
9 rt_ipc_list_resume_all(&(mb->parent.suspend_thread)); (3)
**将所有因为接收不到邮件的而阻塞
的线程从阻塞态中唤醒,所有被唤醒的线程的返回值是-RT_ERROR。**
10
11 /* 也恢复所有阻塞在发送邮件的线程 */
12 rt_ipc_list_resume_all(&(mb->suspend_sender_thread)); (4)
**将所有因为邮箱满了发送不到邮件的而阻塞的线程从阻塞态中恢复过来,所有被唤醒的线程的返回值是-
RT_ERROR。 **
13
14 #if defined(RT_USING_MODULE) && defined(RT_USING_SLAB) (5)
**:如果使能了 RT_USING_SLAB 这个宏定义,表示使用 slab 分配内
存机制,那么需要使用 rt_module_free 函数进行释放内存**
15 /* 邮箱对象属于应用程序模块*/
16 if (mb->parent.parent.flag & RT_OBJECT_FLAG_MODULE)
17 rt_module_free(mb->parent.parent.module_id, mb->msg_pool);
18 else
19 #endif
20
21 /* 释放邮箱内存 */
22 RT_KERNEL_FREE(mb->msg_pool); (6) **释放邮箱内存,一旦释放,将永久性被删除**
23
24 /* 删除邮箱对象 */
25 rt_object_delete(&(mb->parent.parent)); (7) **删除邮箱对象**
26
27 return RT_EOK;
28 }
29 RTM_EXPORT(rt_mb_delete);
例子:
1 /* 定义邮箱控制块 */
2 static rt_mailbox_t test_mail = RT_NULL;
3 rt_err_t uwRet = RT_EOK;
4
5 /* 删除一个邮箱 */
6 uwRet = rt_mbt_delete(test_mail);
7 if (RT_EOK == uwRet)
8 rt_kprintf("邮箱创建成功!\n\n");
3、 邮箱邮件发送函数 rt_mb_send_wait()(阻塞)
1 /**
2 * 如果这个邮箱对象是空的话,这个函数会发送一个邮件到邮箱对象.
3 * 如果这个邮箱对象是满的话,将会挂起当前线程
4 *
5 * @param 邮箱对象
6 * @param 邮箱大小
7 * @param 等待时间
8 *
9 * @return 错误代码
10 */
11 rt_err_t rt_mb_send_wait(rt_mailbox_t mb, (1) **mb 邮箱对象的句柄**
12 rt_uint32_t value, (2) **value 邮件内容**
13 rt_int32_t timeout) (3)**timeout 超时时间**
14 {
15 struct rt_thread *thread;
16 register rt_ubase_t temp;
17 rt_uint32_t tick_delta;
18
19 /* 检查邮箱对象 */
20 RT_ASSERT(mb != RT_NULL); (4)
**检查邮箱句柄 mb 是否有效,如果它是未定义或者未创建的邮箱句
柄,那么是无法进行发送邮件操作的。 **
21
22 /* 初始化系统时间差 */
23 tick_delta = 0;
24 /* 获取当前线程 */
25 thread = rt_thread_self(); (5) **获取当前线程**
26
27 RT_OBJECT_HOOK_CALL(rt_object_put_hook, (&(mb->parent.parent)));
28
29 /* 关中断 */
30 temp = rt_hw_interrupt_disable();
31
32 /* 无阻塞调用 */
33 if (mb->entry == mb->size && timeout == 0) { (6)
**如果邮箱已满,并且是无阻塞调用(timeout=0),那么发送失败,直接退出发送**
34 rt_hw_interrupt_enable(temp);
35
36 return -RT_EFULL;
37 }
38
39 /* 邮箱满了 */
40 while (mb->entry == mb->size) { (7) **如果邮箱满了,进入死循环中。**
41 /* 重置线程错误代码 */
42 thread->error = RT_EOK;
43
44 /* 不等待,返回错误 */
45 if (timeout == 0) { (8) **timeout=0,用户不等待,返回错误码。 **
46 /* 开中断 */
47 rt_hw_interrupt_enable(temp);
48
49 return -RT_EFULL;
50 }
51
52 RT_DEBUG_IN_THREAD_CONTEXT;
53 /* 挂起当前线程 */
54 rt_ipc_list_suspend(&(mb->suspend_sender_thread), (9)
**(9)-(17)的内容都是邮箱满了并且 timeout 不为 0 的情况。因为用
户设置了阻塞时间,不管三七二十一直接先将当前线程挂起。 **
55 thread,
56 mb->parent.parent.flag);
57
58 /* 有等待时间 */
59 if (timeout > 0) { (10) **有等 待 时 间 **
60 /* 获取当前系统时间 */
61 tick_delta = rt_tick_get();
62
63 RT_DEBUG_LOG(RT_DEBUG_IPC, ("mb_send_wait: start timer of thread:%s\n",
64 thread->name));
65
66 /* 重置线程超时时间并开始定时 */
67 rt_timer_control(&(thread->thread_timer), (11)
**重置线程定时器的超时时间,调用 rt_timer_control()函数改变当前线程阻塞时间 thread_timer。**
68 RT_TIMER_CTRL_SET_TIME,
69 &timeout);
70 rt_timer_start(&(thread->thread_timer)); (12) **启动定时器,开始计时。 **
71 }
72
73 /* 开中断 */
74 rt_hw_interrupt_enable(temp);
75
76 /* 进行线程调度 */
77 rt_schedule(); (13) 因**为现在线程是等待着了,要进行线程切换,所以需要进行一次线程调度。**
78
79 /* 从挂起状态恢复 */
80 if (thread->error != RT_EOK) { (14)
**超时时间到了,线程被唤醒,但此时还没发送邮件完成,那么将返回错误码。**
81 /* 返回错误代码 */
82 return thread->error;
83 }
84
85 /* 关中断 */
86 temp = rt_hw_interrupt_disable();
87
88 /* 如果它不是永远等待 */
89 if (timeout > 0) {
90 tick_delta = rt_tick_get() - tick_delta;
91 timeout -= tick_delta;
92 if (timeout < 0)
93 timeout = 0;
94 }
95 }
96
97 /* 将要发送的信息放入邮件中 */
98 mb->msg_pool[mb->in_offset] = value; (15) **如果邮箱还未满,那么可以将要发送的邮件放入邮箱。 **
99 /* 邮件进指针偏移 */
100 ++ mb->in_offset; (16) **更新发送邮件指针的进偏移地址**
101 if (mb->in_offset >= mb->size) (17) **判断邮箱是否满了,若它满了,将 in_offset 设置为 0。 **
102 mb->in_offset = 0;
103 /* 记录邮箱中邮件的数量 */
104 mb->entry ++; (18) **记录邮箱中邮件的数量**
105
106 /* 恢复线程 */
107 if (!rt_list_isempty(&mb->parent.suspend_thread)) { (19)
**如果有线程因为接收不到邮件进入阻塞的话,那么需要恢复该线
程,调用 rt_ipc_list_resume 函数将该线程恢复。**
108 rt_ipc_list_resume(&(mb->parent.suspend_thread));
109
110 /* 开中断 */
111 rt_hw_interrupt_enable(temp);
112
113 rt_schedule(); (20) **恢复线程后进行一次线程调度。 **
114
115 return RT_EOK;
116 }
117
118 /* 开中断 */
119 rt_hw_interrupt_enable(temp);
120
121 return RT_EOK; (21) **返回发送邮件结果。 **
122 }
123 RTM_EXPORT(rt_mb_send_wait);
发送邮件时,发送者需指定发送到的邮箱的对象句柄(即指向邮箱控制块的指针),并且指定发送的邮件内容,如果内容大于 4 个字节,可以将内容的地址作为邮件发送出去,
例子:
1 /* 定义邮箱控制块 */
2 static rt_mailbox_t test_mail = RT_NULL;
3 /************************* 全局变量声明 ****************************/
4 /*
5 * 当我们在写应用程序的时候,可能需要用到一些全局变量。
6 */
7 char test_str1[] = "this is a mail test 1";/* 邮箱消息 test1 */
8 char test_str2[] = "this is a mail test 2";/* 邮箱消息 test2 */
9
10 static void send_thread_entry(void* parameter)
11 {
12 rt_err_t uwRet = RT_EOK;
13 /* 线程都是一个无限循环,不能返回 */
14 while (1) {
15 //如果 KEY1 被单击
16 if ( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON ) { 在这里插入代码片
17 rt_kprintf ( "KEY1 被单击\n" );
18 /* 发送一个邮箱消息 1 */
19 uwRet = rt_mb_send_wait(test_mail,/* 邮箱对象句柄 */
20 (rt_uint32_t)&test_str1,/*邮件内容(地址) */
21 10); /* 超时时间 */
22 if (RT_EOK == uwRet)
23 rt_kprintf ( "邮箱消息发送成功\n" );
24 else
25 rt_kprintf ( "邮箱消息发送失败\n" );
26 }
27 //如果 KEY2 被单击
28 if ( Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON ) {
29 rt_kprintf ( "KEY2 被单击\n" );
30 /* 发送一个邮箱消息 2 */
31 uwRet = rt_mb_send_wait(test_mail,/* 邮箱对象句柄 */
32 (rt_uint32_t)&test_str1,/* 邮件内容(地址) */
33 10); /* 超时时间 */
34 if (RT_EOK == uwRet)
35 rt_kprintf ( "邮箱消息发送成功\n" );
36 else
37 rt_kprintf ( "邮箱消息发送失败\n" );
38 }
39 rt_thread_delay(20); //每 20ms 扫描一次
40 }
41 }
4、 邮箱邮件发送函数 rt_mb_send ()(非阻塞)
rt_mb_send 真正调用的函数是 rt_mb_send_wait,但是它却是不等待的(因为
timeout=0 ) , 这 个 函 数 多 用 于 中 断 与 线 程 的 通 信 , 因 为 中 断 中 不 允 许 阻 塞.
1 /**
2 * 此函数将邮件发送到邮箱对象,
3 * 如果有邮件对象挂起,则会被唤醒。
4 * 此函数将立即返回,如果要阻塞发送,请改用 rt_mb_send_wait。
5 *
6 * @param 邮箱对象
7 * @param 要发送的邮件内容
8 *
9 * @return 返回的错误码
10 */
11 rt_err_t rt_mb_send(rt_mailbox_t mb, rt_uint32_t value)
12 {
13 return rt_mb_send_wait(mb, value, 0);
14 }
15 RTM_EXPORT(rt_mb_send);
例子:
1 /* 定义邮箱控制块 */
2 static rt_mailbox_t test_mail = RT_NULL;
3 /************************* 全局变量声明 ****************************/
4 /*
5 * 当我们在写应用程序的时候,可能需要用到一些全局变量。
6 */
7 char test_str1[] = "this is a mail test 1";/* 邮箱消息 test1 */
8 char test_str2[] = "this is a mail test 2";/* 邮箱消息 test2 */
9
10 static void send_thread_entry(void* parameter)
11 {
12 rt_err_t uwRet = RT_EOK;
13 /* 线程都是一个无限循环,不能返回 */
14 while (1) {
15 //如果 KEY1 被单击
16 if ( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON ) {
17 rt_kprintf ( "KEY1 被单击\n" );
18 /* 发送一个邮箱消息 1 */
19 uwRet = rt_mb_send(test_mail,/* 邮箱对象句柄 */
20 (rt_uint32_t)&test_str1)/* 邮件内容(地址) */
21 if (RT_EOK == uwRet)
22 rt_kprintf ( "邮箱消息发送成功\n" );
23 else
24 rt_kprintf ( "邮箱消息发送失败\n" );
25 }
26 //如果 KEY2 被单击
27 if ( Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON ) {
28 rt_kprintf ( "KEY2 被单击\n" );
29 /* 发送一个邮箱消息 2 */
30 uwRet = rt_mb_send(test_mail,/* 邮箱对象句柄 */
31 (rt_uint32_t)&test_str1)/* 邮件内容(地址) */
32 if (RT_EOK == uwRet)
33 rt_kprintf ( "邮箱消息发送成功\n" );
34 else
35 rt_kprintf ( "邮箱消息发送失败\n" );
36 }
37 rt_thread_delay(20); //每 20ms 扫描一次
38 }
39 }
5、 邮箱邮件接收函数 rt_mb_recv()
- 使用该函数访问指定的邮箱,看看是否有邮件发送过来,接收到邮件就去处理信息,如果还没有邮件发送过来,那我们可以不等这个邮件或者指定等待时间去接收这个邮件,如果超时了还是没有收到邮件,就返回错误代码。
- 只有当接收者接收的邮箱中有邮件时,接收线程才能立即取到邮件,否则接收线程会根据指定超时时间将线程挂起,直到接收完成或者超时
1 rt_err_t rt_mb_recv(rt_mailbox_t mb, (1) **mb 邮箱对象的句柄**
2 rt_uint32_t *value, (2) **value 用于存放邮件内容的地址**
3 rt_int32_t timeout) (3) **timeout 超时时间。 **
4 {
5 struct rt_thread *thread;
6 register rt_ubase_t temp;
7 rt_uint32_t tick_delta;
8
9 /* 邮箱检查 */
10 RT_ASSERT(mb != RT_NULL); (4)
**检查邮箱句柄 mb 是否有效,如果它是未定义或者未创建的邮箱句柄,那么是无法进行接收邮件操作的。 **
11
12 /* 初始化系统时间差变量 */
13 tick_delta = 0;
14 /* 获取当前线程 */
15 thread = rt_thread_self(); (5)**先获取当前线程,在后面需要用到当前线程的信息。**
16
17 RT_OBJECT_HOOK_CALL(rt_object_trytake_hook, (&(mb->parent.parent)));
18
19 /* 关中断 */
20 temp = rt_hw_interrupt_disable();
21
22 /* 非阻塞调用 */
23 if (mb->entry == 0 && timeout == 0) { (6)
**如果邮箱是空的,并且是无阻塞调用(timeout=0)接收函数,那么接收邮件失败。**
24 rt_hw_interrupt_enable(temp);
25
26 return -RT_ETIMEOUT;
27 }
28
29 /* 邮箱是空的 */
30 while (mb->entry == 0) { (7) **如果邮箱是空的,进入死循环中。 **
31 /* 重置线程错误 */
32 thread->error = RT_EOK;
33
34 /* 不等待,返回错误码-RT_ETIMEOUT */
35 if (timeout == 0) {
36 /* 开中断 */
37 rt_hw_interrupt_enable(temp);
38
39 thread->error = -RT_ETIMEOUT;
40
41 return -RT_ETIMEOUT;
42 }
43
44 RT_DEBUG_IN_THREAD_CONTEXT;
45 /* 挂起当前线程 */
46 rt_ipc_list_suspend(&(mb->parent.suspend_thread), (8)
**(8)-(12)的内容都是邮箱是空的并且 timeout 不为 0 的情况。因
为用户设置了阻塞时间,不管三七二十一直接先将当前线程挂起。 **
47 thread,
48 mb->parent.parent.flag);
49
50 /* 有等待时间,开始等待 */
51 if (timeout > 0) {
52 /* 获取开始时候的系统时间 */
53 tick_delta = rt_tick_get(); (9) **获取阻塞开始时候的系统时间。 **
54
55 RT_DEBUG_LOG(RT_DEBUG_IPC, ("mb_recv: start timer of thread:%s\n",
56 thread->name));
57
58 /* 重置线程超时时间,并且开始定时器 */
59 rt_timer_control(&(thread->thread_timer), (10)
**重置线程计时器的超时时间,调用 rt_timer_control()函数改变当
前线程阻塞时间 thread_timer。 **
60 RT_TIMER_CTRL_SET_TIME,
61 &timeout);
62 rt_timer_start(&(thread->thread_timer)); (11)**启动定时器,开始计时。**
63 }
64
65 /* 开中断 */
66 rt_hw_interrupt_enable(temp);
67
68 /* 发起线程调度 */
69 rt_schedule(); (12)
**因为现在线程是等待着了,要进行线程切换,所以进行一次线程调度。**
70
71 /* 解除阻塞了 */
72 if (thread->error != RT_EOK) {
73 /* 返回错误代码 */
74 return thread->error;
75 }
76
77 /* 关中断 */
78 temp = rt_hw_interrupt_disable();
79
80 /* 如果它不是永远等待 */
81 if (timeout > 0) {
82 tick_delta = rt_tick_get() - tick_delta;
83 timeout -= tick_delta;
84 if (timeout < 0)
85 timeout = 0;
86 }
87 }
88
89 /* 将邮件内容放到接收邮件的地址中 */
90 *value = mb->msg_pool[mb->out_offset]; (13)
**将接收到的邮件内容放到接收地址中**
91
92 /* 接收邮件偏移指针自加 */
93 ++ mb->out_offset; (14) **更新接收邮件指针的偏移地址**
94 if (mb->out_offset >= mb->size) (15)
**判断接收邮件指针的偏移地址是否到达邮箱最大容量,如果是,则重置为 0。 **
95 mb->out_offset = 0;
96 /* 记录当前邮件数量 */
97 mb->entry --; (16) **记录当前邮件数量**
98
99 /* 恢复挂起的线程 */
100 if (!rt_list_isempty(&(mb->suspend_sender_thread))) { (17)
**如果有线程因为发送邮件不成功而被阻塞的话,那么需要恢复该线程**
101 rt_ipc_list_resume(&(mb->suspend_sender_thread));
102
103 /* 开中断 */
104 rt_hw_interrupt_enable(temp);
105
106 RT_OBJECT_HOOK_CALL(rt_object_take_hook, (&(mb->parent.parent)));
107
108 rt_schedule(); (18)**进行一次线程调度。**
109
110 return RT_EOK;
111 }
112
113 /* 关中断 */
114 rt_hw_interrupt_enable(temp);
115
116 RT_OBJECT_HOOK_CALL(rt_object_take_hook, (&(mb->parent.parent)));
117
118 return RT_EOK; (19) **返回接收邮件结果。 **
119 }
120 RTM_EXPORT(rt_mb_recv);
接收邮件时,接收者需指定接收邮件的邮箱句柄,并指定接收到的邮件存放位置以及设置指定超时时间,成功收到邮件则返回 RT_EOK;当指定的时间内依然未收到邮件时,将返回-RT_ETIMEOUT。
1 /* 定义邮箱控制块 */
2 static rt_mailbox_t test_mail = RT_NULL;
3
4 static void receive_thread_entry(void* parameter)
5 {
6 rt_err_t uwRet = RT_EOK;
7 char *r_str;
8 /* 线程都是一个无限循环,不能返回 */
9 while (1) {
10 /* 等待接邮箱消息 */
11 uwRet = rt_mb_recv(test_mail, /* 邮箱对象句柄 */
12 (rt_uint32_t*)&r_str, /* 接收邮箱消息 */
13 RT_WAITING_FOREVER); /* 指定超时事件,一直等 */
14
15 if (RT_EOK == uwRet) { /* 如果接收完成并且正确 */
16 rt_kprintf ( "邮箱的内容是:%s\n\n",r_str);
17 LED1_TOGGLE; //LED1 反转
18 } else
19 rt_kprintf ( "邮箱接收错误!错误码是 0x%x\n",uwRet);
20 }
21 }
22