Чтение онлайн

ЖАНРЫ

Разработка ядра Linux
Шрифт:

• В ядре нельзя легко использовать вычисления с плавающей точкой.

• Ядро использует стек небольшого фиксированного размера.

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

• Переносимость очень важна.

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

Отсутствие библиотеки

libc

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

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

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

lib/string.с
. Необходимо лишь подключить заголовочный файл
<linux/string.h>
и пользоваться этими функциями.

Заголовочные файлы

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

Отсутствует наиболее известная функция

printf
. Ядро не имеет доступа к функции
printf
, однако ему доступна функция
printk
. Функция
printk
копирует форматированную строку в буфер системных сообщений ядра (kernel log buffer), который обычно читается с помощью программы
syslog
. Использование этой функции аналогично использованию
printf
:

printk("Hello world! Строка: %s и целое число: %d\n",

 a_string, an_integer);

Одно важное отличие между

printf
и
printk
состоит в том, что в функции
printk
можно использовать флаг уровня вывода. Этот флаг используется программой
syslog
для того, чтобы определить, нужно ли показывать сообщение ядра. Вот пример использования уровня вывода:

printk(KERN_ERR "Это была ошибка !\n");

Функция

printk
будет использоваться на протяжении всей книги. В следующих главах приведено больше информации о функции
printk
.

Компилятор GNU С

Как и все "уважающие себя" ядра Unix, ядро Linux написано на языке С. Может быть, это покажется неожиданным, но ядро Linux написано не на чистом языке С в стандарте ANSI С. Наоборот, где это возможно, разработчики ядра используют различные расширения языка, которые доступны с помощью средств компиляции gcc (GNU Compiler Collection — коллекция компиляторов GNU, в которой содержится компилятор С, используемый для компиляции ядра).

Разработчики ядра используют как расширения языка С ISO C99 [7] так и расширения GNU С. Эти изменения связывают ядро Linux с компилятором gcc, хотя современные компиляторы, такие как Intel С, имеют достаточную поддержку возможностей компилятора gcc для того, чтобы ими тоже можно было компилировать ядро Linux. В ядре не используются какие-либо особенные расширения стандарта C99, и кроме того, поскольку стандарт C99 является официальной редакцией языка С, эти расширения редко приводят к возникновению ошибок в других частях кода. Более интересные и, возможно, менее знакомые отклонения от стандарта языка ANSI С связаны с расширениями GNU С. Давайте рассмотрим некоторые наиболее интересные расширения, которые могут встретиться в программном коде ядра.

7

Стандарт ISO C99 — это последняя основная версия редакции стандарта ISO С. Редакция C99 содержит многочисленные улучшения предыдущей основной редакции этого стандарта. Стандарт ISO C99 вводит поименную инициализацию полей структур и тип

complex
.

Функции
с подстановкой тела

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

Функции с подстановкой тела объявляются с помощью ключевых слов

static
и
inline
в декларации функции. Например,

static inline void dog(unsigned long tail_size);

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

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

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

Встроенный ассемблер

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

Для встраивания ассемблерного кода используется директива компилятора

asm
.

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

Аннотация ветвлений

Компилятор gnu С имеет встроенные директивы, позволяющие оптимизировать различные ветви условных операторов, которые наиболее или наименее вероятны. Компилятор использует эти директивы для соответственной оптимизации кода. В ядре эти директивы заключаются в макросы

likely
и
unlikely
, которые легко использовать. Например, если используется оператор
if
следующего вида:

if (foo) {

 /* ... */

}

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

/* предполагается, что значение переменной foo равно нулю ...*/

if (unlikely(foo)) {

 /* ... */

}

И наоборот, чтобы отметить этот путь выполнения как наиболее вероятный

/* предполагается, что значение переменной foo не равно нулю ...*/

if (likely(foo)) {

 /* ... * /

}

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

unlikely
и
likely
используются для проверки ошибок.

Поделиться с друзьями: