Функция драйвера заключается в изолировании остальной части системы от особенностей аппаратного обеспечения. При помощи стандартных интерфейсов между драйверами и остальной операционной системой основная часть системы ввода-вывода может быть помещена в машинно-зависимую часть ядра.
Когда пользователь обращается к специальному файлу, (виртуальная) файловая система определяет номер старшего и младшего устройств, а так же выясняет, является ли файл блочным специальным файлом или символьным специальным файлом. Номер старшего устройства используется в качестве индекса для одной из двух внутренних хэш-таблиц, содержащих структуры данных для блочных или символьных специальных файлов. Найденная таким образом структура содержит указатели на процедуры открытия устройства, чтения из устройства, записи и т.д. Номер младшего устройства передается в виде параметра. Добавление нового типа устройства в системе linux означает добавление нового элемента к одной из этих таблиц, а так же предоставления соответствуюших процедур выполнения, различных операций с устройством. Каждый драйвер разделен на 2 части, причем обе они являются частью ядра linux и работают в режиме ядра. Верхняя часть драйвера работает в контексте вызывающей стороны и служит интерфейсом к остальной системе linux. Нижняя часть работает в контексте ядра и взаимодействует с устройствами. Драйверам разрешается делать вызовы процедур ядра для выделения памяти, управления таймерами. Набор функций ядра, который они могут вызывать определен в документе под названием интерфейс-драйвер-ядро (Driver-Kernel-Interface). Система ввода-вывода занимается обработкой блочных специальных файлов и символьных специальных файлов.
1. Обработка блочных специальных файлов.
Главная цель при работе с данными файлами заключается в минимизации количества операций передачи данных. Для достижения данной цели в linux-системах между дисковыми драйверами и файловой системой имеется кэш. Кэш представляет собой таблицу в ядре, в которой хранятся тысячи недавно использованных блоков. Когда файловой системе требуется блок диска, то сначала проверяется кэш. Если нужный блок есть в кэше, то он берется оттуда, при этом обращения к диску удается избежать. Если же блока в кэше нет, то он считывается с диска в кэш и оттуда копируется в место назначения. Кэш страниц работает не только при чтении, но и при записи. Когда программа пишет блок, то этот блок не попадает напрямую на диск, а отправляется в кэш. В любой другой системе ОС linux существует решение проблемы излишних перемещений дисковых головок. Для решения этой проблемы в linux используется планировщик ввода-вывода. Цель планировщика – переупорядочить или собрать в пакеты запросы ввода вывода к блочному устройству. Базовый планировщик linux основан на исходном планировщике Linus Elevator Scheduler. Он работает следующим образом:
Дисковые операции сортируются в дважды связанном списке, упорядоченном по адресам сектора дискового запроса. Новые запросы вставляются в этот отсортированный список. Это позволяет избежать излишних перемещений дисковых головок. Этот список запросов затем объединяется, чтобы смежные операции выполнялись за один запрос к диску.
2. Обработка символьных файлов.
Поскольку символьные устройства потребляют или производят потоки символов, то поддержка произвольного доступа не имеет смысла. Исключение составляет дисциплина линии связи. Дисциплина линии связи может быть связана с терминальным устройством и работает как интерпретатор данных, обмен которыми происходит с терминальным устройством.
Работа с сетевыми устройствами в системе linux отличается от вышерассмотренных. Сетевые устройства также потребляют и производят потоки символов, однако асинхронная природа делает их не очень подходящими для интеграции в один интерфейс с другими символьными устройствами. Драйверы сетевого устройства производят пакеты, состоящие из большого количества байтов (IRP-пакеты). Эти пакеты затем маршрутизируются через несколько драйверов сетевых протоколов и в конечном итоге передаются программе пользователя.