Разработка ядра Linux

Лав Роберт

Глава 20

Заплаты, разработка и сообщество

 

 

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

 

Сообщество

Если говорить о том, где физически существует сообщество разработчиков ядра Linux, то можно сослаться на список рассылки разработчиков ядра Linux (Linux Kernel Mail List, или, сокращенно, lkml). Список разработчиков ядра Linux — это то место, где происходит большинство дискуссий, дебатов и флеймов вокруг ядра Linux. Здесь обсуждаются новые возможности, и большая часть кода отправляется в этот список рассылки перед тем, как этот код для чего-нибудь начинает использоваться. В списке рассылки насчитывается до 300 сообщений в день — количество не для слабонервных. Подписаться на этот список (или но крайней мере читать его обзор) рекомендуется всем, кто серьезно занимается разработкой ядра. Даже только наблюдая за работой специалистов, можно узнать достаточно много.

Подписаться на данный список рассылки можно, отправив сообщение

subscribe linux-kernel

в виде обычного текста на адрес [email protected]. Больше информации доступно по Интернет-адресу http://vger.kernel.org/, а список часто задаваемых вопросов (FAQ) — по адресу http://www.tux.org/lkml/.

Много других WWW-сайтов и списков рассылки посвящены как ядру, так и вообще операционной системе Linux. Отличный ресурс для начинающих хакеров — http://www.kernelnewbies.org/, сайт, который сможет удовлетворить желания всех, кто, стачивая зубы, грызет основы разработки ядра. Два других отличных источника информации — это сайт http://www.lwn.net/, Linux Weekly News, на котором есть большая колонка новостей ядра, и сайт http://www.kerneltraffic.org, Kernel Traffic, который содержит сводку сообщений из списка рассылки разработчиков ядра Linux с. комментариями.

 

Стиль написания исходного кода

 

Как и для любого большого программного проекта, для ядра Linux определен стиль написания исходного кода, который определяет форматирование и размещение кода. Это сделано не потому, что стиль написания, который принят для Linux, лучше других (хотя очень может быть), и не потому, что все программисты пишут неразборчиво (хотя тоже бывает), а потому, что одинаковость стиля является важным моментом для обеспечения производительности разработки. Часто говорят, что стиль написания исходного кода не важен, потому что он не влияет на скомпилированный объектный код. Однако для большого программного проекта, в котором задействовано большое количество разработчиков, такого как ядро, важна слаженность стиля. Слаженность включает в себя одинаковость восприятия, что ведет к упрощению чтения, к избежанию путаницы и вселяет надежду на то, что и в будущем стиль останется одинаковым. К тому же, это приводит к увеличению количества разработчиков, которые смогут нормально читать ваш код, и увеличивает количество кода, который вы сможете нормально читать. Для проектов с открытым исходным кодом чем больше будет глаз, тем лучше.

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

 

Отступы

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

Если табуляция в восемь символов кажется очень большой, то не нужно делать так много вложений кода. Почему ваши функции имеют пять уровней вложенности? Необходимо исправлять код, а не отступы.

 

Фигурные скобки

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

if (fox) {

        dog();

        cat();

}

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

if (fox) {

        ant();

        pig();

} else {

        dog();

        cat();

}

или следующим образом.

do {

        dog();

        cat();

} while (fox);

Для функций это правило не действует, потому что внутри одной функции тело другой функции описывать нельзя.

unsigned long func (void)

{

       /* ... */

}

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

if (foo)

bar();

Логика всего этого базируется на K&R.

 

Длинные строки

При написании кода ядра необходимо стараться, насколько это возможно, чтобы длина строки была не больше 80 символов. Это позволяет строкам, при отображении на терминале размером 80×24 символа, вмещаться в одну строку терминала.

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

Некоторые разработчики помещают параметры функции друг под другом, если параметры не помещаются в одной строке, как в следующем примере.

static void get_pirate_parrot(const char *name,

                           unsigned long disposition,

                           unsigned long feather_quality);

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

int find_pirate_flag_by_color(const char *color,

                const char *name, int len);

Поскольку на этот счет нет определенного правила, выбор остается за разработчиками, то есть за вами.

 

Имена

В именах нельзя использовать символы разных регистров. Назвать переменную именем idx, или даже i — это очень хорошо, но при условии, что будет понятно назначение этой переменной. Слишком хитрые имена, такие как theLoopIndex, недопустимы. Так называемая "венгерская запись" (Hungarian notation), когда тип переменной кодируется в ее имени, В данном случае — признак плохого тона. Это С, а не Java и Unix, а не Windows.

Тем не менее, глобальные переменные и функции должны иметь наглядные имена. Если глобальной функции присвоить имя atty(), то это может привести к путанице. Более подходящим будет имя get_active_tty(). Это все-таки Linux, а не BSD.

 

Функции

Существует мнемоническое правило: функции не должны по объему кода превышать двух экранов текста и иметь больше десяти локальных переменных. Каждая функция должна выполнять одно действие, но делать это хорошо. Не вредно разбить функцию на последовательность более мелких функций. Если возникает беспокойство по поводу накладных расходов за счет вызова функций, то можно использовать подстановку тела — inline.

 

Комментарии

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

В ядре используются комментарии в стиле С, хотя компилятор gcc поддерживает также и комментарии в стиле C++. Обычно комментарии кода ядра должны быть похожи на следующие (только на английском языке, конечно).

/*

* get_ship_speed() - возвращает текущее значение скорости

* пиратского корабля

* Необходима для вычисления координат корабля.

* Может переходить в состояние ожидания,

* нельзя вызывать при удерживаемой блокировке.

*/

Комментарии внутри функций встречаются редко, и их нужно использовать только в специальных ситуациях, таких как документирование дефектов, или для важных замечаний. Важные замечания часто начинаются со строки "XXX: ", а информация о дефектах — со строки "FIXME: ", как в следующем примере.

/*

* FIXME: Считается, что dog == cat.

* В будущем это может быть не так

*/

У ядра есть возможность автоматической генерации документации. Она основана на GNOME-doc, но немного модифицирована и называется Kernel-doc. Для создания документации в формате HTML необходимо выполнить следующую команду.

make htmldocs

Для генерации документации в формате postscript команда должна быть следующей.

make psdocs

Документировать код можно путем введения комментариев в специальном формате.

/**

* find_treasure - нахождение сокровищ, помеченных на карте крестом

* @map - карта сокровищ

* @time - момент времени, когда были зарыты сокровища

*

* Должна вызываться при удерживаемой блокировке pirate_ship_lock.

*/

void find_treasure(int dog, int cat)

{

       /* ... */

}

Для более подробной информации см. файл Documentation/kernel-doc-nano-HOWTO.txt.

 

Использование директивы

typedef

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

• Определение нового типа через оператор typedef скрывает истинный вид структур данных.

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

• Использование оператора typedef — признак лени.

Чтобы избежать насмешек, лучше не использовать оператор typedef.

Конечно, существуют ситуации, в которых полезно использовать оператор typedef: сокрытие специфичных для аппаратной платформы деталей реализации или обеспечение совместимости при изменении типа. Нужно хорошо подумать, действительно ли оператор typedef необходим или он используется только для того, чтобы уменьшить количество символов при наборе кода.

 

Использование того, что уже есть

Не нужно изобретать паровоз. Ядро предоставляет функции работы со строками, подпрограммы для сжатия и декомпрессии данных и интерфейс работы со связанными списками — их необходимо использовать.

Не нужно инкапсулировать стандартные интерфейсы в другие реализации обобщенных интерфейсов. Часто приходится сталкиваться с кодом, который переносится с других операционных систем в систему Linux, при этом на основе существующих интерфейсов реализуется некоторая громоздкая функция, которая служит для связи нового кода с существующим. Такое не нравится никому, поэтому необходимо непосредственно использовать предоставляемые интерфейсы.

 

Никаких директив

ifdef

в исходном коде

Использование директив препроцессора ifdef в исходном коде категорически не рекомендуется. Никогда не следует делать чего-нибудь вроде следующего.

 ...

#ifdef CONFIG_FOO

 foo();

#endif

 ...

Вместо этого, если макрос CONFIG_FOO не определен, необходимо определять функцию foo(), как ту, которая ничего не делает.

#ifdef CONFIG_FOO

static int foo(void)

{

 /* ... */

}

#else

static inline int foo(void) { }

#endif

После этого можно вызывать функцию foo() без всяких условий. Пусть компилятор поработает за вас.

 

Инициализация структур

Структуры необходимо инициализировать, используя метки полей. Это позволяет предотвратить некорректную инициализацию при изменении структур. Это также позволяет выполнять инициализацию не всех полей. К сожалению, в стандарте C99 принят довольно "страшненький" формат меток полей, а в компиляторе gcc ранее использовавшийся формат меток полей в стиле GNU признан устаревшим. Следовательно, для кода ядра необходимо использовать новый формат, согласно стандарту C99, каким бы ужасным он ни был.

struct foo my_foo = {

 .a = INITIAL_A,

 .b = INITIAL_B,

};

где а и b — это поля структуры struct foo, а параметры INITIAL_A и INITIAL_B — соответственно, их начальные значения. Если поле не указано при инициализации, то оно устанавливается в свое начальное значение, согласно стандарту ANSI С (указателям присваивается значение NULL, целочисленным полям — нулевое значение, а полям с плавающей точкой— значение 0.0). Например, если структура struct foo также имеет поле int с, то это поле в предыдущем примере будет инициализировано в значение 0.

Да, это ужасно. Но у нас нет другого выбора.

 

Исправление ранее написанного кода

Если в ваши руки попал код, который даже близко не соответствует стилю написания кода ядра Linux, то все равно не стоит терять надежды. Немного упорства, и утилита indent поможет сделать все как надо. Программа indent — отличная утилита GNU, которая включена во многие поставки ОС Linux и предназначена для форматирования исходного кода в соответствии с заданными правилами. Установки по умолчанию соответствуют стилю форматирования GNU, который выглядит не очень красиво. Для того чтобы утилита выполняла форматирование в соответствии со стилем написания кода ядра Linux, необходимо использовать следующие параметры.

indent -kr -i8 -ts8 -sob -180 -ss -bs -psl <файл>

Можно также использовать сценарий scripts/Lindent, который вызывает утилиту indent с необходимыми параметрами.

 

Организация команды разработчиков

Разработчики — это хакеры, которые занимаются развитием ядра Linux. Некоторые делают это за деньги, для некоторых это хобби, но практически все делают это с удовольствием. Разработчики ядра, которые внесли существенный вклад, перечислены в файле CREDITS, который находится в корневом каталоге дерева исходных кодов ядра.

Для различных частей ядра выбираются ответственные разработчики (maintainers), которые официально выполняют их поддержку. Ответственные разработчики — это один человек или группа людей, которые полностью отвечают за свою часть ядра. Каждая подсистема ядра, например сетевая подсистема, также имеет связанного с ней ответственного. Разработчики, которые отвечают за определенный драйвер или подсистему, обычно перечислены в файле MAINTAINERS. Этот файл тоже находится в корневом каталоге дерева исходных кодов.

Среди ответственных разработчиков существует специальный человек, который отвечает за все ядро в целом (kernel maintainer). Исторически, Линус отвечает за разрабатываемую серию ядер (где наиболее интересно) и за первые выпуски стабильной версии ядра. Вскоре после того как ядро становится стабильным, Линус передает бразды правления одному из ведущих разработчиков. Они продолжают поддержку стабильной серии ядра, а Линус начинает работу над новой разрабатываемой серией. Таким образом, серии ядер 2.0, 2.4 и 2.6 все еще активно поддерживаются.

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

 

Отправка сообщений об ошибках

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

Наиболее важная часть сообщения об ошибке — это полное описание проблемы. Необходимо описать симптомы, системные сообщения и декодированное сообщение oops (если такое есть). Еще более важно, чтобы вы смогли пошагово описать, как устойчиво воспроизвести проблему, и кратко описать особенности вашего аппаратного обеспечения.

Следующий этап — определение того, кому отправить сообщение об ошибке. В файле MAINTAINERS приведен список людей, которые отвечают за каждый драйвер и подсистему. Эти люди должны получать все сообщения об ошибках, которые возникают в том коде, поддержку которого они выполняют. Если вы не смогли найти необходимого разработчика, то отправьте вопрос в список рассылки разработчиков ядра Linux по адресу [email protected]. Даже если вы нашли нужное ответственное лицо, то никогда не помешает отправить копию сообщения в список рассылки разработчиков.

Больше информации об этом приведено в файлах REPORTING-BUGS и Documentation/oops-tracing.txt.

 

Генерация заплат

Все изменения исходного кода ядра Linux распространяются в виде заплат (patch). Заплаты представляют собой результат вывода утилиты GNU diff(1) в формате, который может подаваться на вход программы patch(1). Наиболее просто сгенерировать заплату можно в случае, когда имеется два дерева исходных кодов ядра: одно — стандартное, а другое — с вашими изменениями. Обычная схема имен состоит в том, что каталог, в котором находится стандартное ядро, называется linux-x.y.z (каталог, в который разворачивается архив дерева исходного кода в формате tar), a имя модифицированного ядра — linux. Для генерации заплаты на основе двух каталогов с исходным кодом необходимо выполнить следующую команду из каталога, в котором находятся два рассмотренных дерева исходного кода.

diff -urN linux-x.y.z/linux/ > my-patch

Обычно это делается где-нибудь в домашнем каталоге, а не в каталоге /usr/src/linux, поэтому нет необходимости иметь права пользователя root. Флаг -u указывает, что необходимо использовать унифицированный формат вывода команды diff. Без этого флага внешний вид заплаты получается неудобочитаемым. Флаг -r указывает на необходимость рекурсивного анализа каталогов, а флаг -N указывает, что новые файлы, которые появились в измененном каталоге, должны быть включены в результат вывода команды diff. Если же необходимо получить только изменения одного файла, то можно выполнить следующую команду.

diff -u linux-x.y.z/some/file_linux/some/file > my-patch

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

patch -p1 < ../my-patch

В этом примере имя файла, который содержит заплату, — my-patch, а находится он в родительском каталоге по отношению к каталогу, в котором хранится дерево исходного кода ядра. Флаг -p1 означает, что необходимо игнорировать (strip) имя первого каталога в путях всех файлов, в которые будут вноситься изменения. Это позволяет применить заплату независимо от того, какие имена каталогов кода ядра были на той машине, где создавалась заплата.

Полезная утилита diffstat позволяет сгенерировать гистограмму изменений, к которым приведет применение заплаты (удаление и добавление строк). Для того чтобы получить эту информацию для какой-либо заплаты, необходимо выполнить следующую команду.

diffstat -p1 my-patch

Обычно полезно включить результат выполнения этой команды при отправлении заплаты в список рассылки lkml. Так как программа patch(1) игнорирует все строки до того момента, пока не будет обнаружен формат diff, то вначале заплаты можно включить короткое описание.

 

Представление заплат

Заплата должна быть сгенерирована так, как описано в предыдущем разделе. Если заплата касается определенного драйвера или подсистемы, то заплату нужно отправить соответствующему ответственному разработчику, одному из тех, которые перечислены в файле MAINTAINERS. Другой вариант — это отправить сообщение в список рассылки разработчиков ядра по адресу [email protected].

Обычно тема (subject) письма, в котором содержится заплата, должна быть похожа на следующую "[PATCH] короткое описание.". В теле письма должны быть описаны основные технические детали изменений, которые вносятся заплатой, а также обоснования необходимости этих изменений. Описание должно быть максимально конкретным. Также необходимо указать, на какую версию ядра рассчитана заплата.

Большинство разработчиков ядра будут просматривать заплату прямо в теле письма и при необходимости записывать все письмо в файл. Следовательно, лучше всего будет вставить заплату прямо в тело письма, в самом конце сообщения. Будьте внимательны, потому что некоторые злобные почтовые клиенты вводят в сообщения дополнительное форматирование. Это испортит заплату и будет надоедать разработчикам. Если ваш почтовый клиент делает такие вещи, то необходимо поискать возможность включения текста без изменений ("Insert Inline") или что-нибудь аналогичное. Нормально работает также присоединение (attachment) заплаты в виде обычного текста, без перекодировки.

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

После отправки наберитесь терпения и подождите ответа. Не нужно обижаться на негативный ответ — в конце концов это тоже ответ! Обдумайте проблему и вышлите обновленную версию заплаты. Если ответа нет, то попытайтесь разобраться, что было сделано не так, и решить проблему. Спросите у ответственного разработчика и в списке рассылки по поводу комментариев. Если повезет, то ваши изменения будут включены в новую версию ядра!

 

Заключение

Наиболее важными качествами любого хакера являются желание и умение работать — нужно искать себе проблемы и решать их. В этой книге приведено описание основных частей ядра, рассказано об интерфейсах, структурах данных, алгоритмах и принципах работы. Книга предоставляет вид ядра изнутри и делает это в практической форме. Она предназначена для того, чтобы удовлетворить ваше любопытство и стать отправной точкой в разработке ядра.

Тем не менее, как уже было сказано, единственный способ начать разрабатывать ядро — это начать читать и писать исходный код. Операционная система Linux предоставляет возможность работать в сообществе, которое не только позволяет это делать, но и активно побуждает к указанным действиям. Если есть желание действовать — вперед!