Пособие по программированию модулей ядра Linux. Ч.7

от автора


Заключительная часть последней версии руководства по созданию модулей ядра от 02 июля 2022 года. Здесь мы рассмотрим обработку прерываний, криптографию, стандартизацию интерфейсов с помощью модели устройства, а также разберём принцип работы драйвера виртуального устройства ввода и возможность внесения в модуль некоторой оптимизации. В завершение же я укажу на пару неявных, но важных нюансов, а также дам рекомендации по дальнейшему погружению в тему программирования ядра.

▍ Готовые части руководства:

15. Обработка прерываний

▍ 15.1 Обработчики прерываний

Во всех главах (за исключением предыдущей), мы реализовывали в ядре лишь ответы на запросы процессов, для чего-либо работали с особым файлом, либо отправляли ioctl() или системный вызов. Но работа ядра состоит не только в реагировании на запросы процессов. Ещё одной его немаловажной ответственностью является взаимодействие с подключённым к машине оборудованием.

Существует два типа взаимодействий между ЦПУ и остальным оборудованием компьютера. Первый – это когда ЦПУ отдаёт ему распоряжения. Распоряжение подразумевает, что оборудование должно сообщить что-либо процессору. Второй, называемый прерываниями, уже гораздо сложнее в реализации, поскольку обрабатывается, когда это нужно оборудованию, а не процессору. Как правило, аппаратные средства оснащены очень небольшим объёмом оперативной памяти, и если своевременно не считать предоставляемую ими информацию, она будет потеряна.

В Linux — аппаратные прерывания называются IRQ (Interrupt ReQuests). Существует два типа IRQ, короткие и длинные. Короткие прерывания, как и предполагает их имя, должны выполняться в краткий промежуток времени, во время которого остальная часть машины будет заблокирована, и обработка никаких других прерываний производиться не будет. Длительные же IRQ выполняются продолжительно и не препятствуют выполнению других прерываний (за исключением IRQ от одного и того же устройства). По возможности желательно объявлять обработчики прерываний длительными.

Когда ЦПУ получает прерывание, он прекращает все свои текущие действия (если только не обрабатывает более важное прерывание; в таком случае сначала он заканчивает его), сохраняет определённые параметры в стеке и вызывает обработчик прерываний. Это означает, что определённые действия в самом обработчике недопустимы, поскольку система находится в неизвестном состоянии. Для решения этой проблемы ядро разделяет обработку прерываний на две части. Первая выполняется сразу же и маскирует линию прерываний. Аппаратные прерывания должны обрабатываться быстро, и именно поэтому нам нужна вторая часть, выполняющая всю тяжёлую работу, отделённую от обработчика. По историческим причинам BH (аббревиатура для Нижних половин) статистически ведёт учёт этих отделённых функций. Начиная с Linux 2.3, на смену BH пришёл механизм Softirq и его более высокоуровневая абстракция Tasklet.

Реализуется этот механизм через вызов request_irq(), который при получении прерывания активизирует его обработчик.

На практике же обработка IRQ может представлять сложности. Зачастую аппаратные устройства реализуют в себе цепочку из двух контроллеров прерываний, чтобы всё IRQ, поступающие от контроллера В, каскадировались в определённое IRQ от контроллера А. Естественно, для этого ядру необходимо разобраться, какое в действительности это было прерывание, что накладывает дополнительную нагрузку. В других архитектурах предлагается особый вид менее нагружающих систему прерываний, называемых «fast IRQ», или FIQ. Для их использования обработчики должны быть написаны на ассемблере, в связи с чем ядру они уже не подходят. Можно сделать так, чтобы эти обработчики работали аналогично другим, но тогда они утратят своё преимущество в скорости. Ядра с поддержкой SMP, работающие в системах с несколькими процессорами, должны решать множество и других проблем. Недостаточно просто знать о том, что произошло определённое прерывание, также важно понимать, для какого (или каких) ЦПУ оно предназначено. Тем, кого интересуют дополнительные подробности, рекомендую обратиться к документации по “APIC” (Advanced Programmable Interrupt Controller — улучшенный программируемый обработчик прерываний).

Функция request_irq получает номер IRQ, имя функции, флаги, имя для /proc/interrupts и параметр, передаваемый в обработчик прерываний. Как правило, доступно определённое число IRQ, какое именно – зависит от оборудования. В качестве флагов могут использоваться SA_SHIRQ, указывающий, что вы хотите поделиться этим IRQ с остальными обработчиками прерываний (обычно ввиду того, что ряд устройств сидят на одном IRQ) и SA_INTERRUPT, обозначающий быстрое прерывание. Эта функция сработает успешно, только если для данного прерывания ещё не установлен обработчик, или если вы также хотите им поделиться.

▍ 15.2 Обнаружение нажатий клавиш

Многие популярные одноплатные компьютеры, такие как Raspberry Pi или Beegleboards, оборудованы множеством контактов ввода-вывода. Подключение к этим выводам кнопок и настройка срабатывания их нажатий является классическим случаем, в котором могут потребоваться прерывания. Поэтому вместо того, чтобы тратить время процессора и энергию на опрос об изменении входного состояния, лучше настроить вход на активацию ЦПУ для последующего выполнения определённой функции обработки.

Вот пример, в котором кнопки подключены к выводам 17 и 18, а светодиод к выводу 4. При желании можете изменить номера выводов на своё усмотрение.

intrpt.c

/*  * intrpt.c – Обработка ввода-вывода с помощью прерываний.  *  * За основу взят пример RPi Стефана Вендлера (devnull@kaltpost.de)  * из репозитория https://github.com/wendlers/rpi-kmod-samples  *  * При нажатии одной кнопки светодиод загорается, а при нажатии другой  * гаснет.  */   #include <linux/gpio.h> #include <linux/interrupt.h> #include <linux/kernel.h> #include <linux/module.h>   static int button_irqs[] = { -1, -1 };   /* Определение вводов-выводов для светодиодов.  * Номера выводов можете изменить.  */ static struct gpio leds[] = { { 4, GPIOF_OUT_INIT_LOW, "LED 1" } };   /* Определение вводов-выводов для BUTTONS.  * Номера вводов-выводов можете изменить.  */ static struct gpio buttons[] = { { 17, GPIOF_IN, "LED 1 ON BUTTON" },                                  { 18, GPIOF_IN, "LED 1 OFF BUTTON" } };   /* Функция обработки прерываний, активируемая нажатием кнопки. */ static irqreturn_t button_isr(int irq, void *data) {     /* Первая кнопка. */     if (irq == button_irqs[0] && !gpio_get_value(leds[0].gpio))         gpio_set_value(leds[0].gpio, 1);     /* Вторая кнопка. */     else if (irq == button_irqs[1] && gpio_get_value(leds[0].gpio))         gpio_set_value(leds[0].gpio, 0);       return IRQ_HANDLED; }   static int __init intrpt_init(void) {     int ret = 0;       pr_info("%s\n", __func__);       /* Регистрация вводов-выводов светодиодов. */     ret = gpio_request_array(leds, ARRAY_SIZE(leds));       if (ret) {         pr_err("Unable to request GPIOs for LEDs: %d\n", ret);         return ret;     }       /* Регистрация вводов-выводов для BUTTON. */     ret = gpio_request_array(buttons, ARRAY_SIZE(buttons));       if (ret) {         pr_err("Unable to request GPIOs for BUTTONs: %d\n", ret);         goto fail1;     }       pr_info("Current button1 value: %d\n", gpio_get_value(buttons[0].gpio));       ret = gpio_to_irq(buttons[0].gpio);       if (ret < 0) {         pr_err("Unable to request IRQ: %d\n", ret);         goto fail2;     }       button_irqs[0] = ret;       pr_info("Successfully requested BUTTON1 IRQ # %d\n", button_irqs[0]);       ret = request_irq(button_irqs[0], button_isr,                       IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,                       "gpiomod#button1", NULL);       if (ret) {         pr_err("Unable to request IRQ: %d\n", ret);         goto fail2;     }       ret = gpio_to_irq(buttons[1].gpio);       if (ret < 0) {         pr_err("Unable to request IRQ: %d\n", ret);         goto fail2;     }       button_irqs[1] = ret;       pr_info("Successfully requested BUTTON2 IRQ # %d\n", button_irqs[1]);       ret = request_irq(button_irqs[1], button_isr,                       IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,                       "gpiomod#button2", NULL);       if (ret) {         pr_err("Unable to request IRQ: %d\n", ret);         goto fail3;     }       return 0;   /* Удаление проделанных настроек. */ fail3:     free_irq(button_irqs[0], NULL);   fail2:     gpio_free_array(buttons, ARRAY_SIZE(leds));   fail1:     gpio_free_array(leds, ARRAY_SIZE(leds));       return ret; }   static void __exit intrpt_exit(void) {     int i;       pr_info("%s\n", __func__);       /* Свободные прерывания. */     free_irq(button_irqs[0], NULL);     free_irq(button_irqs[1], NULL);       /* Отключение всех светодиодов. */     for (i = 0; i < ARRAY_SIZE(leds); i++)         gpio_set_value(leds[i].gpio, 0);       /* Снятие регистрации. */     gpio_free_array(leds, ARRAY_SIZE(leds));     gpio_free_array(buttons, ARRAY_SIZE(buttons)); }   module_init(intrpt_init); module_exit(intrpt_exit);   MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("Handle some GPIO interrupts");

▍ 15.3 Нижняя половина

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

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

bottomhalf.c

/*  * bottomhalf.c – Обработка верхней и нижней частей прерывания.  *  * За основу взят пример RPi Стефана Вендлера (devnull@kaltpost.de)  * из репозитория https://github.com/wendlers/rpi-kmod-samples  *  * При нажатии одной кнопки светодиод загорается, а при нажатии другой  * гаснет.  */   #include <linux/delay.h> #include <linux/gpio.h> #include <linux/interrupt.h> #include <linux/kernel.h> #include <linux/module.h>   /* Макрос DECLARE_TASKLET_OLD присутствует для совместимости.  * См. https://lwn.net/Articles/830964/  */ #ifndef DECLARE_TASKLET_OLD #define DECLARE_TASKLET_OLD(arg1, arg2) DECLARE_TASKLET(arg1, arg2, 0L) #endif   static int button_irqs[] = { -1, -1 };   /* Определение вводов-выводов для светодиодов.  * Номера вводов-выводов можно изменить.  */ static struct gpio leds[] = { { 4, GPIOF_OUT_INIT_LOW, "LED 1" } };   /* Определение вводов-выводов для BUTTONS.  * Номера вводов-выводов можно изменить.  */ static struct gpio buttons[] = {     { 17, GPIOF_IN, "LED 1 ON BUTTON" },     { 18, GPIOF_IN, "LED 1 OFF BUTTON" }, };   /* Тасклет, содержащий большой объём обработки. */ static void bottomhalf_tasklet_fn(unsigned long data) {     pr_info("Bottom half tasklet starts\n");     /* Выполнение длительных действий. */     mdelay(500);     pr_info("Bottom half tasklet ends\n"); }   static DECLARE_TASKLET_OLD(buttontask, bottomhalf_tasklet_fn);       /* Функция прерывания, активизируемая при нажатии кнопки. */ static irqreturn_t button_isr(int irq, void *data) {     /* Быстрое выполнение действия прямо сейчас. */     if (irq == button_irqs[0] && !gpio_get_value(leds[0].gpio))         gpio_set_value(leds[0].gpio, 1);     else if (irq == button_irqs[1] && gpio_get_value(leds[0].gpio))         gpio_set_value(leds[0].gpio, 0);       /* Неспешное выполнение остального через планировщик. */     tasklet_schedule(&buttontask);       return IRQ_HANDLED; }   static int __init bottomhalf_init(void) {     int ret = 0;       pr_info("%s\n", __func__);       /* Регистрация вводов-выводов светодиодов. */     ret = gpio_request_array(leds, ARRAY_SIZE(leds));       if (ret) {         pr_err("Unable to request GPIOs for LEDs: %d\n", ret);         return ret;     }       /* Регистрация вводов-выводов BUTTONS. */     ret = gpio_request_array(buttons, ARRAY_SIZE(buttons));       if (ret) {         pr_err("Unable to request GPIOs for BUTTONs: %d\n", ret);         goto fail1;     }       pr_info("Current button1 value: %d\n", gpio_get_value(buttons[0].gpio));       ret = gpio_to_irq(buttons[0].gpio);       if (ret < 0) {         pr_err("Unable to request IRQ: %d\n", ret);         goto fail2;     }       button_irqs[0] = ret;       pr_info("Successfully requested BUTTON1 IRQ # %d\n", button_irqs[0]);       ret = request_irq(button_irqs[0], button_isr,                       IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,                       "gpiomod#button1", NULL);       if (ret) {         pr_err("Unable to request IRQ: %d\n", ret);         goto fail2;     }       ret = gpio_to_irq(buttons[1].gpio);       if (ret < 0) {         pr_err("Unable to request IRQ: %d\n", ret);         goto fail2;     }       button_irqs[1] = ret;       pr_info("Successfully requested BUTTON2 IRQ # %d\n", button_irqs[1]);       ret = request_irq(button_irqs[1], button_isr,                       IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,                       "gpiomod#button2", NULL);       if (ret) {         pr_err("Unable to request IRQ: %d\n", ret);         goto fail3;     }       return 0;   /* Удаление проделанных настроек. */ fail3:     free_irq(button_irqs[0], NULL);   fail2:     gpio_free_array(buttons, ARRAY_SIZE(leds));   fail1:     gpio_free_array(leds, ARRAY_SIZE(leds));       return ret; }   static void __exit bottomhalf_exit(void) {     int i;       pr_info("%s\n", __func__);       /* Освобождение прерываний. */     free_irq(button_irqs[0], NULL);     free_irq(button_irqs[1], NULL);       /* Отключение всех светодиодов. */     for (i = 0; i < ARRAY_SIZE(leds); i++)         gpio_set_value(leds[i].gpio, 0);       /* Отмена регистрации. */     gpio_free_array(leds, ARRAY_SIZE(leds));     gpio_free_array(buttons, ARRAY_SIZE(buttons)); }   module_init(bottomhalf_init); module_exit(bottomhalf_exit);   MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("Interrupt with top and bottom half");

16. Криптография

На заре становления интернета все его пользователи полностью доверяли друг другу…но ничего хорошего из этого не вышло. Изначально это руководство писалось в безмятежную эру, в которой мало кого заботила криптография – по крайней мере, разработчиков ядра. Сегодня же времена совсем другие. Для обработки криптографии в ядре реализован собственный API, предоставляющий стандартные методы шифрования/дешифровании и ваши любимые хеш-функции.

▍ 16.1 Хеш-функции

Вычисление и проверка хешей является стандартной операцией. Ниже приведён пример вычисления хеша sha256 в модуле ядра.

cryptosha256.c

/*  * cryptosha256.c  */ #include <crypto/internal/hash.h> #include <linux/module.h>   #define SHA256_LENGTH 32   static void show_hash_result(char *plaintext, char *hash_sha256) {     int i;     char str[SHA256_LENGTH * 2 + 1];       pr_info("sha256 test for string: \"%s\"\n", plaintext);     for (i = 0; i < SHA256_LENGTH; i++)         sprintf(&str[i * 2], "%02x", (unsigned char)hash_sha256[i]);     str[i * 2] = 0;     pr_info("%s\n", str); }   static int cryptosha256_init(void) {     char *plaintext = "This is a test";     char hash_sha256[SHA256_LENGTH];     struct crypto_shash *sha256;     struct shash_desc *shash;       sha256 = crypto_alloc_shash("sha256", 0, 0);     if (IS_ERR(sha256))         return -1;       shash = kmalloc(sizeof(struct shash_desc) + crypto_shash_descsize(sha256),                     GFP_KERNEL);     if (!shash)         return -ENOMEM;       shash->tfm = sha256;       if (crypto_shash_init(shash))         return -1;       if (crypto_shash_update(shash, plaintext, strlen(plaintext)))         return -1;       if (crypto_shash_final(shash, hash_sha256))         return -1;       kfree(shash);     crypto_free_shash(sha256);       show_hash_result(plaintext, hash_sha256);       return 0; }   static void cryptosha256_exit(void) { }   module_init(cryptosha256_init); module_exit(cryptosha256_exit);   MODULE_DESCRIPTION("sha256 hash test"); MODULE_LICENSE("GPL");

Установите модуль:

sudo insmod cryptosha256.ko sudo dmesg

И увидите, что для тестовой строки вычисляется хеш.

В завершение удалите тестовый модуль:

sudo rmmod cryptosha256

▍ 16.2 Шифрование с симметричным ключом

Вот пример симметричного шифрования строки с помощью алгоритма AES и пароля.

cryptosk.c

/*  * cryptosk.c  */ #include <crypto/internal/skcipher.h> #include <linux/crypto.h> #include <linux/module.h> #include <linux/random.h> #include <linux/scatterlist.h>   #define SYMMETRIC_KEY_LENGTH 32 #define CIPHER_BLOCK_SIZE 16   struct tcrypt_result {     struct completion completion;     int err; };   struct skcipher_def {     struct scatterlist sg;     struct crypto_skcipher *tfm;     struct skcipher_request *req;     struct tcrypt_result result;     char *scratchpad;     char *ciphertext;     char *ivdata; };   static struct skcipher_def sk;   static void test_skcipher_finish(struct skcipher_def *sk) {     if (sk->tfm)         crypto_free_skcipher(sk->tfm);     if (sk->req)         skcipher_request_free(sk->req);     if (sk->ivdata)         kfree(sk->ivdata);     if (sk->scratchpad)         kfree(sk->scratchpad);     if (sk->ciphertext)         kfree(sk->ciphertext); }   static int test_skcipher_result(struct skcipher_def *sk, int rc) {     switch (rc) {     case 0:         break;     case -EINPROGRESS || -EBUSY:         rc = wait_for_completion_interruptible(&sk->result.completion);         if (!rc && !sk->result.err) {             reinit_completion(&sk->result.completion);             break;         }         pr_info("skcipher encrypt returned with %d result %d\n", rc,                 sk->result.err);         break;     default:         pr_info("skcipher encrypt returned with %d result %d\n", rc,                 sk->result.err);         break;     }       init_completion(&sk->result.completion);       return rc; }   static void test_skcipher_callback(struct crypto_async_request *req, int error) {     struct tcrypt_result *result = req->data;       if (error == -EINPROGRESS)         return;       result->err = error;     complete(&result->completion);     pr_info("Encryption finished successfully\n");       /* Расшифровка данных. */ #if 0     memset((void*)sk.scratchpad, '-', CIPHER_BLOCK_SIZE);     ret = crypto_skcipher_decrypt(sk.req);     ret = test_skcipher_result(&sk, ret);     if (ret)         return;       sg_copy_from_buffer(&sk.sg, 1, sk.scratchpad, CIPHER_BLOCK_SIZE);     sk.scratchpad[CIPHER_BLOCK_SIZE-1] = 0;       pr_info("Decryption request successful\n");     pr_info("Decrypted: %s\n", sk.scratchpad); #endif }   static int test_skcipher_encrypt(char *plaintext, char *password,                                  struct skcipher_def *sk) {     int ret = -EFAULT;     unsigned char key[SYMMETRIC_KEY_LENGTH];       if (!sk->tfm) {         sk->tfm = crypto_alloc_skcipher("cbc-aes-aesni", 0, 0);         if (IS_ERR(sk->tfm)) {             pr_info("could not allocate skcipher handle\n");             return PTR_ERR(sk->tfm);         }     }       if (!sk->req) {         sk->req = skcipher_request_alloc(sk->tfm, GFP_KERNEL);         if (!sk->req) {             pr_info("could not allocate skcipher request\n");             ret = -ENOMEM;             goto out;         }     }       skcipher_request_set_callback(sk->req, CRYPTO_TFM_REQ_MAY_BACKLOG,                                   test_skcipher_callback, &sk->result);       /* Очистка ключа. */     memset((void *)key, '\0', SYMMETRIC_KEY_LENGTH);       /* Использование самого популярного в мире пароля. */     sprintf((char *)key, "%s", password);       /* AES 256 с заданным симметричным ключом. */     if (crypto_skcipher_setkey(sk->tfm, key, SYMMETRIC_KEY_LENGTH)) {         pr_info("key could not be set\n");         ret = -EAGAIN;         goto out;     }     pr_info("Symmetric key: %s\n", key);     pr_info("Plaintext: %s\n", plaintext);       if (!sk->ivdata) {         /* См. https://en.wikipedia.org/wiki/Initialization_vector */         sk->ivdata = kmalloc(CIPHER_BLOCK_SIZE, GFP_KERNEL);         if (!sk->ivdata) {             pr_info("could not allocate ivdata\n");             goto out;         }         get_random_bytes(sk->ivdata, CIPHER_BLOCK_SIZE);     }       if (!sk->scratchpad) {         /* Текст для шифрования. */         sk->scratchpad = kmalloc(CIPHER_BLOCK_SIZE, GFP_KERNEL);         if (!sk->scratchpad) {             pr_info("could not allocate scratchpad\n");             goto out;         }     }     sprintf((char *)sk->scratchpad, "%s", plaintext);       sg_init_one(&sk->sg, sk->scratchpad, CIPHER_BLOCK_SIZE);     skcipher_request_set_crypt(sk->req, &sk->sg, &sk->sg, CIPHER_BLOCK_SIZE,                                sk->ivdata);     init_completion(&sk->result.completion);       /* Шифрование данных. */     ret = crypto_skcipher_encrypt(sk->req);     ret = test_skcipher_result(sk, ret);     if (ret)         goto out;       pr_info("Encryption request successful\n");   out:     return ret; }   static int cryptoapi_init(void) {     /* Самый популярный пароль в мире. */     char *password = "password123";       sk.tfm = NULL;     sk.req = NULL;     sk.scratchpad = NULL;     sk.ciphertext = NULL;     sk.ivdata = NULL;       test_skcipher_encrypt("Testing", password, &sk);     return 0; }   static void cryptoapi_exit(void) {     test_skcipher_finish(&sk); }   module_init(cryptoapi_init); module_exit(cryptoapi_exit);   MODULE_DESCRIPTION("Symmetric key encryption example"); MODULE_LICENSE("GPL");

▍ 17. Драйвер виртуального устройства ввода

Драйвер устройства ввода – это модуль, обеспечивающий возможность взаимодействия с интерактивным устройством через события. Например, клавиатура может отправлять событие нажатия или отпускания клавиши, сообщая ядру наши намерения. Драйвер устройства ввода выделяет новую структуру ввода с помощью функции input_allocate_device(), настраивает в ней битовые поля, ID устройства, версию и прочее, после чего регистрирует его через вызов input_register_device().

В качестве примера приведу vinput – API, обеспечивающий удобство разработки драйверов виртуальных устройств. Этот драйвер должен экспортировать vinput_device(), содержащую имя виртуального устройства, и структуру vinput_ops, которая описывает:

  • Функцию инициализации: init()
  • Функцию внедрения события ввода: send()
  • Функцию обратного чтения: read()

Далее с помощью vinput_register_device() и vinput_unregister_device() новое устройство добавляется в список поддерживаемых виртуальных устройств ввода.

int init(struct vinput *);

Этой функции передаётся struct vinput, уже инициализированная с помощью выделенной struct input_dev. Функция init() отвечает за инициализацию возможностей устройства ввода и его регистрацию.

int send(struct vinput *, char *, int);

Эта функция получает пользовательскую строку и внедряет соответствующее событие с помощью вызова input_report_XXXX или input_event. Данная строка уже скопирована от пользователя.

int read(struct vinput *, char *, int);

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

Устройства vinput создаются и уничтожаются с помощью sysfs, а внедрение событий выполняется через узел /dev. Имя устройства используется пользовательским пространством для экспорта нового виртуального устройства ввода.

Структура class_attribute аналогична другим типам атрибутов, о которых шла речь в разделе 8:

struct class_attribute {     struct attribute attr;     ssize_t (*show)(struct class *class, struct class_attribute *attr,                     char *buf);     ssize_t (*store)(struct class *class, struct class_attribute *attr,                     const char *buf, size_t count); };

В vinput.c макрос CLASS_ATTR_WO(export/unexport), определённый в include/linux/device.h (в данном случае device.h включён в include/linux/input.h) сгенерирует структуры class_attribute, названные class_attr_export/unexport. После этого он поместит их в массив vinput_class_attrs, и макрос ATTRIBUTE_GROUPS(vinput_class) сгенерирует struct attribute_group vinput_class_group, которую нужно будет присвоить в vinput_class. В завершении выполняется вызов class_register(&vinput_class) для создания атрибутов в sysfs.

Для создания записи sysfs vinputX и узла /dev:

echo "vkbd" | sudo tee /sys/class/vinput/export

Для обратного экспорта устройства нужно echo его ID в unexport.

echo "0" | sudo tee /sys/class/vinput/unexport

vinput.h

 /*  * vinput.h  */   #ifndef VINPUT_H #define VINPUT_H   #include <linux/input.h> #include <linux/spinlock.h>   #define VINPUT_MAX_LEN 128 #define MAX_VINPUT 32 #define VINPUT_MINORS MAX_VINPUT   #define dev_to_vinput(dev) container_of(dev, struct vinput, dev)   struct vinput_device;   struct vinput {     long id;     long devno;     long last_entry;     spinlock_t lock;       void *priv_data;       struct device dev;     struct list_head list;     struct input_dev *input;     struct vinput_device *type; };   struct vinput_ops {     int (*init)(struct vinput *);     int (*kill)(struct vinput *);     int (*send)(struct vinput *, char *, int);     int (*read)(struct vinput *, char *, int); };   struct vinput_device {     char name[16];     struct list_head list;     struct vinput_ops *ops; };   int vinput_register(struct vinput_device *dev); void vinput_unregister(struct vinput_device *dev);   #endif

vinput.c

/*  * vinput.c  */  #include <linux/cdev.h> #include <linux/input.h> #include <linux/module.h> #include <linux/slab.h> #include <linux/spinlock.h>   #include <asm/uaccess.h>   #include "vinput.h"   #define DRIVER_NAME "vinput"   #define dev_to_vinput(dev) container_of(dev, struct vinput, dev)   static DECLARE_BITMAP(vinput_ids, VINPUT_MINORS);   static LIST_HEAD(vinput_devices); static LIST_HEAD(vinput_vdevices);   static int vinput_dev; static struct spinlock vinput_lock; static struct class vinput_class;   /* Поиск имени устройства vinput в связанном списке vinput_devices,  * добавленном в vinput_register().  */ static struct vinput_device *vinput_get_device_by_type(const char *type) {     int found = 0;     struct vinput_device *vinput;     struct list_head *curr;       spin_lock(&vinput_lock);     list_for_each (curr, &vinput_devices) {         vinput = list_entry(curr, struct vinput_device, list);         if (vinput && strncmp(type, vinput->name, strlen(vinput->name)) == 0) {             found = 1;             break;         }     }     spin_unlock(&vinput_lock);       if (found)         return vinput;     return ERR_PTR(-ENODEV); }   /* Поиск ID виртуального устройства в связанном списке vinput_vdevices,  * добавленном в vinput_alloc_vdevice().  */ static struct vinput *vinput_get_vdevice_by_id(long id) {     struct vinput *vinput = NULL;     struct list_head *curr;       spin_lock(&vinput_lock);     list_for_each (curr, &vinput_vdevices) {         vinput = list_entry(curr, struct vinput, list);         if (vinput && vinput->id == id)             break;     }     spin_unlock(&vinput_lock);       if (vinput && vinput->id == id)         return vinput;     return ERR_PTR(-ENODEV); }   static int vinput_open(struct inode *inode, struct file *file) {     int err = 0;     struct vinput *vinput = NULL;       vinput = vinput_get_vdevice_by_id(iminor(inode));       if (IS_ERR(vinput))         err = PTR_ERR(vinput);     else         file->private_data = vinput;       return err; }   static int vinput_release(struct inode *inode, struct file *file) {     return 0; }   static ssize_t vinput_read(struct file *file, char __user *buffer, size_t count,                            loff_t *offset) {     int len;     char buff[VINPUT_MAX_LEN + 1];     struct vinput *vinput = file->private_data;       len = vinput->type->ops->read(vinput, buff, count);       if (*offset > len)         count = 0;     else if (count + *offset > VINPUT_MAX_LEN)         count = len - *offset;       if (raw_copy_to_user(buffer, buff + *offset, count))         count = -EFAULT;       *offset += count;       return count; }   static ssize_t vinput_write(struct file *file, const char __user *buffer,                             size_t count, loff_t *offset) {     char buff[VINPUT_MAX_LEN + 1];     struct vinput *vinput = file->private_data;       memset(buff, 0, sizeof(char) * (VINPUT_MAX_LEN + 1));       if (count > VINPUT_MAX_LEN) {         dev_warn(&vinput->dev, "Too long. %d bytes allowed\n", VINPUT_MAX_LEN);         return -EINVAL;     }       if (raw_copy_from_user(buff, buffer, count))         return -EFAULT;       return vinput->type->ops->send(vinput, buff, count); }   static const struct file_operations vinput_fops = {     .owner = THIS_MODULE,     .open = vinput_open,     .release = vinput_release,     .read = vinput_read,     .write = vinput_write, };   static void vinput_unregister_vdevice(struct vinput *vinput) {     input_unregister_device(vinput->input);     if (vinput->type->ops->kill)         vinput->type->ops->kill(vinput); }   static void vinput_destroy_vdevice(struct vinput *vinput) {     /* Сначала удаление из списка. */     spin_lock(&vinput_lock);     list_del(&vinput->list);     clear_bit(vinput->id, vinput_ids);     spin_unlock(&vinput_lock);       module_put(THIS_MODULE);       kfree(vinput); }   static void vinput_release_dev(struct device *dev) {     struct vinput *vinput = dev_to_vinput(dev);     int id = vinput->id;       vinput_destroy_vdevice(vinput);       pr_debug("released vinput%d.\n", id); }   static struct vinput *vinput_alloc_vdevice(void) {     int err;     struct vinput *vinput = kzalloc(sizeof(struct vinput), GFP_KERNEL);       try_module_get(THIS_MODULE);       memset(vinput, 0, sizeof(struct vinput));       spin_lock_init(&vinput->lock);       spin_lock(&vinput_lock);     vinput->id = find_first_zero_bit(vinput_ids, VINPUT_MINORS);     if (vinput->id >= VINPUT_MINORS) {         err = -ENOBUFS;         goto fail_id;     }     set_bit(vinput->id, vinput_ids);     list_add(&vinput->list, &vinput_vdevices);     spin_unlock(&vinput_lock);       /* Выделение устройства ввода. */     vinput->input = input_allocate_device();     if (vinput->input == NULL) {         pr_err("vinput: Cannot allocate vinput input device\n");         err = -ENOMEM;         goto fail_input_dev;     }       /* Инициализация устройства. */     vinput->dev.class = &vinput_class;     vinput->dev.release = vinput_release_dev;     vinput->dev.devt = MKDEV(vinput_dev, vinput->id);     dev_set_name(&vinput->dev, DRIVER_NAME "%lu", vinput->id);       return vinput;   fail_input_dev:     spin_lock(&vinput_lock);     list_del(&vinput->list); fail_id:     spin_unlock(&vinput_lock);     module_put(THIS_MODULE);     kfree(vinput);       return ERR_PTR(err); }   static int vinput_register_vdevice(struct vinput *vinput) {     int err = 0;       /* Регистрация устройства ввода. */     vinput->input->name = vinput->type->name;     vinput->input->phys = "vinput";     vinput->input->dev.parent = &vinput->dev;       vinput->input->id.bustype = BUS_VIRTUAL;     vinput->input->id.product = 0x0000;     vinput->input->id.vendor = 0x0000;     vinput->input->id.version = 0x0000;       err = vinput->type->ops->init(vinput);       if (err == 0)         dev_info(&vinput->dev, "Registered virtual input %s %ld\n",                  vinput->type->name, vinput->id);       return err; }   static ssize_t export_store(struct class *class, struct class_attribute *attr,                             const char *buf, size_t len) {     int err;     struct vinput *vinput;     struct vinput_device *device;       device = vinput_get_device_by_type(buf);     if (IS_ERR(device)) {         pr_info("vinput: This virtual device isn't registered\n");         err = PTR_ERR(device);         goto fail;     }       vinput = vinput_alloc_vdevice();     if (IS_ERR(vinput)) {         err = PTR_ERR(vinput);         goto fail;     }       vinput->type = device;     err = device_register(&vinput->dev);     if (err < 0)         goto fail_register;       err = vinput_register_vdevice(vinput);     if (err < 0)         goto fail_register_vinput;       return len;   fail_register_vinput:     device_unregister(&vinput->dev); fail_register:     vinput_destroy_vdevice(vinput); fail:     return err; } /* Этот макрос генерирует структуру class_attr_export и export_store() */ static CLASS_ATTR_WO(export);   static ssize_t unexport_store(struct class *class, struct class_attribute *attr,                               const char *buf, size_t len) {     int err;     unsigned long id;     struct vinput *vinput;       err = kstrtol(buf, 10, &id);     if (err) {         err = -EINVAL;         goto failed;     }       vinput = vinput_get_vdevice_by_id(id);     if (IS_ERR(vinput)) {         pr_err("vinput: No such vinput device %ld\n", id);         err = PTR_ERR(vinput);         goto failed;     }       vinput_unregister_vdevice(vinput);     device_unregister(&vinput->dev);       return len; failed:     return err; } /* Этот макрос генерирует структуру class_attr_unexport  * и unexport_store().  */ static CLASS_ATTR_WO(unexport);   static struct attribute *vinput_class_attrs[] = {     &class_attr_export.attr,     &class_attr_unexport.attr,     NULL, };   /* Этот макрос генерирует структуру vinput_class_groups. */ ATTRIBUTE_GROUPS(vinput_class);   static struct class vinput_class = {     .name = "vinput",     .owner = THIS_MODULE,     .class_groups = vinput_class_groups, };   int vinput_register(struct vinput_device *dev) {     spin_lock(&vinput_lock);     list_add(&dev->list, &vinput_devices);     spin_unlock(&vinput_lock);       pr_info("vinput: registered new virtual input device '%s'\n", dev->name);       return 0; } EXPORT_SYMBOL(vinput_register);   void vinput_unregister(struct vinput_device *dev) {     struct list_head *curr, *next;       /* Сначала удаление из списка. */     spin_lock(&vinput_lock);     list_del(&dev->list);     spin_unlock(&vinput_lock);       /* Снятие регистрации всех устройств этого типа. */     list_for_each_safe (curr, next, &vinput_vdevices) {         struct vinput *vinput = list_entry(curr, struct vinput, list);         if (vinput && vinput->type == dev) {             vinput_unregister_vdevice(vinput);             device_unregister(&vinput->dev);         }     }       pr_info("vinput: unregistered virtual input device '%s'\n", dev->name); } EXPORT_SYMBOL(vinput_unregister);   static int __init vinput_init(void) {     int err = 0;       pr_info("vinput: Loading virtual input driver\n");       vinput_dev = register_chrdev(0, DRIVER_NAME, &vinput_fops);     if (vinput_dev < 0) {         pr_err("vinput: Unable to allocate char dev region\n");         goto failed_alloc;     }       spin_lock_init(&vinput_lock);       err = class_register(&vinput_class);     if (err < 0) {         pr_err("vinput: Unable to register vinput class\n");         goto failed_class;     }       return 0; failed_class:     class_unregister(&vinput_class); failed_alloc:     return err; }   static void __exit vinput_end(void) {     pr_info("vinput: Unloading virtual input driver\n");       unregister_chrdev(vinput_dev, DRIVER_NAME);     class_unregister(&vinput_class); }   module_init(vinput_init); module_exit(vinput_end);   MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("Emulate input events");

Здесь мы рассматриваем виртуальную клавиатуру как один из примеров использования vinput. Она поддерживает все коды клавиш KEY_MAX. Внедрение производится в формате KEY_CODE, как это определено в include/linux/input.h. Положительное значение означает KEY_PRESS, а отрицательное KEY_RELEASE. Эта клавиатура поддерживает повторение ввода, когда клавиша остаётся нажатой длительное время. Код ниже демонстрирует работу данной симуляции.

Симулирует нажатие «g» ( KEY_G = 34):

echo "+34" | sudo tee /dev/vinput0

Симулирует отпускание «g» ( KEY_G = 34):

echo "-34" | sudo tee /dev/vinput0

vkbd.c

/*  * vkbd.c  */   #include <linux/init.h> #include <linux/input.h> #include <linux/module.h> #include <linux/spinlock.h>   #include "vinput.h"   #define VINPUT_KBD "vkbd" #define VINPUT_RELEASE 0 #define VINPUT_PRESS 1   static unsigned short vkeymap[KEY_MAX];   static int vinput_vkbd_init(struct vinput *vinput) {     int i;       /* Устанавливает битовое поле ввода. */     vinput->input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);     vinput->input->keycodesize = sizeof(unsigned short);     vinput->input->keycodemax = KEY_MAX;     vinput->input->keycode = vkeymap;       for (i = 0; i < KEY_MAX; i++)         set_bit(vkeymap[i], vinput->input->keybit);       /* vinput поможет выделить новую структуру устройства ввода через      * input_allocate_device(), что позволит с лёгкостью его      * зарегистрировать.      */     return input_register_device(vinput->input); }   static int vinput_vkbd_read(struct vinput *vinput, char *buff, int len) {     spin_lock(&vinput->lock);     len = snprintf(buff, len, "%+ld\n", vinput->last_entry);     spin_unlock(&vinput->lock);       return len; }   static int vinput_vkbd_send(struct vinput *vinput, char *buff, int len) {     int ret;     long key = 0;     short type = VINPUT_PRESS;       /* Определяем, какое было получено событие      * (нажатие или отпускание) и сохраняем это состояние.      */     if (buff[0] == '+')         ret = kstrtol(buff + 1, 10, &key);     else         ret = kstrtol(buff, 10, &key);     if (ret)         dev_err(&vinput->dev, "error during kstrtol: -%d\n", ret);     spin_lock(&vinput->lock);     vinput->last_entry = key;     spin_unlock(&vinput->lock);       if (key < 0) {         type = VINPUT_RELEASE;         key = -key;     }       dev_info(&vinput->dev, "Event %s code %ld\n",              (type == VINPUT_RELEASE) ? "VINPUT_RELEASE" : "VINPUT_PRESS", key);       /* Передаём полученное состояние подсистеме ввода. */     input_report_key(vinput->input, key, type);     /* Сообщаем подсистеме ввода, что передача закончена. */     input_sync(vinput->input);       return len; }   static struct vinput_ops vkbd_ops = {     .init = vinput_vkbd_init,     .send = vinput_vkbd_send,     .read = vinput_vkbd_read, };   static struct vinput_device vkbd_dev = {     .name = VINPUT_KBD,     .ops = &vkbd_ops, };   static int __init vkbd_init(void) {     int i;       for (i = 0; i < KEY_MAX; i++)         vkeymap[i] = i;     return vinput_register(&vkbd_dev); }   static void __exit vkbd_end(void) {     vinput_unregister(&vkbd_dev); }   module_init(vkbd_init); module_exit(vkbd_end);   MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("Emulate keyboard input events through /dev/vinput");

▍ 18. Стандартизация интерфейсов: модель устройства

К этому моменту мы рассмотрели все виды модулей, выполняющие всевозможные задачи, но в их интерфейсах не было согласованности с остальной частью ядра. Для внесения согласованности, которая бы обеспечила как минимум стандартизированный способ запускать, приостанавливать и возобновлять работу устройства, была добавлена модель устройства. Ниже показан её пример, который вы можете использовать в качестве шаблона для добавления собственных функций приостановки, возобновления и прочего.

devicemodel.c

/*  * devicemodel.c  */ #include <linux/kernel.h> #include <linux/module.h> #include <linux/platform_device.h>   struct devicemodel_data {     char *greeting;     int number; };   static int devicemodel_probe(struct platform_device *dev) {     struct devicemodel_data *pd =         (struct devicemodel_data *)(dev->dev.platform_data);       pr_info("devicemodel probe\n");     pr_info("devicemodel greeting: %s; %d\n", pd->greeting, pd->number);       /* Код инициализации устройства. */       return 0; }   static int devicemodel_remove(struct platform_device *dev) {     pr_info("devicemodel example removed\n");       /* Код удаления устройства. */       return 0; }   static int devicemodel_suspend(struct device *dev) {     pr_info("devicemodel example suspend\n");       /* Код приостановки устройства. */       return 0; }   static int devicemodel_resume(struct device *dev) {     pr_info("devicemodel example resume\n");       /* Код возобновления работы устройства. */       return 0; }   static const struct dev_pm_ops devicemodel_pm_ops = {     .suspend = devicemodel_suspend,     .resume = devicemodel_resume,     .poweroff = devicemodel_suspend,     .freeze = devicemodel_suspend,     .thaw = devicemodel_resume,     .restore = devicemodel_resume, };   static struct platform_driver devicemodel_driver = {     .driver =         {             .name = "devicemodel_example",             .owner = THIS_MODULE,             .pm = &devicemodel_pm_ops,         },     .probe = devicemodel_probe,     .remove = devicemodel_remove, };   static int devicemodel_init(void) {     int ret;       pr_info("devicemodel init\n");       ret = platform_driver_register(&devicemodel_driver);       if (ret) {         pr_err("Unable to register driver\n");         return ret;     }       return 0; }   static void devicemodel_exit(void) {     pr_info("devicemodel exit\n");     platform_driver_unregister(&devicemodel_driver); }   module_init(devicemodel_init); module_exit(devicemodel_exit);   MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("Linux Device Model example"); 

19. Оптимизации

▍ 19.1 Условия likely и unlikely

Иногда вам может потребоваться максимально быстрое выполнение кода, особенно если он обрабатывает прерывание или выполняет нечто, способное вызвать значительную задержку. Если ваш код содержит логические условия, и вы знаете, что эти условия практически всегда оцениваются как true либо false, тогда можете позволить компилятору выполнить соответствующую оптимизацию с помощью макросов likely и unlikely. К примеру, при выделении памяти вы практически всегда ожидаете успешного завершения операции.

bvl = bvec_alloc(gfp_mask, nr_iovecs, &idx); if (unlikely(!bvl)) {     mempool_free(bio, bio_pool);     bio = NULL;     goto out; }

Когда используется макрос unlikely, компилятор изменяет вывод машинной инструкции, чтобы код продолжал выполнение по ветке false и делал переход, только когда условие true. Это позволяет избежать очистки конвейера процессора. При использовании макроса likely происходит противоположное.

20. Важные нюансы

▍ 20.1 Использование стандартных библиотек

Этого делать нельзя. В модуле ядра допустимо использовать исключительно функции ядра, которые вы можете найти в /proc/kallsyms.

▍ 20.2 Отключение прерываний

Вам может потребоваться делать это ненадолго, что вполне нормально. Если же вы впоследствии их не включите, то система зависнет, и её придётся отключить.

▍ 21. Дальнейшие шаги

Для тех, кто серьёзно заинтересован в освоении программирования ядра, рекомендую ознакомиться с ресурсом kernelnewbies.org и поддиректорией Documentation в исходном коде, которая даёт неплохие базовые понятия для дальнейшего изучения темы, хотя местами могла бы быть написана и получше. Кроме того, как сказал сам Линус Торвальдс, лучший способ изучить ядро – это самостоятельно читать его исходный код.

Если вы желаете внести свой вклад в данное пособие или заметили в нём какие-либо серьёзные недочёты, создайте по этой теме запрос на https://github.com/sysprog21/lkmpg. Будем признательны за ваши пул-реквесты.

Успехов!

Telegram-канал и уютный чат для клиентов


ссылка на оригинал статьи https://habr.com/ru/company/ruvds/blog/689346/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *