Мигаем светодиодом из модуля ядра Linux
Всем привет. В этой статье хочу поделиться опытом создания простого модуля ядра Linux. Статья будет полезна тем, кто хотел бы понять как писать модули ядра, но не знает с чего начать.
Мне давно хотелось разобраться в этой теме, но до недавнего времени не знал как к ней подойти. Хотелось, чтобы модуль был достаточно простым, но сложнее чем сообщение «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
#include
#include
#include
#include
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);
Вроде описал все важные моменты. Если у читателей возникнут какие-то вопросы с удовольствием отвечу на них в комментариях.