The design of the unix operating system by Maurice J

Вид материалаРеферат
Подобный материал:
1   ...   24   25   26   27   28   29   30   31   ...   55
В пространстве процесса ядро стирает адреса пользовательских

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

контексте они теряют свое значение. Однако и в новом контексте

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

силе. Ядро устанавливает в регистрах для режима задачи значения

из сохраненного регистрового контекста, в частности первоначаль-

ное значение указателя вершины стека (sp) и счетчика команд (pc):

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

в заголовок файла. Для setuid-программ и для трассировки процесса

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

во время рассмотрения глав 8 и 11, соответственно. Наконец, ядро

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

ритму namei в самом начале выполнения функции exec. Алгоритмы

namei и iput в функции exec выполняют роль, подобную той, которую

они выполняют при открытии и закрытии файла; состояние файла во

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

ла, если не принимать во внимание отсутствие записи о файле в

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

вой программы. Тем не менее, процесс остается тем же, что и до

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

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

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


--------------------------------------------------------┐

│ main() │

│ { │

│ int status; │

│ if (fork() == 0) │

│ execl("/bin/date","date",0); │

│ wait(&status); │

│ } │

L--------------------------------------------------------


Рисунок 7.21. Пример использования функции exec


В качестве примера можно привести программу (Рисунок 7.21), в

которой создается процесс-потомок, запускающий функцию exec. Сра-

зу по завершении функции fork процесс-родитель и процесс-потомок

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

программы. К моменту вызова процессом-потомком функции exec в его

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

данных располагаются строки "/bin/date" и "date", а в стеке - за-

писи, которые будут извлечены по выходе из exec. Ядро ищет файл

"/bin/date" в файловой системе, обнаружив его, узнает, что его

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

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

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

argv, является имя исполняемого файла (последняя компонента имени

пути поиска файла). Таким образом, процесс имеет доступ к имени

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

полезным (***). Затем ядро копирует строки "/bin/date" и "date"

во внутреннюю структуру хранения и освобождает области команд,

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

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

командная секция файла "/bin/date", в область данных - секция

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

ров (в данном случае это строка символов "date") и помещает его в

область стека. Вызвав функцию exec, процесс-потомок прекращает

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


---------------------------------------

(***) Например, в версии V стандартные программы переименования

файла (mv), копирования файла (cp) и компоновки файла (ln),

поскольку исполняют похожие действия, вызывают один и тот

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

узнает, какие действия в настоящий момент требуются пользо-

вателю.


"date"; когда программа "date" завершится, процесс-родитель, ожи-

дающий этого момента, получит код завершения функции exit.

Вплоть до настоящего момента мы предполагали, что команды и

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

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

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

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

различными процессами. Если бы команды и данные находились в од-

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

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

гаются. Если же команды и данные находятся в разных областях,

система имеет возможность пользоваться механизмами аппаратной за-

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

что-то записать в область, занятую командами, он получит отказ,

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

вершению процесса.


-------------------------------------------------------------┐

│ #include

│ main() │

│ { │

│ int i,*ip; │

│ extern f(),sigcatch(); │

│ │

│ ip = (int *)f; /* присвоение переменной ip значения ад-│

│ реса функции f */ │

│ for (i = 0; i < 20; i++) │

│ signal(i,sigcatch); │

│ *ip = 1; /* попытка затереть адрес функции f */ │


│ printf("после присвоения значения ip\n"); │

│ f(); │

│ } │

│ │

│ f() │

│ { │

│ } │

│ │

│ sigcatch(n) │

│ int n; │

│ { │

│ printf("принят сигнал %d\n",n); │

│ exit(1); │

│ } │

L-------------------------------------------------------------


Рисунок 7.22. Пример программы, ведущей запись в область ко-

манд


В качестве примера можно привести программу (Рисунок 7.22),

которая присваивает переменной ip значение адреса функции f и за-

тем делает распоряжение принимать все сигналы. Если программа

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

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

что-то по адресу в ip встретит порожденный системой защиты отказ,

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

ютере AT&T 3B20 ядро посылает процессу сигнал SIGBUS, в других

системах возможна посылка других сигналов. Процесс принимает сиг-

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

чать в процедуре main. Однако, если программа скомпилирована так,

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

данных), ядро не поймет, что процесс пытается затереть адрес

функции f. Адрес f станет равным 1. Процесс исполнит команду вы-

вода на печать в процедуре main, но когда запустит функцию f,

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

команды. Ядро пошлет процессу сигнал SIGILL и процесс завершится.

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

иск и предотвращение ошибок адресации. Тем не менее, в ранних

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

одной области, поскольку на машинах PDP размер процесса был силь-

но ограничен: программы имели меньший размер и существенно мень-

шую сегментацию, если команды и данные занимали одну и ту же

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

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

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

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

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

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

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

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

файла. Если один и тот же файл исполняется несколькими процесса-

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

на всех. Таким образом, когда ядро при выполнении функции exec

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

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

мого файла, что определяется "магическим числом" в заголовке фай-

ла. Если да, то с помощью алгоритма xalloc ядро ищет существующую

область с командами файла или назначает новую в случае ее отсутс-

твия (см. Рисунок 7.23).

Исполняя алгоритм xalloc, ядро просматривает список активных

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

совпадает с индексом исполняемого файла. В случае ее отсутствия

ядро выделяет новую область (алгоритм allocreg), присоединяет ее

к процессу (алгоритм attachreg), загружает ее в память (алгоритм

loadreg) и защищает от записи (read-only). Последний шаг предпо-

лагает, что при попытке процесса записать что-либо в область ко-

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

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

областей осуществляется проверка ее наличия в памяти (она может

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

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

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

detachreg при выполнении функций exit или exec, уменьшает значе-

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

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

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

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

Напомним, что если область при выполнении алгоритма allocreg

(Раздел 6.5.2) выделяется впервые, ядро увеличивает значение

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

значение счетчика ссылок нами уже было увеличено в самом начале

выполнения функции exec (алгоритм namei). Поскольку ядро уменьша-

ет значение счетчика только один раз в завершение выполнения

функции exec (по алгоритму iput), значение счетчика ссылок на ин-

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

полняемого в настоящий момент, равно по меньшей мере 1. Поэтому

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

мое файла остается нетронутым (не претерпевает изменений). После

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

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

щийся в таблице областей; этот указатель и будет идентифицировать


-------------------------------------------------------------┐

│ алгоритм xalloc /* выделение и инициализация области │

│ команд */ │

│ входная информация: индекс исполняемого файла │

│ выходная информация: отсутствует │

│ { │

│ если (исполняемый файл не имеет отдельной области команд)│

│ вернуть управление; │

│ если (уже имеется область команд, ассоциированная с ин- │

│ дексом исполняемого файла) │

│ { │

│ /* область команд уже существует ... подключиться к │

│ ней */ │

│ заблокировать область; │

│ выполнить пока (содержимое области еще не доступно) │

│ { │

│ /* операции над счетчиком ссылок, предохраняющие │

│ от глобального удаления области │

│ */ │

│ увеличить значение счетчика ссылок на область; │

│ снять с области блокировку; │

│ приостановиться (пока содержимое области не станет│

│ доступным); │

│ заблокировать область; │

│ уменьшить значение счетчика ссылок на область; │

│ } │

│ присоединить область к процессу (алгоритм attachreg);│

│ снять с области блокировку; │

│ вернуть управление; │

│ } │

│ /* интересующая нас область команд не существует -- соз- │

│ дать новую */ │

│ выделить область команд (алгоритм allocreg); /* область │

│ заблоки- │

│ рована */│

│ если (область помечена как "неотъемлемая") │

│ отключить соответствующий флаг; │

│ подключить область к виртуальному адресу, указанному в │

│ заголовке файла (алгоритм attachreg); │

│ если (файл имеет специальный формат для системы с замеще-│

│ нием страниц) │

│ /* этот случай будет рассмотрен в главе 9 */ │

│ в противном случае /* файл не имеет специального фор-│

│ мата */ │

│ считать команды из файла в область (алгоритм │

│ loadreg); │

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

│ областей процесса на "read-only"; │

│ снять с области блокировку; │

│ } │

L-------------------------------------------------------------


Рисунок 7.23. Алгоритм выделения областей команд


файл, связанный с областью. Если бы значение счетчика ссылок ста-

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

му файлу, тем самым делая сомнительным значение указателя на

индекс в записи таблицы областей: если бы пользователю пришлось

исполнить новый файл, используя функцию exec, ядро по ошибке свя-

зало бы его с областью команд старого файла. Эта проблема устра-

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

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

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

цесс во время выполнения функций exit или exec отсоединяет об-

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

(по алгоритму freereg), если только связь индекса с областью не

помечена как "неотъемлемая".


Таблица индексов Таблица областей

-----------------┐ что могло бы прои- -----------------┐

│ │ зойти, если бы счет- │ │

│ │ чик ссылок на индекс │ │

│ │ файла /bin/date был │ │

│ │ равен 0 +----------------+

│ │ │ область команд │

│ │ - - - - - -│- для файла │

│ │ │ │ /bin/who │

+----------------+ +----------------+

│ копия индекса -│- - - - - -- │ │

│ файла /bin/date│ │ │

│ в памяти <+-----------┐ │ │

+----------------+ │ +----------------+

│ │ │ │ область команд │

│ │ L-----------+- для файла │

│ │ указатель на│ /bin/date │

│ │ копию индек-+----------------+

│ │ са в памяти │ │

│ │ │ │

│ │ │ │

L----------------- L-----------------


Рисунок 7.24. Взаимосвязь между таблицей индексов и таблицей

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

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


Рассмотрим в качестве примера ситуацию, приведенную на Рисун-

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

процессе выполнения функции exec по отношению к файлу

"/bin/date" при условии расположения команд и данных файла в раз-

ных областях. Когда процесс исполняет файл "/bin/date" первый

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

тей (Рисунок 7.24) и по завершении выполнения функции exec остав-

ляет счетчик ссылок на индекс равным 1. Когда файл "/bin/date"

завершается, ядро запускает алгоритмы detachreg и freereg, сбра-

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

случае не увеличило значение счетчика, оно по завершении функции

exec останется равным 0 и индекс на всем протяжении выполнения

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

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

пустившему с помощью функции exec файл "/bin/who", тогда ядро мо-

жет выделить этому процессу индекс, ранее принадлежавший файлу "/

bin/date". Просматривая таблицу областей в поисках индекса файла

"/bin/who", ядро вместо него выбрало бы индекс файла "/bin/date".

Считая, что область содержит команды файла "/bin/who", ядро ис-

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

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

команд, должно быть не меньше единицы, чтобы ядро не могло пере-

назначить индекс другому файлу.

Возможность совместного использования различными процессами

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

чиваемое на запуск программы с помощью функции exec. Администра-

торы системы могут с помощью системной функции (и команды) chmod

устанавливать для часто исполняемых файлов режим "sticky-bit",

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

ет файл, для которого установлен режим "sticky-bit", ядро не ос-

вобождает область памяти, отведенную под команды файла, отсоеди-

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

exec, даже если значение счетчика ссылок на индекс становится

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

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

область не подключена больше ни к одному из процессов. Если же

файл будет еще раз запущен на выполнение (уже другим процессом),

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

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

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

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

не будет необходимости; если же команды выгружены во внешнюю па-

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

из файловой системы (см. об этом в главе 9).

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

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

"sticky-bit" (иными словами, когда область помечена как "неотъем-

лемая" часть файла или процесса), в следующих случаях:

1. Если процесс открыл файл для записи, в результате

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

будет затронуто и содержимое области.

2. Если процесс изменил права доступа к файлу (chmod), отменив

режим "sticky-bit", файл не должен оставаться в таблице об-

ластей.

3. Если процесс разорвал связь с файлом (unlink), он не сможет

больше исполнять этот файл, поскольку у файла не будет точки

входа в файловую систему; следовательно, и все остальные про-

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

ответствующей файлу. Поскольку область с командами файла

больше не используется, ядро может освободить ее вместе с ос-

тальными ресурсами, занимаемыми файлом.

4. Если процесс демонтирует файловую систему, файл перестает

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

В остальном - все как в предыдущем случае.

5. Если ядро использовало уже все пространство внешней памяти,

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

памяти за счет областей, имеющих пометку "sticky-bit", но не

используемых в настоящий момент. Несмотря на то, что эти об-

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

ядра являются более срочными.

В первых двух случаях область команд с пометкой "sticky-bit"

должна быть освобождена, поскольку она больше не отражает текущее

состояние файла. В остальных случаях это делается из практических

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

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

цессов (счетчик ссылок на нее имеет нулевое значение); в против-

ном случае это привело бы к аварийному завершению выполнения сис-

темных функций open, unlink и umount (случаи 1, 3 и 4, соответс-

твенно).

Если процесс запускает с помощью функции exec самого себя,

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

sh script

командный процессор shell порождает новый процесс (новую ветвь),