Skip to content

[ipc] mutex take 被 signal 中断后没有正确返回错误 #7908

Open

Description

@polarvid

问题

mtuex 在 suspend 之前没有重置 thread->error 导致被信号中断后没有正确得到反馈。

ret = _ipc_list_suspend(&(mutex->parent.suspend_thread),

参考 sem 的方式如下:

rt-thread/src/ipc.c

Lines 548 to 554 in a6fcb4f

/* reset thread error number */
thread->error = -RT_EINTR;
LOG_D("sem take: suspend thread - %s", thread->parent.name);
/* suspend thread */
ret = _ipc_list_suspend(&(sem->parent.suspend_thread),

Activity

  1. polarvid commented on Aug 3, 2023

    @polarvid
    ContributorAuthor

    另外,这种判断方式感觉不是很好。会导致线程状态误判,参考 #6768 。

    也许改成 rt_wake_up 中去设置错误码会好些。

  2. Yunlk commented on Oct 24, 2026

    @Yunlk
    Contributor

    在 QEMU 上按 issue 描述的场景测了一遍,当前 master 上这个现象已经不复现:被信号打断后 rt_mutex_take_interruptible() 确实返回了 -RT_EINTR。先把过程和数据放上来,请确认一下是不是我理解错了你指的那条路径。

    复现环境

    • bsp/qemu-vexpress-a9,QEMU 11.0.0,arm-none-eabi-gcc 15.2.1,master 1c801efa
    • 需要注意:这个 BSP 默认没有开 RT_USING_SIGNALS,要先打开才能测信号(CONFIG_RT_USING_SIGNALS=y)

    场景

    1. 线程 holder(priority 15)先 rt_mutex_take() 拿到互斥量,然后睡 3 秒
    2. 线程 waiter(priority 16)安装 SIGUSR1 处理函数并 unmask,随后调用 rt_mutex_take_interruptible(mtx, RT_WAITING_FOREVER) 阻塞
    3. 主线程在 waiter 阻塞后调用 rt_thread_kill(waiter, SIGUSR1)

    实测输出

    [M7908] holder: mutex taken, sleeping 3s
    [M7908] waiter: calling rt_mutex_take_interruptible(FOREVER)
    [M7908] main: sending SIGUSR1 to waiter (should interrupt the take)
    [M7908] waiter: returned -4 -> -RT_EINTR (correct)
    [M7908] handler_called=1 waiter_done=1 take_ret=-4
    [M7908] VERDICT: returns -RT_EINTR (ok)
    

    -4 就是 -RT_EINTR,且信号处理函数被调用了一次,waiter 没有卡死。

    为什么已经正确了

    当前 master 用的不是你说的"suspend 前预置 RT_EINTR",而是在恢复之后兜底转换——src/ipc.c 的 _rt_mutex_take() 里:

    /* :1352 suspend 前仍然是置 EOK */
    thread->error = RT_EOK;
    ...
    rt_schedule();
    rt_spin_lock(&(mutex->spinlock));
    
    if (mutex->owner == thread)
    {
        RT_ASSERT(thread->error == RT_EOK);        /* :1463 正常拿到 */
    }
    else
    {
        ret = thread->error;                        /* :1473 */
        /* unexpected resume */
        if (ret == RT_EOK)                          /* :1476-1479 兜底 */
        {
            ret = -RT_EINTR;
        }
        ...

    也就是说:被信号唤醒、但没有真正拿到互斥量时,thread->error 仍是 RT_EOK,于是 :1476 把它转成 -RT_EINTR 返回给调用者。行为上等价于你建议的效果,只是实现位置不同。

    对比一下现在三类 IPC 的处理方式确实不一致:

    对象 做法
    sem / mq suspend 前直接 thread->error = -RT_EINTR(或 RT_EINTR,sem 那处是正值)
    mutex suspend 前置 RT_EOK,恢复后按 owner != thread && error == EOK 推断"意外唤醒"

    两点想确认

    1. 你指的是 rt_mutex_take_interruptible() 还是普通的 rt_mutex_take()? 我只测了前者——普通版用 RT_UNINTERRUPTIBLE 挂起,信号本来就不该打断它,所以那种情况不存在"返回什么错误"的问题。如果你当时测的是普通版,那可能现象不同,请说明。
    2. polarvid 提到的状态误判(ps命令查询线程状态,由于rt_thread_sleep中临时改变状态会导致ps出的状态与实际状态不一致,影响判断 #6768)是否还在? 现在这个兜底是"用 error == RT_EOK 反推被信号打断",本质是个启发式判断。如果某条路径上线程被唤醒、error 恰好是 EOK、又确实没拿到锁,就会被记成 -EINTR;反之若别的原因把 error 置成了非 EOK,返回值也会跟着变。这个隐患没有因为我上面的测试被排除——测试只证明了"信号打断这一条路径"结果是正确的。

    建议

    • 如果确认现象已在后续提交中修掉,这个 issue 可以关了(依据是上面的实测 + ipc.c:1476-1479 的兜底逻辑)。
    • 如果 polarvid 说的状态误判仍要处理,那是一个独立问题,范围比"补一行置 EINTR"要大,建议另开一条,明确"谁在什么时机设置错误码"的约定。

    复现代码是临时 app,没有提交。需要的话我可以整理成 src/utest/ 下的用例——不过它依赖 RT_USING_SIGNALS,进 CI 得确认那几个 BSP 的配置支持。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions