Версия:

Руководство разработчика / Рекомендации / Руководство по написанию кода на C
Руководство разработчика / Рекомендации / Руководство по написанию кода на C

Руководство по написанию кода на C

Руководство по написанию кода на C

Стиль программирования проекта основан на версии стиля программирования ядра Linux.

Последнюю версию стиля программирования Linux можно найти по ссылке: http://www.kernel.org/doc/Documentation/CodingStyle

Мы придерживаемся версии от 13 июля 2007 года, которая приводится ниже в документе.

Здесь мы приводим дополнительные рекомендации, которые либо специфичны для Tarantool’а, либо отличаются от рекомендаций по программированию ядра Linux.

  1. Следующие главы не применимы, поскольку они специфичны для среды программирования ядра Linux: 10 «Конфигурационные файлы Kconfig», 11 «Структуры данных», 13 «Вывод сообщений ядра», 14 «Выделение памяти» и 17 «Не изобретайте макросы снова».
  2. Остальные главы документа «Стиль программирования ядра Linux» изменяются следующим образом:

Общие рекомендации

Для управления версиями мы пользуемся Git. Последние разработки ведутся в ветке, используемой по умолчанию (сейчас 2.0). Наш git-репозиторий находится на github, его можно посмотреть выгрузить с помощью git clone git://github.com/tarantool/tarantool.git (для анонимного пользователя доступ только для чтения).

Если у вас есть вопросы о внутреннем устройстве Tarantool’а, разместите их в списке вопросов к обсуждению для разработчиков: https://groups.google.com/forum/#!forum/tarantool. Однако, предупреждаем: Launchpad молча удаляет сообщения от тех, кто не является подписчиком, поэтому обязательно подпишитесь на список перед публикацией. Кроме того, несколько инженеров всегда находятся на канале #tarantool в irc.freenode.net.

Стиль комментирования кода

Используйте формат комментирования Doxygen, разновидность Javadoc, то есть @tag вместо tag. Основные используемые теги: @param, @retval, @return, @see, @note и @todo.

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

/** Запись всех данных в дескриптор.
  *
  * Эта функция аналогична 'write' во всём кроме того, что она обеспечивает
  * запись всех данных в файл, если не возникает ошибка,
  * которую нельзя игнорировать.
  *
  * @retval 0  Выполнено
  *
  * @reval  1  Ошибка (не EINTR)
  * /
 static int
 write_all(int fd, void \*data, size_t len);

Доступные структуры и важные элементы структуры также должны быть прокомментированы.

Файлы заголовка

Используйте защиту заголовка. Поместите защиту заголовка в первую строку заголовка до авторского права или объявления. Для защиты заголовка используйте имя в верхнем регистре. Выводите имя защиты заголовка из имени файла и добавьте _INCLUDED, чтобы получить имя макроса. Например, core/log_io.h -> CORE_LOG_IO_H_INCLUDE. В файле .c (реализация) следует включить соответствующий заголовок с объявлением перед всеми другими заголовками, чтобы убедиться, что заголовок является автономным. Заголовок «header.h» является автономным, если компилируется без ошибок:

#include "header.h"

Выделение памяти

Предпочтительно использовать предоставляемые распределители slab’ов (salloc) и пулов (palloc) вместо malloc()/free() для любых операций выделения памяти большого объема. Многократное использование malloc()/free() может привести к фрагментации памяти, чего следует избегать.

Всегда освобождайте всю выделенную память, даже выделенную при запуске. Мы стремимся к тому, чтобы valgrind не находил утечек памяти, и в большинстве случаев так же легко освободить выделенную память по free(), как и записать подавление valgrind. Освобождение всей выделенной памяти также помогает динамическому балансированию нагрузки: предполагается, что подключаемый модуль может динамически загружаться и выгружаться несколько раз, перезагрузка не должна приводить к утечке памяти.

Прочее

Допускаются расширения GNU C99. Можно смешивать операторы и объявления в выражениях.

Не слишком актуальный список всех расширений семейства языка C можно найти по ссылке: http://gcc.gnu.org/onlinedocs/gcc-4.3.5/gcc/C-Extensions.html

Стиль программирования ядра Linux

В данном коротком документе описывается предпочтительный стиль программирования для ядра Linux. Стиль программирования – это личное дело каждого, и я не буду никому _навязывать_ свои убеждения, но поскольку это касается всего, что я должен поддерживать, я бы предпочел, чтобы эти правила использовали повсеместно. Пожалуйста, хотя бы рассмотрите описываемые здесь пункты.

Для начала я предлагаю вам распечатать копию стандартов написания кода GNU и НЕ читать их. Сожгите их в качестве весьма символического жеста.

В любом случае, поехали:

Глава 1: Отступы

Табуляция составляет 8 символов, то есть отступы будут также в 8 символов. Появляются отступнические движения, которые призывают делать отступы в 4 (или даже 2!) символа, а это сродни попытке округлить число Пи до 3.

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

Некоторые могут возразить, что отступ в 8 символов делает код слишком широким, особенно на 80-знаковой строке терминала. Ответ: Если вам понадобилось более трех уровней отступа, вы что-то делаете неправильно, и вам следует переписать этот участок.

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

Лучше всего упростить несколько уровней отступов в операторе switch, выравнивая «switch» и его вспомогательные метки «case» в одном столбце вместо использования двойных отступов для меток «case», например:

switch (suffix) {
 case 'G':
 case 'g':
     mem <<= 30;
     break;
 case 'M':
 case 'm':
     mem <<= 20;
     break;
 case 'K':
 case 'k':
     mem <<= 10;
     /* fall through */
 default:
     break;
 }

Не размещайте несколько операторов на одной строке, если вам нечего скрывать:

if (condition) do_this;
   do_something_everytime;

И не размещайте несколько операторов присваивания на одной строке. Стиль программирования ядра чрезвычайно прост. Избегайте сложных выражений.

За пределами комментариев, документации и Kconfig, пробелы никогда не используются для отступов, и приведенный выше пример намеренно нарушен.

Найдите достойный редактор и не оставляйте пробелы в конце строки.

Глава 2: Разрыв длинных строк

Смысл стиля программирования заключается в читаемости и удобстве сопровождения с использованием общедоступных средств.

The limit on the length of lines is 80 columns, reduced to 66 columns for comments, and this is a strongly preferred limit.

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

void fun(int a, int b, int c)
 {
     if (condition)
         printk(KERN_WARNING "Warning this is a long printk with "
                         "3 parameters a: %u b: %u "
                         "c: %u \n", a, b, c);
     else
         next_statement;
 }

Глава 3: Фигурные скобки и пробелы

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

if (x is true) {
     we do y
 }

Это применимо ко всем блокам операторов без функций (if, switch, for, while, do), например:

switch (action) {
 case KOBJ_ADD:
     return "add";
 case KOBJ_REMOVE:
     return "remove";
 case KOBJ_CHANGE:
     return "change";
 default:
     return NULL;
 }

И только в особенных случаях, а именно для функций, открывающая скобка размещается в начале следующей строки:

int function(int x)
 {
     body of function;
 }

Отступники по всему миру утверждали, что такая несогласованность … ну … несогласованна, но все здравомыслящие люди знают: (a) K&R _правы_, (б) K&R правы. Кроме того, функции в любом случае будут особенными (в C их нельзя вложить).

Обратите внимание, что за закрывающей скобкой на отдельной строке ничего нет, _кроме_ тех случаев, когда за ней следует продолжение того же оператора, то есть «while» в do-операторе или «else» в if-операторе, например:

do {
     body of do-loop;
 } while (condition);

и

if (x == y) {
     ..
 } else if (x > y) {
     ...
 } else {
     ....
 }

Обоснование: K&R.

Кроме того, обратите внимание, что такое расположение скобок также сводит к минимуму количество пустых (или почти пустых) строк без потери читаемости. Таким образом, поскольку новые строки на экране – это не возобновляемый ресурс (вспомним о 25-строчных экранах терминала), у вас будет больше пустых строк для комментариев.

Не используйте лишние фигурные скобки, если нужен всего один оператор.

if (condition)
     action();

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

if (condition) {
     do_this();
     do_that();
 } else {
     otherwise();
 }

Глава 3.1: Пробелы

Стиль программирования ядра Linux в том, что касается пробелов, зависит (в основном) от использования функции или ключевого слова. Используйте пробел после (большинства) ключевых слов. Значимые исключения: sizeof, typeof, alignof и __attribute__, которые похожи на функции (и обычно используются с круглыми скобками в Linux, хотя они и не требуются, как в объявлении «sizeof info» после «struct fileinfo info;»).

Поэтому добавляйте пробел после следующих ключевых слов: if, switch, case, for, do, while, но не для sizeof, typeof, alignof или __attribute__. Пример:

s = sizeof(struct file);

Не добавляйте пробелы вокруг (внутри) выражений в круглых скобках. Этот пример неправильный:

s = sizeof( struct file );

Объявляя данных типа указателя или функцию, которая возвращает тип указателя, предпочтительно использовать „*“ рядом с именем данных или именем функции, а не рядом с именем типа. Примеры:

char *linux_banner;
 unsigned long long memparse(char *ptr, char **retptr);
 char *match_strdup(substring_t *s);

Добавляйте по одному пробелу вокруг (с каждой стороны) большинства знаков двухместных и трехместных операций, например, любое из следующих:

= + - < > * / % | & ^ <= >= == != ? :

но не добавляйте пробелы после знаков одноместных операций:

& * + - ~ ! sizeof typeof alignof __attribute__ defined

не нужны пробелы перед знаками одноместных операций увеличения или уменьшения постфикса:

++ –

не нужны пробелы после знаков одноместных операций увеличения или уменьшения префикса:

++ –

и не нужны пробелы вокруг знаков элементов структуры „.“ и «->».

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

Git предупредит, если патчи содержат пробелы в конце строк, и может по желанию удалить пробелы за вас; однако, в серии патчей, это может привести к тому, что последующие патчи в серии не применятся, поскольку изменены контекстные строки.

Глава 4: Именование

C – это спартанский язык, и именование должно быть спартанским. В отличие от разработчиков на Modula-2 и Pascal, разработчики на языке C не используют забавные имена, такие как ThisVariableIsATemporaryCounter. Разработчик на языке C назвал бы такую переменную «tmp», что намного легче написать и не менее сложно понять.

ОДНАКО, хотя на имена со смешанным регистром смотрят неодобрительно, обязательным требованием будут описательные имена глобальных переменных. Назвать глобальную функцию «foo» – это оскорбление.

У ГЛОБАЛЬНЫХ переменных (которые надо использовать, только если без них НЕЛЬЗЯ обойтись) должны быть описательные имена, равно как и у глобальных функций. Если у вас есть функция, которая подсчитывает количество активных пользователей, нужно назвать ее «count_active_users()» или как-то похоже, _НЕ_ следует называть ее «cntusr()».

Кодирование типа функции в названии (так называемая венгерская нотация) – это признак плохого тона, поскольку компилятор в любом случае знает типы и может их проверять, и это только путает программиста. Неудивительно, что MicroSoft делает глючные программы.

Имена ЛОКАЛЬНЫХ переменных должны быть короткими и точными. Если у вас есть счетчик случайных целых чисел, его следует называть «i». Назвать его «loop_counter» будет непродуктивно, если нет никаких шансов, что его перепутают. Аналогично «tmp» может быть практически любым типом переменной, которая используется для хранения временного значения.

Если вы боитесь перепутать имена своих локальных переменных, у вас другая проблема, которая называется синдромом дисбаланса гормона роста функций. См. Главу 6 (Функции).

Глава 5: Директива Typedef

Не используйте что-то вроде «vps_t».

Будет _ошибкой_ использовать typedef для определения структур и указателей. Если вы видите

vps_t a;

в исходном коде, что это означает?

И наоборот, если говорится

struct virtual_container *a;

можно действительно понять, что такое «a».

Многие думают, что typedef «способствует читаемости». Это не так. Эту директиву нужно использовать для:

  1. непрозрачных объектов (где typedef активно используется для _сокрытия_ объекта).

    Пример: «pte_t» и другие непрозрачные объекты, доступ к которым можно получить с помощью соответствующих функций доступа.

    ВНИМАНИЕ! Непрозрачность и функции доступа сами по себе не слишком хороши. Причина, по которой мы используем их для pte_t и т.п., состоит в том, что на самом деле там _нет_ никакой информации для скачивания.

  2. Чисто целочисленные типы, где абстракция _помогает_ избежать путаницы, «int» это или «long».

    u8/u16/u32 – вполне нормальные typedef, хотя они больше подходят для категории (d).

    ВНИМАНИЕ! Опять же – для этого должна быть _причина_. Если что-то представляет собой «unsigned long», должна быть причина для

    typedef unsigned long myflags_t;
    

    но если есть четкая причина, почему при определенных обстоятельствах может быть «unsigned int», а в других случаях может быть «unsigned long», то на здоровье, используйте typedef.

  3. когда вы используете разрыв, чтобы буквально создать _новый_ тип для проверки типов.

  4. Новые типы, идентичные стандартным типам C99, в определенных исключительных обстоятельствах.

    Хотя глазам и мозгу требуется лишь короткое время, чтобы привыкнуть к стандартным типам, например, „uint32_t“, некоторые в любом случае возражают против их использования.

    Таким образом, допускаются специфичные для Linux типы „u8/u16/u32/u64“ и их эквиваленты, идентичные стандартным типам, хотя они и не обязательны новом коде.

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

  5. Типы, которые можно использовать в пользовательском пространстве.

    В некоторых структурах, видимых в пользовательском пространстве, мы не можем требовать использования типов C99 и не можем применять форму „u32“ выше. Таким образом, мы используем __u32 и подобные типы во всех структурах, которые используются и в пользовательском пространстве.

Возможно, есть и другие случаи, но основное правило состоит в следующем: НИКОГДА НЕ используйте typedef, если вы не соблюдаете одно из этих правил.

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

Глава 6: Функции

Функции должны быть короткими и приятными, и выполнять только одно действие. Они должны помещаться на одном или двух экранах текста (размер экрана ISO/ANSI 80x24, как мы все знаем), и выполнять одно действие, но делать это хорошо.

Максимальная длина функции обратно пропорциональна сложности функции и уровню отступов. Итак, если у вас есть концептуально простая функция, которая представляет собой лишь один длинный (но простой) оператор вариант case, где вам нужно делать много мелочей для множества разных случаев, длинная функция – это нормально.

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

Другим критерием функции является количество локальных переменных. Их не должно быть больше 5-10, или вы делаете что-то неправильно. Продумайте функцию заново и разбейте ее на более мелкие части. Человеческий мозг обычно легко отслеживает около 7 разных вещей, а больше – и он уже запутается. Вы знаете, что сейчас вы гений, но, возможно, вам через пару недель захочется понять, что именно вы делали.

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

int system_is_up(void)
 {
     return system_state == SYSTEM_RUNNING;
 }
 EXPORT_SYMBOL(system_is_up);

В прототипах функций включайте имена параметров с типами данных. Хотя для языка C это и не требуется, но рекомендуется для Linux, потому что это простой способ добавить ценную информацию для читателя.

Глава 7: Централизованный выход из функции

Хотя некоторые объявили аналог оператора goto устаревшим, его часто используют компиляторы в виде инструкции безусловной передачи управления.

Оператор goto пригодится, когда функция производит выход из нескольких мест, и необходимо выполнить какие-то общие действия, такие как очистка.

Обоснование:

  • безусловные операторы легче понять и выполнять
  • уменьшается глубина вложения
  • предотвращаются ошибки по причине отсутствия обновления отдельных точек выхода при внесении изменений
  • уменьшает объем работы компилятора для оптимизации избыточного кода ;)
int fun(int a)
 {
     int result = 0;
     char *buffer = kmalloc(SIZE);

     if (buffer == NULL)
         return -ENOMEM;

     if (condition1) {
         while (loop1) {
             ...
         }
         result = 1;
         goto out;
     }
     ...
 out:
     kfree(buffer);
     return result;
 }

Глава 8: Комментирование

Комментарии полезны, но есть и опасность чрезмерного комментирования. НИКОГДА не пытайтесь объяснить в комментарии, КАК работает ваш код: гораздо лучше написать код так, чтобы принцип _работы_ был очевиден, а объяснять плохо написанный код – это пустая трата времени. Как правило, желательно, чтобы комментарии поясняли, ЧТО делает ваш код, а не КАК. Кроме того, постарайтесь не размещать комментарии внутри тела функции: если функция настолько сложна, что нужно отдельно комментировать ее части, скорее всего, вам надо вернуться к главе 6. Можно давать небольшие комментарии, чтобы отметить или предупредить о чем-то особенно умном (или уродливом), но старайтесь избегать лишнего. Вместо этого поставьте комментарии во главе функции, сообщите людям, что она делает, и, возможно, ПОЧЕМУ она это делает.

Комментируя функции API ядра, используйте формат kernel-doc. Более подробную информацию см. в файлах Documentation/kernel-doc-nano-HOWTO.txt и scripts/kernel-doc.

Стиль Linux для комментариев – стиль C89 "/\* ... \*/". Не используйте стиль C99 "// ...".

Для длинных (многострочных) комментариев рекомендуется:

/*
  * Рекомендуется использовать этот стиль для многострочных
  * комментариев в исходном коде ядра Linux.
  * Просьба использовать его согласованно.
  *
  * Описание:  Столбец звездочек слева,
  * в начале и в конце почти пустые строки.
  */

Также важно комментировать данные, являются ли они базовыми или производными типами. Для этого используйте только одно объявление данных в строке (без запятой для объявления массива данных). Это оставляет вам место для небольшого комментария к каждому пункту с объяснением его использования.

Глава 9: Вы устроили беспорядок

Всё в порядке, мы все так делаем. Наверное, опытный пользователь Unix, который вам помогает, сказал, что «GNU emacs» автоматически форматирует исходный код C, и вы заметили, что да, действительно, но используемые по умолчанию значения оставляют желать лучшего ( на самом деле, они хуже, чем случайные – несметное количество обезьян, печатающих в GNU emacs, никогда не создаст хорошую программу).

Итак, вы можете либо избавиться от GNU emacs, либо изменить его для использования более адекватных значений. Чтобы сделать последнее, можно вставить следующее в файл .emacs:

(defun c-lineup-arglist-tabs-only (ignored)
 "Line up argument lists by tabs, not spaces"
 (let* ((anchor (c-langelem-pos c-syntactic-element))
     (column (c-langelem-2nd-pos c-syntactic-element))
     (offset (- (1+ column) anchor))
     (steps (floor offset c-basic-offset)))
     (* (max steps 1)
     c-basic-offset)))

 (add-hook 'c-mode-common-hook
         (lambda ()
             ;; Добавить стиль ядра
             (c-add-style
             "linux-tabs-only"
             '("linux" (c-offsets-alist
                         (arglist-cont-nonempty
                         c-lineup-gcc-asm-reg
                         c-lineup-arglist-tabs-only))))))

 (add-hook 'c-mode-hook
         (lambda ()
             (let ((filename (buffer-file-name)))
             ;; Включить режим ядра для соответсвующих файлов
             (when (and filename
                         (string-match (expand-file-name "~/src/linux-trees")
                                     filename))
                 (setq indent-tabs-mode t)
                 (c-set-style "linux-tabs-only")))))

Это заставит emacs лучше работать со стилем программирования ядра для файлов C в ~/src/linux-trees.

Но даже если вам не удастся заставить emacs форматировать нормально, не все потеряно: используйте «indent».

Опять же, у GNU indent такие же безмозглые настройки, как и у GNU emacs, поэтому надо задать для него несколько параметров командной строки. Тем не менее, это не так уж плохо, потому что даже разработчики GNU indent признают авторитет K&R (люди из GNU не злые, они просто серьезно ошибаются в этом вопросе), поэтому вы просто указываете опции «-kr -i8» (означает «K&R, 8 символов отступа») или используйте «scripts/Lindent», которые делают отступы в новейшем стиле.

В «indent» есть много опций, и особенно когда дело доходит до повторного форматирования комментариев, вы можете захотеть взглянуть на страницу руководства. Но помните: «indent» – это не залог хорошего программирования.

Глава 10: Конфигурационные файлы Kconfig

Для всех конфигурационных файлов Kconfig* в дереве источников отступы несколько отличаются. Строки под определением «config» имеют отступы на позицию табуляции, а текст справки с отступом еще на два пробела. Пример:

config AUDIT
     bool "Auditing support"
     depends on NET
     help
     Enable auditing infrastructure that can be used with another
     kernel subsystem, such as SELinux (which requires this for
     logging of avc messages output).  Does not do system-call
     auditing without CONFIG_AUDITSYSCALL.

Функции, которые все еще могут считаться нестабильными, должны определяться как зависящие от «EXPERIMENTAL»:

config SLUB
     depends on EXPERIMENTAL && !ARCH_USES_SLAB_PAGE_STRUCT
     bool "SLUB (Unqueued Allocator)"
     ...

тогда как крайне опасные функции (например, поддержка записи для определенных файловых систем) должны подчеркнуть это в строке приглашения:

config ADFS_FS_RW
     bool "ADFS write support (DANGEROUS)"
     depends on ADFS_FS
     ...

Полную документацию по файлам конфигурации см. в файле Documentation/kbuild/kconfig-language.txt.

Глава 11: Структуры данных

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

Подсчет ссылок означает, что можно избежать блокировки и позволить нескольким пользователям получать доступ к структуре данных одновременно – и не нужно беспокоиться о том, что структура внезапно исчезнет только потому, что они спали или делали что-то еще.

Обратите внимание, что блокировка _не_ является заменой для подсчета ссылок. Блокировка используется для обеспечения целостности структур данных, а подсчет ссылок – это метод управления памятью. Обычно необходимо и то, и другое, и их нельзя путать друг с другом.

Для многих структур данных действительно могут быть два уровня подсчета ссылок, когда есть пользователи разных «классов». Подсчет подкласса подсчитывает количество пользователей подкласса и уменьшает глобальный счетчик только один раз, когда подсчет подкласса равен нулю.

Примеры такого многоуровневого подсчета ссылок можно найти в управлении памятью («struct mm_struct»: mm_users и mm_count) и в коде файловой системы («struct super_block»: s_count и s_active).

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

Глава 12: Макросы, перечисления и уровни регистровых передач (RTL)

Имена макросов, определяющих постоянные и метки в перечислениях, пишутся заглавными буквами.

#define CONSTANT 0x12345

Рекомендуется использовать перечисления при определении нескольких связанных постоянных.

Ценятся имена макросов, написанные ЗАГЛАВНЫМИ буквами, но похожие на функции макросы можно называть, используя буквы в нижнем регистре.

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

Макросы с несколькими операторами должны быть заключены в блок do - while:

#define macrofun(a, b, c)   \
     do {                    \
         if (a == 5)         \
             do_this(b, c);  \
     } while (0)

Во время использования макросов постарайтесь избегать следующего:

  1. макросы, которые влияют на поток управления:

    #define FOO(x)                  \
         do {                        \
             if (blah(x) < 0)        \
                 return -EBUGGERED;  \
         } while(0)
    

    это _очень_ плохая идея. Он выглядит как вызов функции, но выходит из вызывающей функции; не ломайте внутреннего анализатора у тех, кто прочитает код.

  2. макросы, которые зависят от наличия локальной переменной с магическим именем:

    #define FOO(val) bar(index, val)
    

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

  3. макросы с аргументами, которые используются как l-значения: FOO(x) = y; это вам аукнется, если кто-то, например. сделает FOO встроенной функцией.

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

    #define CONSTANT 0x4000
     #define CONSTEXP (CONSTANT | 3)
    

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

Глава 13: Вывод сообщений ядра

Разработчики ядра любят выглядеть грамотными. Обращайте внимание на орфографию в сообщениях ядра, чтобы произвести хорошее впечатление. Не используйте искаженные слова типа «dont»; вместо этого используйте «do not» или «don’t». Пусть сообщения будут краткими, ясными и недвусмысленными.

Сообщения ядра не должны заканчиваться точкой.

Вывод номеров в круглых скобках (%d) не повышает их ценность, и его следует избегать.

В <linux/device.h> есть несколько макросов для диагностики модели драйвера, которые следует использовать, чтобы убедиться, что сообщения соотнесены с правильным устройством и драйвером и помечены правильным уровнем: dev_err(), dev_warn(), dev_info() и так далее. Для сообщений, не связанных с определенным устройством, <linux/kernel.h> определяет pr_debug() и pr_info().

Придумать хорошие сообщения отладки может быть довольно сложно; и как только у вас будут такие, они могут стать огромным подспорьем для удаленного устранения неполадок. Такие сообщения должны быть скомпилированы, когда символ DEBUG не определен (то есть, по умолчанию они не включены). Если вы используете dev_dbg() или pr_debug(), это сработает автоматически. Во многих подсистемах есть опции Kconfig для включения -DDEBUG. В соответствующем соглашении VERBOSE_DEBUG используется для добавления сообщений dev_vdbg() в сообщения, которые уже включены с помощью DEBUG.

Глава 14: Выделение памяти

В ядре поддерживаются следующие распределители памяти широкого применения: kmalloc(), kzalloc(), kcalloc(), and vmalloc(). Для получения дополнительной информации обратитесь к документации по API.

Предпочтительна следующая форма передачи размера структуры:

p = kmalloc(sizeof(*p), ...);

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

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

Глава 15: Болезнь встраивания (inline)

Похоже, что распространено ошибочное представление о том, что в gcc есть волшебная опция ускорения, называемая встраиванием «inline». Хотя использование встроенных строк может быть оправдано (например, как средство замены макросов, см. Главу 12), довольно часто это не так. Избыток ключевого слова inline приводит к увеличению ядра, что в свою очередь, замедляет работу системы в целом из-за большего объема отпечатка icache для процессора и просто потому, что для pagecache доступно меньше памяти. Просто подумайте: непопадание в pagecache вызывает поиск по диску, который легко занимает 5 миллисекунд. Есть МНОГО циклов процессора, которые могут пройти в эти 5 миллисекунд.

Общее правило состоит в том, чтобы не вводить встраивание в функции, содержащие больше трех строк кода. Исключением из этого правила являются случаи, когда параметр известен как постоянная времени компиляции, и в результате вы знаете, что компилятор сможет оптимизировать большую часть ваших функций во время компиляции. Хороший пример последнего случая – встроенная функция kmalloc().

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

Глава 16: Возвращаемые значения и имена функций

Функции могут возвращать значения множества различных типов, и одним из наиболее распространенных является значение, которое указывает, была функция выполнена или нет. Такое значение может быть представлено как целое число с кодом ошибки (-Exxx = сбой, 0 = выполнено) или логическое значение выполнения (0 = сбой, ненулевое значение = выполнено).

Смешение этих двух видов дает богатую пищу для появления сложных для обнаружения ошибок. Если бы в языке C были явные различия между целыми числами и логическими значениями, тогда компилятор нашел бы для нас эти ошибки… но это не так. Чтобы предотвратить такие ошибки, всегда следуйте этому соглашению:

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

Например, «add work» (добавить работу) – это команда, а функция add_work() возвращает 0 в случае выполнения или -EBUSY при сбое. Точно так же «PCI device present» (есть PCI-устройство) представляет собой утверждение, а функция pci_dev_present() возвращает 1, если ей удается найти подходящее устройство, или 0, если это не так.

Все экспортируемые функции (EXPORT) должны подчиняться этому соглашению, то же относится и ко всем доступным функциям. Закрытые (статические) функции не должны подчиняться, но это рекомендуется.

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

Глава 17: Не изобретайте макросы снова

В файле заголовка include/linux/kernel.h содержатся несколько макросов, которые следует использовать, а не программировать их самостоятельно. Например, если необходимо рассчитать длину массива, воспользуйтесь макросом

#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))

Аналогичным образом, если необходимо рассчитать размер какого-либо элемента структуры, используйте

#define FIELD_SIZEOF(t, f) (sizeof(((t*)0)->f))

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

Глава 18: Редакторские строки режима (modelines) и прочий хлам

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

-*- mode: c -*-

Или так:

/*
 Local Variables:
 compile-command: "gcc -DMAGIC_DEBUG_FLAG foo.c"
 End:
 */

Vim интерпретирует маркеры, которые выглядят так:

/* vim:set sw=8 noet */

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

Приложение I: Источники