Самостоятельная работа №5: Изучение драйверов устройств в GNU/Linux
Блок теории: Драйверы устройств — "мост" между ядром и железом
1. Введение в систему ввода-вывода Linux
Физические принципы организации ввода-вывода
В современных системах взаимодействие с устройствами происходит через иерархию шин и контроллеров:
- Шины: PCIe, USB, SATA, SCSI
- Контроллеры: Управляют физическим взаимодействием с устройствами (например, AHCI для SATA)
- Регистры устройств: Специальные ячейки памяти для обмена данными
Когда процесс запрашивает операцию ввода-вывода:
- Ядро формирует запрос ввода-вывода (I/O request)
- Запрос помещается в очередь ввода-вывода
- Драйвер преобразует запрос в сигналы шины
- Контроллер передаёт сигналы на физическое устройство
Логические принципы организации ввода-вывода
Linux использует абстрактный слой ввода-вывода:
- Блочные устройства (жёсткие диски):
/dev/sda
,/dev/nvme0n1
- Символьные устройства (клавиатура, мышь):
/dev/input/mouse0
- Сетевые интерфейсы:
eth0
,wlan0
- Устройства ядра:
/dev/kmem
,/dev/mem
💡 Ключевой принцип: "Всё есть файл" — все устройства представлены в виде файлов в
/dev
.
2. Архитектура драйверов в ядре Linux
Типы драйверов
Тип | Описание | Примеры |
---|---|---|
Монолитные | Встроены в ядро | Драйверы для основных файловых систем |
Модульные | Загружаются динамически | kmod , nvidia , rtl88xxau |
Встроенные в ядро | Не могут быть выгружены | Драйверы для базовой функциональности |
Структура модуля ядра
Простейший драйвер состоит из:
#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, но с улучшенной отзывчивостью | Современные десктопы |
Как это работает?
Запросы от приложений
│
▼
[Общая очередь]
│
▼
┌───────────────────────┐
│ Планировщик ввода-вывода │
└───────────────────────┘
│
├─▶ [Чтение] → Головка диска
└─▶ [Запись] → Головка диска
4. Взаимодействие компонентов системы ввода-вывода
Путь запроса от приложения к устройству
Приложение (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: Анализ установленных драйверов
Цель: Изучить список загруженных модулей ядра и их зависимости.
Выведите список всех загруженных модулей:
bashlsmod
Проанализируйте вывод:
bashlsmod | less
Обратите внимание на:
- Имя модуля
- Размер
- Количество использующих (Used by)
Найдите модули, относящиеся к вашим устройствам:
bash# Для сетевых устройств lsmod | grep -iE 'net|eth|wifi|wireless' # Для дисков lsmod | grep -iE 'ahci|nvme|sd|scsi' # Для USB lsmod | grep -i usb
Изучите зависимости модуля:
bashmodinfo <имя_модуля> | grep depends # Например: modinfo i915 | grep depends
Вопросы для отчёта:
- Какой модуль отвечает за работу вашего сетевого интерфейса?
- Какие модули зависят от
usbcore
? - Какой модуль управляет вашим GPU?
Задание 2: Работа с параметрами драйверов
Цель: Изучить и изменить параметры драйвера.
Найдите параметры драйвера:
bash# Для всех модулей sudo modinfo -p $(cut -d' ' -f1 /proc/modules) # Для конкретного модуля (например, iwlwifi для Wi-Fi) modinfo iwlwifi | grep -A 10 "parm:"
Посмотрите текущие значения параметров:
bash# Для всех модулей find /sys/module/ -name parameters -type d -exec ls -l {} \; # Для конкретного модуля ls /sys/module/iwlwifi/parameters/ cat /sys/module/iwlwifi/parameters/11n_disable
Измените параметр драйвера (временно):
bash# Отключим агрегацию пакетов 802.11n для теста echo "1" | sudo tee /sys/module/iwlwifi/parameters/11n_disable
Создайте постоянную настройку:
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: Анализ планировщика ввода-вывода
Цель: Изучить и настроить планировщик дисковых операций.
Посмотрите текущий планировщик:
bash# Для всех дисков for disk in /sys/block/*; do echo "$(basename $disk): $(cat $disk/queue/scheduler)" done # Для конкретного диска (например, sda) cat /sys/block/sda/queue/scheduler
Измените планировщик (временно):
bash# Установите NOOP для sda echo "noop" | sudo tee /sys/block/sda/queue/scheduler
Сделайте изменение постоянным:
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
Измерьте производительность с разными планировщиками:
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: Создание простого драйвера (простое ядерное модуля)
Цель: Написать и загрузить свой первый драйвер.
Создайте файл
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);
Создайте Makefile:
makefileobj-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
Соберите и загрузите модуль:
bashmake sudo insmod hello_driver.ko
Проверьте загрузку:
bashdmesg | tail -2 lsmod | grep hello
Выгрузите модуль:
bashsudo rmmod hello_driver dmesg | tail -1
Вопросы для отчёта:
- Что означает уровень
KERN_INFO
вprintk
? - Какие уровни логирования существуют в ядре?
- Почему при сборке модуля используется путь к исходникам ядра?
Контрольные вопросы
Как ядро Linux определяет, какой драйвер использовать для нового устройства?
- Ядро использует идентификаторы устройства (PCI ID, USB VID/PID) и сравнивает их со списком поддерживаемых устройств в драйверах. При подключении устройства вызывается функция
probe()
драйвера.
- Ядро использует идентификаторы устройства (PCI ID, USB VID/PID) и сравнивает их со списком поддерживаемых устройств в драйверах. При подключении устройства вызывается функция
Чем отличаются символьные и блочные устройства?
- Блочные устройства работают с фиксированными блоками данных (обычно 512 байт или 4КБ), поддерживают произвольный доступ. Пример: жёсткие диски.
- Символьные устройства работают с потоком данных, без фиксированной структуры. Пример: терминалы, последовательные порты.
Какой планировщик ввода-вывода оптимален для SSD и почему?
- Для SSD рекомендуется NOOP или NONE, так как SSD не имеют механических задержек (поиска головки). Дополнительная обработка запросов в планировщике только замедляет работу.
Как добавить новый параметр в ваш драйвер?
cstatic int debug_level = 0; module_param(debug_level, int, 0644); MODULE_PARM_DESC(debug_level, "Debug level (0=off, 1=low, 2=high)");
Как отладить драйвер устройства?
- Использовать
printk
с разными уровнями логирования - Анализировать вывод через
dmesg
- Использовать
ftrace
иkprobes
для трассировки - Динамическая отладка через
dynamic_debug
- Использовать
Дополнительные материалы для углублённого изучения
Книги
- Linux Device Drivers, 4th Edition (Jonathan Corbet, Alessandro Rubini, Greg Kroah-Hartman) — Библия драйверописателей.
- Understanding the Linux Kernel (Daniel P. Bovet, Marco Cesati) — Глубокое погружение в архитектуру ядра.
- Linux Kernel Development (Robert Love) — Современное ядро от разработчика.
Онлайн-ресурсы
- The Linux Kernel Module Programming Guide
- Linux Device Drivers Series на LWN.net
- Linux Kernel Source Browser
Инструменты для разработки
- QEMU: Эмуляция оборудования для тестирования драйверов
- kmemleak: Обнаружение утечек памяти в ядре
- kprobes: Динамическая отладка ядра
- SystemTap: Скриптовая отладка ядра
Заключение
Драйверы устройств — это критически важный компонент любой операционной системы. В Linux они реализованы как:
- Модули ядра — обеспечивают гибкость и модульность
- Часть ядра — обеспечивают производительность и стабильность
Понимание работы драйверов необходимо для:
- Диагностики проблем с оборудованием
- Оптимизации производительности системы ввода-вывода
- Разработки собственных драйверов для специализированного оборудования
- Анализа безопасности взаимодействия с оборудованием
💡 Практический совет:
"Чтобы глубже понять драйверы, изучайте исходный код ядра Linux. Начните с простых драйверов в
drivers/char/
и постепенно переходите к более сложным."
Критерии оценки самостоятельной работы
Критерий | Баллы |
---|---|
Выполнение всех практических заданий | 50 |
Качество ответов на контрольные вопросы | 30 |
Анализ результатов экспериментов | 15 |
Ссылки на дополнительные материалы | 5 |
Срок сдачи: 7 дней
Формат отчёта: PDF с описанием выполненных шагов, скриншотами и выводами.
Обязательно включите: Ваши наблюдения по производительности при смене планировщика ввода-вывода.