Другие журналы

научное издание МГТУ им. Н.Э. Баумана

НАУКА и ОБРАЗОВАНИЕ

Издатель ФГБОУ ВПО "МГТУ им. Н.Э. Баумана". Эл № ФС 77 - 48211.  ISSN 1994-0408

ВНУТРЕННЕЕ УСТРОЙСТВО OS UNIX

автор: Родионов С. В.

                                    С.В. Родионов

 

             ВНУТРЕННЕЕ УСТРОЙСТВО OS UNIX

 

             Базовые концепции OS UNIX

 

OS UNIX - многозадачная и многопользовательская ОС разделения времени.

Ее главная отличительная особенность - мобильность исходного кода. Без

особых проблем OS UNIX может быть портирована на компьютерах различной

архитектуры. Другая особенность OS UNIX -  ее высокая технологичность,

которая достигается поддержкой многочисленных инструментальных средств

для разработки прикладного и системного программного обеспечения. Эта

ОС ориентирована на программирующих профессионалов, но не на "рядовых"

пользователей как MS-DOS и MS Windows. Указанные характерные черты OS

UNIX обеспечены успехом реализации следующих базовых концепций.

 

  + Организация процессов - поддерживает многозадачный режим работы на

    основе разделения вычислительных ресурсов для обработки системных

    и прикладных программ.

 

  + Файловая структура - определяет логическую организацию пространства

    внешней памяти в виде иерархических файловых систем.

 

  + Унификация ввода-вывода - обеспечивает универсальный интерфейс

    обмена с периферийными устройствами.

 

  + Многопользовательский режим - регламентирует разделение системных

    ресурсов зарегистрированными пользователями и обеспечивает защиту

    их данных на основе ограничения полномочий.

 

Реализация этих базовых концепций определяет внутреннее устройство и

отражает логическое разделение OS UNIX на соответствующие подсистемы -

управления процессами, файлами, вводом-выводом и многопользовательским

режимом. Их организация мало известна широкому кругу начинающих

пользователей OS UNIX из доступной литературы. Предлагаемое руководство

содержит начальные сведения по внутреннему устройству UNIX, которые

необходимы для более профессионального использования возможностей

данной ОС.

 

 

             ОРГАНИЗАЦИЯ ПРОЦЕССОВ

 

             Понятия ядра и процесса

 

Резидентная в RAM часть OS UNIX называется ядром. Программа ядра

оформлена как выполняемый файл, который хранится в корневом каталоге

корневой файловой системы OS UNIX и имеет имя, однозначно выделяющее

его среди других файлов. Ядро считывается в RAM программой начальной

загрузки, начиная с нулевого адреса. После загрузки ядро постоянно

находится в RAM, обеспечивая управление подсистемами UNIX посредством

соответствующих ядерных подпрограмм, системных таблиц и буферов.

 

Все работы вне ядра оформлены в виде процессов выполнения системных и

прикладных программ. Под процессом понимается единица вычислительной

работы, потребляющая ресурсы, предоставляемые ядром, для обработки

системных и прикладных программ, которые оформлены как командные или

выполняемые файлы на внешнем устройстве. Процесс и выполняемая им

программа - принципиально различные категории. Программа - статическое

понятие, процесс - динамическая категория, отражающая ход выполнения

программы. Это различие принципиально, т.к. в ходе выполнения одного и

того же процесса может неоднократно происходить замена программы.

 

Процессы могут взаимодействовать между собой посредством программных

каналов или сигналов и обращаться к подпрограммам ядра через системные

вызовы. Ядро поддерживает иерархию процессов, предоставляет необходимое

адресное пространство для их выполнения, обеспечивает диспечеризацию и

внутреннюю синхронизацию процессов, осуществляет переключение фазы

выполнения процесса, реализует механизм своппинга и пейджинга процессов.

 

 

            Адресное пространство процесса и ядра

 

Каждый процесс выполняется в собственном виртуальном пространстве

адресов. Размер виртуального адресного пространства фиксирован, но

может отличаться в реализациях OS UNIX на различных платформах.

Совокупность участков RAM, отображаемых виртуальными адресами составляет

образ процесса.

 

Образ процесса составляют следующие сегменты: процедурный сегмент (text),

сегмент инициализированных данных (data), сегмент неинициализированных

данных (bss) и стек (stack). Расположение сегментов в виртуальном

адресном пространстве процесса иллюстрирует следующий рисунок.

 

    ┌──────────┬─────────────┬──────────┬─────────┬──────────┐

    │          │ Свободные   │ basic    │         │          │

    │ stack    │ виртуальные │ static   │  data   │    text  │

    │          │   адреса    │ storage  │         │          │

    └──────────┴─────────────┴──────────┴─────────┴──────────┘

    Расширение ->           <- Расширение                    0

    стека                      сегментов

                               данных

 

    Рис. Структура виртуального адресного пространства процесса

 

Начальные размеры сегментов образа процесса определяет заголовок файла

программы процесса. Сегменты text и data загружаются из файла программы

процесса. Сегменты bss и stack создаются в образе процесса и не занимают

места в файле программы процесса, так как, по умолчанию, начальные

значения составляющих их данных равны 0.

 

Сегмент text -  содержит машинные инструкции и константы. Его содержание

                не изменяется при выполнения процесса.

Сегмент data -  содержит внешние и статические переменные,

                инициализированные при компиляции программы процесса.

Сегмент bss  -  содержит внешние и статические переменные,

              неинициализированные при компиляции программы процесса.

Сегмент stack - используется для хранения значений автоматических

            переменных и параметров функций, а также для

            организации рекурсивных вызовов функций.

 

Между нижней границей стека и сегментов данных обычно существует

адресный зазор, который может быть использован для расширения стека и

сегментов данных при выполнении процесса, пока не исчерпано пространство

свободных адресов. Изменение размера сегментов данных реализуется

системными вызовами динамического распределения памяти brk и sbrk.

 

Хотя ядро не является самостоятельной вычислительной единицей, однако,

программа ядра также как программы процессов хранится в выполняемом

файле формата a.out. Резидентный образ ядра располагается в RAM, начиная

с нулевого адреса, и состоит из набора сегментов, который идентичен

образу процесса. В частности, процедурный сегмент ядра составляют коды

подпрограмм управления процессами, файлами и вводом-выводом, а также

драйверы периферийных устройств. Сегмент данных ядра составляют

системные таблицы и переменные, используемые подпрограммами процедурного

сегмента, а также область отображения контекста текущего процесса. Стек

ядра используется для размещения параметров ядерных функций и системных

вызовов. Образу ядра соответствует виртуальное адресное пространство,

которое по структуре аналогично виртуальному адресному пространству

процесса.

 

Образы процессов и ядра не пересекаются в RAM. Также не пересекаются

образы процессов, выполняющих различные программы. Образы процессов,

которые выполняют одинаковую программу, могут разделять, т.е. совместно

использовать, общий процедурный сегмент. Является процедурный сегмент

разделяемым или нет определяет магическое число в заголовке файла

программы процесса, которое может принимать одно из 3-х значений: 0407,

0410 и 0411 (0413).

 

Если магическое число в заголовке файла программы процесса равно 0407,

то процедурный сегмент не разделяется процессами, которые одновременно

выполняют данную программу, хотя они имеют абсолютно идентичные

процедурные сегменты. Если магическое число процесса равно 0410, то

процедурный сегмент разделяется всеми процессами, которые выполняют эту

программу. Разделяемый процедурный сегмент часто называют сегментом

чистого кода, а программу, которая его содержит - повторно входимой или

реентерабельной. Процедурный сегмент реентерабельной программы может

быть повторно выполнен любыми процессами без повторной загрузки в RAM.

Если магическое число в файле программы процесса равно 0411 (0413), то

процедурный сегмент также является разделяемым, но располагается в

отдельном виртуальном адресном пространстве. Использование разделяемых

процедурных сегментов позволяет эффективно экономить ресурсы RAM при

обработке идентичных программ.

 

 

                Фазы выполнения процесса

 

В каждый момент выполнения процесс может находиться фазе "пользователь"

или в фазе "система". В фазе "пользователь" выполняются инструкции из

адресного пространства процесса, в фазе "система" - инструкции ядра.

Текущая фаза отслеживается в регистре PS. Выполнение процесса в фазе

"пользователь" может быть прервано для перехода в фазу "система" по

одной из следующих 3-х причин: системный вызов, внутреннее (синхронное)

прерывание (ловушка), внешнее (асинхронное) аппаратное прерывание. После

отработки любой из указанных причин прерывания следует возврат в фазу

"пользователь".

 

Прерывание системным вызовом сознательно инициируется процессом в фазе

"пользователь" для обращения к необходимым подпрограммам из адресного

пространства ядра. Внутренние прерывания (ловушки) связаны с обработкой

подпрограммами ядра аварийных ситуаций, которые возникают синхронно при

нарушении границ сегментов в адресном пространстве процесса, при ошибке

косвенной адресации в программе процесса, при десятичном переполнении

или при делении на ноль и при переполнении стека. Результатом обработки

является посылка процессу соответствующего сигнала, который стандартно

вызывает аварийное завершение процесса с фиксацией дампа памяти в файле

core. Внешние прерывания связаны с активизацией работы периферийных

устройств, контроллеры которых имеют более высокий уровень приоритета,

чем процессор, выполняющий инструкции программы процесса в фазе

"пользователь".  Внешние прерывания возникают вне зависимости от хода

выполнения программы процесса, т.е.  асинхронно. При этом, управление

передается соответствующей подпрограмме обработки внешнего прерывания

из адресного пространства ядра. Во всех перечисленых случаях, процессор

переключается на выполнение подпрограмм из адресного пространства ядра,

которое доступно процессу только в фазе "система".

 

При переходе процесса в фазу "система" из фазы "пользователь"

выполняются следующие действия.

 

  + Сохранение специальных (PS, PC, SP) и рабочих регистров в блоке

    управления области контекста процесса ( u_pcb ), которая отображает

    контекст текущего процесса в адресном пространстве ядра.

 

  + Изменение текущего режима в регистре PS на фазу "система".

 

  + Загрузка в регистр PC адреса подпрограммы обработки прерывания из

    системной таблицы векторов прерываний для активизации ее выполнения.

    В частности, вектора системных вызовов находятся в системной таблице

    sysent, где они упорядочены по номерам системных вызовов.

 

  + Восстановление регистров, сохраненных при переходе в фазу "система".

    В частности, восстановление регистра PS означает возврат в фазу

    "пользователь".  Восстановление регистра PC заставляет процесс

    возобновить выполнение, начиная с точки прерывания.

 

 

             Контекст процесса

 

Системные данные, используемые при выполнении процесса (когда его образ

загружен в RAM), составляют контекст процесса. Контекст процесса

формально описан структурой struct user в файле /usr/include/sys/user.h.

Эта структура включает следующие основные поля:

 

  u_procp - адрес дескриптора процесса в таблице процессов;

  u_ofile - адрес таблицы открытых файлов процесса;

  u_signal - адрес таблицы сигналов;

  u_pcb - блок управления процессом;

  u_cdir - текущий каталог процесса;

  u_rdir - корневой каталог процесса;

  u_textvaddr - виртуальный адрес процедурного сегмента;

  u_datavaddr - виртуальный адрес сегмента инициализированных данных;

  u_bssvaddr -  виртуальный адрес сегмента неинициализированных данных.

 

Область контекста процесса имеет фиксированный размер и не входит в

виртуальное адресное пространство процесса. В пространстве физических

адресов контекст процесса располагается перед процедурным сегментом или

перед сегментом данных в случае разделяемого процедурного сегмента.

 

Контекст текущего процесса отображается в область текущего процесса

виртуального адресного пространства ядра. Адрес этой области сохраняет

системная переменная "u", через которую поля контекста текущего процесса

доступны доступны подпрограммам ядра. Доступ к полям контекста процесса

на уровне пользователя реализован через системные вызовы ядра, например,

chdir, chroot, signal, ulimit.

 

 

             Идентификатор процесса

 

Каждый процесс имеет уникальный целочисленный номер, который называется

идентификатором процесса. Он позволяет однозначно указать нужный процесс

в командах пользователя или системных вызовах. Идентификаторы процессов

сохраняют свою уникальность от момента образования процесса до очередной

перезагрузки OS UNIX. Уникальность идентификаторов гарантируется ядром.

 

Идентификаторы процессов динамически назначаются ядром из диапазона от 1

до 30000 в порядке образования процессов. Идентификатор процесса никак

не связан с именем файла программы его выполнения. Процессы не именют

предопределенных идентификаторов. Исключение составляют диспетчерский

процесс (swapper) с идентификатором 0 и процесс инициализации (init) с

идентификатором 1, которые образуются первыми при загрузке OS UNIX.

Процесс имеет возможность узнать свой идентификатор системным вызовом

getpid и идентификатор процесса-предка - системным вызовом getppid.

 

Кроме индивидуальной идентификации процессов в OS UNIX предусмотрена

идентификация групп процессов. Группу процессов образует множество

процессов, запущенных с одного терминала в течении одного сеанса. Все

процессы группы имеют одинаковый идентификатор, который совпадает с

идентификатором лидера группы. Лидером группы обычно является процесс

интепретатора команд, который открывает сеанс работы с терминалом -

так называемый login shell. Идентификатор группы процесса позволяет

узнать системный вызов getpgrp.

 

Личные и групповые идентификаторы процессов могут быть использованы для

персонифицированной и распределенной посылки сигналов системным вызовом

kill или для порождения уникальных имен временных файлов.

 

 

             Дескриптор процесса

 

Системные данные, используемые ядром в течении времени жизни процесса,

составляют дескриптор процесса. Дескриптор процесса резервируется ядром

при образовании процесса и освобождается при его завершении. Дескриптор

процесса формально описан структурой struct proc в заголовочном файле

/usr/include/sys/proc.h. Основные поля структуры struct proc могут быть

классифицированы по характеру данных следующим образом.

 

   + Поля идентификации процесса

 

   p_pid - личный идентификатор процесса;

   p_ppid - идентификатор процесса-предка;

   p_pgrp - идентификатор группы процесса;

   p_uid - реальный идентификатор владельца процесса;

   p_gid - реальный идентификатор группы владельца процесса;

   p_suid - эффективный идентификатор владельца процесса;

   p_sgid - эффективный идентификатор группы владельца процесса.

 

   + Поля диспечеризации процессов

 

   p_pri - приоритет процесса;

   p_cpu - системная составляющая приоритета процесса;

   p_nice - пользовательская составляющая приоритета процесса;

   p_time - время нахождения процесса в RAM или в области своппинга.

 

   + Поля внутренней синхронизации процессов

 

   p_stat - статус процесса;

   p_wchan - идентификатор события, которое ожидает процесс.

 

   + Поля сигнальной коммуникации процессов

 

   p_sig - поле регистрации номеров полученных сигналов;

   p_hold - поле регистрации сигналов с отложенной обработкой;

   p_cursig - номер текущего сигнала;

   p_ignore - маска номеров игнорируемых сигналов;

   p_catch - маска номеров перехваченных сигналов.

 

   + Поля адресации процесса в RAM

 

   p_as - адрес сегментной таблицы процесса;

   p_ubptbl - адрес контекста процесса.

 

   + Поля размеров сегментов процесса

 

   p_tsize - размер процедурного сегмента образа процесса;

   p_dsize - размер сегмента данных образа процесса;

   p_ssize - размер стека образа процесса.

 

   + Поля ссылок на дескрипторы других процессов

 

   p_link - ссылка на следующий элемент очереди процессов;

   p_flink - ссылка на первый элемент очереди процессов;

   p_blink - ссылка на последний элемент очереди процессов;

   p_next - ссылка на следующий элемент таблицы процессов;

   p_pptr - ссылка на дескриптор процесса-предка;

   p_cptr - ссылка на дескриптор младшего процесса-потомка.

 

Множество дескрипторов процессов объединяет таблица процессов. Таблица

процессов расположена в сегменте данных ядра по виртуальному адресу,

заданному системной переменной practive. Она содержит адрес первого

дескриптора в таблице процессов. Остальные дескрипторы в таблице

процессов связаны в линейный список ссылочными полями p_next структуры

struct proc. Списковая организация таблицы процессов позволяет гибко

манипулировать ее элементами без изменения их адресов, а именно, удалять

из таблицы дескриптор завершившегося процесса и добавлять в таблицу

дескриптор нового процесса, а также обеспечивает возможность просмотра

таблицы для поиска нужного элемента. Соответствие дескрипторов таблицы

процессов конкретным процессам обеспечивают поля p_pid, содержащие

идентификаторы процессов.

 

Постоянство адресов дескрипторов в таблице процессов используется для

реализации прямого доступа к любому из них через поле u_procp структуры

struct user контекста процесса. Для текущего процесса, контекст которого

отображен в адресное пространство ядра, поле u_procp доступно через

системную переменную "u". Например, доступ к полю приоритета p_pri

дескриптора текущего процесса может быть реализован такой конструкцией

на C-коде: u->u_procp->p_pri. Обратную связь с контекстом процесса

обеспечивает поле p_ubptbl дескриптора процесса. В более общем случае

дескриптор любого процесса может быть найден путем последовательного

просмотра таблицы процессов, начиная с адреса practive через ссылочные

поля p_next, с использованием в качестве ключа поиска идентификатора

процесса в поле p_pid.

 

Непосредственный доступ к полям структуры struct proc дескриптора

процесса возможен только в фазе "система". Oн может быть реализован

системными вызовами, такими как nice, getpid, getppid, getpgrp, getpuid,

getpeuid, getgid, getegid, а также ядерными функциями, которые реализуют

механизмы синхронизации, диспечеризации, управления памятью и иерархией

процессов. Информацию о текущем состоянии таблицы процессов позволяет

получить команда ps в длинном формате ( с ключом -l ).

 

Максимальный размер таблицы процессов задается при генерации OS UNIX.

Это означает, что при работе UNIX может быть выделено фиксированное

число дескрипторов, которое ограничивает число существующих процессов в

каждый момент времени. Когда предельный размер таблицы процессов будет

исчерпан, образование нового процесса не будет иметь успеха и станет

возможным только после завершения каких-либо процессов и освобождения

некоторого числа дескрипторов в таблице процессов, соответственно.

 

 

             Управление иерархией процессов

 

Множество процессов, существующих в каждый момент, образуют единую

иерархическую структуру, где они связаны отношениями потомок-предок.

Каждый новый процесс может быть образован (порожден) только одним из

существующих процессов, который становится его предком. По отношению к

предку порожденый процесс будет считаться потомком. Иного способа

образования новых процессов и иных отношений между процессами в OS UNIX

не предусмотрено. Следует отметить, что любой процесс-предок может иметь

более одного потомка.

 

При порождении нового процесса его образ строится путем копирования

образа предка в свободное постранство адресов RAM. Конкретно, копируются

стек и сегменты данных. Процедурный сегмент копируется, когда он не

является разделяемым сегментом, который уже загружен в RAM. Кроме того,

потомок наследует контекст предка. В частности, через контекст потомку

передаются таблицы файлов и сигналов, а также текущий каталог предка.

Таким образом, после порождения потомок является точной копией предка.

В этот момент они различаются только личными идентификаторами и

идентификаторами предков в таблице процессов. В дальнейшем тождество

предка и потомка нарушается за счет индивидуального изменения их

сегментов и контекстов.  Например, процесс-потомок может распределять

дополнительную память, расширяя сегмент данных, изменять содержание

сегмента данных путем обработки его переменных, модифицировать таблицу

открытых файлов в своем контексте, открывая и закрывая файлы,

переопределить обработку сигналов в таблице сигналов своего контекста.

 

Для порождения потомка процесс-предок использует системный вызов fork,

который возвращает идентификатор потомка предку и 0 - потомку. Такой

алгоритм возврата позволяет легко различать инструкции предка и потомка

в тексте программы процесса. При аварийном завершении fork возвращает

отрицательный код, например, когда превышено ограничение по общему числу

процессов или числу процессов одного пользователя. Следующий фрагмент

С-кода демонстрирует применение системного вызова fork для порождения

нового процесса.

 

      int pid;     /* идентификатор процесса-потомка */

 

        /* инструкции процесса-предка до развилки */

 

      ...........................................

 

      /* развилка процессов */

 

      switch(pid = fork()) {

        case -1: /* обработка ошибки порождения процесса */

               .........................................

               break;

        case  0: /* инструкции процесса-потомка */

               .........................................

               break;

        default: /* инструкции процесса-предка после развилки */

               ..........................................

               break;

      } /* switch */

 

При успешном завершении системного вызова fork процессы потомка и предка

равноправно существуют в системе. Они могут функционировать параллельно,

конкурируя за ресурсы на основе своих приоритетов, или выполнение предка

может быть приостановлено до завершения потомка системным вызовом wait.

Системный вызов wait возвращает предку идентификатор потомка, который

завершился первым после последнего обращения к wait. Если предок имеет

несколько потомком, то чтобы узнать о завершении каждого из них, нужно

реализовать соответствующее число системных вызовов wait с проверкой их

возвратов. Когда процесс не имеет потомков, wait возвращает код (-1).

Следующий пример С-кода иллюстрирует применение системного вызова wait

при ожидании завершения 2-х потомков.

 

      int pid1, pid2;   /* идентификаторы процессов-потомков */

      int status;       /* статус завершения процесса-потомка */

      int ret = 0;      /* текущий возврат системного вызова wait */

 

      /* инструкции порождения процессов-потомков */

 

      ..............................................

 

      /* цикл ожидания звершения потомков в процессе-предке */

 

      while((ret = wait(&status)) != (-1)) {

 

        /* обработка завершения 1-го потомка */

 

        if(ret == pid1)

          .....................................

 

        /* обработка завершения 2-го потомка */

 

        if(ret == pid2)

          .....................................

 

       } /* while */

 

Каждый процесс процесс может завершиться по сигналу, который генерирует

другой процесс, ядро или аппаратура, а также по собственной инициативе,

используя системный вызов exit. Системный вызов exit освобождает память,

динамически распределенную процессом, закрывает открытые процессом файлы

и осуществляет очистку буферов системного пула, которые использованны

для ввода-вывода в эти файлы. Статус завершения процесса может быть

передан exit как целочисленный аргумент и будет известен потомку, если

последний выполняет системный вызов wait.

 

Для анализа причины завершения потомка системный вызов wait обеспечивает

возврат слова статуса завершения потомка. Адрес слова статуса завершения

потомка передается в wait как параметр. Байт слова статуса с маской FF00

содержит пользовательский код завершения потомка, объявленный им через

аргумент системного вызова exit. Байт слова статуса с маской 00FF, если

он не равен 0, содержит системный код завершения потомка по сигналу. Его

младшие 7 бит сохраняют номер сигнала, по которому завершился потомок.

Ненулевой старший бит системного кода завершения индицирует образование

файла core, содержащего дамп памяти на момент завершения потомка.

 

Следует отметить, что в OS UNIX приняты определенные соглашения, которые

позволяют поддерживать иерархию процессов в нестандартных ситуациях. Две

такие ситуации являются наиболее распространеннными.

 

   + Процесс-предок завершается раньше своих потомков. В этом случае его

     потомки автоматически становятся потомками процесса инициализации

     init с идентификатором 1.

 

   + Процесс-потомок завершается раньше 1-го предполагаемого обращения

     предка к системному вызову wait. Чтобы такая ситуация не стала

     тупиковой из-за бесконечного ожидания предком завершения уже

     несуществующего потомка введено состояние промежуточного завершения

     или состояние "зомби". В состоянии "зомби" процесс не имеет образа

     в RAM, но информация о нем сохраняется в таблице процессов для

     корректной реализации предком системного вызова wait.

 

Дополнительно к рассмотренным средствам управления иерархией процессов,

OS UNIX предоставляет возможность замены программы выполнения процесса с

помощью системных вызовов execl, execv, execle и execve. При реализации

системных вызовов семейства exec образ процесса заменяется сегментами

специфицированного файла новой программы его выполнения. При этом новый

процесс не создается, управление новому образу передается в рамках

существующего процесса с тем же идентификатором. При этом контекст

процесса сохраняется без изменений, исключая таблицу сигналов, в которой

все установки нестандартной обработки сигналов будут игнорированы. При

корректном завершении любого системного вызова семейства exec возврат в

старый образ невозможен. Поэтому обычно они применяются в сочетании с

порождением промежуточного потомка системным вызовом fork, исключительно

для последующей замены программы его выполнения. Комбинация fork-exec

позволяет продолжить старую программу выполнения предка после завершения

потомка по новой программе, заданной системным вызовом семейства exec.

Следующий фрагмент С-кода демонстрирует наиболее распространенную схему

применения системного вызова execl из семейства exec для замены текущей

программы выполнение процесса на программу обработки команды ls в

длинном формате вывода (с ключем -l).

 

      int pid;        /* идентификатор процесса-потомка */

      int status = 0; /* статус завершения процесса-потомка */

 

      /* развилка процессов */

 

      switch(pid = fork()) {

        case -1:  /* ошибка порождения процесса-потомка */

                break;

        case  0:  /* замена программы выполнения потомка */

                execl("/bin/ls", "ls", "-l", (char *) 0);

                exit(1);

         default: /* ожидание предком завершения потомка */

                    while(wait(&status) != pid);

                break;

       } /* switch */

 

Рассмотренные средства управления иерархией процессов используются при

инициализации OS UNIX и для интерпретации команд в сеансах работы

пользователей в монопольном или многопользовательском режимах.

 

 

             Внутренняя синхронизация процессов

 

Внутренняя синхронизация процессов основана на использовании аппарата

событий для изменения состояний процессов в фазе "система". Состояние

процесса отражает поле p_stat структуры struct proc дескритора процесса.

Процесс может находиться в одном из следующих состояний в соответствии

со значением поля p_stat:

 

     SONPROC - процесс выполняется, т.е. его инструкции обрабатываются

             процессором;

     SRUN -    процесс готов к выполнению, но не обладает приоритетом,

             достаночным для использования ресурсов процессора;

     SSLEEP -  процесс ожидает некоторое событие, до наступления

               которого он не претендует на ресурсы процессора;

     SZOMB -   состояние промежуточного завершения, когда процесс не

             имеет образа в RAM, но сохраняет дескриптор в таблице

             процессов.

 

Взаимосвязь состояний процесса средствами внутренней синхронизации

иллюстрирует следующая схема.

 

      fork   ┌──────┐  switch in  ┌─────────┐   exit    ┌───────┐

    ───────->│      │────────────>│         │─────────->│       │

             │ SRUN │    pswtch   │ SONPROC │           │ SZOMB │

    ┌──────->│      │<───────────-│         │───────┐   │       │

    │        └──────┘  switch out └─────────┘       │   └───────┘

    │                                               │

    │                             ┌────────┐       │ sleep

    │           wakeup             │        │      │

    └──────────────────────────────│ SSLEEP │<──────┘

                                   │        │

                                   └────────┘

 

           Рис. Схема внутренней синхронизации процессов

 

В каждый момент времени только один процесс может являться текущим, т.е.

использовать ресурсы процессора и находиться в состоянии SONPROC. Другие

процессы, обработка которых не блокирована ожиданием событий, находятся

в состоянии SRUN. Их дескрипторы образуют очередь дипетчера dispq и

связаны в таблице процессов посредством ссылочных полей p_link структуры

struct proc. Механизм синхронизации обеспечивает переключение текущего

процесса в очередь диспетчера (switch out), когда он уступает процессор

другому процессу, который, соответственно, включается (switch in) на

выполнение. Переключение процессов между состояниями SRUN и SONPROC

обеспечивает системная функция pswtch на основе сравнения приоритетов.

Процесс остается текущим пока его приоритет выше, чем у любого процесса

в очереди диспетчера. Планирование приоритетов процессов, претендующих

на ресурсы процессора, обеспечивает механизм диспечеризации процессов.

 

Для управления переключением процессов ядро использует системные флаги

вытеснения runrun и kprunrun. Флаг runrun уведомляет системную функцию

pswtch, что она должна быть вызвана как только переключаемый процесс

перейдет в фазу "пользователь". Блокировка переключения в фазе "система"

необходима для гарантии целостности системных данных. Флаг kprunrun

позволяет реализовать переключение в фазе "система", когда код текущего

процесса достигает достигает некоторых разрешенных точек вытеснения в

адресном пространстве ядра.

 

Обычно при работе OS UNIX число "спящих" процессов в состоянии SSLEEP

превышает число готовых к выполнению процессов в состоянии SRUN. Это

объяняется наличием большого числа событий в реальной практике работы

OS UNIX. Наиболее распространенная причина погружения процесса в "сон"

связана с отработкой внешних прерываний от периферийных устройств. В

частности, выполнение операций ввода-вывода не может быть реализовано

параллельно с обработкой процессором кода программы процесса. Более

высокий уровень приоритета конроллеров периферийных устройств, чем у

процессора при выполнении программы процесса, заставляет последний

переключаться на обработку внешних прерываний, переводя текущий процесс

в состояние "сна" до завершения отработки прерывания. Поэтому увеличение

числа "спящих" процессов характерно при интенсивном обмене с периферией,

когда большое число процессов ожидает в состоянии SSLEEP наступления

событий, связанных с завершением операций ввода-вывода, чтобы продолжить

процессорную обработку своих данных. Кроме приоритетной обработки

внешних прерываний события могут быть вызваны программными причинами.

Например, выполнение процесса-предка может быть приостановлено системным

вызовом wait до завершения процесса-потомка. В другом случае процесс

"засыпает" при попытке чтения из пустого программного канала или при

записи в полный программный канал.

 

Рассмотренная содержательная трактовка события, как некоторой причины

блокировки использования процессора данным процессом, формализуется

следующим образом. Каждый тип ожидаемого события специфицируется

уникальным целочисленным идентификатором, который сохраняется в поле

p_wchan структуры struct proc дескриптора процесса, когда он находится

в состоянии SSLEEP. Более конкретно, идентификатор события есть адрес

определенной системной таблицы или элемента таблицы, связанной с

ожидаемым ресурсом. Например, если процесс-предок ожидает завершения

потомка, то идентификатор события, с которым будет связано его

пробуждение, есть адрес дескриптора процесса-потомка.

 

Для погружения процесса в состояние "сна" используется системная функция

sleep, которой в качестве аргументов передаются идентификатор события и

приоритет процесса после пробуждения. После выполнения функции sleep

поле состояния дескриптора текущего процесса принимает значение SSLEEP,

в поле p_wchan заносится идентификатор события, в поле приоритета p_pri

фиксируется величина приоритета после пробуждения, а сам дескриптор

включается в очередь дескрипторов "спящих" процессов sleepq. Очередь

"спящих" процессов sleepq имеет организацию, аналогичную очереди

диспетчера dispq. При погружении в "сон" текущего процесса процессор

переключается на обслуживание самого приоритетного процесса из очереди

диспетчера или обработку внешнего прерывания.

 

Для пробуждения процесса из состояния "сна", при наступления ожидаемого

события, выполняется системная функция wakeup, аргументом которой

является идентификатор объявленного события. При вызове функции wakeup

все дескрипторы очереди "спящих" процессов sleepq, у которых значение

поля p_wchan совпадает с аргументом функции wakeup, перемещаются в

очередь диспетчера dispq для готовых к выполнению процессов. Если

приоритет "разбуженного" процесса выше, чем у текущего, то он включается

на обслуживание процессором по алгоритму, рассмотренному выше. Если в

очереди "спящих" процессов нет процессов с идентификатором события,

объявленным функцией wakeup, то "пробуждения" процессов не происходит.

 

Последнее из состояний процесса, достижимых внутренней синхронизацией,

есть состояние промежуточного завершения текущего процесса - SZOMB

(состояние "зомби"). Состояние "зомби" имеет место, если процесс-потомок

завершается по системному вызову exit или по сигналу до планируемой

реализации системного вызова wait в процессе-предке. При этом образ

завершившегося процесса освобождает адресное пространство, но его

дескриптор временно сохраняется в таблице процессов, чтобы обеспечить

корректную обработку системного вызова wait в процессе-предке.

 

В заключении следует отметить, что рассмотренный механизм внутренней

синхронизации обслуживает процессы в фазе "система". Для синхронизации

процессов в фазе "пользователь" используется аппарат сигналов OS UNIX.

 

 

             Диспечеризация процессов

 

Диспечеризация процессов в OS UNIX реализует политику разделения времени

процессора среди всех процессов, функционирующих в системе, на основе их

приоритетов. Приоритеты процессов выражаются целыми числами. Их значения

сохраняются в поле p_pri структуры struct proc дескриптора процесса. Во

всех версиях OS UNIX, кроме System V Release 4, принято, что приоритет

процесса тем выше, чем меньше его численное значение (в SVR4 - наоборот).

Процесс выбирается для обработки процессором из очереди диспетчера dispq

на основе их сравнения приоритетов. Методы назначения приоритета

различны в фазе "система" и в фазе "пользователь".

 

В фазе "система" приоритет процесса задается системной функцией sleep,

которая вызывается для погружении процесса в состояние ожидания события

при внутренней синхронизации процессов. При "пробуждении" процесса после

наступления ожидаемого события системной функцией wakeup, процесс

сохраняет установленный приоритет, пока он находится в фазе "система".

Величина приоритета устанавливается в зависимости от события, ожидаемого

процессом при погружении в состояния "сна", по глобальным параметрам

диспетчера разделения времени как показано в следующей таблице.

 

            Таблица системных приоритетов

 

    ┌────────────┬───────────┬─────────────────────────────────┐

    │ Глобальный │ Системный │ Событие, ожидаемое              │

    │ параметр   │ приоритет │ процессом                       │

    ├────────────┼───────────┼─────────────────────────────────┤

    │  PSWP      │     0     │ Своппинг                        │

    │  PRIBIO    │    20     │ Блоко-ориенированный ввод-вывод │

    │  PZERO     │    25     │ Уровень пробуждения сигналом    │

    │  PPIPE     │    26     │ Пустой/полный программный канал │

    │  TTIPRI    │    28     │ Ввод с терминала                │

    │  TTOPRI    │    29     │ Вывод на терминал               │

    │  PWAIT     │    30     │ Вывод на терминал               │

    │  PSLEEP    │    39     │ Пауза в ожидании сигнала        │

    └────────────┴───────────┴─────────────────────────────────┘

 

Глобальные параметры, определяющие значения системных приоритетов,

задаются при генерации OS UNIX и подобраны таким образом, чтобы

поддерживать равновесие в режиме разделения времени процессора.

Глобальный параметр PZERO определяет границу между низкими и высокими

приоритетами в фазе "система". Системный приоритет со значением больше

PZERO считается низким, не превышающий PZERO - высоким. Процессы,

погруженные в состояние "сна" с высоким приоритетом, не могут быть

выведены из этого состояния каким-либо сигналом. Процессы в состоянии

"сна" с низким приоритетом могут быть "разбужены" сигналом. Текущий и

готовые к выполнению процессы сохраняют фиксированный приоритет, пока

они находятся в фазе "система". После перехода в фазу "пользователь"

приоритет процесса будет переопределен.

 

В фазе "пользователь" приоритет процесса имеет 2 составляющие -

пользовательскую и системную. Значения этих составляющих задают поля

дескриптора процесса p_nice и p_cpu, соответственно. Полное значение

приоритета в фазе "пользователь" устанавливается на основе определенной

комбинации его составляющих в полях p_nice и p_cpu.

 

Начальное значение пользовательской составляющей приоритета определяется

константой NZERO, обычно равной 20. Пользовательская составляющая может

быть изменена системным вызовом nice. Его аргумент определяет величину

модификации поля p_nice и может задаваться в пределах от 0 до NZERO для

непривилигированного процесса или в пределах от -NZERO до NZERO для

привилигированного процесса. Поскольку значение пользовательской

составляющей приоритета наследуется при порождении процесса, то

непривилигированный процесс может только уменьшить приоритет, полученный

от предка. Привилигированный потомок может как уменьшить, так и

увеличить свой приоритет относительно предка.

 

Начальное значение системной составляющей приоритета (p_cpu) в фазе

"пользователь" равно 0. Ее изменение зависит от времени использования

процессора, т.е. времени, пока процесс остается текущим, работая в фазе

"пользователь". Для формирования системной составляющей приоритета

используются прерывания от аппаратного таймера. При частоте сети питания

50 Гц прерывания от таймера генерируются 50 раз в секунду. Каждое

таймерное прерывание увеличивает значение поля p_cpu, т.е. уменьшает

системную составляющую приоритета на 1.

 

Результирующий приоритет процесса в фазе "пользователь" определяется по

формуле:

 

          p_pri = (p_nice - NZERO) + (p_cpu/16) + PUSER

 

Разность (p_nice - NZERO) учитывает модификацию пользовательской

составляющей приоритета системным вызовом nice. Отношение (p_cpu/16)

учитывает усредненное значение системной составляющей приоритета.

Усреднение необходимо, так как неизвестно, какую часть таймерного

интервала проработал процесс на момент очередного прерывания от таймера.

Кроме того, нежелательно частое переключение процессора между процессами

с близкими значениями приоритетов. В частности, для равноприоритетных

процессов в фазе "пользователь" выбранная величина усреднения (1/16)

гарантирует переключение процессора не ранее, чем через 16 таймерных

интервалов (320 мс), когда отношение (p_cpu/16) увеличится на 1.

Константа PUSER, по умолчанию равная 50, вводится для принудительного

увеличения приоритета в фазе "пользователь" по отношению к приоритету

в фазе "система". Это сделано с целью стимулировать переключение

процессора на выполнение процессов в фазе "система", в частности, для

ускорения доступа "разбуженных" процессов к ресурсам процессора.

 

Для вычисления приоритета используется системная функция setpri, которой

в качестве аргумента передается дескриптор процесса. Приоритет процесса,

вычисленный функцией setpri, сравнивается с приоритетом текущего

процесса, который сохраняет системная переменная curpri. Если приоритет

какого-либо процесса из очереди диспетчера выше приоритета текущего,

реализуется переключение процессора на более приоритетный процесс по

схеме внутренней синхронизации процессов.

 

 

             Своппинг и пейджинг процессов

 

Ограниченный объем RAM в общем случае не позволяет разместить в ней

множество образов существующих процессов. Поэтому образы пассивных

процессов, для размещения которых нет места в RAM, располагаются в

специально отведенной области внешней памяти, называемой областью

своппинга. Образы активных процессов располагаются в RAM. Перекачку

(своппинг) образов процессов между оперативной и внешней памятью,

обеспечивает диспетчерский процесс (планировщик своппинга) swapper (или

sched для SUN OS) с идентификатором 0. Он осуществляет загрузку в RAM

образов активных процессов и выгрузку в область своппинга образов

пассивных процессов. Своппинг происходит при практической реализации

механизмов синхронизации, диспечеризации и управлении иерархией

процессов. Например, когда недостаточно места для размещения в RAM

образа порожденного процесса-потомка, образ его предка может быть

выгружен (откачен) в область своппинга, чтобы освободить место под образ

потомка. В другом случае, образ "разбуженного" процесса с высоким

приоритетом может быть загружен (подкачен) из области своппинга в RAM

для реализации процессорной обработки. В более общем случае, образ

любого активизированного процесса может быть загружен (подкачен) из

области своппинга в RAM при наличии необходимости и свободной памяти,

также как образ любого пассивного процесса может выгружен (откачен) из

RAM в область своппинга, чтобы освободить ресурсы памяти для более

приоритетного процесса.

 

Диспетчерский процесс swapper, реализуя механизм своппинга должен решать

2 задачи по определению процессов-претендентов для откачки из и подкачки

в RAM. Для решения этих задач диспетчерский процесс, не имея фазы

"пользователь", осуществляет бесконечный цикл в фазе "система". Цикл

начинается с просмотра таблицы процессов для поиска процесса -

претендента на загрузку или расширение своего образа в RAM. Если

претендентов нет, диспетчер переходит в состояние ожидания их появления,

используя системную функцию sleep, которая назначает ему наивысший

приоритет PSWP после пробуждения. Диспетчер "пробуждается" системной

функцией wakeup при появлении претендента на загрузку в RAM.

 

Если в RAM недостаточно свободного места для подкачки образа процесса -

претендента на загрузку, диспетчер выгружает (откачивает) в область

своппинга необходимое число пассивных процессов. Кандидаты на откачку

определяются прежде всего среди "спящих" процессов с низким приоритетом

(ниже PZERO), которые ожидают завершения сравнительно медленных операций

ввода-вывода. Если таких процессов несколько, выбирается процесс с

максимальным образом в RAM.

 

Чтобы обеспечить однозначность выбора процесса для загрузки в RAM или

для выгрузки в область своппинга при прочих равных условия применяется

временной критерий оценки. С этой целью в поле p_time структуры

struct proc дескриптора любого процесса фиксируется время непрерывного

пребывания в RAM или в области своппинга, соответственно тому, где

находится образ процесса. Для своппинга выбирается процесс с

максимальным значением в поле p_time.

 

В современных версиях OS UNIX применяется альтернативный своппингу

механизм, который использует страничную организацию памяти и называется

пейджинг (paging). Пейджинг реализует перемещение активных страниц

образов процессов из области своппинга в RAM, называемое подкачкой

страниц по требованию, а также обратное перемещение пассивных страниц из

RAM в область своппинга, называемое изъятием страниц. Схема реализации

пейджинга подобна своппингу, за исключением того, что перемещению между

RAM и областью своппинга подвергается не целиком образ процесса, а его

отдельные страницы. При этом в RAM должны присутствовать только активные

страницы образа процесса, к которым происходит частое обращение.

Пассивные  страницы, к которым нет обращений, могут быть откачены в

область своппинга. Таким образом, при пейджинге загрузка в RAM полного

образа процесса не является обязательной. Последнее обстоятельство

обуславливает ряд преимуществ пейджинговой схемы по сравнению с

традиционным механизмом своппинга, из которых наиболее существенными

являются следующие.

 

    + Отсутствие ограничений на размер образа процесса по объему

      физической памяти. Связано с тем, что в RAM размещаются только

      активные страницы процесса, а не полные сегменты его образа.

 

    + Ускоренный старт процесса. Становится возможным, поскольку

      загрузка сравнительно небольшого числа активных страниц может

      быть реализована быстрее, чем загрузка всех сегментов процесса.

 

    + Экономия оперативной памяти. Поскольку для работы процесса

      необходима только загрузка активных страниц, память не

      расходуется на хранение редко используемых кодов и данных.

 

    + Увеличение производительности. Становится ощутимым при

      параллельном выполнении больших процессов, которые часто

      обращаются к небольшому подмножеству своих активных страниц.

 

Недостатком схемы пейджинга являются накладные расходы по обработке

так называемых отказов страниц, которые связаны с поиском нужной

страницы, если она не загружена в RAM. Кроме того, обработка отказов

страниц требует реализации операций ввода-вывода, прерывающих работу

процессора, для подкачки нужной страницы в RAM из области своппинга.

Поэтому частые отказы страниц увеличивают время выполнения процесса.

В идеальном случае процесс работает с небольшим числом активных страниц,

резидентных в RAM, когда отказы страниц с последующей подкачкой нужных

страниц по требованию происходят редко.

 

Для размещения страниц в RAM при подкачке их по требованию используется

пул свободных страниц. Определенный уровень наличия свободных страниц

поддерживает процедура изъятия страниц, которая откачивает содержимое

пассивных страниц в область своппинга и возвращает изъятые страницы в

пул свободных страниц. Сохранение содержимого изъятой страницы в области

своппинга происходит, если содержание страницы было модифицировано.

 

Процедуры подкачки страниц по требованию и изъятия страниц являются

основными в механизме пейджинга. Их реализует следящий процесс pageout

(pagedaemon в SUN OS), называемый демоном страниц. Также как и

диспетчерский процесс swapper, управляющий своппингом, демон страниц это

один из начальных процессов OS UNIX. Он имеет идентификатор 2. Демон

страниц следит за возрастом страниц и определяет какие пассивные

страницы следует отобрать у процессов, чтобы вернуть их в пул свободных

страниц. Он также обеспечивает подкачку страниц по требованию в случае

отказов страниц. Демон страниц активизируется, когда начинает ощущаться

недостаток свободной памяти или для обработки отказов страниц.

 

В общем случае демон страниц успешно решает все проблемы по управлению

памятью с помощью своих базовых процедур подкачки по требованию и

изъятия страниц. Однако, могут возникнуть критические ситуации, когда

демон страниц не может предоставить необходимое число свободных страниц

для удовлетворения запросов процессов к RAM и уровень свободной памяти

опускается ниже определенного предела, заданнного системным параметром

GPGSLO. В этом случае инициатива передается диспетчерскому процессу,

который планирует и реализует своппинг целых процессов. Своппинг

подразумевает возвращение в пул свободных страниц всех страниц образа

процесса, независимо от их возраста. Реализация своппинга продолжается,

пока не восстановлен допустимый уровень свободной памяти. После этого

инициатива по управлению памятью возвращается демону страниц.

 

 

ВЗАИМОДЕЙСТВИЕ ПРОЦЕССОВ В OS UNIX

 

Средства взаимодействия процессов

 

Поскольку OS UNIX является многозадачной ОС, то параллельное выполнение

нескольких процессов является обычной практикой ее функционирования. При

этом актуальной является проблема управления взаимодействием процессов.

Для управления взаимодействием процессов в фазе "пользователь" OS UNIX

предоставляет средства внешней синхронизации и коммуникации процессов.

 

Чтобы обеспечить возможность внешней синхронизации процессов в фазе

"пользователь" OS UNIX имитирует для каждого процесса систему

программных прерываний, называемых сигналами. Сигнальный механизм

позволяет процессу реагировать на различные события, моменты появления

которых не могут быть заранее предусмотрены. Обработка сигналов

позволяет реализовать популярную в настоящее время методологию

событийного программирования.

 

Под коммуникацией процессов в OS UNIX понимается реализация обмена

данными между параллельно выполняющимися процессами. Наиболее очевидным

способом такого взаимодействия процессов является обмен данными через

файл, когда два или более процессов записывают данные в некоторый файл,

выбранный по договоренности, другие процессы читают данные из этого

файла. Для коммуникации процессов через файл характерны следующие

проблемы.

 

+ Ограничения по объему файла обмена.

 

+ Ограничение прав доступа к файлу обмена.

 

+ Отсутствие синхронизации при обмене.

 

+ Возможность искажения или потери данных через в файле

  независимо от процессов, которые используют его для обмена.

 

+ Время существования файла обмена не зависит от времени жизни

  использующих его процессов, поэтому данные в файле продолжают

  сохраняться и тогда, когда их никто не использует.

 

Всилу необходимости решения указанных проблем, коммуникация процессов

через файл применяется редко. С другой стороны, OS UNIX предоставляет

более совершенные средства межпроцессной коммуникации, обычно именуемые

IPC (Inter Process Communication). Средства IPC свободны от указанных

выше недостатков межпроцессного обмена через файл. Традиционно, к

средствам IPC в OS UNIX относятся: именованные и неименованные (обычные)

программные каналы, разделяемая память, семафоры, очереди сообщений.

 

 

             Обработка сигналов

 

Сигналы генерируется, когда происходят определенные события, которые

вызывают посылку сигнала. Сигнальные события могут быть вызвананы

программными и аппаратными причинами, прерыванием от терминала,

результатом измерения времени и выполнением других процессов. Если

инициатор сигнала ядро, другой процесс или интерактивные действия

пользователя, то сигнал считается асинхронным по отношению к

процессу-приемнику сигнала. Когда сигнал инициируется выполнением

инструкций процесса-приемника, сигнал считается синхронным. В обоих

случаях получение сигнала вызывает прерывание работы процесса-приемника

сигнала и выполнение заранее определенных действий по его обработке.

 

Каждый сигнал имеет уникальный номер, однозначно определяющий событие,

которым он вызван. Классические версии OS UNIX определяли 16 сигналов

с номерами от 1 до 16. В современных версиях OS UNIX список сигналов

расширен до 32. Для удобства спецификации сигналов используются

символьные мнемоники. Ниже перечислены мнемоники основных сигналов,

с кратким описанием событий, инициирующих их появление.

 

   Асинхронные терминальные сигналы

 

   1. SIGHUP - разрыв связи с терминалом;

   2. SIGINT - прерывание от терминала, генерируется при нажатии Ctrl-C;

   3. SIGQUIT - сигнал выхода, генерируется при нажатии Ctrl-\.

 

   Синхронные сигналы ловушки

 

   4. SIGILL - попытка выполнить нелегальную машинную инструкцию;

   5. SIGFPE - десятичное переполнение или деление на 0;

   6. SIGBUS - ошибка шины при неверной косвенной адресации;

  11. SIGSEGV - нарушение границ сегментов адресного пространства;

  12. SIGSYS - неверный параметр при обращении к системному вызову;

  13. SIGPIPE - разрыв программного канала.

 

   Сигнал от таймера реального времени

 

  14. SIGARM - генерируется ядром при истечении интервала, заданного

               системным вызовом alarm.

 

   Сигналы от событий в других процессах или системе

 

    9. SIGKILL - безусловное завершение процесса;

   15. SIGTERM - условное завершение процесса;

   16. SIGUSR1 - сигнал, определенный пользователем;

   17. SIGUSR2 - сигнал, определенный пользователем;

   18. SIGCLD -  изменение статуса процесса-потомка;

   19. SIGPWR -  рестарт по сбою питания;

   23. SIGSTOP - приостановка выполнения процесса;

   24. SIGCONT - возобновление выполнения приостановленного процесса.

 

Возможность принудительной посылки конкретному процессу или группе

процессов определенного сигнала даже при отсутствии реальной причины

его возникновения обеспечивают системный вызов и команда kill. Их

аргументы задают номер сигнала и идентификатор процесса-приемника

сигнала. Значение идентификатора процесса-приемника должно быть больше

0, когда сигнал посылается процессу индивидуально. При распределенной

посылке сигнала аргумент идентификации приемника может быть равен 0,

когда сигнал посылается группе процессов, равен (-1), когда сигнал

посылается всем процессам владельца процесса-источника сигнала, либо

меньше (-1), когда сигнал посылается всем процессам группы владельца

процесса, инициатора сигнала.

 

Для организации обработки сигналов используются следующие сигнальные

поля структуры struct proc дескриптора процесса-приемника сигнала:

p_sig, p_hold, p_cursig, p_ignore и p_catch.

 

Поле p_sig используется для регистрации номера принятого сигнала. При

этом применяется следующий способ регистрации. Если принят сигнал с

номером n, то устанавливается бит (n-1) поля p_sig, который будет

сброшен после обработки сигнала. Например, значения поля p_sig, равное

5, означает регистрацию сигналов SIGHUP и SIGQUIT с номерами 1 и 3,

соответственно. Следует отметить, что OS UNIX не поддерживает очередь

сигналов. Поэтому, если процесс принял последовательно несколько

одинаковых сигналов до начала обработки 1-го из них то будет обработан

только результат последнего приема. Очевидно, что разнотипные сигналы

могут быть одновременно зарегистрированы в поле p_sig и обработаны в

порядке их поступления. Поле p_cursig сохраняет номер текущего сигнала,

который должен быть обработан.

 

Поле p_hold фиксирует номера сигналов, обработка которых должна быть

задержена на период выполнения какого-либо блока инструкций процедурного

сегмента программы процесса. Блок программы процесса, когда появление

определенных сигналов нежелательно должен быть ограничен системными

вызовами sighold и sigrelse, которые, соответственно, задерживают и

освобождают обработку указанных сигналов.

 

Поля p_ignore и p_catch определяют форму реакции процесса на получение

сигнала. После получения сигнала возможны 3 альтернативы реакции

процесса - обработка по умолчанию, игнорирование и перехват. Вид реакции

устанавливает системный вызов signal, которому в качестве аргументов

передается номер сигнала и адрес функции обработки.

 

Когда нужно установить стандартную обработку, принятую по умолчанию, в

системный вызов signal вместо адреса функции обработки передается 0 или

макрос SIG_DFL. Стандартной реакцией процесса на большинство сигналов

является принудительное завершение процесса. Реакция по умолчанию на

такие сигналы как SIGQUIT, SIGFPE, SIGSEGV, SIGBUS, SIGSYS приводит к

принудительному завершению процесса с образованием файла дампа памяти.

 

Когда в процессе нужно игнорировать сигнал, в системный вызов signal

вместо адреса функции обработки передается 1 или макрос SIG_IGN. Почти

все сигналы могут быть принудительно игнорированы. По умолчанию

игнорируются сигналы, которые не должны приводить к завершению процесса,

например, SIGCLD, SIGUSR1, SIGUSR2.

 

Под перехватом сигнала понимается передача в системный вызов signal,

адреса функции, которая реализует желаемую реакцию процесса на данный

сигнал. Следует отметить, что после перехвата сигнала автоматически

восстанавливается стандартная реакция на него. Поэтому целесообразно

переустанавливать перехват сигнала системным вызовом signal, когда

нужно сохранить его нестандартную обработку. Когда необходимость в

перехвате сигнала исчерпана следует восстановить обработку по умолчанию,

используя системный вызов signal с аргументом 0 или SIG_DFL. Следует

отметить, что сигнал безусловного завершения SIGKILL с номером 9 не

может быть игнорирован или перехвачен никаким процессом. Следующий

фрагмент С-кода демонстрирует перехват сигнала прерывания от терминала.

 

    #include <signal.h>

 

    /* Функция обработки сигнала SIGINT */

 

    void handler() {

      signal(SIGINT,handler);

      puts("Сигнал SIGINT перехвачен");

      return;

    } /* handler */

 

    /* Основная функция программы процесса */

 

    main() {

 

      /* Установка перехвата сигнала SIGINT */

 

      signal(SIGINT,handler);

 

      /* Код программы процесса, где перехвачен сигнал SIGINT */

 

      .........................................................

 

      /* Восстановление стандартной обработки сигнала SIGINT */

 

      signal(SIGINT,SIG_DFL);

 

     } /* main */

 

Установленная реакция процесса на сигналы отражается контекстом процесса

в форме таблице сигналов u_signal. Записи этой таблицы упорядочены по

номерам сигналов в виде массива адресов их функций обработки. Формально

каждый элемент таблицы сигналов может принимать одно из 3-х значений

в соответствии с выбранной альтернативой обработки:

 

     u_signal[i] = 0 - принята обработка по умолчанию сигнала i+1;

     u_signal[i] = 1 - установлено игнорирование сигнала i+1;

     u_signal[i] = n - адрес функции обработки в адресном пространстве

                   процесса при перехвате сигнала i+1.

 

Порожденный процесс наследует весь контекст обработки сигналов от

процесса-предка. После смены программы выполнения процесса системным

вызовом семейства exec, установленная нестандартная обработка сигналов

будет игнорирована. Если сигнал был принят во время реализации

системного вызова, то выполнение последнего будет прервано для обработки

сигнала. После завершения обработки сигнала прерванный системный вызов

перезапускается автоматически. В заключении следует отметить, что

механизм обработки сигналов в OS UNIX позволяет реализовать популярную

в настоящее время методологию событийного программирования.

 

 

             Программные каналы

 

Программный канал есть средство коммуникации процессов, которое можно

рассматривать как программно реализованную линию связи для передачи

данных между двумя или более процессами. Каналы реализованы в виде

снабженных в виде снабженных специальным механизмом управления буферов

ввода-вывода, которые трактуются как псевдо-файлы. Формальное сходство

с файлами позволяет использовать одинаковый аппарат для доступа к

данным в канале и файле. Такой подход соответствует базовой концепции

унификации ввода-вывода в OS UNIX. При этом каналы имеют специфические

особенности, которые позволяют исключить недостатки организации

межпроцессного обмена через файлы. Основные особенности каналов

перечислены ниже.

 

Каналы принято использовать следующим образом. Один процесс направляет

(записывает) данные в канал. Другой процесс получает (читает) данные из

канала. Следующий рисунок иллюстрирует этот простейший канальный вариант

взаимодействия процессов.

 

     ┌───────────┐                            ┌───────────┐

     │           │  write   ┌───────┐  read   │           │

     │ Процесс-1 │ ──────-> │ Канал │ ─────-> │ Процесс-2 │

     │           │          └───────┘         │           │

     └───────────┘                            └───────────┘

 

        Рис. Одноканальная схема обмена 2-х процессов

 

Следует отметить, что в OS UNIX нет принципиальных припятствий для

организации обмена нескольких процессов через один или более каналов.

Из одноканальных схем наиболее целесообразной в практическом плане

является схема с одним читающим процессом-сервером и несколькими

пишущими процессами-клиентами, которая показана на следующем рисунке.

 

                           ┌───────┐

     ┌──────────┐  write   │       │

     │ Клиент-1 │ ──────-> │       │         ┌────────┐

     └──────────┘          │ Общий │  read   │        │

       ........     ....   │       │ ─────-> │ Сервер │

     ┌──────────┐          │ канал │         │        │

     │ Клиент-n │ ──────-> │       │         └────────┘

     └──────────┘  write   │       │

                     └───────┘

 

        Рис. Одноканальная схема обмена клиент-сервер

 

Обратная одноканальная схема с одним пишущим и несколькими читающими

процессами применима, когда безразличен выбор читающего процесса для

обработки текущих данных, получаемых из канала. Применение канального

обмена в рамках одного процесса обычно является бессмысленным.

 

В большинстве версий OS UNIX канал является однонаправленным механизмом,

т.е. поддерживает передачу данных только в одном направлении. Чтобы

реализовать обмен данными в двух направлениях нужно предусмотреть 2

разнонаправленных канала. Вариант 2-х канального обмена 2-х процессов

показан на следующем рисунке.

 

     ┌───────────┐                               ┌───────────┐

     │           │  write   ┌─────────┐  read    │           │

     │           │ ──────-> │ Канал-1 │ ──────-> │           │

     │           │          └─────────┘          │           │

     │ Процесс-1 │                               │ Процесс-2 │

     │           │   read   ┌─────────┐   write  │           │

     │           │ <─────── │ Канал-2 │ <─────── │           │

     │           │          └─────────┘          │           │

     └───────────┘                               └───────────┘

 

       Рис. Двухканальный двунаправленный обмен 2-х процессов

 

Доступ к данным при любом варианте канального обмена осуществляется

через канальные дескрипторы чтения и записи. Канальные дескрипторы

концептуально эквивалентны пользовательским дескрипторам открытых

файлов в контексте процесса и связаны со входами канала по чтению и

записи. Канал открыт для обмена, пока существует связанная с ним пара

канальных дескрипторов. Через них канальный механизм осуществляет

контроль за смещение указателей чтения-записи, положение которых

определяет в какой блок канала можно записать данные и из какого блока

они должны быть прочитаны.

 

Через канал может быть передан неограниченный объем информации, хотя

мгновенная емкость канала ограничена 10-ю блоками. Ситуации переполнения

канала при записи и чтение пустого канала автоматически блокируются

скрытым механизмом синхронизации обмена. Он обеспечивает приостановку

процесса записи, когда в канале нет места для размещения новых данных,

или процесса чтения, при попытке ввода из пустого канала, который пока

открыт по записи.

 

Формальной моделью канала является кольцевая очередь с дисциплиной

обслуживания FIFO. Состояние канальной очереди определяется указателями

чтения и записи, которые доступны через дескрипторы канала. Смещение

этих указателей определяет, куда следует записать поступившие данные и

откуда они могут быть прочитаны. Следующий рисунок иллюстрирует одно

из возможных состояний канальной очереди.

 

                   Прочитанные блоки

             ┌─────────┬─────────┬─────────┐

             │         │         │         │

             │         │         │         │

             │         │         │         │

             ├─────────┼─────────┼─────────┤

             │         │         │         │

             │         │         │---------│<- Указатель

             │         │         │$$$$$$$$$│    чтения

             ├─────────┤         ├─────────┤

             │         │         │$$$$$$$$$│

 Указатель ->│---------│         │$$$$$$$$$│

   записи    │$$$$$$$$$│         │$$$$$$$$$│

             ├─────────┼─────────┼─────────┤

             │$$$$$$$$$│$$$$$$$$$│$$$$$$$$$│

             │$$$$$$$$$│$$$$$$$$$│$$$$$$$$$│

             │$$$$$$$$$│$$$$$$$$$│$$$$$$$$$│

             └─────────┴─────────┴─────────┘

                 Непрочитанные блоки

 

                Рис. Формальная модель канала

 

OS UNIX поддерживает 2 типа каналов: неименованные (или обычные) и

именованные (или FIFO-файлы). Неименованные каналы поддерживают обмен

данными только между процессами-родственниками, например, между предком

и потомком или между потомками одного предка. Именованные каналы могут

быть использованы для коммуникации любых, необязательно родственных,

процессов. Каналы указанных типов создаются и открываются по-разному,

но используются одинаково.

 

Неименованный канал создается и открывается системным вызовом pipe,

которому в качестве аргументов передается адрес массива 2-х целых чисел.

При успешном завершении системный вызов pipe заполняет адресуемый массив

канальными дескрипторами чтения и записи. Эту инициализацию обычно

выполняет процесс-предок, когда предполагается использовать канальный

механизм для обмена с потомками или между потомками.

 

Полученные канальные дескрипторы будут наследоваться всеми потомками,

которые порождены данным предком после реализации системного вызова pipe

вместе с таблицей открытых файлов контекста предка. Потомки и предок

могут освободить (закрыть) в своих контекстах тот канальный дескриптор,

который не будет использован для обмена. Если обычный канал создается

только для коммуникации потомков и не предполагается реализация их

обмена с предком, последний может освободить оба канальных дескриптора

в своем контексте. Во всех перечисленных случаях нужно использовать

системный вызов close, чтобы освободить лишние канальные дескрипторы.

 

В отличие от обычного канала, каждому именованному каналу должен

соответсвовать оригинальный по маршрутному имени канальный файл, который

может быть расположен в произвольном каталоге файловой системы OS UNIX.

Для создания канального файла в прикладной программе можно использовать

системные вызовы mkfifo или mknod. Системный вызов mkfifo ориентирован

исключительно на создание FIFO-файлов. Системный вызов mknod может быть

использован для создания новых файлов любых типов. Аналогично, в

командном режиме именованный канал может быть создан специальной

командой /etc/mkfifo или универсальной командой /etc/mknod.

 

Для начала работы с именованным каналом он должен быть открыт во всех

процессах, которые будут использовать его как средство обмена. Чтобы

открыть именованный канал, каждый заинтересованный в обмене процесс

должен использовать системный вызов open, которому передается имя

канального файла и желаемый режим обмена - чтение или запись. Системный

вызов open является универсальным средством открытия любых файлов, но

для FIFO-файлов его реализация имеет свои особенности. В частности,

когда именованный канал открывается по чтению (записи), системный вызов

open переводит реализующий его процесс в состояние ожидания, пока канал

не будет открыт по записи (чтению) каким-либо другим процессом. При

успешном завершении системный вызов open возвращает канальный дескриптор

чтения или записи, который может быть использован для работы с каналом в

контексте данного процесса и его потенциальных потомков.

 

Обмен данными через обычный и именованный каналы реализован одинаково

через системные вызовы read и write. Для работы с каналом им передаются

соответствующий канальный дескриптор чтения или записи, адрес и размер

объекта обмена. Также как системный вызов open, системные вызовы read и

write являются универсальным средством ввода-вывода данных через

заданный дескриптор в контексте процесса, которое с равным успехом

применимо для работы с каналом, каталогом, обычным и специальным файлом.

Однако, канальная реализация этих универсальных системных вызовов

ввода-вывода имеет специфические особенности.

 

При канальном обмене системный вызов read применяется для ввода данных

из канала. Он возвращает реальное число прочитанных данных или 0, когда

канал пуст и закрыт по записи всеми пишущими процессами. Попытка чтения

из пустого канала, не закрытого по записи вызывает блокировку читающего

процесса. Эта ситуация возникает, когда процесс чтения данных из канала

опережает процесс записи данных в него. Чтение данных из канала является

деструктивным, т.е. прочитанные данные не могут быть перепрочитаны

вновь.

 

При канальном обмене системный вызов write применяется для вывода данных

в канал. Он возвращает реальное число записанных данных. Попытка записи

в полный канал вызывает блокировку пишущего процесса. Такая ситуация

возникает, когда процесс записи данных в канал опережает процесс чтения

данных из него. Если канал закрыт всеми читающими процессами, то пишущий

процесс получает сигнал SIGPIPE при попытке записи данных в канал.

 

Блокировка чтения пустого и записи полного канала может быть исключена

переводом канала в режим неблокированного ввода-вывода путем установки

режимных флагов O_NDELAY или O_NONBLOCK с помощью системного вызова

fcntl. Установка режимных флагов обеспечивает немедленный возврат кода 0

или (-1) системными вызовами ввода-вывода read и write при попытке чтения

пустого или записи в полный канал. Для именованного канала режим

неблокированного ввода-вывода может быть изначально заказан при открытии

канала системным вызовом open.

 

Для корректного завершения работы с каналом любого типа нужно закрыть

канал, освободив все канальные дескрипторы во всех процессах, которые

использовали канал для обмена. Как указано выше, следует применять

системный вызов close, для освобождения канальных дескрипторов в

контексте всех процессов, использовавших канал для обмена. Время жизни

обычного канала определяет период существования его канальных

дескрипторов в контекстах взаимодействующих процессов. Очевидно, что

обычный канал не может существовать после завершения процессов, которые

использовали его для обмена. В отличие от обычных каналов, именованный

канальный файл сохраняется независимо от существования использующих его

процессов, также как файл любого другого типа. Однако, в отличие от

обычных файлов данные в именованном канале не будут сохранены после

завершения всех процессов, которые использовали его для обмена. Длина

канального файла будет равна нулю. Канальный FIFO-файл нулевой длины

будет присутствовать в файловой системе, пока он не удален командой rm

или системным вызовом unlink.

 

Следующий фрагмент C-кода демонстрирует передачу содержимого текстовой

строки между двумя процессами через существующий именованный канал

chanel. Первый процесс выполняет программу writer, обеспечивая запись

строки в канал.

 

    /* Программа процесса записи строки в канал chanel */

 

    main() {

    static char *str = "Hello"; /* передаваемая строка */

    int fd;                     /* дескриптор канала */

 

    fd = open("chanel",1);      /* открыть канал по записи */

    write(fd,str,strlen(str));  /* записать данные в канал */

    close(fd);                  /* освободить дескриптор канала */

    exit(0);                    /* завершить процесс writer */

    } /* main writer */

 

Второй процесс, выполняя программу reader, обеспечивает чтение данных

из каналального файла chanel и распечатывает их на стандартный вывод.

 

   /* Программа процесса чтения данных из канала chanel */

 

   main() {

   char c;                   /* принимаемый символ */

   int fd;                   /* канальный дескриптор */

 

   fd = open("chanel",0);    /* открыть канал по чтению */

   while(read(fd,&c,1) > 0)  /* чтение данных из канала */

     write(1,&c,1);          /* запись данных в канал */

   close(fd);                /* освободить дескриптор канала */

   exit(0);                  /* завершить процесс reader */

   } /* main reader */

 

Обмен данными через именованный канал chanel может быть реализован

путем запуска процессов writer и reader с отдельных экранов, причем

последовательность запуска значения не имеет.

 

В практике работы с OS UNIX обычные каналы часто используются для

организации конвейера команд. Конвейер образует цепочка параллельных

процессов, реализующая последовательную обработку данных. Процессы

выполнения соседних команд конвейера взаимодействуют через обычный

канал. Для обозначения каналов в конвейере используется символ '|',

который понимает командный процессор при разборе командной строки.

Стандартный вывод каждого конвейерного процесса, кроме последнего в

цепочке команд, перенаправляется на вывод в канал. Стандартный ввод

каждого конвейерного процесса, кроме первого в цепочке команд,

перенаправляется на ввод из канала. Например, следующий конвейер

обеспечивает постраничный просмотр содержания текущего каталога в

длинном формате:

 

             ls -l | more

 

Для программной реализации перенаправления ввода-вывода при конвейерной

обработке могут быть использованы системные вызовы dup, dup2 и fcntl

( режим F_DUPFD ) в сочетании с системным вызовом close. Эти средства

позволяют ассоциировать заданный дескриптор канала с минимальным по

номеру свободным элементом таблицы открытых файлов контекста процесса.

Например, следующий фрагмент C-кода освобождает файловый дескриптор

стандартного вывода ( по умолчанию равный 1 ) и делает его синонимом

дескриптор fd, который соответствует ранее открытому файлу или каналу.

 

       close(1);      /* освобождает дескриптор стандартного вывода */

       dup(fd);       /* дублирует дескриптор fd */

       close(fd);     /* освобождает дубликат дескриптора fd */

 

Аналогичная последовательность действий выполняется для перенаправления

стандартного ввода в канал или файл. Следующий, более представительный,

фрагмент C-кода моделирует интерпретацию приведенного выше конвейера

командным процессором Shell с помощью обычного канала и рассмотренных

средств перенаправления ввода-вывода.

 

      /* Модель конвейера команд:   ls -l | more */

 

     main() {

     int fd[2];     /* массив канальных дескрипторов */

 

     pipe(fd);      /* инициализация канальных дескрипторов */

 

     /* Создание 1-го процесса потомка */

 

     if(fork() == 0) {

       close(1);                       /* Стандартный вывод     */

       dup(fd[1]);                     /* перенаправляется      */

       close(fd[0]);                   /* на вывод в канал      */

       close(fd[1]);                   /* по дескриптору fd[1]. */

       execl("/bin/ls","ls","-l",0);   /* замена программы 1-го */

       exit(1);                        /* потомка               */

     } /* if */

 

     /* Создание 2-го процесса потомка */

 

     if(fork() == 0) {

       close(0);                       /* Стандартный ввод      */

       dup(fd[0]);                     /* перенаправляется      */

       close(fd[0]);                   /* на ввод из канала     */

       close(fd[1]);                   /* по дескриптору fd[0]. */

       execl("/bin/more","more",0);    /* Замена программы 2-го */

       exit(1);                        /* потомка               */

     } /* if */

 

     /* Закрытие канала в процессе-предке */

 

     close(fd[0]);

     close(fd[1]);

 

     /* Ожидание завершения потомков */

 

     while(wait(0) != (-1));

     exit(0);

 

     } /* main */

 

 

 

 

ИЕРАРХИЯ ФАЙЛОВЫХ СТРУКТУР

 

Разновидности файлов

 

На логическом уровне файловые системы OS UNIX образуют иерахическую

структуру с именованными узлами, которые соответствуют файлам.

Различают 3 разновидности файлов, доступ к которым идентичен: обычные

(регулярные) файлы, каталоги (директории) файлов, специальные (байт и

и блокориентированные) файлы. Обычные и специальные файлы об'единяются

в каталоги. Каталоги могут иметь подкаталоги с произвольным уровнем

вложенности.

 

Обозначения базовых имен файлов и каталогов образуют произвольные

цепочки печатных символов ASCII, исключая металитеры '*', '\' и '?',

которые специальным образом обрабатывают командные процессоры OS UNIX.

Расширение базовых имен не является обязательным, поэтому символ '.'

в общем случае не означает начало расширения имени файла и трактуется

как равноправный с другими символ имени файла.

 

Иерархически упорядоченная совокупность каталогов и файлов образует

файловую систему. Файловая система может быть создана на любом внешнем

устройстве с прямым доступом (жестком диске, логическом разделе

жесткого диска, гибком диске) командой mkfs (или newfs). Файловые

системы монтируются к главной (корневой) файловой системе командой

mount, образуя монолитную иерархическую файловую структуру в рамках

которой неразличимы отдельные файловые системы и внешние устройства,

на которых они созданы.

 

Для формального описания иерархических отношений в файловых системах

недостаточно базовых имен каталогов и файлов. В OS UNIX принято

универсальное соглашение, которое позволяет однозначно определить

положение любого файла или каталога по маршрутному имени. Маршрутное

имя образует цепочка имен каталогов, через которые проходит маршрут от

корня иерархии файловой системы до специфицируемого файла. Корневой

каталог обозначает символ '/'. Тот же символ применяется для разделения

имен каталогов в маршрутном имени файла. Маршрутное имя текущего

каталога отображает команда pwd. Желаемое изменение текущего каталога

обеспечивает команда cd. Использование длинных маршрутных имен не

всегда удобно и целесообразно. К файлу можно обращаться по сокращенному

маршрутному имени относительно текущего каталога '.', используя

специальное обозначение родительского каталога "..", для адресации в

направлении корня файловой системы.

 

Исходя из соображений надежности, целесообразно создавать многотомные

файловые структуры, располагая корневую и пользовательские файловые

системы на отдельных физических или логических внешних устройствах,

которые монтируются к корневой при инициализации многопользовательского

режима работы OS UNIX. Схема авто-монтирования определяется файлом

fstab, который расположен в каталоге etc корневой файловой системы, но

может быть изменена явным использованием команд mount и umount. Состав

пользовательских файловых систем может быть произвольным. Иерархия

корневой файловой системы практически универсальна. В различных версиях

OS UNIX корневая файловая система включает следующий классический набор

каталогов и файлов:

 

    + boot - программа начальной загрузки ядра или автономной

           загрузки других OS;

 

    + unix - файл, содержащий программу ядра OS UNIX;

 

    + bin - каталог наиболее популярных команд (ar, cat, cc, cmp,

          dd, df, du, echo, grep, find, file, mv, mkdir, ld, ls,

          lpr, passwd, pr, pwd, rm, rmdir, sort, tar и т.д.);

 

    + dev - каталог специальных файлов (fd0, lp0, null, mem, mouse,

            ha0a, rfd0, rha0a, rsa0a, sa0a, tty00 и т.д.);

 

    + etc - каталог административных команд и таблиц (init, fsck,

            fstab, getty, gettytab, mkfs, mount, umount, passwd, rc,

          printcap, update, sync, shutdown, termcap, ttys и т.д.);

 

    + lib - каталог основных библиотек об'ектных модулей (libc.a,

            libm.a, libx.a, libcursors.a и т.д.);

 

    + tmp - каталог временных файлов;

 

    + mnt - каталог монтирования пользовательских файловых систем.

 

Кроме перечисленных корневая файловая система имеет каталог usr,

который включает подкаталоги:

 

    + adm - каталог файлов системного учета и администрирования

          (cron, messages, wtmp, utmp, syslog и т.д.);

 

    + bin - каталог инструментальных средств (awk, make, lex, yacc,

          ed, vi, emacs, cpp и т.д.);

 

    + lib - каталог дополнительных библиотек об'ектных модулей

          (например, libg++.a, libX11.a, libXt.a, libXaw и т.д.),

          макропакетов подготовки документации (mm, me, ms),

          файлов шрифтов и раскладки клавиатуры;

 

    + man - каталог страниц оперативного руководства, сгруппированного

          по функциональному признаку из подкаталогов man1 - man8;

 

    + include - каталог заголовочных файлов системы программирования C

                и С++.

 

Следует отметить, что различные версии OS UNIX могут незначитено

отличаться по архитектуре корневой файловой системы. В частности,

в версиях BSD и SUN содержание каталога usr образует отдельную

файловую систему, которая монтируется к корневой на этапе начальной

загрузки OS UNIX.

 

 

                 Обычные файлы

 

Обычные файлы сохраняют информацию, которая получается в результате

выполнения прикладных и системных программ. Непосредственно OS UNIX не

накладывает никаких ограничений на структуру данных в обычном файле.

Поэтому в общем случае обычный файл интерпретируется как произвольная

байтовая последовательность. Информационную структуру обычного файла

определяют прикладные и системные программы, которые создают и

обрабатывают его. В OS UNIX принято выделять следующие разновидности

обычных файлов:

 

       + текстовые файлы;

 

       + об'ектные модули;

 

       + выполняемые файлы (загрузочные модули);

 

       + архивы;

 

       + библиотеки;

 

       + компрессированные (сжатые) файлы.

 

Текстовые файлы имеют строковую структуру и содержат символ LF (line

feed - перевод строки) с кодом ASCII 0A (\n) в конце каждой строки.

Текстовые файлы создаются и модифицируются текстовыми редакторами или

иными инструментальными средствами текстовой обработки данных. Тексты

могут иметь различную функциональную ориентацию, которая накладывает

дополнительные ограничения на их структуру. Это могут быть исходные

коды программ на различных языках программирования, документы,

предназныченные для обработки текстовыми процессорами nroff или troff,

различные системные таблицы (например, termcap, printcap, passwd,

ttys и т.д.), формат которых ориентирован на использующие их системные

программы, командные файлы на языке интерпретатора команд (sh, csh),

исходные тексты программ для лексического (lex) и синтаксического

(yacc) анализа, наконец, текстовые файлы для прикладной обработки.

 

Об'ектные модули создаются компиляторами систем программирования по

исходным кодам программ. Файлы об'ектных модулей имеют расширение ".o".

Следующие командные строки вызывает компилятор cc для построения

об'ектных модулей func1.o  и func2.o по исходным кодам func1.c и

func2.c, соответственно, в системе программирования C:

 

           cc -c func1.c

 

           cc -c func2.c

 

Выполняемые файлы (загрузочные модули) создаются редактором связей ld

из об'ектных модулей. Следующая командная строка вызывает редактор

связей ld для создания выполняемого файла prog из об'ектных модулей

func1.o и func2.o:

 

           ld -o prog func1.o func2.o

 

Аналогичный результат может быть достигнут командой сс, которая

обеспечивает встроенный вызов редактора связей ld в любой из следующих

транскрипций:

 

          cc -o prog func1.o func2.o

 

          cc -o prog func1.c func2.c

 

Выполняемый файл состоит из 5-ти секций. Первая секция задается

структурой struct exec, поля которой определят размеры сегмента кода,

сегмента данных, таблицы символов и таблицы перемещений. Вторая секция,

программный сегмент, содержит исполняемый код программы. Третья секция

включает сегмент инициализированных и неинициализированных данных. Две

последние секции содержат таблицы символов и перемещений, которые

используются отладчиком и могут быть исключены командой strip. Это

позволяет уменьшить об'ем выполняемого файла в среднем на 10%.

 

Архивные файлы образуются путем об'единения нескольких обычных файлов

(чаще всего об'ектных модулей) в единый архив командой архивации ar.

В начале архива, созданного командой ar, размещается магическое число

01777545. Затем последовательно размещаются файлы, составляющие архив.

Каждый файл размещается в архиве с границы слова и снабжается

заголовком, который задается структурой struct ar_hdr. Эта структура

содержит следующие поля:

 

       + ar_name - имя файла;

 

       + ar_date - дата модификации файла;

 

       + ar_uid  - идентификатор владельца файла;

 

       + ar_gid  - идентификатор группы владельца файла;

 

       + ar_mode - код защиты файла;

 

       + ar_size - размер файла в байтах.

 

Следует отметить, что аналогичный принцип архивации использует команда

tar, которая может создавать архивы на внешних устройствах, заданных

специальными файлами, например, гибком диске или магнитной ленте.

Символический метод кодирования заголовка позволяет передавать архивы

на компьютеры с различной архитектурой. Важный методический недостаток

архива - последовательный доступ к данным, что значительно увеличивает

время обработки больших архивов. Получить доступ к любому файлу архива

можно только просмотрев заголовки всех предшествующих файлов. Более

того, команда ar будет продолжать просмотр архива до конца в надежде

обнаружить другие файлы с заданным именем. Указанного недостатка лишены

библиотечные файлы.

 

Библиотечный файл может быть получен путем обработки архива командой

ranlib, которая строит в начале архива ссылочную таблицу symdef. Формат

ее элементов задается структурой struct symdef, которая имеет 2 поля:

 

       + stroff - смещение имени файла в таблице symdef;

 

       + cloc   - положение файла внутри библиотеки.

 

Предварительный просмотр таблицы symdef обеспечивает непосредственный

доступ команды ar к любому желаемому файлу архива без перебора

заголовков предшествующих файлов. Это существенно сокращает время

доступа к любому файлу библиотеки по сравнению с обычным архивом.

 

Однако, архивация файлов не сопровождается компрессированием (сжатием)

данных. Компрессированые файлы образуются командами compress или

compact. Команда compress использует адаптивное кодирование по методу

Лемпела-Зива. Команда compact реализует адаптивное кодирование по

методу Хаффмана. Для компрессированной архивации файлов используется

следующая технология: сначала группа файлов обрабатывается командой ar,

затем полученный архив файлов сжимается командой compress или compact,

компрессированный архив может быть сохранен на гибком диске или ленте

командой tar.

 

 

                 Каталоги файлов

 

Каталоги (директории, справочники, оглавления) используются в файловых

системах OS UNIX для группировки файлов, имеющих одного владельца,

связанных общей тематикой или об'единенных по какому-либо иному

организационному принципу. Например, каталог bin в корневой файловой

системе содержит наиболее популярные команды, каталог lib - библиотеки

систем программирования. При разработке большого программного проекта

целесообразно сосредоточить его файлы в отдельном каталоге. Для каждого

пользователя OS UNIX создает собственный домашний каталог, который

автоматически становится текущим в начале его сеанса работы с системой.

Каталог может включать произвольное число файлов и подкаталогов с любым

уровнем вложенности, устанавливая отношения иерархической

принадлежности на множестве узлов файловой системы.

 

В соответствии с концепцией файловых систем OS UNIX, каталог является

файлом, который содержит специальным образом организованное оглавление

всех файлов и подкаталогов, принадлежащих ему в логическом смысле. Это

оглавление устанавливает соответствие между именами файлов в каталоге и

собственно файлами. Указанное соответствие обеспечивается внутренней

организационной структурой каталога как файла. Файл каталога состоит из

записей, соответствующих элементам каталога (файлам и подкаталогам). В

простейшем случае эти записи имеют равную длину и описываются следующей

структурой на C-коде:

 

              struct direct {

                 int d_ino;

                 char d_name[14];

              }.

 

В структуре struct direct поле d_name используется для хранения имени

файла. Если длина имени файла меньше 14 байт, то свободная часть поля

заполняется символами с кодом 0. Поле d_ino структуры struct direct

содержит номер индексного дескриптора для файла с именем, указанным в

поле d_name данной записи файла каталога. Как известно, в индексном

дескрипторе сосредоточена информация о всех атрибутах файла ( тип и

код защиты, идентификаторы владельца и группы владельца, даты создания

и модификации, длина и адреса блоков данных файла ).

 

Любой (даже пустой) каталог содержит 2 обязательных элемента с именами

"." и "..". Элемент каталога, содержащий в поле d_name структуры

struct direct имя ".", имеет в поле d_ino номер индексного дескриптора

файла, где хранится информация о текущем каталоге. Элемент каталога,

содержащий в поле d_name структуры struct direct имя "..", имеет в поле

d_ino номер индексного дескриптора файла, который является родительским

каталогом текущего. Используя имя ".", можно ссылаться на текущий

каталог, не зная его имени. Испльзуя имя "..", можно адресоваться по

дереву файловой системы в направлении корня, не зная имен родительских

каталогов. По договоренности корневому каталогу файловой системы

соответствует индексный дескриптор с номером 1.

 

Таким образом, из структуры каталога видно, что файл каталога содержит

имена файлов и ссылки на их индексные дескрипторы. Такой подход, когда

имя файла, с которым оперирует пользователь, отделено от атрибутов

файла, которые обрабатывает система, позволяет гибко манипулировать

внешним представлением иерархии файлов в файловой системе, не изменяя

положения блоков данных файлов на внешнем устройстве. В частности, один

и тот же файл можно внести в несколько различных каталогов, безразлично

под одинаковыми или разными именами. Хотя имена одного и того же файла

могут быть произвольными в различных каталогах, где присутствует запись

о нем, но им будет сопоставлен одинаковый номер индексного дескриптора

и, соответственно, один и тот же индексный дескриптор, который является

ключем доступа к блокам данных файла на внешнем устройстве. Указанный

ссылочный механизм в файловых системах OS UNIX управляется командой ln

и системным вызовом link.

 

Число ссылок на файл из различных каталогов фиксируется в поле di_nlink

структуры индексного дескриптора struct dinode. Когда число ссылок в

этом поле становится равным 0, индексный дескриптор файла освобождается

и блоки данных файла включаются в список свободных блоков файловой

системы. Следует отметить, что в OS UNIX нет пользовательской команды

удаления файлов с числом ссылок, большим 1. Команда rm, которая по

традиции считается командой удаления файлов, удаляет только ссылку на

указанный файл в соответствующем каталоге, корректируя поле ссылок его

индексного дескриптора. Аналогичный эффект достигается в прикладных

программах системным вызовом unlink.

 

 

                 Специальные файлы

 

Специальные файлы обеспечивают унифицированный доступ пользователя к

периферийным устройствам независимо от их типа. Специальные файлы имеют

особую организационную структуру и их нельзя использовать для хранения

данных, как обычный файл или оглавления, как каталог. Специальные файлы

предназначены для того, чтобы осуществлять связь между иерархической

файловой системой и соответствующими подпрограммами обслуживания

(драйверами) внешних устройств в ядре OS UNIX. Указанная интерпретация

специальных файлов обеспечивает идентичный доступ к внешним устройствам

и обычным файлам. Также как обычный, специальный файл может быть открыт

или закрыт, в него можно записывать и из него можно читать данные. В

результате этих действий над специальным файлом будут реализованы

операции ввода-вывода на соответствующее внешнее устройство. Например,

чтобы напечатать информацию на принтере, нужно направить вывод данных

в соответствующий специальный файл, используя команды и системные

вызовы, аналогичные случаю сохранения данных в обычном файле файловой

системы OS UNIX.

 

Каждому внешнему устройству компьютера OS UNIX ставит в соответствие

как минимум 1 специальный файл. Обычно они сосредоточены в каталоге dev

корневой файловой системы. Связь имени специального файла с конкретным

внешним устройством обеспечивает индексный дескриптор. Тип специального

файла задает поле di_mode структуры индексного дескриптора dinode. Для

специального блокориентированного файла в этом поле установлены биты с

восьмиричной маской 060000, для байториентированного - с маской 020000.

Специальные блок- и байториентированные файлы обеспечивают интерфейс с

внешними устройствами блочной и символьной структуры, соответственно.

 

Символьный (байториентированный) интерфейс ввода-вывода поддерживают:

терминал, принтер, физическая внутренняя память (RAM, ROM, CMOS),

манипулятор типа "мышь" и другие периферийные устройства, подключенные

через последовательные линии связи или параллельный порт. Существует

неформальное соглашение по обозначению байториентированных специальных

файлов. Например, терминалам соответствуют специальные файлы с именами

ttyXX, где XX - номер последовательной линии связи или номер псевдо

терминала на одной физической консоли. Нужно отметить, что специальные

файлы с обозначением ttyXX часто применяются для обслуживания принтеров

с последовательным интерфейсом, которые подключают по последовательным

линиям связи. Принтеры с параллельным интерфейсом, подключенные по

параллельным портам, доступны через специальные файлы lpN, где N

обозначает номер параллельного порта.

 

К периферийным устройствам с блочной структурой традиционно относятся

накопители для жестких или гибких дисков и магнитной ленты. В версиях

OS UNIX, построенных на основе стандарта System V release 4.2, принята

универсальная спецификация специальных файлов для блокориентированных

устройств, расчитанных на SCCI и не-SCCI (например, IDE) интерфейс. Эта

спецификация отражает номер контроллера (c), номер цели (t), т.е. номер

гнезда SCCI адаптера, номер устройства (d), номер раздела (s) для диска

или емкость ленты. В обозначении устройств, подключенных через не-SCCI,

например, через IDE интерфейс, отсутствует номер гнезда SCCI адаптера.

В частности, для жестких дисков, подключенных через SCCI интерфейс,

специальный файл обозначается cCtTdDsS. Значения параметров C, T, D и S

назначаются по следующим соображениям. Каждый контроллер жесткого диска

с номером C может обслуживать до 4-х дисковых накопителей с номерами D

от 0 до 3, где каждый диск имеет до 16-ти разделов с номерами S от 0 до

f. Большинство SCCI адаптеров поддерживают до 7-ми устройств, которые

подключены к гнездам с номерами T от 1 до 7. В аналогичной по форме

спецификации специального файла кассетного стриммера параметр S

обозначает емкость ленты и имеет значение 0 - для накопителя емкостью

60 Мб или 1, если емкость накопителя 120 Мб.

 

Следует отметить, что в различных версиях OS UNIX введены собственные

обозначения блокориентированных специальных файлов, которые отличаются

от универсальных спецификаций стандарта System V release 4.2. Например,

для специальных файлов накопителей на гибких дисках обычно выбирается

обозначение fd, с соответствующей добавкой, которая указывает номер

накопителя, плотность записи и об'ем. Иногда вместо об'ема указывается

число треков на дюйм и число секторов на дорожке.

 

Все перечисленные выше внешние устройства с блочной структурой также

поддерживают байториентированный доступ в "прозрачном" (raw) режиме.

Поэтому для них предусмотрены соответствующие байториентированные

специальные файлы, которые обеспечивают символьный доступ к устройствам

с блочной структурой, когда это необходимо. Имена байториентированных

специальных файлов для внешних устройств с блочной структурой имеют

префикс r или распологаются в отдельном подкаталоге rdsk каталога dev.

 

 

                 Таблицы специальных файлов

 

Рассмотренные способы именования специальных файлов позволяют различать

периферийные устройства на пользовательском уровне. На уровне системы

различие типов внешних устройств и возможное наличие нескольких

устройств одного типа отражается в индексных дескрипторах специальных

файлов структурой из 2-х полей d_minor и d_major. Эти поля размещены в

2-х первых элементах символьного массива di_addr[40] адресного поля

индексного дескриптора dinode, который у каталогов и обычных файлов

используется для адресации блоков данных на внешнем устройстве. Для

специального файла значение в di_addr[1] (di_major) задает тип внешнего

устройства и определяет выбор драйвера для управления им. Порядковый

номер устройства данного типа хранится в di_addr[0] (di_minor). Он

передается функциям драйвера как параметр, определяющий об'ект

настройки драйвера. Значения полей di_major и di_minor задаются

неотрицательными целыми числами.

 

Доступ к драйверам внешних устройств с блочной и символьной структурой

осуществляется через системные таблицы bdevsw и cdevsw, соответственно.

Эти таблицы состоят из записей, которые содержат адреса драйверных

функций байт- и блокориентированных устройств, соответствующего типа.

Формат записей таблиц cdevsw и bdevsw декларируют следующие структуры:

 

       struct cdevsw {                struct bdevsw {

       int (*d_open)();               int (*d_open)();

       int (*d_close)();              int (*d_close)();

       int (*d_read)();               int (*d_strategy)();

       int (*d_write)();              int (*d_print)();

       int (*d_ioctl)();              int (*d_size)();

       };                             };

 

Сами таблицы cdevsw и bdevsw задают внешние массивы указанных структур:

extern struct cdevsw cdevsw[] и extern struct bdevsw bdevsw[], поля

записей которых инициализированы адресами точек входа функций драйверов

периферийных устройств, соответствующих типов. Драйверные функции (с

именами точек входа XXfunc) активизируются в следующих случаях:

 

      XXopen -     вызывается, когда нужно открыть устройство для

                   операций ввода-вывода системным вызовом open;

 

      XXclose -    вызывается, когда нужно закрыть устройство после

               завершения ввода-вывода системным вызовом close;

 

      XXstrategy - определяет стратегию блокориентированного ввода

               (read) и вывода (write);

 

      XXprint    - вызывается для вывода на консоль сообщений об

               ошибках работы с устройством;

 

      XXsize     - вычисляет размер файловой системы для проверки

               возможности реализации запроса вывода данных;

 

      XXread     - вызывается для реализации байториентированного

               ввода системным вызовом read;

 

      XXwrite    - вызывается для реализации байториентированного

               вывода системным вызовом write;

 

      XXioctl    - обеспечивает управление режимами работы устройств

               с символьной структурой ввода-вывода.

 

Префикс XX в именах точек входа драйверных функций имеет конкретное

обозначение, отражающее тип устройства, например, lp в именах точек

входа функций драйвера принтера. Так как принтер является устройством

с символьной структурой ввода-вывода, то интерфейс с драйвером принтера

осуществляется через таблицу cdevsw, в соответствующую запись которой

подставляются адреса точек входа функций драйвера принтера: &lpopen,

&lpclose, &lpwrite, &lpioctl.

 

В зависимости от типа периферийного устройства любое поле в структурах

struct cdevsw и struct bdevsw может быть инициализировано адресом

&nulldev, если соответствующая функция игнорируется, или заглушкой

&nodev, когда требуемая функция отсутствует или является ошибочной,

например, чтение для принтера.

 

Выбор таблицы bdevsw или cdevsw осуществляется по значениям битов с

маской 060000 и 020000 в поле di_mode структуры индексного дескриптора

struct dinode, определяющих блок- или байториентированный интерфейс с

устройством, имя специального файла которого задано для выполнения

операций ввода-вывода. Выбор типа внешнего устройства и, соответственно

обслуживающего драйвера, осуществляется по значению d_major в нулевом

байте поля di_addr структуры индексного дескриптора struct dinode.

Таблицы bdevsw и cdevsw имеют независимую нумерация типов устройств.

Порядковый номер устройства данного типа, определяемый значением

d_minor в 1-м байте поля di_addr, передается функциям драйвера как

параметр.

 

В заключении следует отметить, что специальные файлы, обладая особой

внутренней структурой, не могут быть созданы теми же средствами как

обычные файлы или каталоги. Для создания специальных файлов используют

команду или системный вызов mknod. Их аргументы определяют тип и номер

устройства, байт- или блокориентированный интерфейс, обозначение

специального файла в файловой системе и права доступа к нему. Драйвер

нового устройства должен быть включен в состав ядра OS UNIX.

 

 

ФАЙЛОВАЯ СИСТЕМА OS UNIX

 

              Понятие файловой системы

 

Файловая система - это концептуальное понятие OS UNIX, которое

определяет логическую организацию пространства внешней памяти. Она

может быть образована на любом физическом или виртуальном устройстве

внешней памяти с прямым доступом, например, жестком диске, логическом

разделе жесткого диска, гибком диске, сменном пакете дисков. Разделение

пространства внешней памяти на физические или виртуальные носители

данных в OS UNIX отражается логическим разделением внешней памяти на

файловые системы.

 

Файловая система OS UNIX обеспечивает разбиение пространства физической

памяти внешнего устройства на поименованные участки данных - файлы,

гарантирует защиту данных от аппаратных сбоев и несакционированного

доступа, поддерживает единый интерфейс обращения к данным, минимизирует

время обращения к данным. Это основные функции файловой системы.

 

Строение файловой системы имеет двойственную интерпретацию. Внутренняя

организация файловой системы интерпретируется как последовательность

фиксированных по размеру блоков внешней памяти. Внешнее представление

файловой система имеет иерархическую структуру с поименованными узлами,

которые обозначают либо файлы данных, либо каталоги файлов, либо

специальные файлы доступа к устройствам. Внутренняя организация

файловой системы скрыта от пользователя и недоступна при использовании

стандартных средств. Внешний интерфейс файловой системой обеспечен

соответсвующими командами и системными вызовами ядра.

 

 

              Внутренняя структура файловой системы

 

Упорядоченную последовательность блоков внутреннего представления

файловой системы можно разделить по функциональному признаку на 5

областей, как показано на следующем рисунке.

 

 

   0           1           2           S            N           M

   +-----------+-----------+-----------+------------+-----------+

   |   Блок    |           | Индексный |   Область  | Область   |

   | начальной | Суперблок |   файл    |   данных   | своппинга |

   | загрузки  |           |           |            |           |

   +-----------+-----------+-----------+------------+-----------+

 

           Рис. Расположение блоков файловой системы

 

 

Блок начальной загрузки (блок 0) содержит программу начальной загрузки

OS UNIX, которая читает в RAM либо непосредственно программу ядра,

либо более сложный вторичный загрузчик. Суперблок (блок 1) содержит

заголовок файловой системы, который включает информацию о ее размерах

и характеристиках. Индексный файл (блоки 2-S) содержит индексные

дескрипторы (описатели) файлов, характеризующие их основные атрибуты.

Область данных (блоки S-N) - последовательность блоков, используемых

для хранения и косвенной адресации данных. Область своппинга (блоки N-M)

используется диспетчером OS UNIX для перемещения (выталкивания) из RAM

образов пассивных процессов, когда нет достаточных ресурсов для их

выполнения. По мере возможности образы вытолкнутых процессов могут быть

загружены (подкачены) обратно из области своппинга в RAM.

 

 

              Структура суперблока

 

В терминах системы программирования C устройство суперблока файловой

системы задает структура struct filsys. Ее основные поля имеют

следующий смысл:

 

s_isize, s_fsize    - адреса 1-го блока областей данных и своппинга,

                  соответственно;

s_nfree, s_ninode   - счетчики свободных блоков и индексных

                      дескрипторов файлов, соответственно;

s_free[], s_inode[] - списки адресов свободных блоков и индексных

                  дескрипторов файлов, соответственно;

s_tfree, s_tinode   - общее число свободных блоков и индексных

                  дескрипторов файлов, соответственно;

s_fblock, s_iblock  - флаги запрета доступа к списку свободных блоков

                  и индексных дескрипторов файлов во время их

                  модификации, соответственно;

s_fmod              - флаг модификации суперблока, устанавливается

                      при изменениях файловой системы;

s_ronly             - флаг монтирования, устанавливающий наличие или

                  отсутствие возможности модификации файловой

                  системы;

s_state             - состояние файловой системы, нормальное,

                      активное или поврежденное, соответственно;

s_time              - время последней модификации файловой системы;

s_type              - размер блока файловой системы.

 

 

              Индексные дескрипторы файлов

 

Любой файл файловой системы OS UNIX однозначно специфицирован некоторой

структурой данных, называемой индексным дескриптором или описателем

файла. Индексные дескрипторы файлов имеют фиксированный размер (кратный

размеру блока) и сосредоточены в последовательных блоках индексного

файла. Предельное количество индексных дескрипторов в индексном файле

ограничивает число файлов, которые могут быть созданы в блоках области

данных файловой системы. Целесообразно иметь некоторый избыток индексных

дескрипторов, чтобы уменьшить риск недоиспользования области данных

из-за невозможности создавать файлы, когда размер индексного файла будет

исчерпан. Размер индексного файла задается при создании файловой системы

исходя из среднего размера файла и об'ема пространства внешней памяти, в

котором строится файловая система.

 

Индексные дескрипторы нумеруются в индексном файле, начиная с нуля.

Для системных нужд зарезервированы два индексных дескриптора с номерами

0 и 1. Индексный дескриптор с номером 0 используется для спецификации

дефектных блоков внешней памяти. Индексный дескриптор номер 1 выделен

для корневого каталога файловой системы. Остальные индексные дескрипторы

с номерами больше 1 специального назначения не имеют и используются для

спецификации каталогов, обычных и специальных файлов файловой системы.

 

Логическую организацию индексного дескриптора файла в терминах системы

программирования С отражает структура struct dinode. Ее поля содержат

следующую информацию:

 

di_mode            - тип и код защиты файла;

di_nlink           - число альтернативных имен файла, равное числу

                     ссылок на данный индексный дескриптор из каталогов

                     файловой системы;

di_uid, di_gid     - идентификаторы владельца и группы владельца файла,

                 соответственно, устанавливаются по реальным

                     идентификаторам владельца и группы владельца

                     процесса, который создает данный файл;

di_size            - размер файла в байтах;

di_ctime           - дата создания файла;

di_atime, di_mtime - даты последнего обращения и последней модификации

                 файла, соответственно;

di_addr[40]        - адресация блоков файла в области данных.

 

Содержание полей структуры struct dinode для любого существующего файла

можно контролировать командой ls с ключом -l или системными вызовами

stat и fstat.

 

Для повышения эффективности обработки данных в файловой системе при

открытии любого файла его индексный дескриптор считывается в

таблицу индексных дескрипторов, которая резидентна в RAM. Резидентный

образ индексного дескриптора файла возвращается в индексный файл после

завершения работы с данным файлом во всех процессах.

 

Чтобы динамически поддерживать соответствие резидентных копий индексных

дескрипторов с их оригиналами в индексном файле, содержание таблицы

индексных дескрипторов файлов периодически копируется в индексный файл

следящим процессом (демоном) update, который запускается при переходе

OS UNIX в многопользовательский режим. Указанная процедура синхронизации

позволяет минимизировать нарушение корректности файловой системы при

аппаратных сбоях, когда возможность приведения в соответствие индексного

файла и модифицированных резидентных копий индексных дескрипторов будет

потеряна. Хотя в OS UNIX предусмотрены административные средства ремонта

файловой системы, но исправление существенных нарушений может привести к

потере данных.

 

 

              Тип и код защиты файла

 

Тип и код защиты файла, а также возможность смены группы и владельца

файла при выполнении, устанавливаются полем di_mode структуры

struct dinode индексного дескриптора файла. Битовый формат этого поля

приведен на следующем рисунке.

 

 

 

       тип файла       биты смены              код защиты

 |<----------------->|<----------->|<--------------------------------->|

 |                   |             |                                   |

 |                   |             |                                   |

 |----|----|----|----|----|----|---|---|---|---|---|---|---|---|---|---|

 | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |

 |----|----|----|----|----|----|---|---|---|---|---|---|---|---|---|---|

 | -  | d  | c  | p  |  s |  s | t | r | w | x | r | w | x | r | w | x |

      |    |    |                  |           |           |           |

      | b  | b  |                  |   owner   |   group   |   other   |

                                   |<--------->|<--------->|<--------->|

 

 

              Рис. Формат поля di_mode

 

 

Код защиты предназначен для ограничения несанкционированного доступа к

файлу. Он устанавливается 9-ю младшими битами поля di_mode с маской

0777 и позволяет дифференцировать права доступа к данным файла для 3-х

категорий пользователей: владелец файла (owner), группа владельца файла

(group), остальные пользователи (other). Для каждой категории, OS UNIX

предоставляет три разновидности прав доступа: по чтению, по записи и по

выполнению. В пределах каждой категории пользователей права доступа

задает тройка битов с масками: 0700 - для владельца файла, 070 - для

группы владельца файла, 07 - для остальных пользователей. Старший бит

каждой тройки определяет права доступа по чтению (r), средний - по

записи (w), младший - по выполнению (x). Таким образом, права доступа

любых категорий пользователей устанавливают следующие битовые маски:

0444 - доступ по чтению, 0222 - доступ по записи, 0111 - доступ по

выполнению. Произвольный комбинированный код защиты может быть построен

из базисных прав доступа с помощью операции битовой суммы. Требуемый

код защиты устанавливается при создании файла и может быть изменен

владельцем файла или суперпользователем с помощью команды chmod.

 

Средняя тройка битов поля di_mode с маской 07000 существенна для

выполняемых файлов. Ее составляют бит несговорчивости (t) и биты смены

владельца или группы владельца (s). Бит несговорчивости (или sticky bit)

задается маской 01000 и может быть установлен для файлов реентерабельных

программ или для каталогов. В первом случае, установка этого бита

обеспечивает сохранение образа разделяемого процедурного сегмента после

завершения всех процессов, выполнявших данную реентерабельную программу.

Во втором случае, установка бита несговорчивости для каталога запрещает

непривилигированным пользователям модифицировать или переименовывать

"чужие" файлы в данном каталоге даже при наличии соответствующих прав

доступа. Бит несговорчивости может быть установлен или снят только

суперпользователем с помощью команды chmod.

 

Пара битов с маской 06000 устанавливает возможность смены владельца и

группы владельца процесса, который выполняет данный файл на владельца

и группу владельца файла. При этом, бит с маской 04000 разрешает смену

владельца, а бит с маской 02000 - смену группы владельца процесса.

Установка этих битов позволяет рядовому пользователю получить

привилигированные полномочия владельца или группы владельца файла на

время его выполнения.

 

Старшие 4 бита поля di_mode с битовой маской 0170000 определяют тип

файла в соответствии со следующими масками:

 

  0100000 - обычный регулярный файл (-),

  010000  - именованный программный канал (p),

  020000  - байт-ориентированный специальный файл (c),

  040000  - каталог (d),

  060000  - блок-ориентированный специальный файл (b).

 

Тип файла задается при его создании. Для создания файла любого типа

может быть использован универсальный системный вызов или команда mknod.

На практике это универсальное средство обычно применяется только для

создания специальных файлов. Для создания файлов иных типов используют

специализированные системные вызовы и команды. В частности, каталог

можно создать системным вызовом или командой mkdir, а именованный

программный канал - системным вызовом или командой mkfifo. Регулярный

файл может быть создан системным вызовом creat или командной строкой:

 

          cat < /dev/null > regular-file-name ,

 

которая создает обычный файл с именем regular-file-name нулевой длины.

 

Для существующего файла содержание поля di_mode можно контролировать с

помощью системного вызова stat. Следующий фрагмент C-кода определяет

тип файла с заданным именем anyfile.

 

#include <sys/types.h>

#include <sys/stat.h>

 

main() {

struct stat statbuf[1];    /* буфер индексного дескриптора файла */

 

stat("anyfile",statbuf);  /* заполнить буфер индексного дескриптора */

 

/* анализ типа файла в буфере индексного дескриптора */

 

printf("anyfile - ");

switch(statbuf->st_mode & S_IFMT) {

  case S_IFREG: puts("Регулярный файл");

            break;

  case S_IFIFO: puts("Именованный программный канал");

            break;

  case S_IFCHR: puts("Байт-ориентированный специальный файл");

            break;

  case S_IFBLK: puts("Блок-ориентированный специальный файл");

            break;

  case S_IFDIR: puts("Каталог");

            break;

  default:      puts("Неклассический файл");

                break;

} /* switch */

 

} /* main */

 

Маска типа файла S_IFMT и альтернативы выбора S_IFREG, S_IFIFO, S_IFCHR,

S_IFBLK, S_IFDIR определены в заголовочном файле /usr/include/sys/stat.h

с помощью директивы define. Этот заголовочный файл содержит набор

макроопределений, необходимых для контроля любых полей структуры

индексного дескриптора и битов поля di_mode системным вызовом stat.

 

 

              Адресация блоков данных

 

Расположение файла задается списком адресов блоков области данных

файловой системы. Физическое расположение блоков файла может быть

произвольным, но логически они образуют связную цепочку. Непрерывное

расположение расположение блоков любого файла не является обязательным.

Степень разброса блоков файлов (сегментация файловой системы) влияет

только на время доступа к файлам.

 

Списковую адресацию блоков файла обеспечивает адресное поле di_addr

структуры struct dinode индексного дескриптора. Адресное поле di_addr

имеет длину 40 байт и может хранить 13 трехбайтовых указателей блоков

файла. Эти указатели определяют расположение блоков файла на основе

прямой и косвенной адресации.

 

Первые 10 указателей адресного поля индексного дескриптора используются

для прямой адресации 10-ти начальных блоков файла. Если размер файла

превышает 10 блоков используется 11-й указатель адресного поля di_addr.

Он содержит адрес первичного косвенного блока из 128 четырехбайтовых

указателей, которые позволяют адресовать еще 128 блоков. Следующий

(12-й) указатель адресного поля индексного дескриптора хранит адрес

вторичного косвенного блока, который может содержать 128 указателей

128-ми первичных косвенных блоков. Вторичный косвенный блок позволяет

дополнительно адресовать еще (128 * 128) блоков данных. Для адресации

блоков больших файлов используют 13-й указатель адресного поля di_addr.

Он содержит адрес третичного косвенного блока, который адресует 128

вторичных косвенных блоков. Трехкратная косвенная адресация позволяет

адресовать дополнительно еще (128 * 128 * 128) блоков. Следует отметить,

что ковенные блоки, также как блоки данных файлов принадлежат области

данных файловой системы. Рассмотренный метод адресации блоков в файловой

системе OS UNIX иллюстрирует следующая схема.

 

 

                   Прямая адресация

    <0-9> -----------------------------------------------> 10

 

              1  кратная косвенная адресация

 

    <10>  --------------------------------> 1 -----------> 128

 

              2-х кратная косвенная адресация

 

    <11>  ------------------> 1 ----------> 128 ---------> 128*128

 

                3-х кратная косвенная адресация

 

    <12>  ----> 1 ----------> 128 --------> 128*128 -----> 128*128*128

 

    Поле        Третичный     Вторичные     Первичные      Блоки

    di_addr     косвенный     косвенные     косвенные      данных

              блок          блоки         блоки

 

 

             Рис. Схема адресации блоков данных

 

 

Рассмотренный метод адресации позволяет задавать местоположение до

(10 + 128 + 128*128 + 128*128*128) блоков данных. Очевидно, что наиболее

быстрый доступ осуществляется к коротким файлам, длиной до 10-ти блоков.

В этом случае доступ к данным требует 1 обращение к внешней памяти. При

использовании блоков косвенности требуется, соответственно, 2, 3 или 4

обращения к внешней памяти. Существует мнение, что это не очень дорогая

цена за возможность адресации гигобайтных файлов.

 

 

              Управление файлами

 

Для управления файлами в файловой системе OS UNIX используются три

резидентные системные таблицы: таблица индексных дескрипторов (inode),

системная таблица файлов (file) и таблица открытых файлов в контексте

процесса (u_ofile).

 

Элементами таблицы индексных дескрипторов являются резидентные копии

индексных дескрипторов из индексного файла, по одному на каждый файл

файловой системы, к которому была осуществлена попытка доступа. Любые

модификации характеристик обрабатываемого файла регистрируются в

резидентном образе его индексного дескриптора таблицы inode. После

завершения всех процессов обработки файла, резидентный образ его

индексного дескриптора удаляется из таблицы индексных дескрипторов и

при наличии изменений копируется в соответствующий элемент индексного

файла во внешней памяти.

 

Резидентная таблица индексных дескрипторов (inode) может содержать до

400 элементов. Элементы этой таблицы формально отражает структура

struct inode, близкая по составу полей структуре struct dinode, которая

задает формат индексного дескриптора в индексном файле. Дополнительно

по отношению к структуре struct dinode в структуру struct inode введены

следующие поля:

 

   i_forw, i_back - ссылки на предыдущий и последующий элементы,

                соответственно;

   i_count        - число открытий файла во всех процессах;

   i_number       - номер индексного дескриптора;

 

Каждому элементу таблицы индексных дескрипторов (inode) соответствует

один или несколько элементов системной таблицы файлов (file). Кратность

соответствия зависит от числа открытий файла во всех процессах его

обработки и отражается значением поля i_count структуры struct inode.

Системная таблица файлов состоит из записей - по одной на каждое

открытие файла в одном или различных процессах. Каждая запись системной

таблицы файлов формально отражается структурой struct file, которая

имеет следующие поля:

 

   f_next, f_prev - указатели на следующую и предыдущую запись в

                    системной таблице файлов;

   f_flag         - режим открытия файла (запись, дополнение,

                    чтение и т.д.);

   f_offset       - смещение указателей чтение-записи при работе

                    с файлом;

   f_count        - число ссылок на данную запись из таблицы

                    открытых файлов в контекстах процессов;

   f_inode        - ссылка на соответствующий индексный дескриптор

                в резидентной таблице индексных дескрипторов.

 

Расширение системной таблицы файлов - результат открытия или создания

файлов в пользовательских процессах. При завершении обработки любого

файла значение поля f_count структуры struct file в соответствующей

записи системной таблицы файлов уменьшается на 1. Запись с нулевым

значением поля f_count удаляется из системной таблицы файлов и вместе

с ней исчезает ссылка (f_inode) на соответствующий образ индексного

дескриптора в таблице inode, уменьшая на 1 значение поля i_count в его

структуре struct inode.

 

Для связи с системной таблицей файлов в контексте каждого процесса

введено поле u_ofile, элементами которого являются ссылки на записи

таблицы file. Ссылки образуются в результате открытия существующих или

при создании новых файлов в данном процессе. Размер поля u_ofile

ограничен 20-ю ссылками. Это означает, что любой процесс может вести

одновременную обработку не более 20-ти файлов. Ненулевые ссылки поля

u_ofile в контексте процесса образуют таблицу открытых файлов процесса.

Элементы этой таблицы индексируют пользовательские дескрипторы файлов,

которые открыты процессом. Пользовательские дескрипторы задаются целыми

числами в диапазоне от 0 до 19. Они возвращаются процессу при открытии

файла и специфицируют файл в ходе его обработки. При открытии или

создании файла в поле u_ofile контекста процесса индексируется элемент

с минимальным свободным номером, в котором размещается ссылка на новую

запись системной таблицы файлов. Завершение работы с файлом в процессе

освобождает его пользовательский дескриптор и удаляет ссылку на

соответствующую запись в системной таблице файлов.

 

Модификацию таблицы открытых файлов в контексте процесса можно также

осуществить путем дублирования пользовательских дескрипторов. При этом

в поле u_ofile контекста процесса образуется дополнительная ссылка на

существующую запись системной таблицы файлов, в структуре struct file

которой значение поля f_count увеличивается на единицу. Дублирование

пользовательских дескрипторов применяется для программной реализации

перенаправления ввода-вывода, а также при обработке стандартных потоков

ввода (stdin), вывода (stdout) и протокола диагностики (stderr),

которые связаны со специальным файлом ведущего терминала сеанса работы

пользователя с OS UNIX.

 

Поток стандартного ввода индексируется пользовательским дескриптором 0

в контексте процесса интерпретации команд, который является лидером

сеанса и открывает специальный файл ведущего терминала сеанса. Потоки

стандартного вывода и протокола диагностики индексируются в контексте

этого процесса пользовательскими дескрипторами 1 и 2, соответственно.

Они образуются путем дублирования пользовательского дескриптора потока

стандартного ввода. Поскольку процесс интерпретации команд является

глобальным предком всех процессов сеанса, а процессы-потомки наследуют

контекст предка, то пользовательские дескрипторы 0, 1, 2 будут

зарезервированы для стандартных потоков ввода, вывода и протокола

диагностики в контекстах прямых или отдаленных потомков интерпретатора

команд, если они не были принудительно освобождены в каком-либо из

промежуточных предков данного процесса. Через эту тройку дескрипторов

любой процесс может вести обработку стандартных потоков, не заботясь о

предварительном открытии специального файла ведущего терминала сеанса.

 

Из приведенного описания 3-х системных таблиц следует, что управление

файлами обеспечивает отображение пользовательских дескрипторов,

специфицирующих файлы в процессах их обработки, в индексные дескрипторы

файлов, через которые осуществляется доступ к блокам данных файловой

системы. Такая организация управления файлами гарантирует целостность

файловой системы, когда 1 файл одновременно обрабатывается в нескольких

процессах, поскольку все модификации файла отражает единственный образ

его индексного дескриптора. Данная схема управления не исключает

возможных конфликтов, например, при одновременной записи данных в один

файл различными процессами, что может привести к разрушению информации

в файле. Однако, даже при возникновении подобной маловероятной ситуации

может иметь место только искажение информации в отдельном файле, но не

нарушение целостности всей файловой системы.

 

***

 

Взаимосвязь системных таблиц иллюстрирует логическая схема управления

параллельной обработкой 2-х файлов в 2-х процессах, показанная на

следующем рисунке.

 

                                                   +--------+

    Контекст процесса 1                            | struct |

  -----------------------                          | inode  |

   | 0 | 1 | 2 | 3 | 4 |         Структуры         | файла  |     Блоки

  ---------------+---+---       struct file        |   F1   |---> файла

   |             |   |                             |        |       F1

   |             |   | open F1  +----------+       |        |

   |   u_ofile   |   +--------->| O_RDONLY |------>|        |

   |<----------  |              +----------+       +--------+

                 |   open F2    +----------+       +--------+

                 +------------->| O_RDONLY |------>| struct |

                                +----------+       | inode  |

                     open F2    +----------+       | файла  |     Блоки

                 +------------->| O_RDONLY |------>|   F2   |---> файла

       u_ofile   |              +----------+       |        |       F2

   |<----------  |     open F2  +----------+       |        |

   |             |   +--------->| O_WRONLY |------>|        |

   |             |   |          +----------+       +--------+

   |             |   |

  ---------------+---+----

   | 0 | 1 | 2 | 3 | 4 |

  ------------------------

     Контекст процесса 2

 

 

                     Рис. Схема управления файлами

 

 

Процесс 1 обрабатывает файлы F2 и F1, которые открыты в нем для чтения.

Их пользовательские дескрипторы равны 3 и 4 в контексте процесса 1. Им

соответствуют разные записи в системной таблице файлов и различные

индексные дескрипторы в резидентной таблице индексных дескрипторов.

Процесс 2 обрабатывает только файл F2, который открыт в нем отдельно по

чтению и записи. Эти варианты доступа индексируются пользовательскими

дескрипторами 3 и 4 в контексте процесса 2. Им соответствуют разные

записи в системной таблице файлов, но общий индексный дескриптор,

причем тот же, что для файла F2, открытого процессом 1.

 

 

              Литература

 

1. М.И. Беляков, А.Ю. Ливеровский, В.П. Семик, В.И. Шяудкулис

   Инструментальная мобильная операционная система ИНМОС.

   М., Финансы и статистика, 1985 г.

 

2. М. Дансмур, Г. Дейвис

   Операционная система UNIX и программирование на языке Си.

   М., Радио и связь, 1989 г.

 

3. С. Дунаев

   UNIX System V. Release 4.2. Общее реководство.

   М., Диалог-МИФИ, 1995 г.

 

4. Руководство по внутреннему устройству UNIX SVR4.

   Учебно-методический комплекс.

   Зеленоград, Институт Операционных Систем, 1995 г.

Поделиться:
 
ПОИСК
 
elibrary crossref ulrichsweb neicon rusycon
 
ЮБИЛЕИ
ФОТОРЕПОРТАЖИ
 
СОБЫТИЯ
 
НОВОСТНАЯ ЛЕНТА



Авторы
Пресс-релизы
Библиотека
Конференции
Выставки
О проекте
Rambler's Top100
Телефон: +7 (915) 336-07-65 (строго: среда; пятница c 11-00 до 17-00)
  RSS
© 2003-2024 «Наука и образование»
Перепечатка материалов журнала без согласования с редакцией запрещена
 Тел.: +7 (915) 336-07-65 (строго: среда; пятница c 11-00 до 17-00)