какие типы драйверов существуют в ос unix
25. Типы драйверов unix
Любому программисту должно быть ясно, что простое объявление внешнего устройства специальным файлом не даст возможности работать с этим устройством, если не создан и соответствующим образом не подключен к системе специальный программный код, соответствующий специфике данного устройства. Как и в большинстве современных операционных систем, такого рода программный код в ОС UNIX называется драйвером устройства (в этом контексте слово драйвер лучше всего понимать в значении «управляющий»).
Символьные драйверы являются простейшими в ОС UNIX и предназначаются для обслуживания устройств, которые реально ориентированы на прием или выдачу произвольных последовательностей байтов (например, простой принтер или устройство ввода с перфоленты). Такие драйверы используют минимальный набор стандартных функций ядра UNIX, которые главным образом заключаются в возможности взять данные из виртуального пространства пользовательского процесса и/или поместить данные в такое виртуальное пространство.
Наконец, наиболее сложной организацией отличаются потоковые драйверы. Фактически, такой драйвер представляет собой конвейер модулей, обеспечивающий многоступенчатую обработку запросов пользователя. Потоковые драйверы в среде ОС UNIX в основном предназначены для реализации доступа к сетевым устройствам, которые должны работать в соответствии с многоуровневыми сетевыми протоколами.
Подробности по поводу разных способов организации драйверов в ОС UNIX см. в разделе 3.3.
Последний момент, на который мы хотим обратить внимание в этом пункте, состоит в том, что (опять же, как и в большинстве развитых операционных систем) в ОС UNIX возможны два способа включения драйвера в состав ядра ОС. Первый способ состоит в полном включении драйвера в состав ядра на стадии генерации системы (т.е. драйвер статически объявляется частью ядра системы). Второй способ позволяет обойтись минимальным количеством статических объявлений на стадии генерации ядра (фактически, обеспечиваются лишь необходимые элементы статических таблиц). В любой момент работы системы такой драйвер может быть динамически загружен в ядро системы. После появления (статического или динамического) в ядре ОС UNIX драйверы всех разновидностей функционируют единообразно.
Связь ядра системы с драйверами (рисунок 5.15) обеспечивается с помощью двух системных таблиц:
Для связи используется следующая информация из индексных дескрипторов специальных файлов:
класс устройства (байт-ориентированное или блок-ориентированное),
тип устройства (лента, гибкий диск, жесткий диск, устройство печати, дисплей, канал связи и т.д.)
Класс устройства определяет выбор таблицы bdevsw или cdevsw. Эти таблицы содержат адреса программных секций драйверов, причем одна строка таблицы соответствует одному драйверу. Тип устройства определяет выбор драйвера. Типы устройств пронумерованы, т.е. тип определяет номер строки выбранной таблицы. Номер устройства передается драйверу в качестве параметра, так как в ОС UNIX драйверы спроектированы в расчете на обслуживание нескольких устройств одного типа.
Такая организация логической связи между ядром и драйверами позволяет легко настраивать систему на новую конфигурацию внешних устройств путем модификации таблиц bdevsw и cdevsw.
Драйвер байт-ориентированного устройства в общем случае состоит из секции открытия, чтения и записи файлов, а также секции управления режимом работы устройства. В зависимости от типа устройства некоторые секции могут отсутствовать. Это определенным образом отражено в таблице cdevsw. Секции записи и чтения обычно используются совместно с модулями обработки прерываний ввода-вывода от соответствующих устройств.
На рисунке 5.16 показано взаимодействие секции записи драйвера байт-ориентированного устройства с модулем обработки прерываний. Секция записи осуществляет передачу байтов из рабочей области программы, выдавшей запрос на обмен, в системный буфер, организованный в виде очереди байтов. Передача байтов идет до тех пор, пока системный буфер не заполнится до некоторого, заранее определенного в драйвере, уровня. В результате секция записи драйвера приостанавливается, выполнив системную функцию sleep (аналог функций типа wait).
Рис. 5.16. Взаимодействие секции записи драйвера с модулем обработки прерывания
Модуль обработки прерываний работает асинхронно секции записи. Он вызывается в моменты времени, определяемые готовностью устройства принять следующий байт. Если при очередном прерывании оказывается, что очередь байтов уменьшилась до определенной нижней границы, то модуль обработки прерываний активизирует секцию записи драйвера путем обращения к системной функции wakeup.
Аналогично организована работа при чтении данных с устройства.
После запуска устройства управление возвращается процессу, выдавшему запрос к драйверу.
Рис 5.17. Структурная схема драйвера диска типа RK
Драйверы устройств в ОС Unix
Ввиду существенных различий в работе с символьными и с блочными устройствами, в UNIX различаются два основных типа драйверов: символьные и блочные.
Для символьных устройств используются только символьные драйверы. Для каждого блочного устройства обычно имеется два разных драйвера: блочный и символьный. Блочный драйвер позволяет выполнять операции только с целым числом блоков, как и положено работать с блочными устройствами. Символьный драйвер блочного устройства является более высокоуровневой программой, которая имитирует выполнение операций чтения и записи произвольного количества байт, на самом деле используя обращения к блочному драйверу.
Помимо драйверов реальных физических устройств, система может включать драйверы «псевдоустройств». Примером может служить драйвер, обеспечивающий обращение программ к содержимому системной памяти.
При загрузке системы формируются две таблицы, для символьных и для блочных драйверов. Строки таблицы соответствуют конкретным драйверам, а столбцы – функциям, которые должен уметь выполнять драйвер, так что в ячейках таблицы содержатся адреса, по которым вызываются функции драйвера. Набор функций для символьных и для блочных драйверов слегка разнится, поэтому используются две разных таблицы.
К наиболее важным функциям драйвера относятся следующие.
· Открытие устройства. Как минимум, при этом увеличивается счетчик текущих обращений к устройству, что позволяет ставить обращения в очередь, если устройство занято. Некоторые устройства при открытии могут выполнять еще какие-то начальные действия.
· Закрытие устройства – операция, противоположная открытию.
· Обработка прерывания – выполняет ввод или вывод очередной порции данных, когда устройство переходит в состояние готовности.
· Опрос устройства – эта функция выполняется для тех устройств, которые не генерируют прерываний, или если при разработке драйвера почему-либо решено не использовать прерывания от устройств. Опрос выполняется не постоянно, а с некоторым периодом, по прерыванию от таймера.
· Чтение данных с устройства.
· Запись данных на устройство.
· Вызов стратегии. Это способ выполнения операций ввода/вывода, характерный для блочных устройств. При этом запрос может быть поставлен в очередь. Запрос в ряде случаев может быть удовлетворен путем обращения к дисковому кэшу, без выполнения чтения или записи на устройство.
· Выполнение специальных функций. Набор этих функций зависит от конкретного устройства. Это может быть, например, опрос или установка текущего режима работы устройства, форматирование дорожек диска, перемотка ленты и т.п.
Интересной отличительной особенностью UNIX является то, что для работы с периферийными устройствами прикладные программы могут и должны использовать те же средства, что для работы с файлами. Вообще, устройства в UNIX представлены как специальные файлы, вписанные в каталог файловой системы наравне с обычными файлами. Каждому драйверу устройства соответствует отдельный специальный файл, символьный или блочный, в зависимости от типа драйвера. Как правило, все специальные файлы размещаются в каталоге /dev. Чтобы начать работу с устройством, программа должна вызвать функцию открытия файла, указав ей имя специального файла. При этом происходит обращение к функции открытия из драйвера соответствующего устройства.
С каждым специальным файлом связаны два числа, называемые старшим и младшим номерами устройства. Старший номер определяет номер строки в таблице символьных либо блочных драйверов. Младший номер передается драйверу как дополнительный параметр. Он может означать, например, номер конкретного дискового устройства.
Типы драйверов
Драйверы различаются по возможностям, которые они предоставляют, а также по тому, каким образом обеспечивается к ним доступ и управление. Можно рассматривать три основные типа драйверов:
Символьные драйверы Этот тип драйверов обеспечивает работу с устройствами с побайтовым доступом и обменом данными. К таким устройствам можно отнести модемы, терминалы, принтеры, манипуляторы мышь и т.д. Доступ к таким драйверам не включает использование буферного кэша, таким образом ввод и вывод как правило не буферизуется. При необходимости буферизации для символьных драйверов обычно используется подход, основанный на структурах данных, называемых clist. Блочные драйверы Этот тип драйверов позволяет производить обмен данными с устройством фиксированными порциями (блоками). Например, для жесткого диска данные можно адресовать и, соответственно, читать только секторами, размер которых составляет несколько сотен байтов. Для блочных драйверов обычно используется буферный кэш, который и является интерфейсом между файловой системой и устройством. Хотя операции чтения и записи для процесса допускают обмен данными, размер которых меньше размера блока, на системном уровне это все равно приводит к считыванию всего блока, изменению части его данных и записи измененного блока обратно на диск. Драйверы низкого уровня (raw drivers) Этот тип интерфейса блочных драйверов позволяет производить обмен данными с блочными устройствами, минуя буферный кэш. Это, в частности, означает, что устройство может быть адресовано элементами, размер которых не совпадает с размером блока. Обмен данными происходит независимо от файловой подсистемы и буферного кэша, что позволяет ядру производить передачу непосредственно между пользовательским процессом и устройством, без дополнительного копирования.
На рис. 5.1 приведена упрощенная схема взаимодействия драйверов устройств с другими подсистемами операционной системы UNIX.
Рис. 5.1. Драйверы устройств UNIX
Не все драйверы служат для работы с физическими устройствами, такими как сетевой адаптер, последовательный порт или монитор. Часть драйверов служат для предоставления различных услуг ядра прикладным процессам и не имеют непосредственного отношения к аппаратной части компьютера. Такие драйверы называются программными или драйверами псевдоустройств. Можно привести несколько примеров псевдоустройств и соответствующих им программных драйверов:
/dev/kmem Обеспечивает доступ к виртуальной памяти ядра. Зная виртуальные адреса внутренних структур ядра, процесс может считывать хранящуюся в них информацию. С помощью этого драйвера может, например, быть реализована версия утилиты ps(1), выводящей информацию о состоянии процессов в системе. /dev/ksyms Обеспечивает доступ к разделу исполняемого файла ядра, содержащего таблицу символов. Совместно с драйвером /dev/kmem обеспечивает удобный интерфейс для анализа внутренних структур ядра. /dev/mem Обеспечивает доступ к физической памяти компьютера. /dev/null Является «нулевым» устройством. При записи в это устройство данные просто удаляются, а при чтении процессу возвращается 0 байтов. Примеры использования этого устройства рассматривались в главе 1, когда с помощью /dev/null мы подавляли вывод сообщений об ошибках. /dev/zero Обеспечивает заполнение нулями указанного буфера. Этот драйвер часто используется для инициализации области памяти.
Данный текст является ознакомительным фрагментом.
Продолжение на ЛитРес
Читайте также
Типы драйверов
Типы драйверов Драйверы различаются по возможностям, которые они предоставляют, а также по тому, каким образом обеспечивается к ним доступ и управление. Можно рассматривать три основные типа драйверов: Символьные драйверы Этот тип драйверов обеспечивает работу с
Базовая архитектура драйверов
Базовая архитектура драйверов Драйвер устройства адресуется старшим номером (major number) устройства. Напомним, что среди атрибутов специальных файлов устройств, которые обеспечивают пользовательский интерфейс доступа к периферии компьютера, это число присутствует наряду
Встраивание драйверов в ядро
Встраивание драйверов в ядро Драйвер устройства является частью кода ядра операционной системы и обеспечивает взаимодействие других подсистем UNIX с физическими или псевдоустройствами. Существует два основных метода встраивания кода и данных драйвера в ядро
Загрузка сетевых драйверов
Загрузка сетевых драйверов Первым шагом в настройке сетевых устройств является загрузка соответствующих драйверов. Как было сказано в главе 1, драйверы подготавливаются к работе одним из двух способов: драйвер может быть непосредственно включен в состав ядра Linux либо
Установка драйверов
Установка драйверов Если вы думаете, что сразу же после установки Windows вы можете начинать работу – вы крупно ошибаетесь. И когда после многочисленных перезагрузок и настроек ваши глаза узреют ласковый пейзаж Рабочего стола Windows – не спешите устанавливать программы. Пока
2.6 Отладка драйверов
2.6 Отладка драйверов Разговор о драйверах был бы неполным, если не упомянуть об отладке драйверов. Т.к. драйвера работают в нулевом кольце защиты процессора со всеми вытекающими последствиями, то обыкновенные отладчики пользовательских приложений не пригодны для
Типы, характеризуемые значениями, ссылочные типы и оператор присваивания
Типы, характеризуемые значениями, ссылочные типы и оператор присваивания Теперь изучите следующий метод Main() и рассмотрите его вывод, показанный на рис. 3.12.static void Main(string[] args) < Console.WriteLine("*** Типы, характеризуемые значением / Ссылочные типы ***"); Console.WriteLine(-› Создание p1"); MyPoint
Типы, характеризуемые значениями и содержащие ссылочные типы
Типы, характеризуемые значениями и содержащие ссылочные типы Теперь, когда вы чувствуете разницу между типами, характеризуемыми значением, и ссылочными типами, давайте рассмотрим более сложный пример. Предположим, что имеется следующий ссылочный тип (класс),
Типы, характеризуемые значениями, и ссылочные типы: заключительные замечания
Типы, характеризуемые значениями, и ссылочные типы: заключительные замечания Чтобы завершить обсуждение данной темы, изучите информацию табл. 3.8, в которой приводится краткая сводка основных отличий между типами, характеризуемыми значением, и ссылочными типами.Таблица
Настройки поиска драйверов
Настройки поиска драйверов Часть этих параметров, предназначенная для ограничения мест, откуда можно установить драйвер, имеет тип REG_DWORD и расположена в ветви реестра HKEY_CURRENT_USERSoftwarePoliciesMicrosoftWindowsDriverSearching:• DontPromptForWindowsUpdate – если значение данного параметра равно 1, то будет
Процесс установки драйверов
Процесс установки драйверов Еще одна часть параметров также имеет тип REG_DWORD, но расположена в ветви HKEY_LOCAL_MACHINESOFTWAREPoliciesMicrosoftWindowsDeviceInstallSettings. Эти параметры влияют на процесс установки драйверов устройств.• InstallTimeout – определяет максимально возможное время установки
Ошибки драйверов
Ошибки драйверов Пожалуй, одной из главных причин, приводящей к нестабильности работы Windows ХР, является недостаточно продуманное и небезопасное использование драйверов установленных и подключенных устройств. Чаще всего это касается драйверов принтера и
Ошибки драйверов
Ошибки драйверов Пожалуй, одной из главных причин, приводящих к нестабильности работы операционной системы, является недостаточно продуманное и небезопасное использование драйверов установленных и подключенных устройств. Чаще всего это касается драйверов принтера,
Установка драйверов
Установка драйверов Теперь подробно разберем то, что нужно сделать при проверке кабеля в магазине, а затем и при подключении телефона к своему домашнему компьютеру.Подключение телефона к компьютеру проще всего начать с чтения «Руководства пользователя» к data-кабелю или
Установка драйверов
Установка драйверов Что делать теперь? Теперь вставьте диск с драйверами от материнской платы и установите их. Обычно при этом будут установлены драйверы звуковой, сетевой платы и некоторые дополнительные драйверы.В процессе установки драйверов потребуется
Обновление драйверов
Обновление драйверов Установка последних версий драйверов – эффективное средство для повышения стабильности работы компьютера и устранения неполадок. Существуют два основных способа обновления драйверов: использование службы обновления Windows Vista и поиск на сайте
Глава 1. Введение в драйвера устройств
Интерес к написанию драйверов для Linux устойчиво растет, по мере роста популярности этой операционной системы. Linux – межплатформенная ОС, большая часть кода которой, не зависит от аппаратной платформы, и большинство пользователей (к счастью) могут ничего не знать об ее аппаратных особенностях. Большей частью, особенности каждой аппаратной платформы сокрыты в реализации драйверов, которые и делают возможной работу ОС на данной платформе.
Драйвера устройств играют специальную роль в ядре Linux. Они представляют собой “черные ящики”, которые обрабатывают определенную часть запросов к аппаратной части Linux ядра через хорошо организованный внутренний программный интерфейс. Особенности работы каждого конкретного устройства полностью скрыты в коде драйвера. Запросы пользователей посылаются через стандартный набор системных вызовов, который не зависит от конкретного драйвера (но, несколько зависит от класса обслуживаемого устройства). Эти запросы отображаются на аппаратно-зависимые функции, которые управляют реальными устройствами. Именно набор этих функций и играют роль драйвера устройств. Программный интерфейс реализован таким образом, что драйвера могут быть построены отдельно от ядра, и прилинкованы (связаны с ядром) в запущенное ядро по мере надобности. Это свойство драйверов в Linux называется модульностью и сильно упрощает написание и управление драйверами.
Таким образом, драйвера в Linux могут быть реализованы как часть ядра, а могут быть реализованы как модули, линкуемые (устанавливаемые) в ядро по мере необходимости.
Эта книга научит вас тому, как можно написать свой собственный драйвер и как просматривать определенные части ядра. Мы будем рассматривать аппаратно-независимое приближение. При необходимости, будет представлена техника программирования и интерфейс, не применительно к какому-либо устройству. Каждый драйвер уникален, как и программист, который его пишет. Лично вам потребуется глубокое понимание специфики вашего устройства. Но несмотря на уникальность каждого конкретного драйвера, большинство принципов одинаковы для всех драйверов. Эта книга не научит вас работать с вашим устройством, но она расскажет вам об общих принципах, которые помогут вам заставить ваше устройство работать.
Пока вы будете учиться писать драйвера, вы многое узнаете о ядре Linux, и это поможет вам понять, как работает машина вообще, и почему некоторые вещи работают не так, как вы ожидаете. Мы будем знакомиться с новыми идеями постепенно, начиная с очень простых драйверов. Каждую новую концепцию мы будем сопровождать простым кодом, не относящемуся к какой-то конкретной аппаратной конструкции.
В этой главе мы не будем писать какого-либо кода. Однако, мы познакомимся с некоторыми базовыми концепциями, знание которых поможет нам в дальнейшем программировании.
Роль драйверов устройств
Как программисту, при написании драйвера, вам предстоит разрешить компромисс между временем, затраченным на программирование, и гибкостью результирующего кода. И хотя многим может показаться странным говорить о “гибкости” драйвера (англ. flexibility, имеется в виду гибкость, с точки зрения политики управления драйвером), но нам нравится это слово, потому что оно подчеркивает роль драйвера устройства, который обеспечивает механизм взаимодействия с устройством, но не политику управления им.
Различие между механизмом управления и политикой управления представляет собой одну из лучших идей в Unix архитектуре. Большинство проблем программирования можно разделить на две части: “какие характеристики необходимо обеспечить” (функциональность и связанный с ней механизм реализации), и “как эти характеристики можно будет использовать” (политика). Если эти два элемента реализуются разными частями программы, или даже разными программами, то такой программный пакет легче разрабатывать и адаптировать к конкретным целям.
Для примера, Unix управление графическим дисплеем разделено между X сервером, с одной стороны, и менеджерами окон и сессии с другой. X серверу, который предоставляет унифицированный интерфейс для пользовательских программ, известны особенности оборудования. Ни менеджер окон, ни менеджер сессии ничего не знают об аппаратных особенностях дисплея. Пользователи могут использовать один и тот же оконный менеджер на разных аппаратных платформах, или использовать различные оконные менеджеры на одной и той-же рабочей станции. Даже совершенно различные десктопы, такие как KDE и GNOME могут сосуществовать на одной системе, благодаря хорошо продуманной архитектуре графической подсистемы Unix.
Другой пример слойной структуры – это семейство протоколов TCP/IP. Операционная система предоставляет абстракцию, называемую сокет (socket), которая реализует передачу данных, но не управляет политикой такой передачи. С другой стороны, различные сетевые серверы, предоставляющие различные сервисы, опираясь на транспорт сокетов, реализуют политику передачи данных. Кроме того, серверы, наподобии ftpd реализуют сервис передачи файлов, в то время как пользователи могут использовать любую клиентскую программу, которую они предпочитают, для работы с этим сервером. Таким образом, политика, должна быть максимально свободна от реализации всех остальных функций системы.
Ко всем типам драйверов применимо одно и то-же разделение механизма управления и политики управления. Драйвер дисковода для гибких дисков не содержит в своем коде никакой политики, его роль – показать содержимое дискеты как последовательность блоков данных. Более высокие уровни операционной системы обеспечивают политику управления: какие пользователи могут получить доступ к дисководу, читается ли диск напрямую или через файловую систему, и какие пользователи могут монтировать файловую систему, расположенную на дискете. Т.к., в зависимости от разных причин, требуется различный способ работы с устройством, очень важно, чтобы код драйвера был свободен от политики настолько, насколько это возможно.
При написании драйвера, программист должен уделить особенное внимание следующей фундаментальной концепции: пишите код взаимодействия с аппаратурой, но не форсируйте проблемы политики использования драйвера, т.к. различные пользователи могут иметь различные требования к этой политике. Управлять такой политикой должны пользовательские приложения. Драйвер, который обеспечивает доступ к оборудованию без дополнительных ограничений называется гибким. Однако, для упрощения кода драйвера, в некоторых случаях, требуется присутствие в его коде и некоторых политических решений. Например, драйвер ввода/вывода может предоставлять только побайтовый доступ к аппаратным ресурсам, для того, чтобы не перегружать драйвер дополнительным кодом доступа к индивидуальным битам.
Вы можете смотреть на ваш драйвер с различных точек зрения. С одной стороны это программный слой, лежащий между приложением и реальным устройством. Эта особая роль драйвера позволяет программисту определить представление устройства в программе – различные драйвера могут иметь различные характеристики даже для одного и того же устройства. При разработке драйвера решаются различные компромиссы. Например, простое устройство может использоваться одновременно различными программами, и программист должен решить, каким образом будут обрабатываться такие параллельные запросы. Вы можете реализовать отображение памяти на устройство независимо от аппаратных особенностей платформы, или вы можете предоставить пользовательскую библиотеку, реализующую особенную политику управления, для приложений, использующих драйвер. Основной компромисс заключается между желанием предоставить наиболее полный набор функций управления устройством и между тем временем, которое вы желаете потратить на реализацию драйвера и устранение ошибок.
Драйверы не реализующие политику управления имеют некоторые общие особенности. В них реализована поддержка синхронных и асинхронных операций. Они обеспечивают параллельные запросы (например, одновременно от разных приложений). Они в состоянии предоставить наиболее полные характеристики оборудования. Также, в них максимально сокращено количество программных слоев, что несколько упрощает проектирование и ускоряет работу драйвера. Драйвера этого сорта не только работают лучше, с точки зрения конечных пользователей, но и более просты в использовании.
Многие драйвера устройств реализуются совместно с пользовательскими программами, упрощающими конфигурирование и доступ к устройству. Диапазон этих программ достаточно широк, от простых утилит до сложных графических приложений. Часто, также, предоставляются клиентские библиотеки, обеспечивающие более высокоуровневый доступ к функциям драйвера.
В рамках данной книги описывается ядро операционной системы Linux, поэтому мы попытаемся обойти стороной вопросы политики управления устройствами, построения клиентских программ и клиентских библиотек. Конечно, в особых случаях, мы будем касаться политики управления устройством, но не станем акцентировать на этом внимание. Вы должны понимать, что пользовательские программы являются неотъемлемой частью комплекса управления устройством, и что, как минимум, этот комплекс подчиняется общесистемной политике.
Структура ядра
В системе Unix, множество, конкурирующих за ресурсы, процессов заняты решением различных задач. Каждый процесс запрашивает системные ресурсы, такие как процессорное время, память, сетевые и файловые службы, и прочее. Ядро представляет собой программный код, в задачу которого входит разделение ресурсов вычислительной системы между различными процессами. И хотя различия между различными задачами ядра не всегда прозрачны для понимания, можно попытаться изобразить взаимодействие различных элементов ядра в виде структурной схемы, как показано на рисунке 1-1.
Рисунок 1-1. Структурная схема ядра.
Управление процессами Одной из функций ядра является управление созданием и уничтожением процессов, обеспечение взаимодействие процессов с внешним миром (ввод/вывод) и обеспечение взаимодействия процессов между собой (сигналы, трубы (pipes), и IPC-примитивы (interprocess communication). Также, в функции управления процессами, входит диспетчеризация процессов, которая управляет разделением времени процессора между процессами. Управление памятью Память компьютера представляет собой очень важный ресурс компьютера, и реализация политики его разделения существенно влияет на производительность операционной системы. Ядро предоставляет огромные виртуальные пространства памяти некоторым или всем процессам на фоне ограниченных физических ресурсов. Различные части ядра взаимодействуют с подсистемой управления памятью через определенный набор функций, начиная от простой пары malloc()/free() и заканчивая множеством более сложных экзотических функций. Файловые системы Unix жестко связан с концепцией файловых систем: почти все в Unix может быть представлено как файл. Ядро выстраивает структурированную файловую систему из неструктурированного аппаратного слоя, и результирующая файловая абстракция жестко вплетена во все компоненты системы. В дополнении к этому, Linux поддерживает множество типов файловых систем, которые различным способом организуют данные на физических носителях информации. Для примера, дискета может быть отформатирована либо в Linux стандарте, файловой системе ext2, либо, в распространенных на Windows платформе, форматах FATx. Управление устройствами Практически каждая системная операция, в конечном счете, отображается на физическое устройство. За исключением процессора, памяти и некоторых других элементов, все остальные операции управления устройствами выполняются кодом, специфичным для адресуемого устройства. Этот код называется драйвером устройства. Ядро должно включать в себя драйвер каждого устройства, управляемого системой, начиная от жесткого диска и заканчивая клавиатурой и мышью. Именно этот аспект функциональности ядра представляет главный интерес для данной книги. Сетевые службы Сетевой транспорт должен быть реализован в ядре операционной системы, т.к. большинство сетевых операций не специфичны для процессов – поступление пакетов является асинхронным событием. Пакеты должны быть собраны, идентифицированы и диспетчеризованы перед тем, как будут переданы на дальнейшую обработку в пользовательские процессы. Система должна управлять передачей пакетов данных между программами через сетевые интерфейсы. Маршрутизация и система распознавания различных классов сетевых адресов, также должны быть реализована в ядре операционной системы.
Продвигаясь к концу книги вы познакомитесь более подробно с функциями ядра в главе 16.
Одной из замечательных характеристик ядра Linux является способность расширять функциональность ядра во время его работы. Это означает, что вы можете добавить требуемые функции в ядро без перезагрузки операционной системы.
Драйвер, который может быть прилинкован (добавлен) к ядру во время его работы называется модулем. Ядро Linux поддерживает несколько типов (или классов) модулей. Модуль представляет собой объектный код (т.е. код не линкованный в исполняемую программу), который может быть динамически установлен в ядро с помощью программы insmod, и удален из ядра, с помощью команды rmmod.
На рисунке 1-1 изображены различные классы модулей, исполняющих специфические задачи. Говорят, что модули принадлежат определенному классу, в зависимости от функциональности, которую они предлагают. Положение модулей на рисунке 1-1 покрывает наиболее важные, но далеко не полные классы модулей, т.к. функциональность, реализуемая модулями в Linux, все более и более расширяется.
Классы устройств и модулей
В Unix, устройства подразделяются на три класса (типа). Каждый модуль реализует поддержку одного из этих классов устройств, и, таким образом, подразделяется на модули символьных, блочных и сетевых устройств. Такое разделение модулей на различные классы не является жестким. Программист может создать большой модуль, реализующий различные драйверы в одном куске кода. Однако, более верным стилем программирования является создание различных модулей для каждой новой функциональности, которую они добавляют, т.к. декомпозиция является ключевым элементом масштабируемости и расширяемости.
Рассмотрим более подробно все три класса устройств:
Символьные устройства (character devices)
Символьным называется устройство, которое может быть представлено потоком байт (как файл). Такие драйвера реализуют, по меньшей мере, системные вызовы open(), close(), read() и write(). Текстовая консоль (/dev/console) и последовательные порты (/dev/ttyS0 и аналогичные) представляют собой примеры символьных устройств, т.к. они отлично представляются через абстракцию потока. Доступ к символьным устройствам реализуется через специальные файлы, называемые интерфейсами устройств, которые обычно располагаются в каталоге /dev. Отличие между символьным устройством и файлом, заключается в том, что открыв обычный файл, вы можете перемещаться по нему как вперед, так и назад, в то время как символьное устройство представляет собой последовательный канал данных. Однако, существуют символьные устройства, которые представляются как область данных, и вы также можете перемещаться по ней как вперед, так и назад, используя функции lseek() и mmap().
Блочные устройства (block devices)
Доступ к блочным устройствам, так же как и к символьным, осуществляется через специальные файлы-интерфейсы устройств, расположенные, обычно, в каталоге /dev. На блочных устройствах, как правило, размещаются файловые системы. В большинстве Unix систем, блочные устройства могут быть представлены только как множество блоков. Размер блока кратен степени двух и часто равен одному килобайту данных. Linux позволяет приложениям читать и писать в блочные устройства, также как и в символьные. Разница заключается в том, что при обращении к блочному устройству передается блок данных, а не один байт (символ). Для пользователя, блочные и символьные устройства неразличимы. Драйвер блочного устройства взаимодействует с ядром через более широкий блочно-ориентированный интерфейс, но это скрыто от пользователя и приложений, которые взаимодействуют с устройством через файл интерфейса устройства, расположенного, как правило, в каталоге /dev. Интерфейсы блочных устройств наиболее удобны для монтирования файловых систем.
Сетевые интерфейсы (network interfaces)
Не будучи потокоориетированным, сетевой интерфейс не может быть легко представлен через файловый интерфейс устройства, наподобии /dev/tty1. В ОС Unix, доступ к сетевому интерфейсу осуществляется через уникальное имя, такое как eth0, которое не является элементом файловой системы. Взаимодействие между ядром и драйвером сетевого устройства полностью отличается от взаимодействия с символьным и блочным устройством. Вместо чтения и записи, ядро вызывает функции, относящиеся к передаче пакетов.
В ОС Linux представлены и другие классы модулей. Модули каждого класса предоставляют интерфейс для предоставления определенного типа устройств. Поэтому, можно говорить о модулях шины USB, последовательного порта и т.д. К наиболее общему, нестандартному классу устройств относятся устройства SCSI. И хотя любое устройство подсоединенное к шине SCSI представляется файлом интерфейсом в каталоге /dev как символьное или блочное устройство, внутренняя организация таких драйверов отличается.
SCSI есть акроним от Small Computer System Interface, и представляет собой учрежденный стандарт на рынке рабочих станций и high-end серверов.
Также как сетевые карты обеспечивают аппаратно-зависимую функциональность сетевой подсистемы, также, контроллеры SCSI обеспечивают функциональность подсистемы SCSI для доступа к устройствам на соответствующей шине. SCSI представляет коммуникационный протокол между компьютером и периферийными устройствами, и каждое SCSI устройство работает в этом протоколе, независимо от того, какой контроллер установлен на вашем компьютере. Поэтому ядро Linux включает в себя реализацию SCSI (т.е. отображает файловые операции на коммуникационный протокол SCSI). Разработчик драйвера должен реализовать отображение между SCSI абстракцией и физической шиной. Это отображение зависит от SCSI контроллера и не зависит от устройств, подключенных к SCSI шине.
Другие классы устройств добавлены к ядру в последнее время, включая драйвера USB, FireWire и I2O. По подобию управления драйверами SCSI, разработчики ядра классифицируют характеристики классов устройств и передают их реализацию на уровень драйверов. Такая классификация необходима, прежде всего, для того, чтобы избежать дублирования работы и ошибок, упрощая, тем самым, процесс написания таких драйверов.
В добавление к драйверам устройств, в ядре модуляризируются и другие функции для поддержки программно-аппаратных технологий. Помимо драйверов устройств, файловые системы, возможно, представляют собой важнейший класс модулей системы Linux. Тип файловой системы определяет способ организации информации о дереве файлов и каталогов на блочном устройстве. По своей сути это не драйвер устройства, т.к. нет явного устройства, связанного со способом размещения информации на нем. Драйвер файловой системы представляет собой программный слой, отображающий низкоуровневые структуры данных (блоки) в высокоуровневые (деревья). Тип файловой системы определяет набор атрибутов и максимальную длину имени файла. Модуль файловой системы должен реализовать самый нижний уровень системных вызовов при обращении к файлам и каталогам. Реализация заключается в отображение имен файлов и путей на структуру данных, сохраняемых в блоках данных устройства. При этом учитывается дополнительная информации, такая как режимы доступа. Такой интерфейс совершенно не зависит от реальной передачи данных с диска и на диск (или другую среду), который реализуется драйвером блочного устройства.
Если вы понимаете как сильно система Unix зависит от используемой файловой системы, то вы легко представите себе жизненную важность такой программной концепции для системных операций. Способность декодировать информацию о файловой системе лежит на самом нижнем уровне иерархии ядра, и это очень важно. Даже если вы пишете блочный драйвер для вашего нового устройства CD-ROM, он будет бесполезным, если вы не сможете выполнить команды ls или cp для данных, которые содержаться на установленном в нем диске. Linux поддерживает концепцию модулей файловых систем, чей программный интерфейс описывает различные операции, которые могут быть выполнены над inode, каталогом, файлом и суперблоком файловой системы. Написание модулей для файловых систем достаточно редкая задача, т.к. официальное ядро уже содержит код для большинства типов существующих файловых систем.
Замечания о безопасности
Вопросы безопасности для современных систем представляют собой весьма актуальную задачу постоянно растущей важности. По мере продвижения по книге, мы будем затрагивать эти вопросы. Однако, имеются несколько важных понятий, которые стоит затронуть уже сейчас.
Нарушение безопасности системы может быть рассмотрено в двух аспектах – случайном и преднамеренном. Обычный пользователь может случайно нарушить безопасность системы в результате неправильного использования программ, или в результате использования программ, содержащих ошибки. Программист, имея, как правило, более высокую квалификацию, нежели обычный пользователь, потенциально представляет большую заботу для системы безопасности. Запуск чужой программы под логином суперпользователя означает, иногда, передачу создателю программы прав суперпользователя. И хотя свободный доступ к компилятору напрямую не означает дыру в безопасности, но эта дыра может стать явной после запуска откомпилированной программы или, тем более модуля. Помните, что модуль установленный в ядро может делать все что угодно, без каких либо серьезных ограничений.
Любая проверка безопасности в системе порождается кодом ядра. Если ядро имеет дыру в безопасности, значит эту дыру имеет операционная система. При использовании официальной версии ядра, загрузка модулей разрешена только авторизованным пользователям. Системный вызов create_module() проверяет авторизацию пользователя при загрузке модуля в ядро. Таким образом, при использовании официальной версии ядра, использование привилегированного кода допустимо только суперпользователем или злоумышленником получившем права привилегированного пользователя.
Версия ядра 2.0 позволяет запускать привилегированный код только суперпользователю, в то время как версия 2.2 имеет более изощренные способы проверки авторизации. Мы обсудим это в главе 5 «Capabilities and Restricted Operations» и «Enhanced Char Driver Operations».
По возможности, разработчики драйверов должны избегать политики безопасности в своем коде. Безопасность системы это проблема политики управления, которая часто решается на верхних уровнях ядра или системы лучшим способом, и управляется системным администратором. Однако, всегда имеются исключения из этого правила. Как разработчик драйвера, вы должны избегать ситуаций, в которых, через запросы к драйверу можно получить злоумышленный доступ к системным ресурсам. Т.е. вы должны обеспечить адекватный контроль таких запросов. Например, операции с устройством, которые управляют глобальными ресурсами (такими как установка линий прерываний) или операции, которые не должны производиться другими пользователями (такие как установка размера блока по умолчанию на магнитном накопителе) обычно доступны только привилегированным пользователям, и такая проверка должна быть реализована в самом драйвере.
Конечно же, разработчики драйверов должны быть особенно внимательны и избегать ошибок при реализации системы безопасности. Язык программирования C, позволяет легко допустить некоторые типы ошибок. Например, сегодня известно множество дыр в безопасности системы, которые открываются через переполнение буфера. Программисты забывают проверить количество данных передаваемых в буфер обмена между программами. Такие ошибки могут иметь непредсказуемые последствия для системы в целом, и для ее безопасности в частности. Поэтому таких ошибок надо избегать. К счастью, в контексте драйверов устройств, избежать таких ошибок относительно легко, так как интерфейс драйвера с пользователем жестко определен и хорошо контролируется.
Стоит ознакомиться с некоторыми основными идеями безопасности. К любым входным данным, полученным от пользовательского процесса, стоит относиться с большим подозрением. Никогда не доверяйте данным не проверив их. Будьте внимательны при инициализации памяти. Любая память полученная из ядра должна быть инициализирована (например, обнулена) сразу, или, хотя бы, перед тем, как она будет передана пользовательскому процессу или устройству. В противном случае, может произойти утечка информации. Если ваше устройство интерпретирует данные посланные в него, то будьте уверены, что пользователь не может послать ничего, что могло бы подвергнуть систему риску сбоя. Наконец, думайте о возможных последствиях дисковых операций. Если какие либо специфические операции (например, форматирование диска) могут повредить систему, то они должны быть ограничены привилегиями пользователей.
Будьте внимательны, когда получаете программный продукт со стороны, особенно если он связан с работой в ядре. Помните, что каждый, кто имеет источники кода, может его модифицировать и перекомпилировать. В результате, всем известная программа может превратиться в троянского коня. Как правило, вы можете доверять ядру полученному вами из дистрибутива, но вы не должны использовать ядра, компиляция которых произведена кем-то, кому вы не доверяете. Например, ядро, откомпилированное с преднамеренным злым умыслом, может позволить загружать в себя модули кому угодно, что полностью открывает двери к системе через создание модулей.
Заметьте, что ядро Linux может быть собрано без поддержки модулей вообще, что закроет одну из потенциальных дыр в безопасности системы. В этом случае, конечно, все необходимые драйвера должны быть встроены в ядро. Также, используя ядра версии 2.2 и выше, возможно запрещение загрузки модулей после загрузки системы через специальный механизм.
Нумерация версий
Перед началом программирования, мы бы хотели прокомментировать схему нумерации версий ядра, используемую в Linux. Также, следует сказать о версиях, для которых материал книги является актуальным.
Прежде всего, обратите внимание, что каждый программный пакет, используемый в ОС Linux имеет свой собственный номер релиза, и что часто, имеются зависимости между релизами различных пакетов. Создатели Linux дистрибутивов решают проблемы совместимости пакетов. Поэтому пользователи, которые инсталлируют пакеты из таких дистрибутивов, не интересуют вопросы совместимости версий зависимых пакетов. Взяв новый пакет со стороны, или решив апгрейдить существующий пакет, вы можете столкнуться с проблемой совместимости. К счастью, большинство современных дистрибутивов поддерживают автоматическую проверку зависимостей пакетов. И, обычно, менеджеры пакетов не позволят установить пакет до удовлетворения зависимостей.
Для того, чтобы запустить примеры приводимые в книге вам не будут нужны, какие-нибудь особые версии инструментов, за исключением ядра – можно использовать любой современный Linux дистрибутив. Те читатели, кому интересно, могут ознакомиться с особенностями, используемой ими версии ядра, в файле Documentation/Changes в каталоге источников ядра (как правило /usr/src/linux/).
Что касается версий ядра, то четно нумерованные версии (например 2.2.х или 2.4.х) отражают стабильность версии и предназначены для использования в готовых дистрибутивах. Нечетно нумерованные версии (например 2.3.х), наоборот, отражают нестабильность и предназначены для тестеров и разработчиков.
Эта книга охватывает версии ядра от 2.0 до 2.4. Акцент сделан на описании характеристик версии 2.4, последней, на момент написания книги. Версия 2.2 отражена в книге практически полностью. Сосредоточено внимание на различиях 2.2 и 2.4, а также на характеристиках недоступных в 2.0. В общем, примеры приведенные в книге должны соответствовать широкому диапазону версий ядра. Примеры тестировались на версии 2.4.4, и применимы, также для версий 2.2.18 и 2.0.38 (по оригинальному английскому тексту непонятно, тестировались ли примеры на чем-нибудь кроме 2.4.4).
В тексте книги специально не рассматриваются нечетно-нумерованные версии ядра. Т.к. разработчики, экспериментирующие с новыми характеристиками нестабильных ядер, обычно, достаточно искусно работают с кодом ядра и данная книга не представляет для них интереса. Обычные пользователи, которые могут стать читателями данной книги, не имеют причин для использования нестабильных ядер. Следует заметить, что на экспериментальные версии ядер не предоставляется никаких гарантий.
Заметьте, что даже если вы пользуетесь четно-нумерованными ядрами, какие-либо гарантии вам может обеспечить только коммерческий дистрибьютор, на основе договора о купле-продаже.
Как уже говорилось, Linux является аппаратно-независимой операционной системой. Это уже давно не “Unix клон для PC клонов”. Linux успешно работает на процессорах Alpha и SPARC, на платформах 68000 и PowerPC. Число платформ, поддерживаемых Linux растет, наверное, каждый месяц. Эта книга, насколько это возможно, платформенно-независима. Примеры были протестированны на нескольких платформах, таких как PC, Alpha, ARM, IA-64, M68k, PowerPC, SPARC, SPARC64, и VR41xx (MIPS). Т.к. код был протестирован как на 32-битных, так и на 64-битных процессорах, он, предположительно, должен откомпилироваться и запуститься на любых других платформах.
Условия лицензии
Операционная система Linux, лицензирована GNU General Publib License (GPL). GPL разработана в рамках проекта GNU организацией Free Software Foundation. GPL позволяет всем желающим заниматься распространением, и даже продажей, продуктов, выпущенных под данной лицензией. Пользователи таких продуктов могут собирать точные копии бинарных файлов из источников, предоставляемых согласно лицензии. Кроме того, все программные продукты, произведенные на основе продуктов, лицензированных GPL, также должны выпускаться под лицензией GPL.
Главной целью этой лицензии является разрешение свободного распространения знаний. Кому угодно, на основе определенных правил, разрешена модификация существующих программ. В тоже время, люди, занимающиеся продажей программного обеспечения, могут спокойно делать свою работу, продавая, в том числе, программы лицензированные GPL. Несмотря на простейшие цели, проставленные GPL, до сих пор не прекращаются бесконечные споры о GPL и ее использовании. Если вы хотите ознакомиться с лицензией, вы можете найти ее в различных местах вашей файловой системы, включая каталог размещения источников ядра: /usr/src/linux. Название файла с лицензией – COPYING.
Модули третьих фирм и ваши собственные модули не являются частью ядра Linux, и вы имеете право не лицензировать их под GPL. Модули линкуются в ядро при установке, но не являются частью ядра. Модули, в какой-то степени, похожи на пользовательские программы, работающие с ядром через системные вызовы. Заметьте, что освобождение от GPL применимо только к модулям, использующим открытый интерфейс модулей. Модули, которые взаимодействуют с ядром на более глубоком уровне должны твердо придерживаться условий GPL.
В общем, если ваш код входит в ядро, вы должны использовать GPL, как только вы отдаете свой код за пределы собственного пользования. Также, изменяя код, вы можете не использовать GPL, пока вы являетесь единоличным пользователем измененного кода. Если же вы распространяете код, то вы должны включать источник кода в дистрибутив, чтобы пользователи, которые его приобрели, могли бы сами скомпилировать бинарные формы для них. С другой стороны, если вы сами пишите модуль, вы можете распространять его в бинарной форме. Однако, это не всегда практично, т.к. модули должны быть скомпилированы с той версией ядра, в которое они должны быть слинкованы (установлены). Это подробно обсуждается в главе 2 «Building and Running Modules» (раздел «Version Dependency») и главе 11 «kmod and Advanced Modularization» (раздел «Version Control in Modules»). Новые версии ядра (даже младшие стабильные релизы) часто отказывают в линковке прекомпилированным модулям, требуя перекомпиляцию. Линус Торвальдс высказал мнение, что он не видит в этом проблемы, т.к. модуль должен быть скомпилирован для той версии ядра, в которую он, предположительно, будет установлен. Как разработчик модуля вы будете более полезны пользователям, если источники вашего модуля будут доступны для них.
Примеры программ, содержащиеся в книге, включают код ядра, применяется соглашения GPL, т.е. вносятся необходимые комментарии.
Вступление в общество разработчиков ядра (Kernel Development Community)
Как только вы начнете писать модули для ядра Linux, вы станете частью большого сообщества разработчиков. Внутри этого сообщества, вы найдете не только людей, занимающихся подобной работой, но и группу высококвалифицированных инженеров, работающих над улучшением Linux системы. Эти люди могут помочь вам советами, идеями и критическими замечаниями. Среди них, вы легко найдете тестеров вашего нового драйвера.
Центральное место взаимодействия разработчиков Linux ядра – linux-kernel mailing list. На него подписаны все основные разработчики, начиная с Линуса Торвальдса. Трафик этого почтового листа достигает 200 сообщений в день и более. Несмотря на это, данная информация очень важна для тех, кто интересуется разработкой ядра.
Обзор книги
Итак, мы вступаем в мир программирования ядра. Глава 2 “Построение и запуск модулей” ознакомит вас с построением модулей, объясняя секреты мастерства на основе приводимых примеров. В главе 3 “Символьные устройства” подробно рассматриваются драйвера символьных устройств и приводится полный код memory-based драйвера устройства, с которым можно производить операции чтения и записи. Использование памяти как аппаратной основы позволяет кому угодно запустить простой код без использования специального оборудования.
Техника отладки имеет жизненно важное значение для программиста и обсуждается в главе 4 “Техника отладки”. Получив навыки отладки, мы познакомимся с более сложными составляющими драйвера символьного устройства, такими как блоковые операции, использование выбора, и вызовами ioctl. Об этом подробно рассказано в главе 5 «Enhanced Char Driver Operations».
Перед началом работы по управлению аппаратными средствами, мы обсудим некоторые элементы программного интерфейса ядра. Глава 6 «Flow of Time» расскажет об управлении временем в ядре. Глава 7 «Getting Hold of Memory» объясняет основы динамического распределения памяти в ядре.
Далее, мы сфокусируем внимание на аппаратной части. Глава 8. «Hardware Management» описывает управление портами ввода вывода и буферами памяти, которые находятся в устройстве. После этого, в главе 9 “Управление прерываниями” мы познакомимся с управлением прерываниями, возникающими в аппаратуре. К несчастью, не у каждого будет возможность запустить примеры кода из этих глав, просто потому, что для работы этих программ потребуется специальное оборудование. Мы пытались свести к минимуму требования к тестовому оборудованию, но вам, все таки, понадобится засучить рукава и взять в руки паяльник для сборки собственного устройства. Устройство представляет собой простейшую контактную пару подсоединенную к параллельному порту. Мы надеемся, что у вас не будет проблем со сборкой такого устройства.
В главе 10 «Judicious Use of Data Types» обсуждаются дополнительные вопросы, связанные с написанием кода ядра. Например, вопросы связанные с переносимостью кода под разные платформы.
К следующей части книги, мы приходим уже с большим багажом базовых знаний. Глава 11 «kmod and Advanced Modularization» вводит нас в глубинные вопросы модуляризации.
Далее рассматривается третий по важности класс драйверов. В главе 14, “Network Drivers” в деталях рассматривается сетевой интерфейс и анализируется код простого сетевого драйвера.
Некоторые характеристики драйверов устройств напрямую зависят от интерфейса шины, на которую они установлены. Так, в главе 15 «Overview of Peripheral Buses» проведен обзор главных характеристик, наиболее широко применяемых сегодня, интерфейсных шин. Особое внимание уделяется, поддерживаемым в ядре, шинам PCI и USB.
Наконец, в главе 16 «Physical Layout of the Kernel Source» проводится ознакомительный тур по источникам ядра. Эту главу можно назвать стартовой точкой для всех тех, кто хочет понять общую картину, но напуган огромным количеством источников кода Linux ядра.



