Мигаем светодиодом из модуля ядра 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 <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);
    

    Вроде описал все важные моменты. Если у читателей возникнут какие-то вопросы с удовольствием отвечу на них в комментариях.
    • +26
    • 12.3k
    • 7
    Share post

    Similar posts

    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 7

    • UFO just landed and posted this here
        +2
        Не уверен, что статья подходит для Хабра. Чаще такого рода статьи вижу на Geektimes поэтому написал сюда.
        +1

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

          0
          Нет, частоту не замерял.
          +1
          Таймер програмный или хардварный?
            0
            Таймер скорее программный. На сколько мне известно Linux при старте настраивает 1 хардварный таймер. И в дальнейшем по нему все синхронизирует. Предполагаю что hrtimer записывает процессы в очередь и с какой-то периодичностью проверяет не пора ли процессы из этой очереди вызывать.
            0
            Вообще, ключевое слово static для функции в C++ значит абсолютно тоже самое, что и в C: видимость функции ограничивается текущей единицей трансляции (файлом).

            Only users with full accounts can post comments. Log in, please.