嵌入式技术论坛
直播中

ss

6年用户 8762经验值
擅长:电源/新能源 制造/封装 RF/无线
私信 关注
[经验]

rt-thread的信号回调函数对内核有何影响呢

前言
信号 signal,并不是线程间同步的信号量 semaphore。后者是线程间同步机制的一种,而前者是线程间异步通信的一种。
官方文档里对其解释是:“信号(又称为软中断信号),在软件层次上是对中断机制的一种模拟,在原理上,一个线程收到一个信号与处理器收到一个中断请求可以说是类似的。”
信号本质是软中断,用来通知线程发生了异步事件,用做线程之间的异常通知、应急处理。一个线程不必通过任何操作来等待信号的到达,事实上,线程也不知道信号到底什么时候到达。线程之间可以互相通过调用 rt_thread_kill 发送信号。
以上画线部分是我特意要大家注意的,我们要看待中断回调函数那样,看待信号回调函数被执行的实机,但不需要过分担忧的是回调函数执行时间,因为终究信号回调函数还是在线程上下文被执行的。
从官方文档可以清楚了解到,使用信号很简单,安装信号、解除信号掩码、发送信号、处理信号等几个过程。
一个示例引起的血案
官方原版示例笔者就不贴出来了,直接拷贝到自己的项目完美运行。但是,笔者经过如下修改,发现一点儿疑问。
/* 线程 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 中阻塞函数列表
前一段时间在文章 rt-thread 那些你必须知道的几类 api 里总结了 禁止在中断中调用、必须在任务调度器运行以后才能使用、不能用在线程自己身上的几类 api。
可能还缺一种:哪些 api 会引起线程调度,使得当前线程放弃 cpu 使用权——所有调用 rt_schedule 的函数都属于这类。这里边又分三种情况,一种是时间片耗尽让出 cpu 使用权;一种是释放资源或者信号让出 cpu 使用权;还有一种是等待资源而被动放弃 cpu。最后这种情况,是有目地的,往往希望有资源可用了之后从阻塞中恢复继续运行,如果线程从阻塞中恢复运行但同时没有资源可用是不是就乌龙了?以下的关注重点也是这类函数。
所有第三类引起线程调度的函数和上面的 rt_thread_mdelay 一样,在 signal 面前可能遇到一样的遭遇。大体上,分这么几类:
延时函数
线程间同步机制函数
线程间通信机制部分函数(signal除外)
posix 下的 select poll 等接口(可能使用了线程间同步和通信机制)
这几类在遇到 signal 之后行为分别是什么样的?
被阻塞函数遇到 signal 后什么反应?
延时函数遇到 signal
这个前面已经经过测试的了,它会退出阻塞提前结束延时,但是应用层并不知道是达到延时时间还是有信号。
线程间同步通信机制函数遇到 signal
rt_sem_take 线程 error 非 RT_EOK (包括 RT_EINTR)直接返回线程错误状态
            /* do schedule */
            rt_schedule();
            if (thread->error != RT_EOK)
            {
                return thread->error;
            }
rt_mutex_take 考虑到了 signal 的影响,返回继续阻塞等待 time 时间。这是 ipc 里唯一例外的一个。
                /* 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 */
其它,其余的都和 rt_sem_take 一样。
完成量遇到 signal
rt_completion_wait 返回线程错误状态。
            /* do schedule */
            rt_schedule();
            /* thread is waked up */
            result = thread->error;
            level = rt_hw_interrupt_disable();
        }
    }
    ...
    return result;
select poll 等接口与 signal
因为文件描述符对应的设备不尽相同,设备底层实现 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 才合适?
现做以下约定,等待资源而被动放弃 cpu 的线程在此约定下,当有 signal 的时候会提前结束阻塞,返回应用层,应用层可以根据线程错误状态区别处理。
复位线程错误状态为 RT_EOK 。
调用 rt_schedule 进行线程调度,线程被阻塞挂起。
从 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 ,当时笔者隐隐中认为 ipc 中的不一致行为总有些隐患,感觉所有的阻塞等待都应该处理一下意外唤醒后的超时等待。却没意识到有什么意外情况可以让这些函数从阻塞等待中提前退出。通过研究 signal 实现原理的过程中发现,这种意外情况还有存在的,只是担忧的问题重点变了,不是处理阻塞等待剩余时间,而是在 signal 的影响下通知应用层的问题。
解决方案
有了上面的梳理,下面的修改方向就有了,改动范围也确定了。
几个延时函数返回 thread->error 代替目前的 RT_EOK ;
rt_mutex_take 去掉 goto __again 也返回 thread->error ;
rt_wqueue_wait 返回 thread->error 代替目前的 RT_EOK 。
poll 目前返回值是 >= 0 的,返回 0 可能是超时,也可能是被信号中断了。暂时不发表修改意见。
结束语
以上搜索不一定完整完全,但应该包括了大部分受到影响的函数。如果看客有发现其它的 api 有不符合上述约定行为的,请留言告知,谢谢!

更多回帖

发帖
×
20
完善资料,
赚取积分