
Заключительная часть последней версии руководства по созданию модулей ядра от 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 – Обработка ввода-вывода с помощью прерываний. * * За основу взят пример 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 – Обработка верхней и нижней частей прерывания. * * За основу взят пример 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 */ #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 */ #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 */ #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 */ #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 */ #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 */ #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/

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