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

时间:2014-05-29 07:12:27

标签: multithreading linux-kernel embedded-linux multicore low-latency

描述

我正在使用在多核ARMv7a SoC上运行的嵌入式Linux系统(使用内核3.4和仿生,类似Android)。我们有一个用户空间线程,它基本上处理来自内核的事件。事件是从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:* tracing我必须排除它。我在移植系统调用时遇到了一些困难:*跟踪器到ARM内核。系统调用跟踪器工作,但如果我也启用sched:*我在ring_buffer代码中得到各种各样的恐慌。

在sys_poll()中插入一些自定义跟踪点之后,我发现sys_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

所以某个地方我的线程变成了TASK_INTERRUPTIBLE,然后自愿走进调度程序,然后......醒来后显然没有理由 20ms

这种情况的发生似乎至少在一定程度上依赖于时间,并且观察它的各种尝试通常会使其更难复制。

问题

  1. 任何想法导致了什么?
  2. 有关找出我的线程在哪里睡着的简单方法的任何建议吗?
  3. 有关找出线程唤醒原因的简单方法的任何建议吗?
  4. 我已经考虑过以某种方式调整unwind_backtrace()来生成一个字符串,我可以填入每个trace_sched_switch()调用,但这看起来有点令人生畏。沿着同样的路线做什么更简单?
  5. 任何想法为什么跟踪系统调用:*和sched:*会使它在环形缓冲区代码中的未处理页面错误中爆炸,需要移动尾部?它似乎是取消引用用户空间指针(基于数字相似性),但每次都是不同的。
  6. 我已尝试并检查的内容

    1. 这不是正常的IRQ,运行时间太长或者是禁用中断的东西。跟踪irq:*表明。它可能是某种TrustZone NMI但不知何故我对此表示怀疑。

    2. 它不应该是任何类型的RT限制/时间限制,因为:

      a)sched_rt_runtime_us = 10000和sched_rt_period_us = 10000

      b)线程具有相当低的占空比(<每秒30ms,每秒60-80次事件)

    3. 它可能不是从用户空间跟踪或写入/sys/kernel/debug/tracing/trace_marker的工件 - 它在没有该宏的情况下发生并且禁用了跟踪(甚至是从内核编译出来的)。此外,与trace.c和ring_buffer.c中的代码相关的代码似乎大部分都是无锁的。

    4. 没有优先级为0的其他内容,而且它没有被抢占,而是似乎愿意不安排自己。

    5. 我在syscall_trace()的顶部放了一个恐慌(),以确保我在sys_poll()的路上不会意外地落入其中一条跟踪/审核路径。它没有发射,所以不是这样。

    6. 非常感谢您提前

      更新#1

      我放弃了找到一些简单的东西,并实现了一个unwind_backtrace_to_str()函数,让我可以使用回溯信息检测各种跟踪点。在对trace_sched_switch()和trace_sched_wake()添加回溯后,我设法隔离了几个延迟原因,主要有两个:

      • 优先级倒置由于mm-&gt; mmap_sem由同一进程中的某个其他线程执行fork() / mmap() / {{1}因此,在munmap()futex_wait()期间,RT线程无法使用。通过重构一些代码并在某些地方使用vfork()而不是fork(),可以避免这个问题。

      • 从与运行所需的CPU不同的源CPU调用tracing_mark_write()时无法运行计划任务。这似乎是一个更大的问题。我通过调度程序对其进行了跟踪,看起来在sched_wake()调用wake_up_process()的情况下调用try_to_wake_up(),这会调用ttwu_queue(),这就是事情变得有趣的地方。

      ttwu_queue()内,我们不会输入&#39; if&#39;因为cpus_share_cache()总是为我们的任何核心返回true(听起来是正确的,共享L2)。这意味着它只需为任务调用ttwu_do_activate()并退出。 ttwu_do_activate()似乎只将任务放在正确的运行队列上,并将其标记为TASK_RUNNING,但没有任何SMP处理。

      我在p->state = TASK_RUNNING;

      中的ttwu_do_wakeup()之后添加了以下内容
      #ifdef CONFIG_SMP
            if (task_cpu(p) != smp_processor_id())
                smp_send_reschedule(task_cpu(p));
      #endif
      

      它通过强制目标CPU运行调度程序来解决问题。但是,我怀疑这不是它应该如何工作,即使这是一个真正的错误,那么可能还有一个更精确的解决方案。我检查了最新的内核(3.14),core.c中的代码看起来几乎一样。

      为什么会这样?如果ttwu_queue_remote()返回true,为什么不调用cpus_share_cache()?那么如果他们共享缓存会怎么样 - 我可以看到这与迁移决策有什么关系,但唤醒是在本地还是远程完成?也许我们的cpus_share_cache()应该返回false?这个功能似乎没有得到很好的记录(或者我没有找到合适的位置)

2 个答案:

答案 0 :(得分:9)

因为没有任何答案,只是一个疯狂的猜测.. 你说系统是多核的。您是否为用户线程分配了关联,以便在发生中断的同一核心上运行?并且中断仅发生在特定核心上吗?我怀疑用户线程在一个核心上运行但是中断发生在另一个核心并且不能立即恢复到这里(没有睡觉了?)的情况。可能数据竞争允许它入睡,例如就在中断处理程序发布一些线程轮询的数据之前。因此,它被暂停直到下一个系统中断(例如计时器)。

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

以响应更新#1

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

there is some synchronization看起来像是正确的方向,但显然它错过了某些东西。我尝试在不同的架构/版本上运行一个复制器,以了解它是一般错误还是仅适用于您的平台/内核版本。我希望p->on_cpu加载/存储...

不是缺少围栏

无论如何,回到你的具体问题,如果你不能或不想使用热修复的自定义内核版本,我的线程亲和力建议仍然是实际有效的。

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

答案 1 :(得分:7)

我们结束了以下修复:

    调度程序中上面提到的
  • smp_send_reschedule(task_cpu(p));允许跨CPU预防。我将跟进维护人员,看看它是否是正确的解决方案。

  • 为我们的平台实施get_user_pages_fast(),如果不需要,则锁定mmap_sem。这消除了mmap/munmapfutex_wait

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

现在好像一切都在顺利进行。

感谢您的帮助。