Multithreading 跟踪内核中神秘的高优先级线程挂起

Multithreading 跟踪内核中神秘的高优先级线程挂起,multithreading,linux-kernel,embedded-linux,multicore,low-latency,Multithreading,Linux Kernel,Embedded Linux,Multicore,Low Latency,说明 我正在开发一个嵌入式Linux系统(使用内核3.4和仿生的、类似Android的),它运行在多核ARMv7a SoC上。我们有一个用户空间线程,它基本上是为来自内核的事件提供服务。事件是从IRQ生成的,必须由用户空间以非常低的延迟来响应 线程以SCHED_FIFO优先级0运行。它是系统中唯一的优先级为0的线程。 螺纹的近似代码: while (1) { struct pollfd fds[1]; fds[0].fd = fd;

说明

我正在开发一个嵌入式Linux系统(使用内核3.4和仿生的、类似Android的),它运行在多核ARMv7a SoC上。我们有一个用户空间线程,它基本上是为来自内核的事件提供服务。事件是从IRQ生成的,必须由用户空间以非常低的延迟来响应

线程以SCHED_FIFO优先级0运行。它是系统中唯一的优先级为0的线程。 螺纹的近似代码:

    while (1)
    {
        struct pollfd fds[1];
        fds[0].fd = fd;
        fds[0].events = POLLIN|POLLRDNORM|POLLPRI;

        int ret = poll(fds, 1, reallyLongTimeout);
        FTRACE("poll() exit");
        if (ret > 0)
        {
            // notify worker threads of pending events
        }
    }
通常,我们会获得很好的延迟(线程在IRQ发生的同一毫秒内返回poll()),但是,我们会随机地延迟到几十毫秒,这会破坏一切。在到处跟踪之后,我得出结论,延迟发生在IRQ触发之后,poll()系统调用返回之前,因为线程使自己处于休眠状态。然后过了一段时间,不知何故被某种未知的力量唤醒,一切都在继续

我怀疑有其他IRQ,但在启用sched:,IRQ:,timer:*跟踪后,我不得不排除它。我在将系统调用:*跟踪程序移植到ARM内核时遇到了一些困难。syscalls跟踪器可以工作,但如果我也启用sched:*我会在ring\u缓冲区代码中遇到各种恐慌

在向sys\u poll()中插入一些自定义跟踪点之后,我得出了一个令人不安的结论:在sys\u poll()返回之后,但在它重新出现在用户空间之前,我的线程就睡着了

下面是一个带注释的跟踪,其中包含我在fs/select.c中的自定义跟踪点:

 <my thread>-915   [001] ...1    17.589394: custom: do_poll:786 - calling do_pollfd
 <my thread>-915   [001] ...1    17.589399: custom: do_poll:794 - failed, no events
 <my thread>-915   [001] ...1    17.589402: custom: do_poll:823 - going to sleep, count = 0, timed_out = 0

.... // everything going OK, then the IRQ happens, which runs a tasklet:

 <random proc>-834 [000] d.h1    17.616541: irq_handler_entry: irq=17 name=hwblock handler=hw_block_process_irq
 <random proc>-834 [000] d.h1    17.616569: softirq_raise: vec=6 [action=TASKLET]
 <random proc>-834 [000] d.h1    17.616570: irq_handler_exit: irq=17 ret=handled
 <random proc>-834 [000] ..s2    17.616627: softirq_entry: vec=6 [action=TASKLET]

.... // the tasklet signals the wait queue of the poll, which wakes up my thread:

 <my thread>-915   [001] ...1    17.616827: custom: do_poll:826 - woke up, count = 0, timed_out = 0
 <my thread>-915   [001] ...1    17.616833: custom: do_poll:772 - start of loop
 <my thread>-915   [001] ...1    17.616840: custom: do_poll:786 - calling do_pollfd
 <my thread>-915   [001] ...1    17.616852: custom: do_poll:788 - success, event!
 <my thread>-915   [001] ...1    17.616859: custom: do_poll:810 - bailing, count = 1, timed_out = 0
 <my thread>-915   [001] ...1    17.616862: custom: do_sys_poll:880 - before free_wait()
 <my thread>-915   [001] ...1    17.616867: custom: do_sys_poll:882 - before __put_user()
 <my thread>-915   [001] ...1    17.616872: custom: sys_poll:940 - do_sys_poll - exit

.... // the tasklet exits, and my thread appears to be about to be

 <random proc>-834 [000] .Ns2    17.616922: softirq_exit: vec=6 [action=TASKLET]


.... // wait wait, why is my thread going back to sleep, and what was it doing for 75us?

 <my thread>-915   [001] d..3    17.616947: sched_stat_wait: comm=<another thread> pid=1165 delay=1010000 [ns]
 <my thread>-915   [001] ...2    17.616957: sched_switch: prev_comm=<my thread> prev_pid=915 prev_prio=0 prev_state=S ==> next_comm=<another thread> next_pid=1165 next_prio=120

.... // everything running on for 20ms as if nothing is wrong, then my thread suddenly gets woken up.
.... // nothing pid 947 is doing should have any effect on <my thread>

<random proc>-947  [000] d..4    17.636087: sched_wakeup: comm=<my thread> pid=915 prio=0 success=1 target_cpu=001
<random proc>-1208 [001] ...2    17.636212: sched_switch: prev_comm=<rancom proc> prev_pid=1208 prev_prio=120 prev_state=R ==> next_comm=<my thread> next_pid=915 next_prio=0
<my thread>-915    [001] ...1    17.636713: tracing_mark_write: poll() exit
它通过强制目标CPU运行调度程序来解决问题。然而,我怀疑这不是它应该如何工作,即使这是一个真正的错误,那么可能有一个更完善的修复。我检查了最新的内核(3.14),core.c中的代码看起来基本相同


你知道为什么吗?如果
cpu\u share\u cache()
返回true,它为什么不调用
ttwu\u queue\u remote()
?那么,如果它们共享缓存呢?我可以看出这与迁移决策有多大关系,但唤醒是本地完成的还是远程完成的?也许我们的
cpu\u共享\u缓存()
应该返回false?该函数似乎没有很好的文档记录(或者我没有找到正确的位置)

只是一个猜测,因为还没有任何答案。。 你说这个系统是多核的。您是否为用户线程分配了在中断发生的同一内核上运行的亲和性?中断是否只发生在特定的内核上?我怀疑有一种情况,当一个用户线程在一个内核上运行,但中断发生在另一个内核上,并且无法立即恢复(还没有休眠?)。数据竞争可能会让它进入休眠状态,例如,就在中断处理程序发布线程轮询的某些数据之前。因此,在下一次系统中断(例如定时器)之前,它将被暂停

因此,尝试将中断和线程分配给同一个内核,以便序列化它们并避免潜在的数据争用

响应更新#1

看来我对核心之间的数据竞争的看法是正确的,因为在目标核心上提高IRQ解决了这个问题。我猜它不在内核代码中,因为过多的重新调度IRQ,所以额外的调度开销只是为了非常罕见的情况,或者只是因为使用通常的同步(假设共享缓存)可以更快地完成

这看起来是正确的方向,但显然它遗漏了一些东西。我会尝试在不同的体系结构/版本上运行一个复制程序,以了解它是一个通用的bug,还是只针对您的平台/内核版本。我希望这不是cpu加载/存储上缺少的围栏

无论如何,回到您的具体问题,如果您不能或不想在热修复程序中使用自定义内核构建,我对线程关联的建议仍然是实际有效的


此外,如果您无法将中断固定到一个特定的内核,您可能希望在每个内核上运行这样一个轮询线程(也显式固定到它),以确保至少一个线程将在IRQ之后立即获得事件。当然,这会给用户线程代码带来额外的同步负担。

我们最后进行了以下修复:

  • smp_发送_重新调度(任务_cpu(p))在调度程序中允许跨CPU预防。我会和一个维护人员联系,看看它是否正确

  • 为我们的平台实现
    get\u user\u pages\u fast()
    ,该平台不必锁定
    mmap\u sem
    。这消除了
    mmap/munmap
    futex\u wait

  • 在用户空间代码中不需要使用
    fork()
    的几个地方切换到
    vfork()+execve()
    。这消除了
    mmap/munmap
    和产生其他进程的调用之间的争用

现在看来一切都很顺利


感谢您的帮助。

您是否有任何自定义设备驱动程序可能会影响进程的调度结构?Yuriy Romanenko,您当前的
uname-a
是什么?如果需要RT任务,是否测试了linux内核()的RT(实时)版本?它可能会修复这个bug…看起来您至少部分地了解了这里的某些内容。请参阅我的编辑。@YuriyRomanenko,谢谢,我在这里没有太多要添加的内容(尽管已更新),仍然建议如果您不想破解内核,请尝试设置线程亲和力。我怀疑这是一个围栏/屏障问题。任务不在\u cpu或\u rq上。我所看到的最病态的情况是CPU0在CPU1上执行RT任务的sched_唤醒,而CPU1处于空闲状态且处于WFI中。然后什么也没发生。CPU1在100毫秒内没有运行该任务,它这样做的唯一原因是因为某些硬件IRQ将其唤醒,并且在退出的过程中,它变为bac
#ifdef CONFIG_SMP
      if (task_cpu(p) != smp_processor_id())
          smp_send_reschedule(task_cpu(p));
#endif