LinuxCon Europe 2013 - О поиске "гонок" в ядре Linux
22 октября состоялось выступление нашего сотрудника Евгения Шатохина на конференции LinuxCon Europe 2013. Речь шла о средствах поиска таких трудноуловимых ошибок, как «состояния гонки», они же «data races» в компонентах ядра Linux (слайды, пояснения к слайдам).
Такие ситуации далеко не всегда просто выявить, а последствия у них могут быть самыми разными, от незначительных до критических. Для ядра Linux это особенно актуально: код драйверов, например, может выполняться многими потоками одновременно. Добавим к этому обработку прерываний и других асинхронных событий, а также учтём, что правила синхронизации доступа к общим данным из разных потоков далеко не всегда описаны чётко (а нередко — не описаны вообще)…
Об инструментах, позволяющих выявлять data races в компонентах ядра Linux, в основном, и шла речь в выступлении. Наиболее подробно — о системах KernelStrider и RaceHound, одним из основных разработчиков которых Евгений и является.
KernelStrider собирает информацию об анализируемом компоненте ядра (например, драйвере) в процессе работы этого компонента. Информация об обращениях к памяти, выделении и освобождении памяти, блокировках и т. д. затем, уже в user space, анализируется инструментом ThreadSanitizer (Google). Алгоритм поиска races коротко описан тут.
KernelStrider может в некоторых случаях выдавать сообщения о data races там, где data races нет («false positives»). Например, при анализе сетевых драйверов такое было в случаях, когда драйвер отключал генерацию прерываний соотв. устройством и затем обращался к каким-то общим данным, не опасаясь конфликтов с функцией-обработчиком прерываний.
Система RaceHound позволяет проверить результаты, полученные с помощью KernelStrider, найти среди них те, которые, действительно говорят о data races. RaceHound работает так:
- На инструкцию в коде ядра, которая может быть «замешана» в data race, ставится программная точка прерывания («software breakpoint»).
- Когда эта точка прерывания срабатывает, RaceHound определяет адрес области памяти, куда эта инструкция обратится, и ставит аппаратную точку прерывания («hardware breakpoint»), чтобы отследить обращения нужного вида к этой области (только запись или произвольные обращения).
- Делается небольшая задержка перед выполнением интересующей инструкции.
- Если во время задержки какой-то другой поток обратится к указанной области памяти, аппаратная точка прерывания сработает и RaceHound сообщит о найденной data race.
То есть при анализе компонентов ядра KernelStrider работает своего рода «детективом-аналитиком», сужая круг «подозреваемых» — мест в коде, которые могут участвовать в data races. RaceHound тогда — система слежки за этими подозреваемыми. Если ей удаётся поймать подозреваемого с поличным (то есть при выполнении конфликтующих доступов к памяти) — всё ясно. Если не удаётся — это ничего не значит.
Как во время, так и после доклада, вопросов было довольно много. Слушателей интересовали, например, такие вещи, как:
- Планируется ли поддержка ARM (пока всё работает только на x86), — возможно, но не в ближайшем будущем.
- Может ли KernelStrider пропускать data races (то есть возможны ли false negatives) — да, может в некоторых случаях, в основном, из-за особенностей алгоритма работы ThreadSanitizer и из-за неточностей используемых правил определения порядка событий.
- Переживает ли KernelStrider suspend/resume — да, переживает.
- Можно ли с помощью KernelStrider и RaceHound анализировать не только модули ядра, но и само ядро — пока нет.
- Можно ли в KernelStrider инструментировать код анализируемого драйвера не при загрузке этого драйвера, как сейчас, а при компиляции — очень вероятно; это одно из возможных направлений развития.
- и т. д.
В обсуждении data races, найденных указанными выше инструментами, активно участвовали сотрудники Intel, что немудрено: речь шла о races в сетевом драйвере e1000, разработанном как раз в этой компании. Во время обсуждения выяснился интересный факт: для обращений к памяти в некоторых частях сетевых драйверов средства синхронизации использовать почему-то не принято, хотя там из-за этого могут быть «гонки» (они там и обнаружились). Это, в частности, относится к NAPI и функциям драйвера, участвующим в передаче данных по сети. Вроде бы всё так делается, чтобы избежать потерь производительности из-за блокировок, но ни оценок этих потерь, ни конкретных рекомендаций, как при этом избежать проблем из-за «гонок», пока найти не удалось.
В целом, похоже, что отношение к data races у многих разработчиков ядра такое:
— Что-то из-за этой data race упало или стало работать неправильно?
— Пока не замечали.
— А, ну, ладно.
И на этом разговор кончается.
Логично? Вроде бы да, но, если вспомнить, например, вот эту статью, всё уже не так очевидно.
[ Хронологический вид ]Комментарии
Понятно что в сетевых никто и не будет сделать за этим - если гонки заложены в основу сетевых алгоритмов и лечатся банальным реконнектом, то смысл?
Речь идёт об обращениях к внутренним структурам драйвера, а тут всё не так очевидно. Например, в одном из случаев, о котором я писал интеловцам, были одновременные обращений к переменной, в которой хранится текущее количество пакетов в очереди передачи (Tx queue). Один thread читал эту переменную, другой в то же время увеличивал её значение на 1. Может, это и не приведёт в данном случае к неприятностям, но, как минимум, тут надо разобраться.
Вот описание этой "гонки", если интересно: http://sourceforge.net/mailarchive/message.php?msg_id=31452821 (самое первое в списке).
Войдите, чтобы комментировать.