Итак, готовы ли вы начать охоту за ошибками? Этот путь может оказаться длинным и полным разочарований. Некоторые ошибки ставили в тупик все сообщество разработчиков ядра на несколько месяцев. К счастью, на каждую из таких злостных ошибок находятся простые, которые легко исправить. Если вам повезет, то все проблемы, с которыми вы столкнетесь, будут простыми и тривиальными. Однако чтобы это проверить, необходимо начать исследования. Для этого понадобится следующее.
• Сама проблема. Может звучать глупо, но дефект должен быть конкретным и хорошо определенным. Очень помогает, если его хотя бы кто-нибудь может устойчиво воспроизвести. Однако, к сожалению, дефекты обычно ведут себя не так хорошо, как хотелось бы, и не всегда могут быть хорошо определены.
• Версия ядра, в которой существует дефект (обычно это последняя версия, хотя кто может это гарантировать?). Еще лучше, если известна версия ядра, в которой проблема впервые появилась. Мы рассмотрим, как это установить, если нет такой информации.
• Немного удачи, опыта и их комбинации.
Если дефект нельзя воспроизвести, то многие из приведенных ниже подходов становятся бесполезными. Очень важно, чтобы проблему можно было повторить. Если этого не удается сделать, то исправление дефекта становится возможным только путем визуального анализа кода для того, чтобы найти в нем ошибку. На самом деле так случается достаточно часто (например, с разработчиками ядра), но очевидно, что шансы добиться успеха становятся более весомыми, если появляется возможность воспроизвести проблему.
Может также показаться странным, что существуют дефекты, которые кто-то не может воспроизвести. Дело в том, что в пользовательских программах дефекты чаще всего проявляются очень просто, например вызов функции foo
приводит к созданию файла core
. В ядре все совсем по-другому. Взаимодействия между ядром, пространством пользователя и аппаратурой могут быть достаточно тонкими. Состояния конкуренции за ресурсы могут возникать с вероятностью одно на миллион итераций алгоритма. Плохо спроектированный или даже не правильно скомпилированный код может обеспечивать удовлетворительную производительность на одной системе, но неудовлетворительную на другой, Очень часто происходит так, что на какой-то случайной машине, при очень специфическом характере загрузке, начинают проявляться дефекты, которые больше нигде не проявляются. Чем больше доступно дополнительной информации при локализации дефекта, тем лучше. Во многих случаях, как только удалось устойчиво воспроизвести проблему, можно считать, что большая половина работы сделана.
Дефекты в ядре могут быть такими же разнообразными, как и дефекты в пользовательских программах. Они возникают по различным причинам и проявляются в разнообразных формах. Дефекты занимают диапазон от явно неправильного кода (например, запись правильного значения в неправильное место) до ошибок синхронизации (например, если не правильно блокируется совместно используемая переменная). Эти дефекты проявляются в любой форме; от плохой производительности до неправильного функционирования и даже до потери данных.
Часто, между тем моментом, когда в ядре возникла ошибка и тем моментом, когда пользователь ее заметил происходит большая цепь событий. Например, разделяемая структура данных, у которой нет счетчика использования может привести к возникновению состояния конкуренции за ресурс (race condition). Если не принять необходимых мер, то один процесс может освободить память, в которой хранится структура, в то время, как другой процесс может эту структуру все еще использовать. Спустя некоторое время второй процесс может обратиться к этим данным, что в свою очередь может привести к попытке разыменования указателя со значением NULL
, если будут считаны случайные данные ("мусор"), или вообще не привести ни к чему плохому (если данные в соответствующей области памяти еще не были перезаписаны). Разыменование указателя со значением NULL
приводит к выводу сообщения "oops"
, в то время, как случайный "мусор" может привести к потере данных (и соответственно к неправильному функционированию, или опять же к выводу сообщения "oops"
, но уже по другом поводу). Пользователь же заметит только неправильное функционирование или сообщение "oops"
. Разработчик ядра при этом должен пойти но обратному пути: исходя из ошибки определить, что к данным было обращение после того, как память с этими данными была освобождена, что это произошло в результате возникновения конкуренции за ресурс и исправить ошибку путем правильного учета количества ссылок на совместно используемую структуру данных. Для этого также вероятно потребуется применение блокировок.
Читать дальше