Как стать автором
Обновить
2757.98
RUVDS.com
VDS/VPS-хостинг. Скидка 15% по коду HABR15

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

Время на прочтение25 мин
Количество просмотров11K
Автор оригинала: Jim Huang et al.

Заключительная часть последней версии руководства по созданию модулей ядра от 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-канал и уютный чат для клиентов
Теги:
Хабы:
Всего голосов 42: ↑41 и ↓1+40
Комментарии0

Публикации

Информация

Сайт
ruvds.com
Дата регистрации
Дата основания
Численность
11–30 человек
Местоположение
Россия
Представитель
ruvds