Всем привет. В этой статье хочу поделиться опытом создания простого модуля ядра Linux. Статья будет полезна тем, кто хотел бы понять как писать модули ядра, но не знает с чего начать.
Мне давно хотелось разобраться в этой теме, но до недавнего времени не знал как к ней подойти. Хотелось, чтобы модуль был достаточно простым, но сложнее чем сообщение «Hello world!» выведенное в log файле. В итоге я решил попробовать помигать светодиодом. Дополнительная цель была вывести параметр отвечающий за частоту мигания в sysfs.
Для проведения эксперимента я использовал плату Orange Pi One с Ubuntu Linux на борту (версия ядра 3.4.113). Для того чтобы собрать модуль ядра вам понадобиться компилятор gcc, утилита make и заголовочные файлы ядра. Чтобы установить заголовочные файлы запустите следующую команду:
Далее я разберу на мой взгляд самые интересные части модуля. В целях экономии места весь код тут приводить не буду, он вместе с make файлом доступен на github.
В модуле я использовал заголовочные файлы:
kernel.h и module.h нужно включать всегда при написании модуля ядра, gpio.h собственно отвечает за работу с GPIO, hrtimer.h (high resolution timer) – заголовочный файл таймера, moduleparam.h – нужен для вывода параметров в sysfs.
Чтобы не светить свои переменные и функции в ядро системы, все они должны быть описаны как static. На всякий случай отмечу, что ядро написано на C и static, в отличие от С++ означает то, что объект доступен только внутри исполняемого файла.
Точкой входа является:
Здесь я инициализирую переменные которые в дальнейшем буду использовать в том числе:
ktime_set инициализирует тип данных ktime_t задавая ему нужное количество секунд (gpio_blink_interval_s) и наносекунд (0). В дальнейшем эту переменную будет использовать таймер.
Далее идет запрос на использование GPIO:
Эта функция в случае успеха возвращает 0, так что в дальнейшем я проверяю что она вернула. Далее выбранный пин нужно установить на вывод сигнала и указать значение по умолчанию.
Если никаких ошибок не было, то инициализирую и запускаю таймер
Функция обратного вызова таймера будет gpio_blink_timer_callback. В этой функции я меняю значение пина на противоположное
задаю когда таймер должен сработать в следующий раз
и возвращаю HRTIMER_RESTART.
Теперь разберем как показать какую нибудь переменную в sysfs. Для этого я использую макрос
Первый параметр этого макроса — имя файла в sysfs. Второй — структура данных содержащая функции обратного вызова. Третий параметр — указатель на реальную переменную и четвертый права доступа к файлу в sysfs.
Функции из kp_ops вызываются когда пользователь меняет значения sysfs файла или читает его значение. Вот как я их инициализировал:
В данном случае интерес представляет set, так как в нем устанавливается новое значение gpio_timer_interval.
В точке выхода я очищаю все используемые ресурсы
Точку входа и выхода обязательно нужно указать в соответствующих макросах
Вроде описал все важные моменты. Если у читателей возникнут какие-то вопросы с удовольствием отвечу на них в комментариях.
Мне давно хотелось разобраться в этой теме, но до недавнего времени не знал как к ней подойти. Хотелось, чтобы модуль был достаточно простым, но сложнее чем сообщение «Hello world!» выведенное в log файле. В итоге я решил попробовать помигать светодиодом. Дополнительная цель была вывести параметр отвечающий за частоту мигания в sysfs.
Для проведения эксперимента я использовал плату Orange Pi One с Ubuntu Linux на борту (версия ядра 3.4.113). Для того чтобы собрать модуль ядра вам понадобиться компилятор gcc, утилита make и заголовочные файлы ядра. Чтобы установить заголовочные файлы запустите следующую команду:
sudo apt-get install linux-headers-$(uname -r)
Далее я разберу на мой взгляд самые интересные части модуля. В целях экономии места весь код тут приводить не буду, он вместе с make файлом доступен на github.
В модуле я использовал заголовочные файлы:
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/gpio.h>
#include <linux/hrtimer.h>
#include <linux/moduleparam.h>
kernel.h и module.h нужно включать всегда при написании модуля ядра, gpio.h собственно отвечает за работу с GPIO, hrtimer.h (high resolution timer) – заголовочный файл таймера, moduleparam.h – нужен для вывода параметров в sysfs.
Чтобы не светить свои переменные и функции в ядро системы, все они должны быть описаны как static. На всякий случай отмечу, что ядро написано на C и static, в отличие от С++ означает то, что объект доступен только внутри исполняемого файла.
Точкой входа является:
static int blink_module_init(void)
Здесь я инициализирую переменные которые в дальнейшем буду использовать в том числе:
gpio_timer_interval = ktime_set(gpio_blink_interval_s, 0);
ktime_set инициализирует тип данных ktime_t задавая ему нужное количество секунд (gpio_blink_interval_s) и наносекунд (0). В дальнейшем эту переменную будет использовать таймер.
Далее идет запрос на использование GPIO:
err = gpio_request(BLINK_PIN_NR, "blink_led");
Эта функция в случае успеха возвращает 0, так что в дальнейшем я проверяю что она вернула. Далее выбранный пин нужно установить на вывод сигнала и указать значение по умолчанию.
err = gpio_direction_output(BLINK_PIN_NR, GPIOF_INIT_LOW);
Если никаких ошибок не было, то инициализирую и запускаю таймер
hrtimer_init(&gpio_blink_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
gpio_blink_timer.function = &gpio_blink_timer_callback;
hrtimer_start(&gpio_blink_timer, gpio_timer_interval, HRTIMER_MODE_REL);
Функция обратного вызова таймера будет gpio_blink_timer_callback. В этой функции я меняю значение пина на противоположное
gpio_value ^= 0x01;
gpio_set_value(BLINK_PIN_NR, gpio_value);
задаю когда таймер должен сработать в следующий раз
hrtimer_forward_now(&gpio_blink_timer, gpio_timer_interval);
и возвращаю HRTIMER_RESTART.
Теперь разберем как показать какую нибудь переменную в sysfs. Для этого я использую макрос
module_param_cb(gpio_blink_interval_s, &kp_ops, &gpio_blink_interval_s, 0660);
Первый параметр этого макроса — имя файла в sysfs. Второй — структура данных содержащая функции обратного вызова. Третий параметр — указатель на реальную переменную и четвертый права доступа к файлу в sysfs.
Функции из kp_ops вызываются когда пользователь меняет значения sysfs файла или читает его значение. Вот как я их инициализировал:
static const struct kernel_param_ops kp_ops =
{
.set = &set_blink_interval,
.get = &get_blink_interval
};
В данном случае интерес представляет set, так как в нем устанавливается новое значение gpio_timer_interval.
gpio_timer_interval = ktime_set(gpio_blink_interval_s, 0);
В точке выхода я очищаю все используемые ресурсы
static void blink_module_exit(void)
{
hrtimer_cancel(&gpio_blink_timer);
gpio_set_value(BLINK_PIN_NR, 0);
gpio_free(BLINK_PIN_NR);
printk(KERN_ALERT "Blink module unloaded\n");
}
Точку входа и выхода обязательно нужно указать в соответствующих макросах
module_init(blink_module_init);
module_exit(blink_module_exit);
Вроде описал все важные моменты. Если у читателей возникнут какие-то вопросы с удовольствием отвечу на них в комментариях.
Комментарии (7)
besitzeruf
14.04.2018 21:29+1Таймер програмный или хардварный?
mksma Автор
14.04.2018 23:09Таймер скорее программный. На сколько мне известно Linux при старте настраивает 1 хардварный таймер. И в дальнейшем по нему все синхронизирует. Предполагаю что hrtimer записывает процессы в очередь и с какой-то периодичностью проверяет не пора ли процессы из этой очереди вызывать.
FrozenWalrus
15.04.2018 17:43Вообще, ключевое слово static для функции в C++ значит абсолютно тоже самое, что и в C: видимость функции ограничивается текущей единицей трансляции (файлом).
caway
Почему не хабр?
Makefile бесполезен.
mksma Автор
Не уверен, что статья подходит для Хабра. Чаще такого рода статьи вижу на Geektimes поэтому написал сюда.