[Перевод] Пособие по программированию модулей ядра 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 – Обработка ввода-вывода с помощью прерываний.
*
* За основу взят пример RPi Стефана Вендлера (devnull@kaltpost.de)
* из репозитория https://github.com/wendlers/rpi-kmod-samples
*
* При нажатии одной кнопки светодиод загорается, а при нажатии другой
* гаснет.
*/
#include
#include
#include
#include
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
#include
#include
#include
#include
/* Макрос 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
#include
#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
#include
#include
#include
#include
#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_attribut
e, названные 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
#include
#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
#include
#include
#include
#include
#include
#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
#include
#include
#include
#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
#include
#include
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-канал и уютный чат для клиентов