Основы операционных систем 137 ва и видеоустройства и т. д. Некоторые группы в свою очередь могут разбиваться на подгруппы: под- группа жестких дисков, подгруппа мышек, подгруппа принтеров. Нас такие детали не интересуют. Мы не ставим перед собой цель осуществить систематизацию всех возможных устройств, которые могут быть подключены к вычислительной системе. Единственное, для чего нам понадобится эта классификация, так это для иллюстрации того положения, что устройства могут быть разделены на группы по выполняемым ими функциям, и для понимания функций драйверов, и интерфейса между ними и базовой подсистемой ввода-вывода. Для этого мы рассмотрим только две группы устройств: символьные и блочные. Как уже упоминалось в предыдущем разделе, символьные устройства – это устройства, которые умеют передавать данные толь- ко последовательно, байт за байтом, а блочные устройства – это устройства, которые могут передавать блок байтов как единое целое. К символьным устройствам обычно относятся устройства ввода информации, которые спонтанно гене- рируют входные данные: клавиатура, мышь, модем, джойстик. К ним же относятся и устройства вывода информации, для которых характерно представление данных в виде линейного потока: принтеры, звуко- вые карты и т. д. По своей природе символьные устройства обычно умеют совершать две общие опера- ции: ввести символ (байт) и вывести символ (байт) – get и put. Для блочных устройств, таких как магнитные и оптические диски, ленты и т. п. естественными являются операции чтения и записи блока информации – read и write, а также, для устройств прямого доступа, опе- рация поиска требуемого блока информации – seek. Драйверы символьных и блочных устройств должны предоставлять базовой подсистеме ввода-вывода функции для осуществления описанных общих операций. Помимо общих операций, некоторые устройст- ва могут выполнять операции специфические, свойственные только им – например, звуковые карты уме- ют увеличивать или уменьшать среднюю громкость звучания, дисплеи умеют изменять свою разрешаю- щую способность. Для выполнения таких специфических действий в интерфейс между драйвером и ба- зовой подсистемой ввода-вывода обычно входит еще одна функция, позволяющая непосредственно пе- редавать драйверу устройства произвольную команду с произвольными параметрами, что позволяет за- действовать любую возможность драйвера без изменения интерфейса. В операционной системе Unix та- кая функция получила название ioctl (от input-output control). Помимо функций read, write, seek (для блочных устройств), get, put (для символьных устройств) и ioctl, в состав интерфейса обычно включают еще следующие функции. • Функцию инициализации или повторной инициализации работы драйвера и устройства – open. • Функцию временного завершения работы с устройством (может, например, вызывать отключение устройства) – close. • Функцию опроса состояния устройства (если по каким-либо причинам работа с устройством про- изводится методом опроса его состояния, например, в операционных системах Windows NT и Windows 9x так построена работа с принтерами через параллельный порт) – poll. • Функцию останова драйвера, которая вызывается при останове операционной системы или вы- грузке драйвера из памяти, halt. Существует еще ряд действий, выполнение которых может быть возложено на драйвер, но поскольку, как правило, это действия базовой подсистемы ввода-вывода, мы поговорим о них в следующем разделе. Приведенные выше названия функций, конечно, являются условными и могут меняться от одной опера- ционной системы к другой, но действия, выполняемые драйверами, характерны для большинства опера- ционных систем, и соответствующие функции присутствуют в интерфейсах к ним. Функции базовой подсистемы ввода-вывода Базовая подсистема ввода-вывода служит посредником между процессами вычислительной системы и набором драйверов. Системные вызовы для выполнения операций ввода-вывода трансформируются ею в вызовы функций необходимого драйвера устройства. Однако обязанности базовой подсистемы не сво- дятся к выполнению только действий трансляции общего системного вызова в обращение к частной Основы операционных систем 138 функции драйвера. Базовая подсистема предоставляет вычислительной системе такие услуги, как под- держка блокирующихся, неблокирующихся и асинхронных системных вызовов, буферизация и кэширо- вание входных и выходных данных, осуществление spooling'a и монопольного захвата внешних уст- ройств, обработка ошибок и прерываний, возникающих при операциях ввода-вывода, планирование по- следовательности запросов на выполнение этих операций. Давайте остановимся на этих услугах подроб- нее. Блокирующиеся, неблокирующиеся и асинхронные системные вызовы Все системные вызовы, связанные с осуществлением операций ввода-вывода, можно разбить на три группы по способам реализации взаимодействия процесса и устройства ввода-вывода. • К первой, наиболее привычной для большинства программистов группе относятся блокирующие- ся системные вызовы. Как следует из самого названия, применение такого вызова приводит к бло- кировке инициировавшего его процесса, т. е. процесс переводится операционной системой из со- стояния исполнение в состояние ожидание. Завершив выполнение всех операций ввода-вывода, предписанных системным вызовом, операционная система переводит процесс из состояния ожи- дание в состояние готовность. После того как процесс будет снова выбран для исполнения, в нем произойдет окончательный возврат из системного вызова. Типичным для применения такого системного вызова является случай, когда процессу необходимо получить от устройства строго определенное количество данных, без которых он не может выполнять работу далее. • Ко второй группе относятся неблокирующиеся системные вызовы. Их название не совсем точно отражает суть дела. В простейшем случае процесс, применивший неблокирующийся вызов, не пе- реводится в состояние ожидание вообще. Системный вызов возвращается немедленно, выполнив предписанные ему операции ввода-вывода полностью, частично или не выполнив совсем, в зави- симости от текущей ситуации (состояния устройства, наличия данных и т. д.). В более сложных ситуациях процесс может блокироваться, но условием его разблокирования является завершение всех необходимых операций или окончание некоторого промежутка времени. Типичным случаем применения неблокирующегося системного вызова может являться периодическая проверка на поступление информации с клавиатуры при выполнении трудоемких расчетов. • К третьей группе относятся асинхронные системные вызовы. Процесс, использовавший асин- хронный системный вызов, никогда в нем не блокируется. Системный вызов инициирует выпол- нение необходимых операций ввода-вывода и немедленно возвращается, после чего процесс про- должает свою регулярную деятельность. Об окончании завершения операции ввода-вывода опе- рационная система впоследствии информирует процесс изменением значений некоторых пере- менных, передачей ему сигнала или сообщения или каким-либо иным способом. Необходимо чет- ко понимать разницу между неблокирующимися и асинхронными вызовами. Неблокирующийся системный вызов для выполнения операции read вернется немедленно, но может прочитать за- прошенное количество байтов, меньшее количество или вообще ничего. Асинхронный системный вызов для этой операции также вернется немедленно, но требуемое количество байтов рано или поздно будет прочитано в полном объеме. Буферизация и кэширование Под буфером обычно понимается некоторая область памяти для запоминания информации при обмене данных между двумя устройствами, двумя процессами или процессом и устройством. Обмен информа- цией между двумя процессами относится к области кооперации процессов, и мы подробно рассмотрели его организацию в соответствующей лекции. Здесь нас будет интересовать использование буферов в том случае, когда одним из участников обмена является внешнее устройство. Существует три причины, при- водящие к использованию буферов в базовой подсистеме ввода-вывода. • Первая причина буферизации – это разные скорости приема и передачи информации, которыми обладают участники обмена. Рассмотрим, например, случай передачи потока данных от клавиату- ры к модему. Скорость, с которой поставляет информацию клавиатура, определяется скоростью набора текста человеком и обычно существенно меньше скорости передачи данных модемом. Для того чтобы не занимать модем на все время набора текста, делая его недоступным для других процессов и устройств, целесообразно накапливать введенную информацию в буфере или не- скольких буферах достаточного размера и отсылать ее через модем после заполнения буферов. Основы операционных систем 139 • Вторая причина буферизации – это разные объемы данных, которые могут быть приняты или по- лучены участниками обмена единовременно. Возьмем другой пример. Пусть информация постав- ляется модемом и записывается на жесткий диск. Помимо обладания разными скоростями совер- шения операций, модем и жесткий диск представляют собой устройства разного типа. Модем яв- ляется символьным устройством и выдает данные байт за байтом, в то время как диск является блочным устройством и для проведения операции записи для него требуется накопить необходи- мый блок данных в буфере. Здесь также можно применять более одного буфера. После заполнения первого буфера модем начинает заполнять второй, одновременно с записью первого на жесткий диск. Поскольку скорость работы жесткого диска в тысячи раз больше, чем скорость работы мо- дема, к моменту заполнения второго буфера операция записи первого будет завершена, и модем снова сможет заполнять первый буфер одновременно с записью второго на диск. • Третья причина буферизации связана с необходимостью копирования информации из приложе- ний, осуществляющих ввод-вывод, в буфер ядра операционной системы и обратно. Допустим, что некоторый пользовательский процесс пожелал вывести информацию из своего адресного про- странства на внешнее устройство. Для этого он должен выполнить системный вызов с обобщен- ным названием write, передав в качестве параметров адрес области памяти, где расположены дан- ные, и их объем. Если внешнее устройство временно занято, то возможна ситуация, когда к мо- менту его освобождения содержимое нужной области окажется испорченным (например, при ис- пользовании асинхронной формы системного вызова). Чтобы избежать возникновения подобных ситуаций, проще всего в начале работы системного вызова скопировать необходимые данные в буфер ядра операционной системы, постоянно находящийся в оперативной памяти, и выводить их на устройство из этого буфера. Под словом кэш (cash – "наличные"), этимологию которого мы не будем здесь рассматривать, обычно понимают область быстрой памяти, содержащую копию данных, расположенных где-либо в более мед- ленной памяти, предназначенную для ускорения работы вычислительной системы. Мы с вами сталкива- лись с этим понятием при рассмотрении иерархии памяти. В базовой подсистеме ввода-вывода не следу- ет смешивать два понятия, буферизацию и кэширование, хотя зачастую для выполнения этих функций отводится одна и та же область памяти. Буфер часто содержит единственный набор данных, существую- щий в системе, в то время как кэш по определению содержит копию данных, существующих где-нибудь еще. Например, буфер, используемый базовой подсистемой для копирования данных из пользовательско- го пространства процесса при выводе на диск, может в свою очередь применяться как кэш для этих дан- ных, если операции модификации и повторного чтения данного блока выполняются достаточно часто. Функции буферизации и кэширования не обязательно должны быть локализованы в базовой подсистеме ввода-вывода. Они могут быть частично реализованы в драйверах и даже в контроллерах устройств, скрытно по отношению к базовой подсистеме. Spooling и захват устройств О понятии spooling мы говорили в первой лекции нашего курса, как о механизме, впервые позволившем совместить реальные операции ввода-вывода одного задания с выполнением другого задания. Теперь мы можем определить это понятие более точно. Под словом spool мы подразумеваем буфер, содержащий входные или выходные данные для устройства, на котором следует избегать чередования его использо- вания (возникновения interleaving – см. раздел "Interleaving, race condition и взаимоисключения" лекции 5) различными процессами. Правда, в современных вычислительных системах spool для ввода данных практически не используется, а в основном предназначен для накопления выходной информации. Рассмотрим в качестве внешнего устройства принтер. Хотя принтер не может печатать информацию, по- ступающую одновременно от нескольких процессов, может оказаться желательным разрешить процессам совершать вывод на принтер параллельно. Для этого операционная система вместо передачи информации напрямую на принтер накапливает выводимые данные в буферах на диске, организованных в виде от- дельного spool-файла для каждого процесса. После завершения некоторого процесса соответствующий ему spool-файл ставится в очередь для реальной печати. Механизм, обеспечивающий подобные действия, и получил название spooling. В некоторых операционных системах вместо использования spooling для устранения race condition при- меняется механизм монопольного захвата устройств процессами. Если устройство свободно, то один из Основы операционных систем 140 процессов может получить его в монопольное распоряжение. При этом все другие процессы при попытке осуществления операций над этим устройством будут либо блокированы (переведены в состояние ожи- дание), либо получат информацию о невозможности выполнения операции до тех пор, пока процесс, за- хвативший устройство, не завершится или явно не сообщит операционной системе о своем отказе от его использования. Обеспечение spooling и механизма захвата устройств является прерогативой базовой подсистемы ввода- вывода. Обработка прерываний и ошибок Если при работе с внешним устройством вычислительная система не пользуется методом опроса его со- стояния, а задействует механизм прерываний, то при возникновении прерывания, как мы уже говорили раньше, процессор, частично сохранив свое состояние, передает управление специальной программе об- работки прерывания. Мы уже рассматривали действия операционной системы над процессами, происхо- дящими при возникновении прерывания, в разделе "Переключение контекста" лекции 2, где после воз- никновения прерывания осуществлялись следующие действия: сохранение контекста, обработка преры- вания, планирование использования процессора, восстановление контекста. Тогда мы обращали больше внимания на действия, связанные с сохранением и восстановлением контекста и планированием исполь- зования процессора. Теперь давайте подробнее остановимся на том, что скрывается за словами "обработ- ка прерывания". Одна и та же процедура обработки прерывания может применяться для нескольких устройств ввода- вывода (например, если эти устройства используют одну линию прерываний, идущую от них к контрол- леру прерываний), поэтому первое действие собственно программы обработки состоит в определении того, какое именно устройство выдало прерывание. Зная устройство, мы можем выявить процесс, кото- рый инициировал выполнение соответствующей операции. Поскольку прерывание возникает как при удачном, так и при неудачном ее выполнении, следующее, что мы должны сделать, – это определить ус- пешность завершения операции, проверив значение бита ошибки в регистре состояния устройства. В некоторых случаях операционная система может предпринять определенные действия, направленные на компенсацию возникшей ошибки. Например, в случае возникновения ошибки чтения с гибкого диска можно попробовать несколько раз повторить выполнение команды. Если компенсация ошибки невоз- можна, то операционная система впоследствии известит об этом процесс, запросивший выполнение опе- рации, (например, специальным кодом возврата из системного вызова). Если этот процесс был заблоки- рован до выполнения завершившейся операции, то операционная система переводит его в состояние го- товность. При наличии других неудовлетворенных запросов к освободившемуся устройству операцион- ная система может инициировать выполнение следующего запроса, одновременно известив устройство, что прерывание обработано. На этом, собственно, обработка прерывания заканчивается, и система может приступать к планированию использования процессора. Действия по обработке прерывания и компенсации возникающих ошибок могут быть частично перело- жены на плечи соответствующего драйвера. Для этого в состав интерфейса между драйвером и базовой подсистемой ввода-вывода добавляют еще одну функцию – функцию обработки прерывания intr. Планирование запросов При использовании неблокирующегося системного вызова может оказаться, что нужное устройство уже занято выполнением некоторых операций. В этом случае неблокирующийся вызов может немедленно вернуться, не выполнив запрошенных команд. При организации запроса на совершение операций ввода- вывода с помощью блокирующегося или асинхронного вызова занятость устройства приводит к необхо- димости постановки запроса в очередь к данному устройству. В результате с каждым устройством ока- зывается связан список неудовлетворенных запросов процессов, находящихся в состоянии ожидания, и запросов, выполняющихся в асинхронном режиме. Состояние ожидание расщепляется на набор очере- дей процессов, дожидающихся различных устройств ввода-вывода (или ожидающих изменения состоя- ний различных объектов – семафоров, очередей сообщений, условных переменных в мониторах и т. д. – см. лекцию 6). Основы операционных систем 141 После завершения выполнения текущего запроса операционная система (по ходу обработки возникшего прерывания) должна решить, какой из запросов в списке должен быть удовлетворен следующим, и ини- циировать его исполнение. Точно так же, как для выбора очередного процесса на исполнение из списка готовых нам приходилось осуществлять краткосрочное планирование процессов, здесь нам необходимо осуществлять планирование применения устройств, пользуясь каким-либо алгоритмом этого планирова- ния. Критерии и цели такого планирования мало отличаются от критериев и целей планирования процес- сов. Задача планирования использования устройства обычно возлагается на базовую подсистему ввода- вывода, однако для некоторых устройств лучшие алгоритмы планирования могут быть тесно связаны с деталями их внутреннего функционирования. В таких случаях операция планирования переносится внутрь драйвера соответствующего устройства, так как эти детали скрыты от базовой подсистемы. Для этого в интерфейс драйвера добавляется еще одна специальная функция, которая осуществляет выбор очередного запроса, – функция strategy. В следующем разделе мы рассмотрим некоторые алгоритмы планирования, связанные с удовлетворением запросов, на примере жесткого диска. Алгоритмы планирования запросов к жесткому диску Прежде чем приступить к непосредственному изложению самих алгоритмов, давайте вспомним внутрен- нее устройство жесткого диска и определим, какие параметры запросов мы можем использовать для пла- нирования. Строение жесткого диска и параметры планирования Современный жесткий магнитный диск представляет собой набор круглых пластин, находящихся на од- ной оси и покрытых с одной или двух сторон специальным магнитным слоем (см. рис. 13.2). Около каж- дой рабочей поверхности каждой пластины расположены магнитные головки для чтения и записи ин- формации. Эти головки присоединены к специальному рычагу, который может перемещать весь блок го- ловок над поверхностями пластин как единое целое. Поверхности пластин разделены на концентриче- ские кольца, внутри которых, собственно, и может храниться информация. Набор концентрических колец на всех пластинах для одного положения головок (т. е. все кольца, равноудаленные от оси) образует ци- линдр. Каждое кольцо внутри цилиндра получило название дорожки (по одной или две дорожки на каж- дую пластину). Все дорожки делятся на равное число секторов. Количество дорожек, цилиндров и секто- ров может варьироваться от одного жесткого диска к другому в достаточно широких пределах. Как пра- вило, сектор является минимальным объемом информации, которая может быть прочитана с диска за один раз. При работе диска набор пластин вращается вокруг своей оси с высокой скоростью, подставляя по очере- ди под головки соответствующих дорожек все их сектора. Номер сектора, номер дорожки и номер ци- линдра однозначно определяют положение данных на жестком диске и, наряду с типом совершаемой операции – чтение или запись, полностью характеризуют часть запроса, связанную с устройством, при обмене информацией в объеме одного сектора. Основы операционных систем 142 Рис. 13.2. Схема жесткого диска При планировании использования жесткого диска естественным параметром планирования является время, которое потребуется для выполнения очередного запроса. Время, необходимое для чтения или за- писи определенного сектора на определенной дорожке определенного цилиндра, можно разделить на две составляющие: время обмена информацией между магнитной головкой и компьютером, которое обычно не зависит от положения данных и определяется скоростью их передачи (transfer speed), и время, необхо- димое для позиционирования головки над заданным сектором, – время позиционирования (positioning time). Время позиционирования, в свою очередь, состоит из времени, необходимого для перемещения го- ловок на нужный цилиндр, – времени поиска (seek time) и времени, которое требуется для того, чтобы нужный сектор довернулся под головку, – задержки на вращение (rotational latency). Времена поиска пропорциональны разнице между номерами цилиндров предыдущего и планируемого запросов, и их лег- ко сравнивать. Задержка на вращение определяется довольно сложными соотношениями между номера- ми цилиндров и секторов предыдущего и планируемого запросов и скоростями вращения диска и пере- мещения головок. Без знания соотношения этих скоростей сравнение становится невозможным. Поэтому естественно, что набор параметров планирования сокращается до времени поиска различных запросов, определяемого текущим положением головки и номерами требуемых цилиндров, а разницей в задержках на вращение пренебрегают. Алгоритм First Come First Served (FCFS) Простейшим алгоритмом, к которому мы уже должны были привыкнуть, является алгоритм First Come First Served (FCFS) – первым пришел, первым обслужен. Все запросы организуются в очередь FIFO и об- служиваются в порядке поступления. Алгоритм прост в реализации, но может приводить к достаточно длительному общему времени обслуживания запросов. Рассмотрим пример. Пусть у нас на диске из 100 цилиндров (от 0 до 99) есть следующая очередь запросов: 23, 67, 55, 14, 31, 7, 84, 10 и головки в началь- ный момент находятся на 63-м цилиндре. Тогда положение головок будет меняться следующим образом: 63 23 67 55 14 31 7 84 10 и всего головки переместятся на 329 цилиндров. Неэффективность алгоритма хорошо иллюстрируется двумя последними перемещениями с 7 цилиндра через весь диск на 84 цилиндр и затем опять через весь диск на цилиндр 10. Простая замена порядка двух последних перемещений (7 10 84) позволила бы существенно сократить общее время обслуживания запросов. Поэтому давайте перейдем к рассмотрению другого алгоритма. Основы операционных систем 143 Алгоритм Short Seek Time First (SSTF) Как мы убедились, достаточно разумным является первоочередное обслуживание запросов, данные для которых лежат рядом с текущей позицией головок, а уж затем далеко отстоящих. Алгоритм Short Seek Time First (SSTF) – короткое время поиска первым – как раз и исходит из этой позиции. Для очередного обслуживания будем выбирать запрос, данные для которого лежат наиболее близко к текущему положе- нию магнитных головок. Естественно, что при наличии равноудаленных запросов решение о выборе ме- жду ними может приниматься исходя из различных соображений, например по алгоритму FCFS. Для предыдущего примера алгоритм даст такую последовательность положений головок: 63 67 55 31 23 14 10 7 84 и всего головки переместятся на 141 цилиндр. Заметим, что наш алгоритм похож на алгоритм SJF плани- рования процессов, если за аналог оценки времени очередного CPU burst процесса выбирать расстояние между текущим положением головки и положением, необходимым для удовлетворения запроса. И точно так же, как алгоритм SJF, он может приводить к длительному откладыванию выполнения какого-либо запроса. Необходимо вспомнить, что запросы в очереди могут появляться в любой момент времени. Если у нас все запросы, кроме одного, постоянно группируются в области с большими номерами цилиндров, то этот один запрос может находиться в очереди неопределенно долго. Точный алгоритм SJF являлся оптимальным для заданного набора процессов с заданными временами CPU burst. Очевидно, что алгоритм SSTF не является оптимальным. Если мы перенесем обслуживание запроса 67-го цилиндра в промежуток между запросами 7-го и 84-го цилиндров, мы уменьшим общее время обслуживания. Это наблюдение приводит нас к идее целого семейства других алгоритмов – алго- ритмов сканирования. Алгоритмы сканирования (SCAN, C-SCAN, LOOK, C-LOOK) В простейшем из алгоритмов сканирования – SCAN – головки постоянно перемещаются от одного края диска до другого, по ходу дела обслуживая все встречающиеся запросы. По достижении другого края на- правление движения меняется, и все повторяется снова. Пусть в предыдущем примере в начальный мо- мент времени головки двигаются в направлении уменьшения номеров цилиндров. Тогда мы и получим порядок обслуживания запросов, подсмотренный в конце предыдущего раздела. Последовательность пе- ремещения головок выглядит следующим образом: 63 55 31 23 14 10 7 0 67 84 и всего головки переместятся на 147 цилиндров. Если мы знаем, что обслужили последний попутный запрос в направлении движения головок, то мы мо- жем не доходить до края диска, а сразу изменить направление движения на обратное: 63 55 31 23 14 10 7 67 84 и всего головки переместятся на 133 цилиндра. Полученная модификация алгоритма SCAN получила на- звание LOOK. Допустим, что к моменту изменения направления движения головки в алгоритме SCAN, т. е. когда го- ловка достигла одного из краев диска, у этого края накопилось большое количество новых запросов, на обслуживание которых будет потрачено достаточно много времени (не забываем, что надо не только пе- ремещать головку, но еще и передавать прочитанные данные!). Тогда запросы, относящиеся к другому краю диска и поступившие раньше, будут ждать обслуживания несправедливо долго. Для сокращения времени ожидания запросов применяется другая модификация алгоритма SCAN – циклическое сканиро- вание. Когда головка достигает одного из краев диска, она без чтения попутных запросов (иногда суще- ственно быстрее, чем при выполнении обычного поиска цилиндра) перемещается на другой край, откуда вновь начинает движение в прежнем направлении. Для этого алгоритма, получившего название C-SCAN, последовательность перемещений будет выглядеть так: Основы операционных систем 144 63 55 31 23 14 10 7 0 99 84 67 По аналогии с алгоритмом LOOK для алгоритма SCAN можно предложить и алгоритм C-LOOK для ал- горитма C-SCAN: 63 55 31 23 14 10 7 84 67 Существуют и другие разновидности алгоритмов сканирования, и совсем другие алгоритмы, но мы на этом закончим, ибо было сказано: "И еще раз говорю: никто не обнимет необъятного". Заключение Функционирование любой вычислительной системы обычно сводится к выполнению двух видов работы: обработка информации и операции по осуществлению ее ввода-вывода. С точки зрения операционной системы "обработкой информации" являются только операции, совершаемые процессором над данными, находящимися в памяти на уровне иерархии не ниже чем оперативная память. Все остальное относится к "операциям ввода-вывода", т. е. к обмену информацией с внешними устройствами. Несмотря на все многообразие устройств ввода-вывода, управление их работой и обмен информацией с ними строятся на относительно небольшом количестве принципов. Основными физическими принципа- ми построения системы ввода-вывода являются следующие: возможность использования различных ад- ресных пространств для памяти и устройств ввода-вывода; подключение устройств к системе через пор- ты ввода-вывода, отображаемые в одно из адресных пространств; существование механизма прерывания для извещения процессора о завершении операций ввода-вывода; наличие механизма прямого доступа устройств к памяти, минуя процессор. Механизм, подобный механизму прерываний, может использоваться также и для обработки исключений и программных прерываний, однако это целиком лежит на совести разработчиков вычислительных сис- тем. Для построения программной части системы ввода-вывода характерен "слоеный" подход. Для непосред- ственного взаимодействия с hardware используются драйверы устройств, скрывающие от остальной час- ти операционной системы все особенности их функционирования. Драйверы устройств через жестко оп- ределенный интерфейс связаны с базовой подсистемой ввода-вывода, в обязанности которой входят: ор- ганизация работы блокирующихся, неблокирующихся и асинхронных системных вызовов, буферизация и кэширование входных и выходных данных, осуществление spooling и монопольного захвата внешних устройств, обработка ошибок и прерываний, возникающих при операциях ввода-вывода, планирование последовательности запросов на выполнение этих операций. Доступ к базовой подсистеме ввода-вывода осуществляется посредством системных вызовов. Часть функций базовой подсистемы может быть делегирована драйверам устройств и самим устройствам ввода-вывода