Подтвердить что ты не робот

Отслеживание таинственной высокоприоритетной темы приостановлено внутри ядра

Описание

Я работаю над встроенной системой Linux (используя ядро ​​3.4 и bionic, похожее на 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), однако случайным образом у нас есть задержки в десятки миллисекунд, которые разрушают все. Проследив все это, я пришел к выводу, что задержка происходит после срабатывания IRQ и до того, как системный вызов poll() вернется, потому что поток ставит себя в спящий режим. Затем некоторое время после чего-то пробуждается какой-то неизвестной силой, и все продолжается.

Я подозревал какое-то другое IRQ, но после включения графика:, irq:, timer: * tracing Я должен был это исключить. Мне было трудно переносить syscalls: * трассировщики в ядро ​​ARM. Трассировщики syscalls работают, но если я также включу расписание: * Я получаю всевозможные паники внутри кода 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, а затем добровольно идет в планировщик, а затем... просыпается, по-видимому, без причины 20 мс позже.

Возникновение этого кажется по меньшей мере несколько зависящим от времени, и различные попытки его наблюдать часто усложняют его воспроизведение.

Вопросы

  • Любые идеи, которые вызывают это?
  • Любые предложения по легким способам узнать, где моя нить засыпает?
  • Любые предложения по простым способам узнать, почему мой поток просыпается?
  • Я решил каким-то образом адаптировать unwind_backtrace() для создания одной строки, которую я могу использовать в каждом вызове trace_sched_switch(), но это кажется немного сложным. Что-нибудь проще по тем же линиям?
  • Любая идея, почему трассировка syscalls: * и sched: * заставляет его взорваться с необработанной ошибкой страницы внутри кода кольцевого буфера, где нужно переместить хвост? Кажется, что он разыменовывает указатель пространства пользователя (основанный на сходстве чисел), но каждый раз каждый раз.

Что я уже пробовал и проверил

  • Это не нормальный IRQ, который работает слишком долго или что-то с отключенными прерываниями. Трассировка с irq: * показывает это. Это может быть какой-то NMI TrustZone, но почему-то я сомневаюсь в этом.

  • Это не должно быть дросселирование/временное изменение RT, потому что:

    a) sched_rt_runtime_us = 10000 и sched_rt_period_us = 10000

    b) Поток имеет довольно низкий рабочий цикл (< 30ms в секунду, при 60-80 событий в секунду).

  • Вероятно, это не артефакт трассировки или записи в /sys/kernel/debug/tracing/trace_marker из пользовательского пространства - это происходит без этого макроса и с отключенной трассировкой (даже скомпилированной из ядра). Также код, связанный с кодом в trace.c и ring_buffer.c, как правило, не блокируется.

  • Нет ничего другого с приоритетом 0, и он не получает предварительный упущенный, а скорее, похоже, охотно расчитывает сам.

  • Я помещаю панику() вверху syscall_trace(), чтобы убедиться, что я случайно не попадаю в один из путей трассировки/аудита по пути из sys_poll(). Он не стрелял, так что не он.

Спасибо вам заблаговременно

Обновление # 1

Я отказался от поиска чего-то легкого и реализовал функцию unwind_backtrace_to_str(), которая позволяет мне обрабатывать различные точки трассировки с информацией обратной линии. После добавления обратных трасс в trace_sched_switch() и trace_sched_wake() мне удалось выделить несколько причин задержки, причем основными двумя были:

  • Приоритетная инверсия из-за mm- > mmap_sem, взятая какой-либо другой нитью в том же процессе, что делает fork()/mmap()/munmap() и, следовательно, недоступна во время futex_wait() или tracing_mark_write() для потока RT. Этого можно избежать в большинстве случаев путем реструктуризации кода и использования vfork() вместо fork() в некоторых местах.

  • Невозможность запуска запланированной задачикогда sched_wake() вызывается из другого исходного процессора, чем процессор, которому он должен работать. Это, кажется, большая проблема. Я проследил его через планировщик, и кажется, что в плохом случае wake_up_process() вызывает try_to_wake_up(), который завершает вызов ttwu_queue(), в котором вещи становятся интересными.

Внутри ttwu_queue() мы не вводим 'if', потому что 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

и он исправляет проблему, заставляя целевой процессор запускать планировщик. Тем не менее, я подозреваю, что это не то, как он должен работать, и даже если это реальная ошибка, то, вероятно, более четкое исправление. Я проверил последнее ядро ​​(3.14), а код в core.c выглядит примерно так же.

Любые идеи, почему это так? Почему он не вызывает ttwu_queue_remote(), если cpus_share_cache() возвращает true? Итак, что, если они разделяют кеш - я мог видеть, насколько это релевантно для решения о миграции, но независимо от того, выполняется ли пробуждение локально или удаленно? Может быть, наш cpus_share_cache() должен возвращать false? Эта функция не кажется хорошо документированной (или я не ищу в нужных местах)

4b9b3361

Ответ 1

Просто дикая догадка, так как пока нет ответа. Вы говорите, что система многоядерна. Вы назначаете аффинность для пользовательского потока для работы на том же ядре, где происходит прерывание? И происходит ли прерывание только на конкретном ядре? Я подозреваю, что ситуация, когда пользовательский поток работает на одном ядре, но прерывание происходит на другом, и не может возобновиться здесь немедленно (пока не спать?). Вероятно, гонка данных позволяет ей засыпать, например. как раз перед обработчиком прерываний публикуются некоторые данные, которые опроса потоков. Таким образом, он приостанавливается до следующего прерывания системы (например, таймера).

Итак, попробуйте назначить прерывание и поток одному ядру, чтобы сериализовать их и избежать потенциальных расов данных.

в ответ на обновление # 1

Похоже, я был прав относительно гонки данных между ядрами, так как повышение IRQ на целевом ядре устраняет проблему. Я думаю, что это не в коде ядра из-за чрезмерного переназначения IRQ и, следовательно, дополнительных накладных расходов на планирование только для очень редких случаев или просто потому, что это можно сделать быстрее, используя обычную синхронизацию, предполагающую общий кэш.

И есть некоторая синхронизация, которая выглядит как правильное направление, но, по-видимому, она что-то пропускает. Я попробую запустить репродуцирование в другой архитектуре/версиях, чтобы понять, является ли это общей ошибкой или просто специфична для ваших версий платформы/ядра. Я надеюсь, что это не недостающий забор на загрузке/хранении p->on_cpu.

В любом случае, возвращаясь к вашей конкретной проблеме, если вы не можете или не хотите использовать настраиваемую сборку ядра с помощью исправления, мое предложение с привязкой к потоку остается актуальным и действительным.

Кроме того, если вы не можете привязать прерывание к одному конкретному ядру, вы можете запустить такой поток опроса на каждом ядре (также явно привязанный к нему), чтобы гарантировать, что хотя бы один из потоков получит событие сразу после IRQ. Конечно, это приводит к дополнительной нагрузке синхронизации на код пользовательского потока.

Ответ 2

Мы закончили со следующими исправлениями:

  • smp_send_reschedule(task_cpu(p));, упомянутый выше в планировщике, чтобы обеспечить защиту от нескольких процессоров. Я буду следить за сопровождающим, чтобы узнать, правильно ли оно исправлено.

  • Реализация get_user_pages_fast() для нашей платформы, которая не блокирует mmap_sem, если этого не требуется. Этот удаленный конфликт между mmap/munmap и futex_wait

  • Переключение на vfork() + execve() в нескольких местах в коде пользовательского пространства, где fork() не нужно. Этот удаленный конфликт между mmap/munmap и вызовами, которые порождают другие процессы.

Кажется, что все работает гладко.

Благодарим вас за вашу помощь.