1. 等待队列是什么
等待队列是一个轻量级的线程间异步通讯方式。
他有两个特点:
-
轻量:
API
较少 -
异步:
2. 等待队列怎么使用
用户只需要使用其中的五个 API
就可以了。
-
初始化等待队列
rt_inline void rt_wqueue_init(rt_wqueue_t *queue)
queue : 消息队列控制块
-
加入等待队列
int rt_wqueue_wait(rt_wqueue_t *queue, int condition, int msec)
queue : 等待队列的控制块
condition :这个形参没有使用到, 必须传递 0
msec : 需要等待的时间,单位是毫秒
-
唤醒等待队列
void rt_wqueue_wakeup(rt_wqueue_t *queue, void *key)
queue : 等待队列的控制块
key : 唤醒条件,目前源码中未使用
-
在等待队列中插入一个节点
void rt_wqueue_add(rt_wqueue_t *queue, struct rt_wqueue_node *node)
queue : 等待队列控制块
node : 插入队列的节点
-
从等待队列移除一个节点
void rt_wqueue_remove(struct rt_wqueue_node *node)
node : 需要移除的节点
3. 等待队列的实现
-
初始化等待队列
rt_inline void rt_wqueue_init(rt_wqueue_t *queue) // 这里使用了关键字 inline { RT_ASSERT(queue != RT_NULL); // 断言检查 queue->flag = RT_WQ_FLAG_CLEAN; // 设置标志位 rt_list_init(&(queue->waiting_list)); // 初始化链表 }
-
加入等待队列
int rt_wqueue_wait(rt_wqueue_t *queue, int condition, int msec) { int tick; rt_thread_t tid = rt_thread_self();// 获取当前调用等待队列的线程 rt_timer_t tmr = &(tid->thread_timer);// 获取当前线程的定时器 struct rt_wqueue_node __wait;// 等待队列的节点 rt_base_t level; /* current context checking */ RT_DEBUG_NOT_IN_INTERRUPT; tick = rt_tick_from_millisecond(msec);// 获取传递进来的时间的转换成 tick,所以超时时间的单位是 ms if ((condition) || (tick == 0)) // condition 必须传递 0 return 0; __wait.polling_thread = rt_thread_self(); //获取当前线程给到 __wait __wait.key = 0; // key 赋值 0 __wait.wakeup = __wqueue_default_wake; // 这个函数建议用户自己实现一个,默认的这个会返回0 rt_list_init(&__wait.list); // 初始化链表 level = rt_hw_interrupt_disable(); // 关中断 if (queue->flag == RT_WQ_FLAG_WAKEUP) // 如果队列已经是唤醒状态了 { /* already wakeup */ goto __exit_wakeup; } rt_wqueue_add(queue, &__wait); // 把 __wait 插入到 queue-> waiting_list 的前面 rt_thread_suspend(tid);// 挂起这个线程 /* start timer */ if (tick != RT_WAITING_FOREVER) // 启动线程的定时器 { rt_timer_control(tmr, RT_TIMER_CTRL_SET_TIME, &tick); rt_timer_start(tmr); } rt_hw_interrupt_enable(level);// 开中断 rt_schedule(); // 进行一次调度 level = rt_hw_interrupt_disable(); // 关中断 __exit_wakeup: queue->flag = 0; // 设置 FLAG ,这个用宏的方式可能更优雅 rt_hw_interrupt_enable(level); // 开中断 rt_wqueue_remove(&__wait); // 从等待队列中移除 __wait 这个节点 return 0; }
-
唤醒等待队列
void rt_wqueue_wakeup(rt_wqueue_t *queue, void *key)
{
rt_base_t level;
register int need_schedule = 0;
rt_list_t *queue_list;
struct rt_list_node *node;
struct rt_wqueue_node *entry;
queue_list = &(queue->waiting_list);
level = rt_hw_interrupt_disable(); // 关中断
/* set wakeup flag in the queue */
queue->flag = RT_WQ_FLAG_WAKEUP; // 设置等待队列的标志位
if (!(rt_list_isempty(queue_list))) // 检查链表是否为空
{
for (node = queue_list->next; node != queue_list; node = node->next)// 循环遍历
{
entry = rt_list_entry(node, struct rt_wqueue_node, list);// 获取等待队列控制块
if (entry->wakeup(entry, key) == 0) // wakeup 一定会返回 0
{
rt_thread_resume(entry->polling_thread);// 恢复这个线程
need_schedule = 1; //需要调度设置为 1
rt_wqueue_remove(entry); // 从等待队列的链表中移除这个等待队列
break;// 跳出循环遍历
}
}
}
rt_hw_interrupt_enable(level); // 开启中断
if (need_schedule)
rt_schedule();// 开启调度
}
- 默认唤醒函数
int __wqueue_default_wake(struct rt_wqueue_node *wait, void *key)
{
return 0;
}
这里未作任何的操作就进行了返回 0
,这里做的太简单粗暴了。
- 等待队列插入一个节点
void rt_wqueue_add(rt_wqueue_t *queue, struct rt_wqueue_node *node)
{
rt_base_t level;
level = rt_hw_interrupt_disable();// 关中断
rt_list_insert_before(&(queue->waiting_list), &(node->list));// 把 node->list 插入到 queue->waiting_list
rt_hw_interrupt_enable(level); // 开中断
}
- 等待队列移除一个节点
void rt_wqueue_remove(struct rt_wqueue_node *node)
{
rt_base_t level;
level = rt_hw_interrupt_disable(); //关中断
rt_list_remove(&(node->list)); // 移除节点 node
rt_hw_interrupt_enable(level); // 开中断
}
4. 总结
- 等待队列的使用了关键字
rt_inline
。在rt_device
注册设备时候都使用了rt_wqueue_init
,这里有一个 C 语言 的小技巧,可以自己去搜索inline
来学习一下。 - 进入等待队列的形参
condition
必须传递0
, 超时时间的单位是ms
。 - 唤醒等待队列的条件
key
,并未实现。如果有需要使用到等待队列的场景不要擅自修改源码,因为 RTT 设备框架 大量使用了,擅自修改会导致报错,自己可以参考官方的实现重造一个函数,去实现对key
的操作 - 等待队列的源码位于
/rt_thread_master/components/drivers/src/waitqueue.c
- 用户可以自行创建一个
API
来实现自定义的wake_up