Всем привет. В этой статье хочу поделиться опытом создания простого модуля ядра 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 <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)


  1. caway
    14.04.2018 07:29

    Почему не хабр?
    Makefile бесполезен.


    1. mksma Автор
      14.04.2018 07:45
      +1

      Не уверен, что статья подходит для Хабра. Чаще такого рода статьи вижу на Geektimes поэтому написал сюда.


  1. Zuy
    14.04.2018 18:31
    +1

    А не замеряли, какая максимальная частота получается если в цикле пином махать из ядра?


    1. mksma Автор
      14.04.2018 22:31

      Нет, частоту не замерял.


  1. besitzeruf
    14.04.2018 21:29
    +1

    Таймер програмный или хардварный?


    1. mksma Автор
      14.04.2018 23:09

      Таймер скорее программный. На сколько мне известно Linux при старте настраивает 1 хардварный таймер. И в дальнейшем по нему все синхронизирует. Предполагаю что hrtimer записывает процессы в очередь и с какой-то периодичностью проверяет не пора ли процессы из этой очереди вызывать.


  1. FrozenWalrus
    15.04.2018 17:43

    Вообще, ключевое слово static для функции в C++ значит абсолютно тоже самое, что и в C: видимость функции ограничивается текущей единицей трансляции (файлом).