信号 signal,并不是线程间同步的信号量 semaphore。后者是线程间同步机制的一种,而前者是线程间异步通信的一种。
官方文档里对其解释是:“信号(又称为软中断信号),在软件层次上是对中断机制的一种模拟,在原理上,一个线程收到一个信号与处理器收到一个中断请求可以说是类似的。”
信号本质是**软中断**,用来通知线程发生了异步事件,**用做线程之间的异常通知、应急处理**。一个**线程不必通过任何操作来等待信号的到达**,事实上,**线程也不知道信号到底什么时候到达**。线程之间可以互相通过调用 `rt_thread_kill` 发送信号。
以上画线部分是我特意要大家注意的,我们要看待中断回调函数那样,看待信号回调函数**被执行的实机**,但不需要过分担忧的是回调函数**执行时间**,因为**终究信号回调函数还是在线程上下文被执行的**。
从官方文档可以清楚了解到,使用信号很简单,安装信号、解除信号掩码、发送信号、处理信号等几个过程。
更多关于信号的原理详见官方文档 [信号]( https://www.rt-thread.org/document/site/#/rt-thread-version/rt-thread-standard/programming-manual/ipc2/ipc2?id=%e4%bf%a1%e5%8f%b7 )
官方原版示例笔者就不贴出来了,直接拷贝到自己的项目完美运行。但是,笔者经过如下修改,发现一点儿疑问。
/* 线程 1 的入口函数 */
static void thread1_entry(void *parameter)
{
...
while (cnt < 10)
{
...
tick = rt_tick_get();
rt_thread_mdelay(1000);
tick = rt_tick_get();
}
...
}
把延时时间增长,前后添加测时。多次运行发现 tick 值改变只有 300 (`rt_thread_mdelay(300)`)。这说明了线程响应 signal 后,处理了信号回调函数之后放弃了之前的延时!那么问题来了,应用层想要的延时时间不足,应用层知道吗?答案是,*不知道!*
前一段时间在文章 rt-thread 那些你必须知道的几类 api 里总结了 *禁止在中断中调用*、*必须在任务调度器运行以后才能使用*、*不能用在线程自己身上*的几类 api。
可能还缺一种:哪些 api 会引起线程调度,使得当前线程放弃 cpu 使用权——所有调用 `rt_schedule` 的函数都属于这类。这里边又分三种情况,一种是时间片耗尽让出 cpu 使用权;一种是释放资源或者信号让出 cpu 使用权;还有一种是等待资源而被动放弃 cpu。最后这种情况,是有目地的,往往希望有资源可用了之后从阻塞中恢复继续运行,如果线程从阻塞中恢复运行但同时没有资源可用是不是就乌龙了?以下的关注重点也是这类函数。
所有第三类引起线程调度的函数和上面的 `rt_thread_mdelay` 一样,在 signal 面前可能遇到一样的遭遇。大体上,分这么几类:
这几类在遇到 signal 之后行为分别是什么样的?
这个前面已经经过测试的了,它会退出阻塞提前结束延时,但是应用层并不知道是达到延时时间还是有信号。
/* do schedule */
rt_schedule();
if (thread->error != RT_EOK)
{
return thread->error;
}
/* do schedule */
rt_schedule();
if (thread->error != RT_EOK)
{
#ifdef RT_USING_SIGNALS
/* interrupt by signal, try it again */
if (thread->error == -RT_EINTR) goto __again;
#endif /* RT_USING_SIGNALS */
其它,其余的 ipc 都和 `rt_sem_take` 一样。
`rt_completion_wait` 返回线程错误状态。
/* do schedule */
rt_schedule();
/* thread is waked up */
result = thread->error;
level = rt_hw_interrupt_disable();
}
}
...
return result;
因为文件描述符对应的设备不尽相同,设备底层实现 `poll` 的方式可能也千差万别,但是他们大概率是使用上面的线程间同步和通信机制了。
`poll` 实现过程调用个超时等待函数 `poll_wait_timeout` ,它也没有区分超时和信号两种情况。
rt_schedule();
level = rt_hw_interrupt_disable();
}
ret = !pt->triggered;
rt_hw_interrupt_enable(level);
return ret;
我们发现,`rt_sem_take` 结束了阻塞,并可能返回了 `RT_EINTR` ,而 `rt_mutex_take` 继续了循环阻塞。
现做以下约定,等待资源而被动放弃 cpu 的线程在此约定下,当有 signal 的时候会提前结束阻塞,返回应用层,应用层可以根据线程错误状态区别处理。
1. 复位线程错误状态为 `RT_EOK` 。
2. 调用 `rt_schedule` 进行线程调度,线程被阻塞挂起。
3. 从 `rt_schedule` 恢复唤醒,有一定手段通知到应用层(返回线程错误状态),应用层可以区分出是因为资源可用还是因为信号。
哪些 api 做到了以上这几点呢?
```
rt_completion_wait
rt_sem_take
rt_event_recv
rt_mb_send_wait
rt_mb_recv
rt_mq_send_wait
rt_mq_recv
rt_data_queue_push
rt_data_queue_pop
rt_mp_alloc
哪些 api 没有做到以上几点?
```
rt_mutex_take
rt_thread_sleep
rt_thread_delay
rt_thread_delay_until
rt_thread_mdelay
rt_wqueue_wait
笔者曾经在 gitee 上提交过一个 [issue]( https://gitee.com/rtthread/rt-thread/issues/I44JNS ) ,当时笔者隐隐中认为 ipc 中的不一致行为总有些隐患,感觉所有的阻塞等待都应该处理一下意外唤醒后的超时等待。却没意识到有什么意外情况可以让这些函数从阻塞等待中提前退出。通过研究 signal 实现原理的过程中发现,这种意外情况还有存在的,只是担忧的问题重点变了,不是处理阻塞等待剩余时间,而是在 signal 的影响下通知应用层的问题。
有了上面的梳理,下面的修改方向就有了,改动范围也确定了。
以上搜索不一定完整完全,但应该包括了大部分受到影响的函数。如果看客有发现其它的 api 有不符合上述约定行为的,请留言告知,谢谢!
本人能力有限,文中难免有错误。望各位同仁不吝赐教。
相关文章
全部0条评论
快来发表一下你的评论吧 !