Поскольку драйверы низкого уровня не используют буферный кэш, они самостоятельно обеспечивают необходимые буферы для совершения операции ввода/вывода. На рис. 5.8 показаны отличия в характере выполнения операции ввода/вывода с блочными устройствами в случаях, когда запрос формируется при участии буферного кэша (драйверы блочных устройств), и когда манипуляция буфером производится драйвером самостоятельно (драйверы низкого уровня).
Рис. 5.8. Различные типы доступа к блочным устройствам
Очевидно, что побайтная передача данных между драйвером символьного устройства и прикладным процессом весьма неэффективна. При таком режиме работы байт должен быть сначала скопирован в адресное пространство драйвера, затем некоторое время должно пройти, прежде чем драйвер сможет передать этот символ физическому устройству. Если при этом устройство оказывается занятым, процесс должен ожидать завершения предыдущей операции, что, скорее всего, вынудит его перейти в состояние сна и приведет к переключению контекста.
Существует несколько способов преодолеть данную ситуацию, но все они предполагают обеспечение некоторой буферизации данных драйвером устройства. Первый способ заключается в использовании прерываний, когда при поступлении на устройство следующего символа, генерируется аппаратное прерывание, которое обрабатывается функцией xx intr()
драйвера независимо от функции xx write()
. Функция обработки прерывания записывает данные в буфер, которые затем считываются функцией xx write()
.
Если устройство не поддерживает прерываний, их поступление можно сэмулировать с помощью функции xx poll()
драйвера устройства, которая вызывается ядром через определенные промежутки времени (обычно каждый сигнал таймера). Обычно функция xx poll()
, в свою очередь, вызывает функцию xx intr()
, скажем, на каждый десятый сигнал таймера, обеспечивая тем самым независимое считывание и буферизацию данных.
Буферизация данных для символьных устройств осуществляется с помощью специальных структур данных, называемых clist
. Каждая структура clist
имеет следующие поля:
int c_cc;
struct cblock *с_cf;
struct cblock *c_cl;
Поле с_ cc
содержит число символов в буфере cblock
. Поля c_cf
и c_cl
указывают, соответственно, на первый и последний элементы cblock
, организованные в виде связанного списка и фактически обеспечивающие буферы хранения данных. Каждая структура cblock
может хранить несколько символов. Когда буфер хранения заполняется, ядро автоматически выделяет новую структуру cblock
и помещает ее в связанный список. Поля структуры cblock
и их использование приведены на рис. 5.9.
Рис. 5.9. Буферизация данных с помощью clist
Пример буферизации с использованием структуры clist
в драйвере терминала показан на рис. 5.10.
Рис. 5.10. Пример использования буферов clist в драйвере терминала
Архитектура терминального доступа
Алфавитно-цифровой терминал — последовательное устройство, и операционная система производит обмен данными с терминалом через последовательный интерфейс, называемый терминальной линией . С каждой терминальной линией в UNIX ассоциирован специальный файл символьного устройства /dev/ttyxx. [55] В зависимости от версии UNIX вместо символов xx в имени файла терминала присутствует идентификатор, позволяющий поставить в соответствии специальному файлу конкретную терминальную линию. Например, в SCO UNIX виртуальные экраны системного монитора имеют имена /dev/tty01 , /dev/tty02 и т.д.
Терминальные драйверы выполняют ту же функцию, что и остальные драйверы: управление передачей данных от/на терминалы. Однако терминалы имеют одну особенность, связанную с тем, что они обеспечивают интерфейс пользователя с системой. Обеспечивая интерактивное использование системы UNIX, терминальные драйверы имеют свой внутренний интерфейс с модулями, интерпретирующими ввод и вывод строк. Модуль, отвечающий за такую обработку, называется дисциплиной линии (line discipline).
Читать дальше