Наши партнеры

UnixForum





Библиотека сайта rus-linux.net

Драйверы устройств в Linux

Часть 3: Расширения языка C для ядра, используемые в драйверах Linux

Оригинал: "Device Drivers, Part 3: Kernel C Extras in a Linux Driver"
Автор: Anil Kumar Pugalia
Дата публикации: January 1, 2011
Перевод: Н.Ромоданов
Дата перевода: июнь 2012 г.

Эта статья из серии статей о драйверах устройств для Linux, в которой рассказывается об использовании журнала сообщений ядра и о расширениях компилятора GCC, используемых в ядре.

Воодушевленная тем, как Пагс произвел впечатление на профессора на прошедшем уроке, Светлана хотела сделать то же самое. И вскоре представилась возможность: надо было узнать, куда выдает информацию команда printk. Поэтому сразу, как только она оказалась в лабораторном классе, она села за самый лучший компьютер, зарегистрировалась в системе и начала работу. Поскольку она знала профессора хорошо, она решила, что он должен был на прошедшем уроке намекнуть о возможном решении. Просмотрев все, что они изучили, она вспомнила о демонстрационной выдаче сообщения об ошибке для insmod vfat.k - при помощи команды dmesg | tail. Она сразу же попыталась этим воспользоваться и увидела данные, которые были выданы командой printk.

Но как они здесь оказались? Легкое прикосновение к ее плечу отвлекло ее от размышлений. "Пойдем, выпьем кофе?" - предложил Пагс.

"Но мне нужно ..."

"Я знаю, о чем ты думаешь" - прервал Пагс. "Пойдем, я расскажу тебе все о dmesg."

Журнал сообщений ядра

Попивая кофе Пагс начал свое объяснение.

Что касается параметров, то в printf и printk они одинаковые, разница лишь в том, что при программировании ядра, нам не потребуется беспокоиться о плавающих форматах %f, %lf и тому подобном. Но, в отличие от команды printf, команда printk не предназначена для выдачи дампа своих данных в какую-нибудь консоль.

На самом деле, она не может это делать; это нечто, что сидит в фоновом режиме и выполняется точно также, как библиотека, только тогда, когда она запускается либо из пространства аппаратных средств, либо из пространства пользователя. Все вызовы команды printk помещают свои выходные данные в кольцевой буфер (журнал) ядра. Затем демон syslog, работающий в пользовательском пространстве, берет их для окончательной обработки и перенаправляет на различные устройства в соответствие с тем, что задано в файле конфигурации /etc/syslog.conf.

В прошлой статье в вызовах printk вы должны были обратить внимание на макрос KERN_INFO. Это, в действительности, строковая константа, которая объединяется в одну строку со строкой формата, идущей за ней. Обратите внимание, что между ними нет запятой (,), это не два отдельных аргумента. В исходном коде ядра есть восемь таких макросов, которые определены в linux/kernel.h, а именно:

#define KERN_EMERG "<0>"   /* system is unusable                */
#define KERN_ALERT "<1>"   /* action must be taken immediately    */
#define KERN_CRIT "<2>"    /* critical conditions     */
#define KERN_ERR "<3>"     /* error conditions            */
#define KERN_WARNING "<4>" /* warning conditions      */
#define KERN_NOTICE "<5>"  /* normal but significant condition    */
#define KERN_INFO "<6>"    /* informational           */
#define KERN_DEBUG "<7>"   /* debug-level messages        */

Теперь, в зависимости от этих уровней журналирования (т. е. первых трех символов в строке формата), демон пользовательского пространства syslog перенаправляет каждое сообщения в соответствие с заданной конфигурацией. Обычно местом, куда перенаправлются сообщения всех уровней журналирования, является журнальный файл /var/log/messages. Таким образом, все данные, выдаваемые командой printk, по умолчанию находятся в этом файле. Впрочем, можно изменить настройку — например, пересылать сообщения на последовательный порт (например, /dev/ttyS0) или на все консоли, как это обычно происходит в случае возникновения события KERN_EMERG.

Сообщения теперь находятся в буфере /var/log/messages, причем в нем находятся сообщения не только из ядра, но и от различных демонов, работающих в пользовательском пространстве. К тому же, этот файл обычно нельзя читать от имени обычного пользователя. Поэтому для непосредственного разбора сообщений, находящихся в кольцевом буфере ядра, предоставляется утилита пользовательского пространства dmesg, которая выводит дамп буфера в стандартный выходной поток. На рис.1 показаны фрагменты вывода в стандартный выходной поток.

Рис.1: Журналирование сообщений ядра

Расширения GCC, предназначенные для ядра

Светлана, расстроившись из-за того, что теперь она не сможет сказать, что сама нашла решение, ответила: "Поскольку ты мне все объяснил о печати в ядре, почему бы тебе также не рассказать о странном языке C, используемом в драйвере — о специальных ключевых словах __init, __exit и т.д."

Это не специальные ключевые слова. Язык C, используемый в ядре, - это не "странный язык C"; это просто стандартный C с некоторыми дополнительными расширениями из компилятора GCC. Макросы __init и __exit - это только два из этих расширений. Впрочем, они совсем не актуальны в случае, когда мы используем их в динамически загружаемых драйверах; они актуальны только тогда, когда тот же самый код будет встроен в ядро. Все функции, отмеченные как __init, автоматически помещаются компилятором GCC при компиляции в секцию инициализации init, а все функции, отмеченные как __exit, помещаются в секцию выхода exit образа ядра.

Какая от этого польза? Предполагается, что все функции с отметкой __init должны выполняться только один раз во время загрузки системы (и не должны выполняться снова до следующей загрузки системы). Так что, как только они будут выполнены во время загрузки системы, ядро удалит их и высвободит оперативную память (будет удалена вся секция init). Аналогичным образом предполагается, что при остановке системы будут вызваны все функции из секции exit.

Теперь, когда система, так или иначе, завершает работу, зачем вам нужно освобождать память? Поэтому секция exit даже не загружается в ядро - еще одна крутой прием оптимизации. Это прекрасный пример того, как ядро и GCC работают рука об руку для достижения максимальной оптимизации; мы по ходу дела рассмотрим еще много других приемов оптимизации. И именно из-за этого ядро Linux можно компилировать только с помощью компиляторов на базе GCC — они сильно связаны друг с другом.

Правила выхода из функций ядра

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

Возьмем, к примеру, правило программирования ядра, касающееся возврата значений из функций. Для любой функции ядра требуется обработка ошибок, как правило, возвращаемых в виде целочисленного типа, причем возвращаемое значение должно соответствовать следующему правилу. При ошибке мы возвращаем отрицательное число: минус добавляется макросом, находящимся в заголовке ядра Linux linux/errno.h, который включает в себя заголовки различных ошибок в исходном коде ядра, а именно - asm/errno.h, asm-generic/errno.h, asm-generic/errno-base.h.

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

Язык С ядра — чистый С

После того, как Светлана вернулась в лабораторию, она вспомнила, что профессор упомянул, что при программировании ядра не следует использовать заголовочные файлы /usr/include. Но Пагс сказал, что язык С, используемый для ядра, является просто стандартным языком C с некоторыми расширениями GCC. Так что, здесь конфликт?

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

Означает ли это, что все стандартные библиотеки, и, следовательно, все стандартные функции ANSI не являются частью "чистого" C? Да, это верно. Из-за этого очень тяжело кодировать ядро?

Ну, не по этой причине. На самом деле, разработчики ядра разработали свой собственный набор необходимых функций, которые являются частью кода ядра. Функция printk является лишь одной из них. Аналогичным образом многие функции, предназначенные для работы со строками, функции работы с памятью и многое другое, являются частью исходного кода ядра; они расположены различных директориях kernel, ipc, lib и так далее, вместе с соответствующими заголовочными файлами, которые находятся в директории include/linux.

"О, да! Именно поэтому мы должны иметь исходный код ядра для сборки драйвера" — согласилась Светлана.

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

"В лаборатории установлен весь исходный код. Но если я захочу попробовать наши драйвера на моей системе Linux у себя в комнате общежития, то как это можно будет сделать?" - спросила Светлана.

"В нашей лаборатории используется система Fedora, в которой исходный код ядра, в отличие от стандартной установки, при которой код расположен в директории /usr/src/linux, обычно устанавливается в директории /usr/src/kernels/<kernel-version>. Администраторы в лаборатории должны устанавливать его из командной строки с помощью команды yum install kernel-devel. Я использую систему Mandriva и устанавливаю исходный код ядра с помощью команды urpmi kernel-source", - ответил Пагс.

"Но у меня система Ubuntu" — сказала Светлана.

"Хорошо! В этом случае для получения исходного кода просто воспользуйся утилитой apt-get, - возможно, apt-get install linux-source" - ответил Пагс.

Подведем итог

Лабораторные занятия почти закончились, когда вдруг Светлана из любопытства неожиданно спросила: "Эй, Пагс, какую тему мы будем изучать следующей на наших занятиях по драйверам устройств для Linux?"

"Хм ... скорее всего, символьные драйвера" — отреагировал Пагс.

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


К предыдущей статье Оглавление К следующей статье