введение_1 (1085732), страница 11
Текст из файла (страница 11)
Еще одно важное понятие в UNIX — это специальный файл. Специальные файлы служат для того, чтобы устройства ввода-вывода выглядели как файлы. При этом можно прочесть информацию из специальных файлов или записать ее туда с помощью тех же самых системных вызовов, что используются для чтения и записи файлов. Существует два вида специальных файлов: блочные специальные файлы и символьные специальные файлы. Блочные специальные файлы используются для моделирования устройств, состоящих из набора произвольно адресуемых блоков, таких как диски. Открывая блочный специальный файл и читая, скажем, блок 4, программа может напрямую получить доступ к четвертому блоку на устройстве, без обращения к содержащейся на нем файловой системе. Таким же образом символьные специальные файлы используются для моделирования принтеров, модемов и других устройств, которые принимают или выдают поток символов. По соглашению специальные файлы хранятся в каталоге /dev. Например, /dev/lp может быть строковым принтером.
И последнее понятие, которое мы обсудим во введении, — это каналы (pipe), имеющие отношение и к процессам и к файлам. Канал (также иногда называемый трубой) представляет собой псевдофайл, который можно использовать для связи двух процессов, как показано на рис. 1.16. Если процессы А и В захотят пообщаться с помощью канала, они должны установить его заранее. Когда процесс А хочет отправить данные процессу В, он пишет их в канал, как если бы это был выходной файл. Процесс В может прочесть данные, читая их из канала, как если бы он был
файлом с входными данными. Таким образом, соединение между процессами в UNIX выглядит очень похожим на обычное чтение и запись файлов. Более того, только сделав специальный системный вызов, процесс может обнаружить, что выходной файл, в который он пишет данные, не реальный файл, а канал. Файловые системы очень важны. Мы расскажем о них значительно больше в главе 6, а также в главах 10 и 11.
Процесс Процесс
А
В
Канал
Рис. 1.16. Два процесса, соединенные каналом
Безопасность
Компьютеры содержат большое количество информации, конфиденциальность которой пользователи зачастую хотят сохранить: электронную почту, бизнес-планы и многое другое. В задачу операционной системы входит управление системой защиты подобных файлов, так чтобы они, например, были доступны только пользователям, имеющим на это права.
В качестве простейшего примера, дающего представление о том, как работает система безопасности, рассмотрим систему UNIX. В UNIX для защиты файлов им присваивается 9-битовый двоичный код. Этот код защиты состоит из трех полей по три бита; одно для владельца, второе для других членов группы владельца (пользователи разделяются на группы системным администратором) и третье — для всех остальных. В каждом поле есть бит, определяющий доступ для чтения, бит, определяющий доступ для записи, и бит, разрешающий выполнение. Эти три бита называются rwx-битами (read, write, execute). Например, код защиты rwxr-x--x означает, что владелец файла может читать, писать или выполнять файл, другие члены группы могут читать или выполнять файл (но не писать в него), а остальные могут только выполнять файл (но не читать или писать). Для каталога х означает разрешение на поиск. Дефис означает, что соответствующее разрешение отсутствует.
Кроме защиты файлов, существует еще множество других вопросов безопасности: защита системы от нежелательных гостей, людей, и не только (вирусов).
Оболочка
Операционная система представляет собой программу, выполняющую системные вызовы. Редакторы, компиляторы, ассемблеры, компоновщики и командные интерпретаторы не являются частью операционной системы, несмотря на их большую важность и полезность. Поскольку есть риск запутаться в этих вещах, в данном разделе мы кратко рассмотрим только командный интерпретатор UNIX, называемый оболочкой (shell). Хотя она не входит в операционную систему, но во всю пользуется многими функциями операционной системы и поэтому является хорошим примером того, как могут применяться системные вызовы. Кроме этого,
оболочка предоставляет основной интерфейс между пользователем, сидящим за своим терминалом, и операционной системой, если, конечно, пользователь не использует графический интерфейс. Существует множество оболочек, например sh, csh, ksh и bash. Все они поддерживают описанные ниже функции, поскольку произошли от первоначальной оболочки (sh).
Когда какой-либо пользователь входит в систему, запускается оболочка. Стандартным входным и выходным устройством для оболочки является терминал (монитор с клавиатурой). Оболочка начинает работу с печати приглашения (prompt) — знака доллара, говорящего пользователю, что оболочка ожидает ввода команды. Если теперь пользователь напечатает, например,
date
оболочка создаст дочерний процесс и запустит программу date. Пока дочерний процесс работает, оболочка ожидает его завершения. После завершения дочернего процесса оболочка опять печатает приглашение и пытается прочесть следующую входную строку. Пользователь может перенаправить стандартный вывод данных в файл:
date >file
Таким же образом можно переопределить устройство, с которого читаются входные данные, как показано ниже:
sort <filel >file2
Эта команда предписывает программе сортировки считать данные из файла 1 и вывести результат в файл 2.
Выходные данные одной программы можно использовать в качестве входных данных для другой, соединив их каналом. Так, команда
cat filel file2 file3 | sort >/dev/lp
предписывает программе cat объединить (concatenate) три файла и послать выходные данные программе sort, которая расставит все строки в алфавитном порядке. Результат работы sort перенаправляется в файл /dev/lp, обычно обозначающий принтер. Если пользователь наберет знак & после команды, оболочка не будет ждать окончания ее выполнения. В этом случае она немедленно напишет новое приглашение. То есть в результате команды
cat filel file2 file3 | sort >/dev/lp &
сортировка запустится как фоновое задание, разрешая пользователю продолжать нормальную работу во время выполнения сортировки. Оболочка имеет множество других интересных особенностей, для обсуждения которых у нас здесь, к сожалению, недостаточно места. Но большинство книг по UNIX описывают оболочки довольно подробно.
Повторное использование идей
Кибернетика (наука о компьютерах), как и множество других областей знания, находится в сильной зависимости от технологий. Причиной отсутствия автомобилей у древних римлян являлось вовсе не то, что они очень любили ходить пеш-
ком. Машин не было потому, что римляне просто не знали, как их сконструировать. И персональные компьютеры существуют не потому, что миллионы людей долгое время хотели иметь свой собственный компьютер, но сдерживали это желание, а потому, что теперь можно относительно дешево их производить. Мы часто забываем, как сильно влияет технология на наше видение систем, и действительно полезно поразмышлять об этом время от времени.
Часто случается, что из-за изменений в технологии некоторые идеи устаревают. Но другие изменения в технологии могут вновь оживить их. Такое случается главным образом тогда, когда происходящие изменения имеют отношение к относительной производительности различных частей системы. Например, когда скорость центрального процессора начинает намного превышать быстродействие памяти, кэш становится очень важной деталью, увеличивающей скорость «медленной» памяти. Если новые технологии в области памяти когда-нибудь создадут память намного более быструю, чем процессор, кэш станет не нужным. Но если затем процессоры опять станут более быстрыми, чем память, кэш появится снова. В биологии вымирание происходит навсегда, но в кибернетике иногда это бывает только на несколько лет.
Из-за такого непостоянства в данной книге время от времени мы будем рассматривать «устаревшие» концепции, то есть идеи, не оптимальные для современных технологий. Но изменения в технологии могут вернуть к жизни некоторые из так называемых «устаревших понятий». По этой причине важно понять, почему концепция является устаревшей и какие изменения в окружающей обстановке могут оживить ее.
Чтобы пояснить нашу точку зрения, рассмотрим несколько примеров. Ранние компьютеры имели вмонтированный в аппаратуру набор команд. Затем появилось микропрограммирование, при котором интерпретатор выполнял команды программно. Аппаратное выполнение устарело. После этого были созданы RISC-компьютеры, и микропрограммирование (то есть интерпретируемое выполнение) тоже стало устаревшим понятием, поскольку исполнение команд напрямую оказалось быстрее. Теперь мы наблюдаем возрождение интерпретации в форме ап-плетов Java, которые передаются по Интернету и интерпретируются по прибытии. Здесь скорость выполнения не играет решающей роли, поскольку задержки в сети настолько велики, что основное время тратится на них. Но все это тоже однажды может измениться.
Ранние компьютерные системы размещали файлы на диске, располагая их в соседних секторах, один за другим. Хотя эта схема осуществляется очень просто, она не является гибкой, поскольку если файл увеличился в размере, уже не будет места для его хранения. Концепция непрерывного размещения файлов была отвергнута и стала устаревшей. До тех пор, пока не появились компакт-диски. Для них не существует проблемы роста файлов. Внезапно простота непрерывного размещения файлов оказалась гениальной идеей, и на ней сейчас базируются файловые системы компакт-дисков.
И наконец, рассмотрим динамическое связывание. Система MULTICS проектировалась так, чтобы она могла функционировать днем и ночью без остановок. Чтобы программно исправлять системные ошибки, необходимо было найти способ, позволяющий заменять библиотечные процедуры во время их использования.
Для этой цели придумали понятие динамического связывания. После того как система MULTICS отжила свое, это понятие было на время забыто. Но его открыли заново, когда современным операционным системам понадобился способ, позволяющий нескольким программам делить между собой одну библиотечную процедуру, не создавая для себя собственной копии (потому что графические библиотеки выросли до невероятных размеров). Сейчас большинство систем снова поддерживает некоторую форму динамического связывания. Список можно еще продолжить, но мораль описанных выше примеров такова: идея, которая сегодня является устаревшей, завтра может стать гвоздем сезона.
Не только технологии влияют на системы и программное обеспечение. Важную роль играет и экономика. В 60-х и 70-х годах большинство терминалов было механически печатающими устройствами или алфавитно-цифровыми дисплеями с электронно-лучевыми трубками, предназначенным для вывода 25 х 80 символов, а не графическими терминалами с растровым отображением. Этот выбор был обусловлен не технологиями. Растровые графические терминалы использовались еще до 1960 года. Но они стоили несколько десятков тысяч долларов каждый. Только после сильного падения цен люди (а не только военные) смоли задуматься о предоставлении каждому пользователю собственного терминала.
Системные вызовы
Интерфейс между операционной системой и программами пользователя определяется набором системных вызовов, предоставляемых операционной системой. Чтобы на самом деле понять, что же делает операционная система, мы должны подробно рассмотреть этот интерфейс. Системные вызовы, доступные в интерфейсе, меняются от одной операционной системы к другой (хотя лежащая в их основе концепция практически одинакова).
Теперь мы столкнулись с проблемой выбора между (1) неопределенными обобщениями («операционные системы имеют системные вызовы для чтения файлов») и (2) какой-либо конкретной системой («в UNIX существует системный вызов для чтения с тремя параметрами: один для задания файла, второй — для того, чтобы указать, куда нужно поместить прочитанные данные, третий задает количество байтов, которое нужно прочитать»).
Мы выбрали второй подход. При этом способе нужно проделать больше работы, но он обеспечивает лучшее понимание того, что в реальности происходит в операционной системе. Несмотря на то, что это обсуждение затрагивает конкретно стандарт POSIX (международный стандарт 9945-1), а, следовательно, также и операционные системы UNIX, System V, BSD, Linux, MINIX и т. д., у большинства других современных операционных систем есть системные вызовы, выполняющие те же самые функции, хотя детали могут быть различны. Так как фактический механизм обращения к системным функциям является в высокой степени машинно-зависимым и часто должен реализовываться на ассемблере, существуют библиотеки процедур, делающие возможным обращение к системным процедурам из программ на С и других языках с тем же успехом.
Очень полезно всегда помнить следующее. Любой компьютер с одним процессором в каждый конкретный момент времени может выполнить только одну команду. Если процесс выполняет программу пользователя в пользовательском режиме и нуждается в системной службе, например чтении данных из файла, он должен выполнить прерывание или команду системного вызова для передачи управления операционной системе. Затем операционная система по параметрам вызова определяет, что требуется вызывающему процессу. После этого она обрабатывает системный вызов и возвращает управление команде, следующей за системным вызовом. В известном смысле выполнение системного вызова похоже на осуществление вызова процедуры, только первый проникает в ядро, а второй этого не делает.
Для того чтобы прояснить механизм системных вызовов, кратко рассмотрим системный вызов read. Как упоминалось выше, у него есть три параметра: первый служит для задания файла, второй указывает на буфер, третий задает количество байтов, которое нужно прочитать. Как практически все системные вызовы, он запускается из программы на С с помощью вызова библиотечной процедуры с тем же именем, что и системный вызов: read. Вызов из программы на С может выглядеть так:
count = read(fd, buffer, nbytes);