Описание
Я работаю над встроенной системой 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? Эта функция не кажется хорошо документированной (или я не ищу в нужных местах)