меются данные для обработки, и планирует собственное обслуживание. Модуль включает очередь в список очередей, требующих обслуживания и запускает меха- низм диспетчеризации; планировщик (диспетчер) вызывает процедуры обслужива- 322 ния для каждой очереди в списке. Ядро может планировать обслуживание модулей по программному прерыванию, подобно тому, как оно вызывает функции в таблице ответных сигналов (см. главу 8); обработчик программных прерываний вызывает индивидуальные процедуры обслуживания. +----------+ | Индекс | +-----------------------+ файла | | |устройства| v +----------+ +------------+-----------+ Заголовок | Очередь | Очередь | потока | для вывода | для ввода | +------+-----+-----------+ | ^ | | v | +------------+-----------+ Строковый | Очередь | Очередь | интерфейс | для вывода | для ввода | +------+-----+-----------+ | ^ | | v | +------------+-----+-----+ Терминальный | Очередь | Очередь | драйвер | для вывода | для ввода | +------------+-----------+ Рисунок 10.22. Продвижение модуля к потоку Процессы могут "продвигать" модули к открытому потоку, используя вызов системной функции ioctl. Ядро помещает выдвинутый модуль сразу под заголов- ком потока и связывает указатели очереди таким образом, чтобы сохранить дву- направленную структуру списка. Модули, расположенные в потоке ниже, не бес- покоятся о том, связаны ли они с заголовком потока или же с выдвинутым моду- лем: интерфейсом выступает процедура "вывода" следующей очереди в потоке; а следующая очередь принадлежит только что выдвинутому модулю. Например, про- цесс может выдвинуть модуль строкового интерфейса в поток терминального драйвера с целью обработки символов стирания и удаления (Рисунок 10.22); мо- дуль строкового интерфейса не имеет тех же составляющих, что и строковые ин- терфейсы, рассмотренные в разделе 10.3, но выполняет те же функции. Без мо- дуля строкового интерфейса терминальный драйвер не обработает вводные симво- лы и они поступят в заголовок потока в неизмененном виде. Сегмент программы, открывающий терминал и выдвигающий строковый интерфейс, может выглядеть сле- дующим образом: fd = open("/dev/ttyxy",O_RDWR); ioctl(fd,PUSH,TTYLD); где PUSH - имя команды, а TTYLD - число, идентифицирующее модуль строкового интерфейса. Не существует ограничения на количество модулей, могущих быть выдвинутыми в поток. Процесс может выталкивать модули из потока в порядке поступления, "первым пришел - первым вышел", используя еще один вызов сис- темной функции ioctl ioctl(fd,POP,0); При том, что модуль строкового интерфейса выполняет обычные функции по 323 управлению терминалом, соответствующее ему устройство может быть средством сетевой связи вместо того, чтобы обеспечивать связь с одним-единственным терминалом. Модуль строкового интерфейса работает одинаково, независимо от того, какого типа модуль расположен ниже него. Этот пример наглядно демонст- рирует повышение гибкости вследствие соединения модулей ядра. 10.4.1 Более детальное рассмотрение потоков Пайк описывает реализацию мультиплексных виртуальных терминалов, исполь- зующую потоки (см. [Pike 84]). Пользователь видит несколько виртуальных тер- миналов, каждый из которых занимает отдельное окно на экране физического терминала. Хотя в статье Пайка рассматривается схема для интеллектуальных графических терминалов, она работала бы и для терминалов ввода-вывода тоже; каждое окно занимало бы целый экран и пользователь для переключения вирту- альных окон набирал бы последовательность управляющих клавиш. +---------+ +---------+ +-----------------+ Уровень | shell 1 | | shell 2 | | mpx | пользователя +---------+ +---------+ +-----------------+ ---------------+---------------+-----------+---+-------+---- Уровень ядра | ^ | ^ +--+ ^ | ^ | ^ | | | | | +--+ | | | | | | | | | | | | | | v | v | со- v | v | со- | | терми- +-+++ терми- +-+++ об-+-+++ +-+++об- | | нальная | | | нальная | | | ще-| | | | | |ще- | | линия +++-+ линия +++-+ ния+++-+ +++-+ния | | | ^ +-----------+-^------+ ^ | ^ | | | | | +---------+-+--------+ | | | | | | | | | | +-----------+ | | | v | v | v | v +-----------+ v | +-+++-+++ +-+++-+++ +-+++ псевдо- | | | | | | | | | | псевдо- | | | терми- +++-+++-+ +++-+++-+ терми- +-+-+ нальная | ^ | ^ | ^ | ^ нальная терми- пара 1 | +-+ | | +-+ | пара 2 нальный +-----+ +-----+ драйвер Рисунок 10.23. Отображение виртуальных окон на экране физи- ческого терминала На Рисунке 10.23 показана схема расположения процессов и модулей ядра. Пользователь вызывает процесс mpx, контролирующий работу физического терми- нала. Mpx читает данные из линии физического терминала и ждет объявления об управляющих событиях, таких как создание нового окна, переключение управле- ния на другое окно, удаление окна и т.п. Когда mpx получает уведомление о том, что пользователю нужно создать но- вое окно, он создает процесс, управляющий новым окном, и поддерживает связь с ним через псевдотерминал. Псевдотерминал - это программное устройство, ра- ботающее по принципу пары: выходные данные, направляемые к одной составляю- щей пары, посылаются на вход другой составляющей; входные данные посылаются тому модулю потока, который расположен выше по течению. Для того, чтобы отк- рыть окно (Рисунок 10.24), mpx назначает псевдотерминальную пару и открывает одну из составляющих пары, направляя поток к ней (открытие драйвера служит гарантией того, что псевдотерминальная пара не была выбрана раньше). Mpx ветвится и новый процесс открывает другую составляющую псевдотерминальной 324 +----------------------------------------------------------------+ | /* предположим, что дескрипторы файлов 0 и 1 уже относятся к | | физическому терминалу */ | | для(;;) /* цикл */ | | { | | выбрать(ввод); /* ждать ввода из какой-либо линии */ | | прочитать данные, введенные из линии; | | переключить(линию с вводимыми данными) | | { | | если выбран физический терминал: /* данные вводятся по ли- | | нии физического терми- | | нала */ | | если(считана управляющая команда) /* например, создание | | нового окна */ | | { | | открыть свободный псевдотерминал; | | пойти по ветви нового процесса: | | если(процесс родительский) | | { | | выдвинуть интерфейс сообщений в сторону mpx; | | продолжить; /* возврат в цикл "для" */ | | } | | /* процесс-потомок */ | | закрыть ненужные дескрипторы файлов; | | открыть другой псевдотерминал из пары, выбрать stdin, | | stdout, stderr; | | выдвинуть строковый интерфейс терминала; | | запустить shell; /* подобно виртуальному терминалу */| | } | | /* "обычные" данные, появившиеся через виртуальный терминал */ | | демультиплексировать считывание данных с физического тер-| | минала, снять заголовки и вести запись на соответствую- | | щий псевдотерминал; | | продолжить; /* возврат в цикл "для" */ | | | | если выбран логический терминал: /* виртуальный терминал | | связан с окном */ | | закодировать заголовок, указывающий назначение информации| | окна; | | переписать заголовок и информацию на физический терминал;| | продолжить; /* возврат в цикл "для" */ | | } | | } | +----------------------------------------------------------------+ Рисунок 10.24. Псевдопрограмма мультиплексирования окон пары. Mpx выдвигает модуль управления сообщениями в псевдотерминальный по- ток, чтобы преобразовывать управляющие сообщения в информационные (об этом в следующем параграфе), а порожденный процесс помещает в псевдотерминальный поток модуль строкового интерфейса перед запуском shell'а. Этот shell теперь выполняется на виртуальном терминале; для пользователя виртуальный терминал неотличим от физического. Процесс mpx является мультиплексором, направляющим вывод данных с вирту- альных терминалов на физический терминал и демультиплексирующим ввод данных с физического терминала на подходящий виртуальный. Mpx ждет поступления дан- ных по любой из линий, используя системную функцию select. Когда данные пос- тупают от физического терминала, mpx решает вопрос, являются ли поступившие 325 данные управляющим сообщением, извещающим о необходимости создания нового окна или удаления старого, или же это информационное сообщение, которое не- обходимо разослать процессам, считывающим информацию с виртуального термина- ла. В последнем случае данные имеют заголовок, идентифицирующий тот вирту- альный терминал, к которому они относятся; mpx стирает заголовок с сообщения и переписывает данные в соответствующий псевдотерминальный поток. Драйвер псевдотерминала отправляет данные через строковый интерфейс терминала про- цессам, осуществляющим чтение. Обратная процедура имеет место, когда процесс ведет запись на виртуальный терминал; mpx присоединяет заголовок к данным, информируя физический терминал, для вывода в какое из окон предназначены эти данные. Если процесс вызывает функцию ioctl с виртуального терминала, строковый интерфейс терминала задает необходимые установки терминала для его виртуаль- ной линии; для каждого из виртуальных терминалов установки могут быть раз- личными. Однако, на физический терминал должна быть послана и кое-какая ин- формация, зависящая от типа устройства. Модуль управления сообщениями преоб- разует управляющие сообщения, генерируемые функцией ioctl, в информационные сообщения, предназначенные для чтения и записи их процессом mpx, и эти сооб- щения передаются на физическое устройство. 10.4.2 Анализ потоков Ричи упоминает о том, что им была предпринята попытка создания потоков только с процедурами "вывода" или только с процедурами обслуживания. Однако, процедура обслуживания необходима для управления потоками данных, так как модули должны иногда ставить данные в очередь, если соседние модули на время закрыты для приема данных. Процедура "вывода" так же необходима, поскольку данные должны иногда доставляться в соседние модули незамедлительно. Напри- мер, строковому интерфейсу терминала нужно вести эхо-сопровождение ввода данных на терминале в темпе с процессом. Системная функция write могла бы запускать процедуру "вывода" для следующей очереди непосредственно, та, в свою очередь, вызывала бы процедуру "вывода" для следующей очереди и так да- лее, не нуждаясь в механизме диспетчеризации. Процесс приостановился бы в случае переполнения очередей для вывода. Однако, со стороны ввода модули не могут приостанавливаться, поскольку их выполнение вызывается программой об- работки прерываний, иначе был бы приостановлен совершенно безобидный про- цесс. Связь между модулями не должна быть симметричной в направлениях ввода и вывода, хотя это и делает схему менее изящной. Также было бы желательно реализовать каждый модуль в виде отдельного процесса, но использование большого количества модулей привело бы к перепол- нению таблицы процессов. Модули наделяются специальным механизмом диспетче- ризации - программным прерыванием, независимым от обычного планировщика про- цессов. По этой причине модули не могут приостанавливать свое выполнение, так как они приостанавливали бы тем самым произвольный процесс (тот, который прерван). Модули должны хранить внутри себя информацию о своем состоянии, что делает лежащие в их основе программы более громоздкими, чем если бы при- остановка выполнения была разрешена. В реализации потоков можно выделить несколько отклонений или несоответс- твий: * Учет ресурсов процесса в потоках затрудняется, поскольку модулям необя- зательно выполняться в контексте процесса, использующего поток. Ошибочно предполагать, что все процессы одинаково используют модули потоков, пос- кольку одним процессам может потребоваться использование сложных сетевых протоколов, тогда как другие могут использовать простые строковые интер- фейсы. * Пользователи имеют возможность переводить терминальный драйвер в режим без обработки, в котором функция read возвращает управление через корот- кий промежуток времени в случае отсутствия данных (например, если 326 newtty.c_cc[VMIN] = 0 на Рисунке 10.17). Эту особенность сложно реализо- вать в потоковой среде без подключения специальной программы на уровне заголовка потока. * Потоки выступают средствами линейной связи и не могут позволить произво- дить с легкостью мультиплексирование на уровне ядра. В примере использо- вания окон, рассмотренном в предыдущем разделе, выполнялось мультиплек- сирование на уровне пользовательского процесса. Несмотря на эти несоответствия, с потоками связываются большие надежды в совершенствовании разработки модулей драйвера. .te1 10.5 ВЫВОДЫ Данная глава представляет собой обзор драйверов устройств в системе UNIX. Устройства могут быть либо блочного, либо символьного типа; интерфейс между устройствами и остальной частью ядра определяется типом устройств. Ин- терфейсом для устройств блочного типа выступает таблица ключей устройств ввода-вывода блоками, состоящая из точек входа, соответствующих процедурам открытия и закрытия устройств и стратегической процедуре. Стратегическая процедура управляет передачей данных от и к устройству блочного типа. Интер- фейсом для устройств символьного типа выступает таблица ключей устройств по- символьного ввода-вывода, которая состоит из точек входа, соответствующих процедурам открытия и закрытия устройства, чтения, записи и процедуре ioctl. Системная функция ioctl использует при обращении к устройствам символьного типа свой собственный интерфейс, который позволяет осуществлять передачу уп- равляющей информации между процессами и устройствами. По получении прерыва- ния от устройства ядро вызывает программу обработки соответствующего преры- вания, опираясь на информацию, хранящуюся в таблице векторов прерываний, и на параметры, сообщенные устройством, от которого поступило прерывание. Дисковые драйверы превращают номера логических блоков, используемые фай- ловой системой, в физические адреса на диске. Блочный интерфейс дает возмож- ность ядру буферизовать данные. Взаимодействие без обработки ускоряет ввод-вывод на диск, но игнорирует буферный кеш, увеличивая тем самым шансы разрушить файловую систему. Терминальные драйверы осуществляют непосредственное взаимодействие с пользователями. Ядро связывает с каждым терминалом три символьных списка, один для неструктурированного ввода с клавиатуры, один для ввода с обработ- кой символов стирания, удаления и возврата каретки и один для вывода. Сис- темная функция ioctl дает процессам возможность следить за тем, как ядро об- рабатывает вводимые данные, переводя терминал в канонический режим или уста- навливая значения различных параметров для режима без обработки символов. Getty-процесс открывает терминальные линии и ждет связи: он формирует группу процессов во главе с регистрационным shell'ом, инициализирует с помощью фун- кции ioctl параметры терминала и обращается к пользователю с предложением зарегистрироваться. Установленный таким образом операторский терминал посы- лает процессам в группе сигналы в ответ на возникновение таких событий, как "зависание" пользователя или нажатие им клавиши прерывания. Потоки выступают средством повышения модульности построения драйверов устройств и протоколов. Поток - это полнодуплексная связь между процессами и драйверами устройств, которая может включать в себя строковые интерфейсы и протоколы для промежуточной обработки данных. Модули потоков характеризуются четко определенным взаимодействием и гибкостью, позволяющей использовать их в сочетании с другими модулями. Эта гибкость имеет особое значение для сете- вых протоколов и драйверов. .te1 10.6 УПРАЖНЕНИЯ *1. Предположим, что в системе имеются два файла устройств с одними и теми же старшим и младшим номерами, при том, что оба устройства - символьно- 327 го типа. Если два процесса желают одновременно открыть физическое уст- ройство, не будет никакой разницы, открывают ли они один и тот же файл устройства или же разные файлы. Что произойдет, когда они станут закры- вать устройство ? *2. Вспомним из главы 5, что системной функции mknod требуется разрешение суперпользователя на создание нового специального файла устройства. Ес- ли доступ к устройству управляется правами доступа к файлу, почему фун- кции mknod нужно разрешение суперпользователя ? 3. Напишите программу, которая проверяет, что файловые системы на диске не перекрываются. Этой программе потребовались бы два аргумента: файл уст- ройства, представляющий дисковый том, и дескриптор файла, откуда берут- ся номера секторов и их размер для диска данного типа. Для проверки от- сутствия перекрытий этой программе понадобилась бы информация из супер- блоков. Будет ли такая программа всегда правильной ? 4. Программа mkfs инициализирует файловую систему на диске путем создания суперблока, выделения места для списка индексов, включения всех инфор- мационных блоков в связанный список и создания корневого каталога. Как бы вы написали программу mkfs ? Как изменится эта программа при наличии таблицы содержимого тома ? Каким образом следует инициализировать таб- лицу содержимого тома ? 5. Программы mkfs и fsck (глава 5) являются программами пользовательского уровня, а не частью ядра. Прокомментируйте это. 6. Предположим, что программисту нужно разработать базу данных, работающую в среде ОС UNIX. Программы базы данных выполняются на пользовательском уровне, а не в составе ядра. Как система управления базой данных будет взаимодействовать с диском ? Подумайте над следующими вопросами: * Использование стандартного интерфейса файловой системы вместо непос- редственной работы с неструктурированными данными на диске, * Потребность в быстродействии, * Необходимость знать, когда фактически данные располагаются на диске, * Размер базы данных: должна ли она помещаться в одной файловой систе- ме, занимать собой весь дисковый том или же располагаться на несколь- ких дисковых томах ? 7. Ядро системы UNIX по умолчанию предполагает, что файловая система рас- полагается на идеальных дисках. Однако, диски могут содержать ошибки, которые делают непригодными и выводят из строя определенные сектора, несмотря на то, что остальная часть диска осталась "пригодной". Как дисковому драйверу (или интеллектуальному контроллеру диска) следует учитывать небольшое количество плохих секторов. Как это отразилось бы на производительности системы ? 8. При монтировании файловой системы ядро запускает процедуру открытия для данного драйвера, но позже освобождает индекс специального файла уст- ройства по завершении выполнения вызова системной функции mount. При демонтировании файловой системы ядро обращается к индексу специального файла устройства, запускает процедуру закрытия для данного драйвера и вновь освобождает индекс. Сравните эту последовательность операций над индексом, а также обращений к процедурам открытия и закрытия драйвера, с последовательностью действий, совершаемых при открывании и закрывании устройства блочного типа. Прокомментируйте результаты сравнения. 9. Выполните программу, приведенную на Рисунке 10.14, но направьте вывод данных в файл. Сравните содержимое файла с содержимым выводного потока, когда вывод идет на терминал. Вам придется прервать процессы, чтобы ос- тановить их; только прежде пусть они получат достаточно большое коли- чество данных. Что произойдет, если вызов функции write в программе за- менить на printf(output); 10. Что произойдет, если пользователь попытается выполнить редактирование текста на фоне программы: ed file & Обоснуйте ответ. 328 11. К файлам терминалов обычно устанавливаются следующие права доступа crw--w--w- 2 mjb lus 33,11 Oct 25 20:27 tty61 при входе пользователя в систему. То есть, чтение и запись разрешаются пользователю с именем "mjb", а остальным пользователям разрешена только запись. Почему ? 12. Предположим, что вам известно имя файла терминала вашего товарища. На- пишите программу записи сообщений с вашего терминала на терминал вашего товарища. Какая еще информация вам нужна, чтобы закодировать приемлемое воспроизведение обычной команды write ? 13. Выполните команду stty: если параметры не указаны, она выбирает значе- ния установок терминала и сообщает их пользователю. В противном случае пользователь может в интерактивном режиме сделать различные установки сам. 14. Напишите элементарный строковый интерфейс, записывающий идентификатор машины в начале каждой строки выводного потока. 15. В каноническом режиме пользователь может на время приостановить вывод данных на терминал, нажав последовательность клавиш , и продол- жить вывод, нажав . Как в стандартном строковом интерфейсе реа- лизуется эта особенность ? *16. Процесс начальной загрузки порождает getty-процесс для каждой терми- нальной линии в системе. Что произошло бы, если бы для одного и того же терминала существовали бы одновременно два getty-процесса, ожидающие регистрации пользователя ? Может ли ядро помешать этому ? 17. Пусть командный процессор shell реализован таким образом, что он "игно- рирует" конец файла и продолжает считывать данные из стандартного вво- да. Что произошло бы, если бы пользователь (в регистрационном shell'е) угадал конец файла и продолжил ввод с клавиатуры ? *18. Предположим, что процесс считывает данные с операторского терминала, но игнорирует или улавливает сигналы о "зависании". Что произойдет, когда процесс продолжит считывать данные с операторского терминала после за- висания ? 19. Программа getty-процесса несет ответственность за открытие терминальной линии, а программа login - за проверку регистрационных имен и паролей. Какие преимущества в том, что эти функции выполняются отдельными прог- раммами ? 20. Рассмотрим два метода реализации драйвера косвенного терминала ("/dev /tty"), описанные в разделе 10.3.6. Какие различия между ними чувствует пользователь ? (Совет: подумайте о системных функциях stat и fstat). 21. Разработайте метод планирования выполнения модулей потока, в соответст- вии с которым ядро имеет в своем составе специальный процесс, выполняю- щий процедуры обслуживания модулей тогда, когда выполнение этих проце- дур запланировано. *22. Разработайте схему построения виртуальных терминалов (окон) с использо- ванием традиционных (не потоковых) драйверов. *23. Разработайте метод реализации виртуальных терминалов с использованием потоков, в котором мультиплексированием ввода-вывода между виртуальным и физическим терминалами занимался бы один из модулей ядра, а не поль- зовательский процесс. Опишите механизм соединения потоков со сверткой и разверткой. Что лучше: включить модуль, осуществляющий мультиплексиро- вание, в состав ядра или построить его как пользовательский процесс ? 24. Команда ps сообщает интересную информацию об активности процессов в ра- ботающей системе. В традиционных реализациях ps считывает информацию из таблицы процессов, прямо из памяти ядра. Такой метод не совсем удобен в среде разработки, когда размер записей таблицы процессов меняется и ко- манде ps становится нелегко обнаружить в таблице соответствующие поля. Разработайте драйвер, нечувствительный к изменениям среды. 329