Skip to content

Самостоятельная работа №5: Изучение драйверов устройств в GNU/Linux


Блок теории: Драйверы устройств — "мост" между ядром и железом


1. Введение в систему ввода-вывода Linux

Физические принципы организации ввода-вывода

В современных системах взаимодействие с устройствами происходит через иерархию шин и контроллеров:

  • Шины: PCIe, USB, SATA, SCSI
  • Контроллеры: Управляют физическим взаимодействием с устройствами (например, AHCI для SATA)
  • Регистры устройств: Специальные ячейки памяти для обмена данными

Когда процесс запрашивает операцию ввода-вывода:

  1. Ядро формирует запрос ввода-вывода (I/O request)
  2. Запрос помещается в очередь ввода-вывода
  3. Драйвер преобразует запрос в сигналы шины
  4. Контроллер передаёт сигналы на физическое устройство

Логические принципы организации ввода-вывода

Linux использует абстрактный слой ввода-вывода:

  • Блочные устройства (жёсткие диски): /dev/sda, /dev/nvme0n1
  • Символьные устройства (клавиатура, мышь): /dev/input/mouse0
  • Сетевые интерфейсы: eth0, wlan0
  • Устройства ядра: /dev/kmem, /dev/mem

💡 Ключевой принцип: "Всё есть файл" — все устройства представлены в виде файлов в /dev.


2. Архитектура драйверов в ядре Linux

Типы драйверов

ТипОписаниеПримеры
МонолитныеВстроены в ядроДрайверы для основных файловых систем
МодульныеЗагружаются динамическиkmod, nvidia, rtl88xxau
Встроенные в ядроНе могут быть выгруженыДрайверы для базовой функциональности

Структура модуля ядра

Простейший драйвер состоит из:

c
#include <linux/module.h>
#include <linux/kernel.h>

static int __init my_driver_init(void) {
    printk(KERN_INFO "My driver loaded!\n");
    return 0;
}

static void __exit my_driver_exit(void) {
    printk(KERN_INFO "My driver unloaded!\n");
}

module_init(my_driver_init);
module_exit(my_driver_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Simple driver example");

Подсистемы драйверов в ядре

  • Device Model: Система управления устройствами (/sys/devices)
  • Bus Subsystem: Управление шинами (PCI, USB)
  • Class Subsystem: Логическая группировка устройств (звук, сеть)
  • Driver Core: Ядро системы драйверов

3. Алгоритмы планирования запросов к жёстким дискам

Зачем нужен планировщик ввода-вывода?

Жёсткие диски имеют механические задержки:

  • Поиск головки (seek time): Движение головки к нужному цилиндру
  • Вращательная задержка (rotational latency): Ожидание нужного сектора

Планировщик оптимизирует порядок запросов для минимизации задержек.

Основные алгоритмы

АлгоритмПринципГде используется
CFQ (Completely Fair Queuing)Распределяет квоты времени между процессамиСтарые десктопные системы
DeadlineУстанавливает дедлайны для запросовСерверные системы
NOOPПростая FIFO-очередьSSD/NVMe диски
BFQ (Budget Fair Queuing)Аналог CFQ, но с улучшенной отзывчивостьюСовременные десктопы

Как это работает?

plaintext
Запросы от приложений


  [Общая очередь]


┌───────────────────────┐
│ Планировщик ввода-вывода │
└───────────────────────┘

        ├─▶ [Чтение] → Головка диска
        └─▶ [Запись] → Головка диска

4. Взаимодействие компонентов системы ввода-вывода

Путь запроса от приложения к устройству

plaintext
Приложение (cat /etc/passwd)


VFS (Virtual File System)


Файловая система (ext4, btrfs)


Блочный слой (библиотека bio)


Планировщик ввода-вывода (CFQ, Deadline)


Драйвер устройства (ahci, nvme)


Аппаратный контроллер (SATA, PCIe)


Физическое устройство (HDD, SSD)

Ключевые структуры ядра

  • bio (Block I/O): Описывает запрос ввода-вывода
  • request_queue: Очередь запросов для планировщика
  • gendisk: Описывает диск как устройство
  • block_device: Представление блочного устройства

Практическая часть: изучение драйверов устройств


Задание 1: Анализ установленных драйверов

Цель: Изучить список загруженных модулей ядра и их зависимости.

  1. Выведите список всех загруженных модулей:

    bash
    lsmod
  2. Проанализируйте вывод:

    bash
    lsmod | less

    Обратите внимание на:

    • Имя модуля
    • Размер
    • Количество использующих (Used by)
  3. Найдите модули, относящиеся к вашим устройствам:

    bash
    # Для сетевых устройств
    lsmod | grep -iE 'net|eth|wifi|wireless'
    
    # Для дисков
    lsmod | grep -iE 'ahci|nvme|sd|scsi'
    
    # Для USB
    lsmod | grep -i usb
  4. Изучите зависимости модуля:

    bash
    modinfo <имя_модуля> | grep depends
    # Например:
    modinfo i915 | grep depends

Вопросы для отчёта:

  • Какой модуль отвечает за работу вашего сетевого интерфейса?
  • Какие модули зависят от usbcore?
  • Какой модуль управляет вашим GPU?

Задание 2: Работа с параметрами драйверов

Цель: Изучить и изменить параметры драйвера.

  1. Найдите параметры драйвера:

    bash
    # Для всех модулей
    sudo modinfo -p $(cut -d' ' -f1 /proc/modules)
    
    # Для конкретного модуля (например, iwlwifi для Wi-Fi)
    modinfo iwlwifi | grep -A 10 "parm:"
  2. Посмотрите текущие значения параметров:

    bash
    # Для всех модулей
    find /sys/module/ -name parameters -type d -exec ls -l {} \;
    
    # Для конкретного модуля
    ls /sys/module/iwlwifi/parameters/
    cat /sys/module/iwlwifi/parameters/11n_disable
  3. Измените параметр драйвера (временно):

    bash
    # Отключим агрегацию пакетов 802.11n для теста
    echo "1" | sudo tee /sys/module/iwlwifi/parameters/11n_disable
  4. Создайте постоянную настройку:

    bash
    # Создайте файл конфигурации
    echo "options iwlwifi 11n_disable=1" | sudo tee /etc/modprobe.d/iwlwifi-disable-11n.conf
    
    # Перезагрузите модуль
    sudo modprobe -r iwlwifi
    sudo modprobe iwlwifi

Вопросы для отчёта:

  • Какой параметр отвечает за энергосбережение Wi-Fi?
  • Как изменить размер очереди для сетевого драйвера?
  • Как отключить аппаратное ускорение для сетевого интерфейса?

Задание 3: Анализ планировщика ввода-вывода

Цель: Изучить и настроить планировщик дисковых операций.

  1. Посмотрите текущий планировщик:

    bash
    # Для всех дисков
    for disk in /sys/block/*; do 
      echo "$(basename $disk): $(cat $disk/queue/scheduler)"
    done
    
    # Для конкретного диска (например, sda)
    cat /sys/block/sda/queue/scheduler
  2. Измените планировщик (временно):

    bash
    # Установите NOOP для sda
    echo "noop" | sudo tee /sys/block/sda/queue/scheduler
  3. Сделайте изменение постоянным:

    bash
    # Создайте udev-правило
    echo 'ACTION=="add|change", KERNEL=="sda", ATTR{queue/scheduler}="noop"' | sudo tee /etc/udev/rules.d/60-scheduler.rules
    
    # Примените правило без перезагрузки
    sudo udevadm control --reload-rules
    sudo udevadm trigger
  4. Измерьте производительность с разными планировщиками:

    bash
    # Установите fio для тестирования
    sudo apt install fio
    
    # Тест с текущим планировщиком
    fio --name=randread --ioengine=libaio --rw=randread --bs=4k --direct=1 \
        --size=256M --numjobs=4 --runtime=60 --group_reporting
    
    # Смените планировщик и повторите тест

Вопросы для отчёта:

  • Какой планировщик используется по умолчанию в вашей системе?
  • Почему для SSD рекомендуется NOOP, а не CFQ?
  • Как влияет параметр nr_requests на производительность?

Задание 4: Создание простого драйвера (простое ядерное модуля)

Цель: Написать и загрузить свой первый драйвер.

  1. Создайте файл hello_driver.c:

    c
    #include <linux/init.h>
    #include <linux/module.h>
    #include <linux/kernel.h>
    
    MODULE_LICENSE("GPL");
    MODULE_AUTHOR("Your Name");
    MODULE_DESCRIPTION("Hello World Driver");
    MODULE_VERSION("0.1");
    
    static int __init hello_init(void) {
        printk(KERN_INFO "Hello: module loaded\n");
        return 0;
    }
    
    static void __exit hello_exit(void) {
        printk(KERN_INFO "Goodbye: module unloaded\n");
    }
    
    module_init(hello_init);
    module_exit(hello_exit);
  2. Создайте Makefile:

    makefile
    obj-m += hello_driver.o
    
    all:
    	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
    
    clean:
    	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
    
    install:
    	sudo insmod hello_driver.ko
    
    uninstall:
    	sudo rmmod hello_driver
  3. Соберите и загрузите модуль:

    bash
    make
    sudo insmod hello_driver.ko
  4. Проверьте загрузку:

    bash
    dmesg | tail -2
    lsmod | grep hello
  5. Выгрузите модуль:

    bash
    sudo rmmod hello_driver
    dmesg | tail -1

Вопросы для отчёта:

  • Что означает уровень KERN_INFO в printk?
  • Какие уровни логирования существуют в ядре?
  • Почему при сборке модуля используется путь к исходникам ядра?

Контрольные вопросы

  1. Как ядро Linux определяет, какой драйвер использовать для нового устройства?

    • Ядро использует идентификаторы устройства (PCI ID, USB VID/PID) и сравнивает их со списком поддерживаемых устройств в драйверах. При подключении устройства вызывается функция probe() драйвера.
  2. Чем отличаются символьные и блочные устройства?

    • Блочные устройства работают с фиксированными блоками данных (обычно 512 байт или 4КБ), поддерживают произвольный доступ. Пример: жёсткие диски.
    • Символьные устройства работают с потоком данных, без фиксированной структуры. Пример: терминалы, последовательные порты.
  3. Какой планировщик ввода-вывода оптимален для SSD и почему?

    • Для SSD рекомендуется NOOP или NONE, так как SSD не имеют механических задержек (поиска головки). Дополнительная обработка запросов в планировщике только замедляет работу.
  4. Как добавить новый параметр в ваш драйвер?

    c
    static int debug_level = 0;
    module_param(debug_level, int, 0644);
    MODULE_PARM_DESC(debug_level, "Debug level (0=off, 1=low, 2=high)");
  5. Как отладить драйвер устройства?

    • Использовать printk с разными уровнями логирования
    • Анализировать вывод через dmesg
    • Использовать ftrace и kprobes для трассировки
    • Динамическая отладка через dynamic_debug

Дополнительные материалы для углублённого изучения

Книги

  1. Linux Device Drivers, 4th Edition (Jonathan Corbet, Alessandro Rubini, Greg Kroah-Hartman) — Библия драйверописателей.
  2. Understanding the Linux Kernel (Daniel P. Bovet, Marco Cesati) — Глубокое погружение в архитектуру ядра.
  3. Linux Kernel Development (Robert Love) — Современное ядро от разработчика.

Онлайн-ресурсы

Инструменты для разработки

  • QEMU: Эмуляция оборудования для тестирования драйверов
  • kmemleak: Обнаружение утечек памяти в ядре
  • kprobes: Динамическая отладка ядра
  • SystemTap: Скриптовая отладка ядра

Заключение

Драйверы устройств — это критически важный компонент любой операционной системы. В Linux они реализованы как:

  • Модули ядра — обеспечивают гибкость и модульность
  • Часть ядра — обеспечивают производительность и стабильность

Понимание работы драйверов необходимо для:

  • Диагностики проблем с оборудованием
  • Оптимизации производительности системы ввода-вывода
  • Разработки собственных драйверов для специализированного оборудования
  • Анализа безопасности взаимодействия с оборудованием

💡 Практический совет:

"Чтобы глубже понять драйверы, изучайте исходный код ядра Linux. Начните с простых драйверов в drivers/char/ и постепенно переходите к более сложным."


Критерии оценки самостоятельной работы

КритерийБаллы
Выполнение всех практических заданий50
Качество ответов на контрольные вопросы30
Анализ результатов экспериментов15
Ссылки на дополнительные материалы5

Срок сдачи: 7 дней
Формат отчёта: PDF с описанием выполненных шагов, скриншотами и выводами.
Обязательно включите: Ваши наблюдения по производительности при смене планировщика ввода-вывода.

Контакты: bystrovno@basealt.ru