Пособие по программированию модулей ядра 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 не будет опубликован. Обязательные поля помечены *