Решение проблем, возникающих при выполнении команд ./configure, make и make install

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

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

Мы можем разделить ошибки на три категории:

  • Ошибки при выполнении команды ./configure
  • Ошибки при выполнении команды make
  • Ошибки при выполнении команды make install

Очевидно, что ошибки при выполнении команды ./configure, возникают во время выполнения скрипта конфигурации, ошибки при выполнении команды make возникают во время выполнения команды make, а ошибки при выполнении команды make install, соответственно, возникают при выполнении команды make install. Далее будет представлен лист типичных ошибок и способ их решения, разделенный на эти три категории.

Ошибки при выполнении команды ./configure

Следующий список содержит некоторые общие ошибки, которые может выдать каманда ./configure. Ошибки отсортированы по частоте возникновения. Сначала наиболее часто встречающиеся. Вещи между ( и ) являются опциональными, они могут не появлятся. OR, выделенное жирным курсивом означает, что несколько ошибок имеют одно решение. Текст между < и > показывает тип строки, которая должна появиться в этом месте.

1. (configure:) (error:) <somename> (&ltsomeversion> (or higher)) not found. (Please check your installation!) OR checking for <somename>… (configure:) (error:) not found. OR (configure:) (error:) <somename> (<someversion> (or newer)) is required to build <package-you’re-trying-to-compile>

  • Это обычно означает что -dev или -devel версия пакета ,который называется <somename> не установлена у вас на компьютере. Используйте менеджер пакетов вашего дистрибутива (или любой другой способ найти и установить пакет), чтобы найти пакет <somename> и установить его, если это возможно, -dev или -devel версию. Если -dev или -devel версия уже установлена, или её не сущечтвует, посмотрете на версию уже установленной. Она достаточно новая? Если она ниже, чем <someversion>, попробуйте обновить пакет. Если обновить пакет не представляется возможным, вы можете попробовать скомпилировать более мтарую версию программы. Более старые версии обычно используют более старые версии библиотек и программ, необходимых для компиляции.

2. (configure:) (error:) cannot find header (file) <somename>.h OR (configure:) (error:) (header) (file) <somename>.h missing! OR <similar>

  • Конфигурационный скрипт не может найти .h файл, необходимый для компиляции. Эта ошибка похожа на предыдущую, в которой необходимо установить -dev или -devel версию пакета. Однако, обычно не понятно какой пакет нужно установить для решения этой проблемы, так как <somename> может быть очень общим названием. Попробуйте поискать в интернетет <somename>.h, чтобы узнать в каком пакете этот файл находится, а затем установите этот пакет (и его -dev или -devel версия, если это возможно) с помощью менеджера пакетов вашего дистрибутива.

3. (configure:) (error:) no acceptable cc found in <somedirectories>

  • Вы используете для установки компилятор gcc, А переменная окружения CC отсутствует или не установлена. Убедитесь, что пакет gcc установлен, используя менеджер пакетов вашего дистрибутива. Если этот пакет не установлен, установите его. Если он установлен, попробуйте выполнить следующую команду:
[rechosen@localhost ~]$ export CC="/usr/bin/cc"

Если это помогло, вы можете добавить эту команду в /etc/profile (это файл, содержащий команды, которые выполняются когда пользователь входит в систему) и тогда вам не придется набирать её снова.

4. (configure:) (error:) C++ preprocessor «/lib/cpp» fails sanity check

  • Ваш пакет g++ отсутствует или поврежден. Используйте Используйте менеджер пакетов вашего дистрибутива (или любой другой способ найти и установить пакет), чтобы найти пакет g++ и установить его. Не забудьте, что в некоторых дистрибутивах этот пакет называется не g++. Fedora, например, использует название gcc-c++ в соем репозитарии yum. Если вы не можете найти g++, попробуйте поискать c++, cpp или gcc.

5. (configure:) (error:) C++ preprocessor «CC (-E)» fails sanity check

  • Эта ошибка вызвана странным «багом» в некоторых версиях библиотеки libtool, которая заставляет конфигурационный скрипт проверять все компиляторы поддерживающиеся libtool. Наиболее простое решение — установить g++.

Ошибки при выполнении команды make

Так как ошибки при выполнении команды make обычно очень специфичны, я дам список основных вещей, которые могут помочь:

  • Если вы компилируете программу с использованием gcc 4 (используйте gcc -dumpversion чтобы это выяснить), попробуйте использовать более старые версии компилятора. Сначала убедитесь, что у вас установлена более старая версия. Обычно это можно узнать, использовав следующую команду:
    [rechosen@localhost ~]$ ls /usr/bin/gcc*

    Если она вернет что-то вроде этого:

    /usr/bin/gcc /usr/bin/gcc32

    То можете использовать команды gcc32, чтобы скомпилировать программу в более ранними версиями gcc. Если команда не вернет подобной строки, то используя менеджер пакетов вашего дистрибутива, найдите и установите более ранние версии gcc (обычно они называются compat-gcc или gcc-<versionnumber>). После установки, вам должна быть доступна альтернативная версия gcc. Её можно найти используя команду ls. Заставить команды ./configure, make и make install использовать более старую версию gcc можно так:

    [rechosen@localhost ~]$ CC="/usr/bin/gcc32" ./configure
    [rechosen@localhost ~]$ CC="/usr/bin/gcc32" make
    [rechosen@localhost ~]$ CC="/usr/bin/gcc32" make install

    Конечно путь /usr/bin/gcc32 надо заменить на тот, по которому у вас находится альтернативная версия gcc.

  • Иногда ошибки могут вызваны простым «багом» программы. Попробуйте скачать последнюю версию программы (используя её cvs, svn или другой репозитарий, или скачав последний снимок) и скомпилируйте её, возможно эта ошибка уже исправлена.
  • Ошибка при выполнении комадны make может быть также вызвана неправильной версией необходимой библиотеки или программы. Эта проблема часто встречается для очень новых или очень старых пакетов. Проверьте зависимости пакета (они обчно написаны на сайте программы) и сравните номера версий с версиями, установленными у вас на компьютере (их обчно можно посмотреть, используя менеджер пакетов вашего дистрибутива). Если номер версии в вашей системе больше того, которые написан на сайте, возможно вы пытаетесь скомпилировать очень старый пакет. Если вам дейсвительно необходимо его скомпилировать, попробуйте установить более старые версии зависимых пакетов.Как бы то небыло, обычно лучше поискать другой способ установки этого пакета или поискать альтернативу. Если номер версии в системе меньше, чем на сайте, вы можете попробовать обновить соответствующий пакет.Вы можетепопробовать обновить требуемую библиотеку или скомпилировать более старую весию программы.Так же проверьте, может уже есть этот пакет, скомпилированный для авшего дистрибутива. Его установка, обычно, проще, чем исправление ошибок компиляции.
  • Другая вещь, которую стоит попробовать — это поиск специфической ошибки в интернете. Если вы не нашли ничего полезного, попробуйте убрать такие вещи, как номер строки (он может измениться с новой версией), номер версии (его можно заменить звездочкой, если он содержится в названии программы) и специальные символы, такие как кавычки, так как они влияют на поисковый сервис. Обычно можно найти много информации в листе рассылок. Иногда выходит патч, который исправляет ошибки в исходном коде. Его можно применить слудеющим образом:
    [rechosen@localhost ~]$ patch -Np1 <patchfile>

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

Ошибки при выполнении команды make install

Эти ошибки обычно легко понять, но я все равно про них напишу. Есть два наиболее частых случая, почему команда make install возвращает ошибку:

  • У вас нет прав пользователя root. Попробуйте выполнить команду make install, используя команду sudo, или станеть пользователем root, используя команду su. Команда sudo применяется следующим образом:
    [rechosen@localhost ~]$ sudo make install

    Она спросит пароль; обычно используется собственный пароль или пароль пользователя root. Вы можете испльзовать команду su, чтобы стать польpователем root:

    [rechosen@localhost ~]$ su

    Эта команда тоже спросит пароль, но в данном случае наобходим именно пароль пользователя root. После того, как вы стали пользователем root, просто выполните команду make install.

  • Пакет, который вы только что скомпилировали не имеет команды установки. В этом случае вам надо скопировать скомпилированный бинарный файл в директорию bin вручную. Если вы выполните команду ls в директории исходного кода, исполняемый файл должен быть светло зеленого цвета. Его надо скопировать в /usr/bin (или, если хотите, в /usr/local/bin) следующей командой:
    [rechosen@localhost ~]$ cp <executablefile> /usr/bin

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

Другие проблемы

Вот список некоторых других возможных проблем и их решения:

  • Все проходит хорошо, но когда я набираю имя программы, которую только что установил, bash говорить, что не может её найти. Это обычно происходит из-за того, что make install устанавливает все в /usr/local или in /opt/<packagename>. Посмотрите на вывод команды make install: куда скопированы файлы? Попробуйте добвавить эту директорию в переменную PATH (следующий пример приведен для пакета, установленного в /usr/local):
    [rechosen@localhost ~]$ export PATH="$PATH:/usr/local/bin"

    Вам надо заменить /usr/local/bin на директорию, в которой установлены исполняемые файлы вашего пакета. Если это помогло, добавьте эту строку в /etc/profile, чтобы вам не пришлось набирать её каждый раз. Кстати, вы можете контролировать место, куда установится пакет, указав следующую опцию, когда запускаете конфигурационный скрипт:

    [rechosen@localhost ~]$ ./configure --prefix=/usr

    Измените /usr на директорию, в которую хотите установить пакет. Не забудьте, что вы устанавливаете только префикс; бинарные файлы установятся в свою поддиректорию, библиотеки в свою, заголовочные файлы в свою и т.д. Например при использовании указанного префикса, бинарные файлы будут установлены в /usr/bin.

  • Я хочу установить очень старую версию пакета, но я не могу найти исходный код в интернете. У вас еще остается маленький шанс. Попробуйте найти rpm файл пакета той версии, которую вы хотите и скачайте соответствующий src rpm файл. Распаковать его можно следующим образом:
    [rechosen@localhost ~]$ rpm2cpio <pmfile> | cpio -idv

    Теперь можно использовать исходный код, извлеченный из rpm файла.

Введение в написание модулей ядра Linux

С давних пор, первым шагом в изучении нового языка программирования, является написание программы, которая печатает «Hello, world!». В этой статье мы используем такой подход при изучении написания простых модулей ядра Linux и драйверов устройств. Мы изучим, как напечатать «Hello, world!» из модуля ядра тремя различными способами: printk(), из /proc файла и из устройства в /dev.

Приготовления.

Модуль ядра — это часть кода ядра, которая может быть динамически загружена и выгружена во время работы ядра. Так как он запускается как часть ядра и ему необходимо близко взаимодействовать с ядром, модуль ядра не может быть скомпилирован в вакууме. Ему нужны, по крайней мере, заголовочные файлы и настройки для ядра, в которое он будет загружен. Компилирование модуля также требует набор средств разработки, такие как компилятор. Для простоты, мы сжато опишем как установить требуемые вещи для создания модуля ядра для Debian, Fedora и «ванильного» ядра Linux из тарболла. Во всех случаях, вы должны скомпилировать свой модуль относительно исходного кода работающего на вашей системе ядра.

Исходные коды ядра обычно находятся в директории /usr/src/linux, права доступа к которой имеет только root. В настоящее время, рекомендуется размещать исходные коды ядра в домашней директории, права на которую будут иметь не-root пользователи. Комманды в этой стстье запускаются не из-под root, используя sudo для временного получения привелегий root-а только тогда, когда это необходимо. Для установки sudo, прочитайте sudo(8), visudo(8) и sudoers(5). Вы можете запускать все комманды и из-под root, если пожелаете. В этом случае, вам потребуются права root-а для того, чтобы следовать инструкциям из этой статьи.

Приготовления для компилирования модулей ядра в Debian.

Пакет module-assistant для Debian настраивает систему для сборки внешних модулей ядра. Установите его с помощью:

$ sudo apt-get install module-assistant

Это все. Теперь вы можете скомпилировать модуль ядра. Дополнительно, вы можете прочитать Debian Linux Kernel Handbook, чтобы иметь более глубокое представление о работе с ядром в Debian.

Приготовления для компилирования модулей в Fedora

Пакет kernel-devel в Fedora имеет все необходимые заголовочные файлы ядра и инструменты для сборки внешних модулей ядра. Установите его с помощью:

$ sudo yum install kernel-devel

И снова, это все, что нужно, — теперь вы можете компилировать модули ядра. Документацию по теме можно найти в Fedora release notes.

«Ванильное» ядро и его настройки.

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

Исходные коды стандартного ядра Linux размещаются на http://kernel.org/. Ссылка на последний стабильный релиз находится на главной странице. Скачайте полностью весь релиз, а не патч. Например, на момент написания статьи, последний стабильный релиз был расположен на http://kernel.org/pub/linux/kernel/v2.6/linux-2.6.21.5.tar.bz2. Для более быстрой загрузки, найдите ближайшее к вам зеркало из списка зеркал и скачивайте с него. Используйте wget, это самый простой способ скачать исходные коды в режиме докачки. HTTP часто блокируется и если скачивание прервется, оно продолжится с того момента, на котором остановилась:

$ wget -c "http://kernel.org/pub/linux/kernel/v2.6/linux-.tar.bz2"

Распакуйте исходные коды ядра:

$ tar xjvf linux-.tar.bz2

Теперь ваше ядро расположено в linux-/. Войдите в эту директорию с начните конфигурацию:

$ cd linux-
 
$ make menuconfig

Существует набор параметров для make, чтобы автоматически собрать и установить ядро в различной форме: пакет Debian, пакет RPM, тарболл и т.д. Чтобы узнать их всех, наберите:

$ make help

Параметр, который будет работать почти на каждом дистрибутиве:

$ make tar-pkg

Когда сборка завершится, установите новое ядро с помощью:

$ sudo tar -C / -xvf linux-.tar

Затем создайте ссылку на дерево исходных кодов ядра в его стандартном расположении:

$ sudo ln -s 
 
<расположение директории с исходным кодом> 
 
/lib/modules/'uname -r'/build

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

«Hello, World!» с помощью printk()

Для нашего первого модуля, чтобы напечатать «Hello, world!», мы будем использовать модуль, который использует возможность ядра показа сообщений. В общем-то, printk() является printf() для ядра. Сообщения printk() направляются в буффер сообщений ядра и копируются /var/log/messages.

Скачайте тарболл модуля hello_prink и распакуйте его:

$ tar xzvf hello_printk.tar.gz

Он содержит два файла: Makefile, который содержит инструкции для сборки модуля, и hello_printk.c, исходный код модуля. Для начала, осмотрим Makefile:

obj-m := hello_printk.o

obj-m — это список модулей ядра для сборки. .o будет автоматически собран из соответствующего .c файла (нет необходимости указывать список исходных файлов).

KDIR  := /lib/modules/$(shell uname -r)/build

KDIR — это расположение исходного кода ядра. Текущий стандарт это ссылка на соответствующее дерево исходных кодов, содержащее скомпилированные модули.

PWD := $(shell pwd)

PWD — это текущая директория и расположения наших исходных кодов модуля.

default:
 
    $(MAKE) -C $(KDIR) M=$(PWD) modules

default это стандартный параметр make. До тех пор, пока не будет указан другой параметр, make будет исполнять правила для этого параметра. Правило, указанное здесь запускает make с рабочей директорией, содержащей исходные коды ядра и компилирует модули только в $(PWD) (локальной) директории. Это позволяет нам использовать все правила для компилирования модулей, определенные в главном дереве исходного кода ядра.

А сейчас, давайте пройдемся по коду hello_printk.c.

#include
 
#include

Эти включения заголовочных файлов, предоставляемых ядром, требуются для всех модулей. Они включают такие вещи, как определение макроса module_init(), который мы увидим позже.

static int __init
 
hello_init(void)
 
{
 
        printk("Hello, world!\n");
 
        return 0;
 
}

Это функция инициализации модуля, которая выполняется при его первой загрузке. Ключевое слово __init указывает компилятору, что этот код будет выполнен единожды, когда модуль загрузится. Строка, содержащая printk() запишет строку «Hello, world!» в буффер сообщений ядра. Формат аргументов printk(), в большинстве случаев, идентичен формату printf(3).

module_init(hello_init);

Макрос module_init() сообщает ядру, какую функцию выполнить при загрузке модуля. Все остальное, происходящее внутри ядра — результат установок функции инициализации модуля.

static void __exit
 
hello_exit(void)
 
{
 
        printk("Goodbye, world!\n");
 
}
 
module_exit(hello_exit);

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

MODULE_LICENSE("GPL");
 
MODULE_AUTHOR("Valerie Henson ");
 
MODULE_DESCRIPTION("\"Hello, world!\" minimal module");
 
MODULE_VERSION("printk");

MODULE_LICENSE() сообщает ядру, под какой лицензией распространяется исходный код модуля, что влияет на то, к каким символам (функциям, переменным и т.д.) он может получить доступ в главном ядре. Модуль под лицензией GPLv2 (как на примере) имеет доступ ко всем символам. Другие лицензии модуля предупредят ядро о том, что был загружен закрытый модуль, или модуль, которому нельзя доверять. Модули без MODULE_LICENSE() распознаются ядром, как модули, выпущенные не под GPLv2. Макросы MODULE_*() полезны для идентификации информации о модуле в стандартной форме.

Теперь скомпилируем и запустим код. Войдите в директорию с кодом и соберите модуль:

$ cd hello_printk
 
$ make

Затем, загрузите модуль с помощью insmod и проверьте, что он печатает свое сообщение, используя dmesg, программу, которая выводит на экран содержимое буффера сообщений ядра:

$ sudo insmod ./hello_printk.ko
 
$ dmesg | tail

Вы должны увидеть «Hello, world!». А теперь выгрузим модуль, используя rmmod, и проверим сообщение о выходе:

$ sudo rmmod hello_printk
 
$ dmesg | tail

Вы успешно скомпилировали и установили модуль ядра!

«Hello, World!» с помощью /proc

Один из простейших и наиболее популярных способов сделать связь между ядром и пользовательскими программами — файл в /proc. /proc это псевдофайловая система, где операция чтения из файлов возвращает данные, синтезированные ядром, а данные, записываемые в файлы, читаются и оперируются ядром. До появления /proc, связь пользователь-ядро осуществлялась посредством системных вызовов. Использование системных вызовов подразумевает выбор между нахождением системного вызова, который осуществляет нужное действие (зачастую это невозможно), созданием нового системного вызова (требует больших изменений в ядре, использования номера системного вызова, и в общем-то не одобряется), или использованием системного вызова ioctl(), который требует создания специального файла, с которым ioctl() оперирует (может возникнуть ошибка, и ещё больше не одобряется). /proc предоставляет простой, предопределенный путь передачи данных между ядром и пользовательским пространством с полезными инструментами и достаточной свободой действий модулей ядра.

Для наших целей, мы создадим файл в /proc, который выведет «Hello, world!» при чтении. Мы будем использовать /proc/hello_world. Скачайте и распакуйте тарболл hello_proc модуля. Заглянем в код hello_proc.c:

#include
 
#include
 
#include

На этот раз, мы добавили заголовочный файл для procfs, который включает поддержку работы с /proc.

Следующая функция вызовется, когда процесс выполнит read() на созданный нами файл в /proc. Это проще, чем реализовывать это полностью на системном вызове read(), так как нам надо разрешить считать строку «Hello, world!» только один раз.

static int
 
hello_read_proc(char *buffer, char **start, off_t offset, 
 
		int size, int *eof,
 
                void *data)
 
{

Аргументы этой функции нуждаются в подробном объяснении. buffer это указатель на буффер ядра, в который мы пишем выходные данные из read(). start используется для более сложных /proc файлов; здесь мы его игнорируем. offset указывает на то место, с которого надо начинать считывание внутри «файла»; здесь мы передадим 0 для простоты. size это размер буффера в байтах; мы должны удостовериться в том, что мы не пишем данные после того, как буффер закончится. eof указывает на конец файла. Аргумент data, опять же, для более сложных файлов и здесь игнорируется.

А теперь, тело функции:

        char *hello_str = "Hello, world!\n";
 
        int len = strlen(hello_str);
 
        if (size &lt; len)
 
                return&lt; -EINVAL;
 
        if (offset != 0)
 
                return 0;
 
        strcpy(buffer, hello_str);
 
        /*
 
         * Signal EOF.
 
         */
 
        *eof = 1;
 
        return len;
 
}

Затем, нам надо зарегистрировать подсистему /proc в функции инициализации нашего модуля:

static int __init
 
hello_init(void)
 
{
 
        if (create_proc_read_entry("hello_world", 0, 
 
				NULL, hello_read_proc,
 
                                    NULL) == 0) {
 
                printk(KERN_ERR
 
     "Unable to register \"Hello, world!\" proc file\n");
 
                return -ENOMEM;
 
        }
 
        return 0;
 
}
 
module_init(hello_init);

И отменить эту регистрацию, когда модуль выгружен (если мы этого не сделаем, то когда процесс попробует считать /proc/hello_world, файловая система /proc попробует выполнить функцию, которая больше не существует, что вызовет kernel panic).

static void __exit
 
hello_exit(void)
 
{
 
        remove_proc_entry("hello_world", NULL);
 
}
 
module_exit(hello_exit);
 
MODULE_LICENSE("GPL");
 
MODULE_AUTHOR("Valerie Henson ");
 
MODULE_DESCRIPTION("\"Hello, world!\" minimal module");
 
MODULE_VERSION("proc");

Теперь мы готовы скомпилировать и загрузить модуль:

$ cd hello_proc
 
$ make
 
$ sudo insmod ./hello_proc.ko

Появится файл под именем /proc/hello_world, который будет выводить «Hello, world!» при чтении:

$ cat /proc/hello_world
 
Hello, world!

Вы можете создать много других /proc файлов для одного и того же драйвера, добавить функции, позваляющие запись в /proc файлы, создавать директории, полные /proc файлов, и так далее. Для чего-то более сложного, чем этот драйвер, проще и безопаснее использовать вспомогательные функции seq_file при написании интерфейсных функции для /proc. Для расширения своих знаний в этой области, прочтите Driver porting: The seq_file interface.

«Hello, World!» с помощью /dev/hello_world

Теперь мы реализуем «Hello, world!» с помощью файла устройства в /dev, /dev/hello_world. Раньше файл устройства был специальным файлом, создаваемым запуском хитроумного шелл-скрипта под названием MAKEDEV, который вызывал комманду mknod для создания любого возможного файла в /dev, невзирая на то, запущено ли это устройство в системе. Потом был devfs, который создавал /dev файлы, когда к ним впервые обращались, что вызывало много интересных проблем блокировки и множество попыток открыть файл устройства, чтобы проверить его существование. Сейчас с /dev работает udev, который связывает /dev с программами пользовательского окружения. Когда модули ядра регистрируют устройсва, они появляются в файловой системе sysfs, смонтированной в /sys. Программа из пользовательского окружения, udev, следит за изменениями в /sys и динамечески создает записи в /dev, исходя из настроек, обычно размещаемых в /etc/udev.

Скачайте тарболл модуля hello_world. Рассмотрим исходный код hello_dev.c:

#include
 
#include
 
#include
 
#include
 
#include

Как мы видим из списка необходимых заголовочных файлов, для создания устройства требуется немного больше поддержки ядра, чем в предыдущих методах. fs.h включает в себя определения структур для операций над файлами, которые мы должны использовать, применительно к нашему /dev файлу. miscdevice.h включает поддержку регистрации различных файлов устройств. asm/uaccess.h влючает функции для тестирования на чтение или запись в памяти пользовательского окружения, без нарушения прав.

hello_read() — функция, вызываемая процессом системным вызовом read() к /dev/hello. Она выведет «Hello, world!» в буффер, передаваемый вызовом read().

static ssize_t hello_read(struct file * file, char * buf, 
 
                          size_t count, loff_t *ppos)
 
{
 
        char *hello_str = "Hello, world!\n";
 
        int len = strlen(hello_str); 
 
        if (count &lt; len)
 
                return -EINVAL;
 
        if (*ppos != 0)
 
                return 0;
 
        if (copy_to_user(buf, hello_str, len))
 
                return -EINVAL;
 
        *ppos = len;
 
        return len;
 
}

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

static const struct file_operations hello_fops = {
 
        .owner                = THIS_MODULE,
 
        .read                = hello_read,
 
};

Теперь создадим структуру, содержащую информацию для регистрации стороннего устройства в ядре:

static struct miscdevice hello_dev = {
 
        MISC_DYNAMIC_MINOR,
 
        /*
 
         * Name ourselves /dev/hello.
 
         */
 
        "hello",
 
        &amp;hello_fops
 
};

Как обычно, мы регистрируем устройство в функции инициализации модуля:

static int __init
 
hello_init(void)
 
{
 
        int ret;
 
        ret = misc_register(&amp;hello_dev);
 
        if (ret)
 
                printk(KERN_ERR
 
          "Unable to register \"Hello, world!\" misc device\n");
 
        return ret;
 
}
 
module_init(hello_init);

И не забываем выгружать устройство в функции выхода:

static void __exit
 
hello_exit(void)
 
{
 
        misc_deregister(&amp;hello_dev);
 
}
 
module_exit(hello_exit);
 
MODULE_LICENSE("GPL");
 
MODULE_AUTHOR("Valerie Henson ");
 
MODULE_DESCRIPTION("\"Hello, world!\" minimal module");
 
MODULE_VERSION("dev");

Скомпилируем и загрузим модуль:

$ cd hello_dev
 
$ make
 
$ sudo insmod ./hello_dev.ko

В системе появилось устройство с именем /dev/hello, которое выводит «Hello, world!» в момент считывания root-ом:

$ sudo cat /dev/hello
 
Hello, world!

Но оно не может считаться обычным пользователем:

$ cat /dev/hello
 
cat: /dev/hello: Permission denied
 
$ ls -l /dev/hello
 
crw-rw---- 1 root root 10, 61 2007-06-20 14:31 /dev/hello

Это происходит при стандартном правиле udev, которое, при появлении нового устройства, создает файл под названием /dev/<имя_устройства> и присваивает ему права на чтение 0660. Нам необходимо создать вместо устройства ссылку, читаемую обычными пользователями, с именем /dev/hello_world. Для того, чтобы это сделать, напишем правило udev.

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

KERNEL=="hello", SYMLINK+="hello_world", MODE="0444"

Разобьем это правило на части и более подробно разберем каждую из них.

KERNEL==»hello» приводит к выполнению правила, когда устройство с именем в строке (== это оператор сравнения) появляется в /sys. Устройство hello появляется когда мы вызываем misc_register() со структура, содержащей имя устройства «hello». Вот результат в /sys:

$ ls -d /sys/class/misc/hello/
 
/sys/class/misc/hello/

SYMLINK+=»hello_world» приказывает добавить hello_world к списку ссылок, которые должны создаваться при появлении устройства. В нашем случае, мы знаем, что список будет состоять из одной ссылки, но другие устройства могут иметь много udev правил, которые создают другие ссылки, так что было бы неплохо это сделать.

MODE=»0444″ назначает права доступа к оригинальному файлу устройства на 0444, что позволяет получить доступ на чтение к этому файлу.

Очень важно использовать правильный оператор (==, += или =), во избежание появления неожиданного результата.

Теперь, когда мы понимаем, что делает это правило, давайте установим его в директорию /etc/udev. Файлы правил udev раположены в той же манере, что и скрипты инициализации System V в /etc/init.d/. Udev выполняет каждый скрипт из директории правил udev, /etc/udev/rules.d, в алфавитном/номерном порядке. Также, как и скрипты инициализации System V, файлы в /etc/udev/rules.d являются обычными ссылками на реально существующие файлы, так что правила будут выполнятся в правильном порядке.

Скопируйте файл hello.rules из директории hello_dev в /etc/udev и создайте ссылку к нему, которая будет выполнятся до других файлов правил:

$ sudo cp hello.rules /etc/udev/
 
$ sudo ln -s ../hello.rules /etc/udev/rules.d/010_hello.rules

Теперь, перезагрузите драйвер hello world и посмотрите на записи в /dev:

$ sudo rmmod hello_dev
 
$ sudo insmod ./hello_dev.ko
 
$ ls -l /dev/hello*
 
cr--r--r-- 1 root root 10, 61 2007-06-19 21:21 /dev/hello
 
lrwxrwxrwx 1 root root      5 2007-06-19 21:21 /dev/hello_world -&gt; hello

Теперь у нас есть /dev/hello_world! Наконец, проверьте, что вы можете считывать устройства «Hello, world!», как обычный пользователь:

$ cat /dev/hello_world
 
Hello, world!
 
$ cat /dev/hello
 
Hello, world!

За более подробной информацией по написанию правил udev, обращайтесь к Writing udev rules, написанную Дэниэлом Дрэйком.