Добавлен: 29.10.2018
Просмотров: 47977
Скачиваний: 190
1066
Глава 12. Разработка операционных систем
Без инструментария разработчики приложений все будут делать по-своему. Разработка
пользовательского интерфейса представляет собой очень важную задачу, но она не
является темой данной книги, поэтому мы вернемся к обсуждению темы интерфейса
операционной системы.
Парадигмы исполнения
Архитектурная согласованность важна на уровне пользователя, но в равной мере
она важна на уровне интерфейса системных вызовов. Здесь часто полезно различать
парадигму исполнения и парадигму данных, поэтому мы рассмотрим и ту и другую,
начав с первой.
Широкое распространение получили две парадигмы: алгоритмическая и движимая
событиями. Алгоритмическая парадигма основана на идее, что программа запуска-
ется для выполнения некоторой функции, известной заранее или задаваемой в виде
параметров. Эта функция может заключаться в компиляции программы, составлении
ведомости или пилотировании самолета до Сан-Франциско. Базовая логика жестко
прошита в код программы, при этом программа время от времени обращается к систем-
ным вызовам, чтобы получить ввод пользователя, обратиться к системным службам
и т. д. Этот подход проиллюстрирован в листинге 12.1, а.
Другая парадигма исполнения представляет собой парадигму управления событиями
(листинг 12.1, б). Здесь программа выполняет определенную инициализацию, напри-
мер отображает какое-либо окно, а затем ждет, когда операционная система сообщит
ей о первом событии. Этим событием может быть нажатие клавиши или перемещение
мыши. Такая схема полезна для программ, активно взаимодействующих с пользова-
телем.
Листинг 12.1. Код: а — алгоритмический; б — движимый событиями
main() main()
{ {
int ... ; mess_t msg;
init(); init();
do_something(); while (get_message(&msg)) {
read(...); switch (msg.type) {
do_something_else(); case 1: ... ;
write(...); case 2: ... ;
keep_going(); case 3: ... ;
exit(0); }
} }
}
а
б
Каждая парадигма порождает собственный стиль программирования. В алгорит-
мической парадигме алгоритмы занимают центральное положение, а операционная
система рассматривается как поставщик служб. В парадигме управления событиями
операционная система также предоставляет службы, но ее основная роль заключается
в координации активности пользователя и формировании событий, потребляемых
процессами.
12.2. Разработка интерфейса
1067
Парадигмы данных
Парадигма исполнения является не единственной парадигмой, экспортируемой опера-
ционной системой. Не менее важна парадигма данных. Ключевой вопрос здесь заклю-
чается в том, как предстают перед программистом системные структуры и устройства.
В ранних пакетных системах, предназначенных для выполнения программ на языке
FORTRAN, все моделировалось как логическая магнитная лента. Считываемые колоды
карт воспринимались как входные ленты, пробиваемые колоды карт обрабатывались
как выходные ленты. Вывод на принтер также обрабатывался как выходная лента.
Файлы на диске также считались лентами. Произвольный доступ к файлу был возмо-
жен только при помощи перемотки соответствующей ленты и повторного считывания.
Задание запускалось при помощи карт управления заданием, например:
MOUNT(TAPE08, REEL781)
RUN(INPUT, MYDATA, OUTPUT, PUNCH, TAPE08)
Первая карта представляла собой инструкцию для оператора. Он должен был достать
из шкафа бобину номер 781 и установить ее на накопителе 8. Вторая карта являлась
командой операционной системе запустить только что откомпилированную с языка
FORTRAN программу, отображая INPUT (означающий устройство чтения перфокарт)
на логическую ленту 1, дисковый файл MYDATA — на логическую ленту 2, принтер
OUTPUT — на логическую ленту 3, перфоратор PUNCH — на логическую ленту 4 и фи-
зический накопитель на магнитной ленте TAPE08 — на логическую ленту 5.
У языка FORTRAN был четко определенный синтаксис, позволявший читать и пи-
сать логические ленты. При чтении с логической ленты 1 программа получает ввод
с перфокарт. При помощи записи на логическую ленту 3 программа может вывести
результаты на принтер. Обращаясь к логической ленте 5, программа может читать
и писать магнитную ленту 781 и т. д. Обратите внимание на то, что идея магнитной
ленты представляла собой всего лишь парадигму (модель) для объединения устройства
чтения перфокарт, принтера, перфоратора, дисковых файлов и магнитофонов. В данном
примере только логическая лента 5 была физической лентой. Все остальное представ-
ляло собой обычные файлы для подкачки данных. Это была примитивная парадигма,
но она была шагом в правильном направлении.
Затем появилась операционная система UNIX, которая пошла значительно дальше
в этом направлении, используя модель «всё суть файлы». При использовании этой па-
радигмы все устройства ввода-вывода рассматриваются как файлы, которые можно от-
крывать и которыми можно управлять как обычными файлами. Операторы на языке C
fd1 = open("fi le1", O_RDWR);
fd2 = open("/dev/tty", O_RDWR);
открывают настоящий дисковый файл и терминал пользователя. Последующие опера-
торы могут использовать дескрипторы файлов
fd1
и
fd2
, чтобы читать из этих файлов
и писать в них. С этого момента нет разницы между доступом к файлу и доступом
к терминалу, не считая того, что при обращении к терминалу не разрешается операция
перемещения указателя в файле.
Операционная система UNIX не только объединяет файлы и устройства ввода-вывода,
но также позволяет получать доступ к другим процессам через каналы как к файлам.
Более того, если поддерживается отображение файлов на адресное пространство па-
мяти, процесс может обращаться к своей виртуальной памяти так, как если бы это был
1068
Глава 12. Разработка операционных систем
файл. Наконец, в версиях UNIX, поддерживающих файловую систему
/proc
, строка
на языке C
fd3 = open("/proc/501", O_RDWR);
позволяет процессу (попытаться) получить доступ к памяти процесса 501 для чтения
и записи при помощи дескриптора файла
fd3
, что может быть полезно, например, при
отладке программы.
Разумеется, только то, что кто-то сказал, что всё является файлами, не означает, что это
касается абсолютно всего. Например, сетевые сокеты UNIX могут чем-то напоминать
файлы, но у них есть существенное отличие, API-интерфейс сокетов. Еще в одной
операционной системе, Plan 9 от компании Bell Labs, на компромисс не пошли и не
предоставили специализированных интерфейсов для сетевых сокетов. В результате
конструкция Plan 9 оказалась, наверное, чище.
Операционная система Windows старается все сделать похожим на объект. Получив
дескриптор файла, процесса, семафора, почтового ящика или другого объекта ядра,
процесс может выполнять с этим объектом различные действия. Эта парадигма яв-
ляется еще более общей, чем используемая в UNIX, и значительно более общей, чем
в FORTRAN.
Объединяющие парадигмы встречаются и в других контекстах. Следует отметить один
из них — Всемирную паутину (Web). Используемая в Паутине парадигма состоит в том,
что все киберпространство заполнено документами, у каждого из которых есть свой
адрес URL. Обратившись по соответствующему указателю URL (введя его с клавиату-
ры или щелкнув мышью по ссылке), вы получаете этот документ. В действительности
многие «документы» вовсе не являются документами, но формируются программой
или сценарием оболочки, когда поступает запрос. Например, когда пользователь за-
прашивает в интернет-магазине список компакт-дисков конкретного исполнителя,
документ создается на лету программой. Его совершенно точно не существовало до
того, как был получен запрос.
Итак, мы рассмотрели четыре парадигмы, а именно: всё суть ленты, файлы, объекты
или документы. Во всех четырех случаях задача заключается в том, чтобы унифици-
ровать данные, устройства или другие ресурсы для упрощения работы с ними. Каждая
операционная система должна иметь подобную унифицирующую парадигму данных.
12.2.3. Интерфейс системных вызовов
Если исходить из высказанного Корбато принципа минимального механизма, то опера-
ционная система должна предоставлять настолько мало системных вызовов, насколько
это возможно (необходимый минимум), и каждый системный вызов должен быть на-
столько прост, насколько это возможно (но не проще). Объединяющая парадигма дан-
ных может играть главную роль в этом. Например, если файлы, процессы, устройства
ввода-вывода и прочее будут выглядеть как файлы или объекты, все они могут читаться
при помощи всего одного системного вызова read. В противном случае пришлось бы
иметь различные системные вызовы, такие как read_file, read_proc, read_tty и т. д.
В некоторых случаях могут потребоваться несколько вариантов системных вызовов,
но, как правило, на практике лучше иметь один системный вызов, обрабатывающий
общий случай, с различными библиотечными процедурами, скрывающими этот факт от
программистов. Например, в операционной системе UNIX есть системный вызов exec
12.2. Разработка интерфейса
1069
для замены виртуального адресного пространства процесса. Наиболее общий вариант
его использования выглядит следующим образом:
exec(name, argp, envp);
Данный системный вызов загружает исполняемый файл
name
и передает ему аргумен-
ты, на которые указывает argp, и список переменных окружения, на который указывает
envp. Иногда бывает удобнее явно перечислить аргументы, поэтому в библиотеках
содержатся процедуры, вызываемые следующим образом:
execl(name, arg0, arg1, ..., argn, 0);
execle(name, arg0, arg1, ..., argn, envp);
Эти процедуры всего лишь помещают аргументы в массив, после чего обращаются к си-
стемному вызову exec, который и выполняет всю работу. Такая схема является лучшей
в обоих смыслах: благодаря единственному простому системному вызову операционная
система сохраняет свою простоту, в то же самое время программист получает возмож-
ность обращаться к системному вызову exec различными способами.
Разумеется, пытаясь использовать один-единственный системный вызов для всех слу-
чаев жизни, легко дойти до крайностей. Для создания процесса в операционной системе
UNIX требуются два системных вызова: fork, за которым следует exec. У первого вызова
нет параметров, у второго вызова три параметра. Для сравнения: у вызова WinAPI для
создания процесса CreateProcess 10 параметров, один из которых представляет собой
указатель на структуру с дополнительными 18 параметрами.
Давным-давно следовало задать вопрос: «Произойдет ли катастрофа, если мы опустим
что-нибудь из этого?» Правдивый ответ должен был звучать так: «В некоторых случаях
программист будет вынужден совершить больше работы для достижения определенно-
го эффекта, зато мы получим более простую, компактную и надежную операционную
систему». Конечно, человек, предлагающий версию с этими 10 + 18 параметрами, мог
добавить: «Но пользователям нравятся все эти возможности». Возразить на это можно
было бы так: «Еще больше им нравятся системы, которые используют мало памяти
и никогда не ломаются». Компромисс, заключающийся в большей функциональности
за счет использования большего объема памяти, по крайней мере, виден невооружен-
ным глазом, и ему можно дать оценку (так как стоимость памяти известна). Однако
трудно оценить количество дополнительных сбоев в год, которые появятся благодаря
внедрению новой функции. Кроме того, неизвестно, сделали бы пользователи тот же
выбор, если им заранее была известна эта скрытая цена. Этот эффект можно резюмиро-
вать первым законом программного обеспечения Таненбаума: «При увеличении размера
программы количество содержащихся в ней ошибок также увеличивается».
Когда к программе добавляются новые функции, к ней добавляются и новые процеду-
ры, а вместе с ними и новые ошибки. Программисты, полагающие, что при добавлении
новых функций к программе не добавится новых ошибок, либо являются новичками
в программировании, либо верят, что за ними присматривает добрая фея.
Простота является не единственным принципом, которым следует руководствовать-
ся при разработке системных вызовов. Следует также помнить о фразе, сказанной
Б. Лэмпсоном в 1984 году: «Не скрывай мощь».
Если у аппаратного обеспечения есть крайне эффективный способ выполнить что-
либо, программистам следует предоставить простой доступ к этой возможности, а не
хоронить ее внутри некой абстракции. Назначение абстракций заключается в том,
1070
Глава 12. Разработка операционных систем
чтобы скрывать нежелательные свойства, а не полезные свойства. Например, предпо-
ложим, что у аппаратуры есть специальный способ перемещения больших участков
изображений по экрану (то есть в видеопамяти) на высокой скорости. В этом случае
ввод нового системного вызова, предоставляющего доступ к этому механизму, будет
оправдан, так как это лучше, чем читать данные из видеоОЗУ в обычную память, а за-
тем писать эти данные обратно в видеоОЗУ. Новый вызов должен просто перемещать
биты в видеопамяти. Если этот новый системный вызов будет быстрым, это позволит
пользователям создавать более эффективные программы. Если системный вызов мед-
ленный, никто не будет им пользоваться.
При проектировании системы возникает также вопрос использования ориентирован-
ных на соединение вызовов или вызовов, не использующих соединений. Системные
вызовы Windows и UNIX для чтения файлов являются ориентированными на соеди-
нение, что похоже на использование телефона. Сначала вы открываете файл, затем
читаете его и, наконец, закрываете файл. Некоторые протоколы работы с удаленными
файлами также являются ориентированными на соединение. Например, чтобы ис-
пользовать протокол FTP, пользователь сначала регистрируется на удаленной машине,
читает файлы, а затем выходит из системы.
В то же время некоторые протоколы удаленного доступа к файлам не требуют соеди-
нений. Веб-протокол (HTTP) не требует соединений. Чтобы прочитать веб-страницу,
вы просто запрашиваете ее. Не требуется никаких предварительных настроек (TCP-
соединение все-таки требуется, но оно представляет собой более низкий уровень про-
токола; протокол HTTP, используемый для доступа к самой веб-странице, не требует
соединений).
От того, какой из механизмов выбрать — ориентированный на соединение или не тре-
бующий соединений, — зависит, будет ли от механизма требоваться дополнительная
работа (например, по открытию файлов) или же эта работа перекладывается на плечи
использующей механизм прикладной программы. В последнем случае получается
существенный выигрыш в эффективности, если к одному и тому же файлу программа
обращается много раз. Для системы ввода-вывода на одной машине, где стоимость
подготовки ввода-вывода (открытия файла) низка, вероятно, лучше использовать
стандартный способ (сначала открыть, затем использовать). Для удаленных файловых
систем возможно использование обоих вариантов.
Другой вопрос, возникающий при проектировании интерфейса системных вызовов,
заключается в его открытости. Список системных вызовов, определяемых стандартом
POSIX, легко найти. Эти системные вызовы поддерживаются всеми системами UNIX,
как и небольшое количество других вызовов, но полный список всегда публикуется.
Корпорация Microsoft, напротив, никогда не публиковала список системных вызовов
Windows. Вместо этого публикуются функции интерфейса WinAPI, а также вызовы
других интерфейсов. Эти списки содержат огромное количество библиотечных вы-
зовов (более 10 000), но только малое их число являются настоящими системными
вызовами. Аргумент в пользу открытости системных вызовов заключается в том,
что программистам становится известна цена использования функций. Функции,
исполняемые в пространстве пользователя, выполняются быстрее, чем те, которые
требуют переключения в режим ядра. Закрытость системных вызовов также имеет
свои преимущества, заключающиеся в том, что в результате достигается гибкость
в реализации библиотечных процедур. То есть разработчики операционной системы
получают возможность изменять действительные системные вызовы, сохраняя при