Как определить из вывода strace, какая часть моей программы не может получить мьютекс?

Я работаю над встроенной системой Linux (3.12.something), и наше приложение через какое-то случайное время начинает перегружать ЦП. Я запустил strace в нашем приложении, и сразу же, когда возникает проблема, я вижу много строк, подобных этой, в выводе strace:

[48530666] futex(0x485f78b8, FUTEX_WAIT_PRIVATE, 2, NULL) = -1 EAGAIN (Resource temporarily unavailable) <0.009002>

Я почти уверен, что это именно тот дымящийся пистолет, который я ищу, и есть своего рода гонка. Однако теперь мне нужно выяснить, как определить место в коде, которое пытается получить этот мьютекс. Как я могу это сделать? Наш код скомпилирован с помощью GCC и содержит отладочные символы.

Моя текущая мысль (которую я еще не пробовал) состоит в том, чтобы распечатать строку на стандартный вывод и сбросить ее, прежде чем пытаться захватить любой мьютекс в нашей системе, с ожиданием, что строка будет напечатана прямо перед тем, как strace пожалуется на получение блокировки. ., но в коде есть МНОГО мест, которые нужно было бы инструментировать таким образом.

РЕДАКТИРОВАТЬ: Еще одна странная вещь, которую я только что понял, заключается в том, что наша программа не начинает перегружать ЦП, пока не пройдет некоторое случайное время с момента ее запуска (от 5 минут до 5 часов и где-то между ними). За это время происходит ноль futex системных вызовов. Почему они вдруг начинаются? Из того, что я прочитал, я думаю, что, возможно, они правильно используются в пользовательском пространстве, пока что-то не выйдет из строя и не вернется к системному вызову futex()...

Какие-либо предложения?


person Steve    schedule 23.09.2015    source источник
comment
вместо этого вы можете попробовать работать с gdb. У strace также есть переключатель потокового вывода, чтобы вывести вывод strace, разбитый по pid.   -  person Matt Joyce    schedule 23.09.2015
comment
Как бы я использовал gdb, чтобы найти это? Может ли он каким-то образом обнаружить это состояние и автоматически сломаться? (Я не хочу часами смотреть, чтобы проблема не началась...)   -  person Steve    schedule 23.09.2015
comment
Хорошо, похоже, что catch syscall 240 будет работать для этого в gdb. Я попробую.   -  person Steve    schedule 23.09.2015
comment
The feature 'catch syscall' is not supported on this architecture yet. Дох!   -  person Steve    schedule 23.09.2015
comment
Вопрос о том, как найти код, которому не удается получить мьютекс, приводит меня к ветке GotW Херба Саттера, посвященной тому, почему эта парадигма способствует неустойчивому коду. Если код приложения сначала требуется для получения блокировки, затем доступа к данным, а затем освобождения... не ожидайте, что код приложения будет делать это неоднократно. Предоставьте (т.е. замените) объект, который упаковывает защищаемые данные, так что выбора нет. Выполнение этого сейчас приведет к редактированию по жалобе компилятора, но в конечном итоге вы получите два преимущества. Во-первых, код приложения не может пропустить получение мьютекса, и у вас есть код, который вы можете перехватить с помощью отладчика для всех вхождений.   -  person JVene    schedule 24.09.2015


Ответы (2)


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

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

Что касается вызовов futex(), идея состоит в том, чтобы опросить атомарный флаг и после некоторых поворотов использовать фактический мьютекс ОС. Флаг atomic доступен без дорогостоящего переключения между пространством пользователя и пространством ядра. Для более длительных перерывов использование вытеснения ядра (с futex()) позволяет избежать блокировки ЦП опросом. Это объясняет, почему программе не нужны вызовы futex() при нормальной работе.

person Ulrich Eckhardt    schedule 23.09.2015

Вам, в основном, нужно сгенерировать основной файл в этот момент.

Затем вы можете загрузить программу + ядро ​​​​в GDB и посмотреть на нее.

man gcore

or

generate-core-file

В течение этого времени не происходит ни одного системного вызова фьютекса. Почему они вдруг начинаются?

Это связано с тем, что безальтернативный мьютекс, реализованный через фьютекс, не выполняет системный вызов, а только атомарный инкремент, чисто в пользовательском пространстве. Только блокировка CONTESTED видна как системный вызов

person Severin Pappadeux    schedule 23.09.2015