Архитектура операционной системы UNIX - Морис Бах
Шрифт:
Интервал:
Закладка:
2. Если процесс изменил права доступа к файлу (chmod), отменив режим «sticky-bit», файл не должен оставаться в таблице областей.
3. Если процесс разорвал связь с файлом (unlink), он не сможет больше исполнять этот файл, поскольку у файла не будет точки входа в файловую систему; следовательно, и все остальные процессы не будут иметь доступа к записи в таблице областей, соответствующей файлу. Поскольку область с командами файла больше не используется, ядро может освободить ее вместе с остальными ресурсами, занимаемыми файлом.
4. Если процесс демонтирует файловую систему, файл перестает быть доступным и ни один из процессов не может его исполнить. В остальном — все как в предыдущем случае.
5. Если ядро использовало уже все пространство внешней памяти, отведенное под выгрузку задач, оно пытается освободить часть памяти за счет областей, имеющих пометку «sticky-bit», но не используемых в настоящий момент. Несмотря на то, что эти области могут вскоре понадобиться другим процессам, потребности ядра являются более срочными.
В первых двух случаях область команд с пометкой «sticky-bit» должна быть освобождена, поскольку она больше не отражает текущее состояние файла. В остальных случаях это делается из практических соображений. Конечно же ядро освобождает область только при том условии, что она не используется ни одним из выполняющихся процессов (счетчик ссылок на нее имеет нулевое значение); в противном случае это привело бы к аварийному завершению выполнения системных функций open, unlink и umount (случаи 1, 3 и 4, соответственно).
Если процесс запускает с помощью функции exec самого себя, алгоритм выполнения функции несколько усложняется. По команде sh script командный процессор shell порождает новый процесс (новую ветвь), который инициирует запуск shell'а (с помощью функции exec) и исполняет команды файла «script». Если процесс запускает самого себя и при этом его область команд допускает совместное использование, ядру придется следить за тем, чтобы при обращении ветвей процесса к индексам и областям не возникали взаимные блокировки. Иначе говоря, ядро не может, не снимая блокировки со «старой» области команд, попытаться заблокировать «новую» область, поскольку на самом деле это одна и та же область. Вместо этого ядро просто оставляет «старую» область команд присоединенной к процессу, так как в любом случае ей предстоит повторное использование.
Обычно процессы вызывают функцию exec после функции fork; таким образом, во время выполнения функции fork процесс-потомок копирует адресное пространство своего родителя, но сбрасывает его во время выполнения функции exec и по сравнению с родителем исполняет образ уже другой программы. Не было бы более естественным объединить две системные функции в одну, которая бы загружала программу и исполняла ее под видом нового процесса? Ричи высказал предположение, что возникновение fork и exec как отдельных системных функций обязано тому, что при создании системы UNIX функция fork была добавлена к уже существующему образу ядра системы (см. [Ritchie 84a], стр.1584). Однако, разделение fork и exec важно и с функциональной точки зрения, поскольку в этом случае процессы могут работать с дескрипторами файлов стандартного ввода-вывода независимо, повышая тем самым «элегантность» использования каналов. Пример, показывающий использование этой возможности, приводится в разделе 7.8.
7.6 КОД ИДЕНТИФИКАЦИИ ПОЛЬЗОВАТЕЛЯ ПРОЦЕССА
Ядро связывает с процессом два кода идентификации пользователя, не зависящих от кода идентификации процесса: реальный (действительный) код идентификации пользователя и исполнительный код или setuid (от «set user ID» — установить код идентификации пользователя, под которым процесс будет исполняться). Реальный код идентифицирует пользователя, несущего ответственность за выполняющийся процесс. Исполнительный код используется для установки прав собственности на вновь создаваемые файлы, для проверки прав доступа к файлу и разрешения на посылку сигналов процессам через функцию kill. Процессы могут изменять исполнительный код, запуская с помощью функции exec программу setuid или запуская функцию setuid в явном виде.
Программа setuid представляет собой исполняемый файл, имеющий в поле режима доступа установленный бит setuid. Когда процесс запускает программу setuid на выполнение, ядро записывает в поля, содержащие реальные коды идентификации, в таблице процессов и в пространстве процесса код идентификации владельца файла. Чтобы как-то различать эти поля, назовем одно из них, которое хранится в таблице процессов, сохраненным кодом идентификации пользователя. Рассмотрим пример, иллюстрирующий разницу в содержимом этих полей.
Синтаксис вызова системной функции setuid:
setuid(uid)
где uid — новый код идентификации пользователя. Результат выполнения функции зависит от текущего значения реального кода идентификации. Если реальный код идентификации пользователя процесса, вызывающего функцию, указывает на суперпользователя, ядро записывает значение uid в поля, хранящие реальный и исполнительный коды идентификации, в таблице процессов и в пространстве процесса. Если это не так, ядро записывает uid в качестве значения исполнительного кода идентификации в пространстве процесса и то только в том случае, если значение uid равно значению реального кода или значению сохраненного кода. В противном случае функция возвращает вызывающему процессу ошибку. Процесс наследует реальный и исполнительный коды идентификации у своего родителя (в результате выполнения функции fork) и сохраняет их значения после вызова функции exec.
На Рисунке 7.25 приведена программа, демонстрирующая использование функции setuid. Предположим, что исполняемый файл, полученный в результате трансляции исходного текста программы, имеет владельца с именем «maury» (код идентификации 8319) и установленный бит setuid; право его исполнения предоставлено всем пользователям. Допустим также, что пользователи «mjb» (код идентификации 5088) и «maury» являются владельцами файлов с теми же именами, каждый из которых доступен только для чтения и только своему владельцу. Во время исполнения программы пользователю «mjb» выводится следующая информация:
uid 5088 euid 8319
fdmjb -1 fdmaury 3
after setuid(5088): uid 5088 euid 5088
fdmjb 4 fdmaury -1
after setuid(8319): uid 5088 euid 8319
Системные функции getuid и geteuid возвращают значения реального и исполнительного кодов идентификации пользователей процесса, для пользователя «mjb» это, соответственно, 5088 и 8319. Поэтому процесс не может открыть файл «mjb» (ибо он имеет исполнительный код идентификации пользователя (8319), не разрешающий производить чтение файла), но может открыть файл «maury». После вызова функции setuid, в результате выполнения которой в поле исполнительного кода идентификации пользователя («mjb») заносится значение реального кода идентификации, на печать выводятся значения и того, и другого кода идентификации пользователя "mjb": оба равны 5088. Теперь процесс может открыть файл „mjb“, поскольку он исполняется под кодом идентификации пользователя, имеющего право на чтение из файла, но не может открыть файл „maury“. Наконец, после занесения в поле исполнительного кода идентификации значения, сохраненного функцией setuid (8319), на печать снова выводятся значения 5088 и 8319. Мы показали, таким образом, как с помощью программы setuid процесс может изменять значение кода идентификации пользователя, под которым он исполняется.
#include ‹fcntl.h›
main() {
int uid, euid, fdmjb, fdmaury;
uid = getuid(); /* получить реальный UID */
euid = geteuid(); /* получить исполнительный UID */
printf("uid %d euid %dn", uid, euid);
fdmjb = open("mjb", O_RDONLY);
fdmaury = open("maury", O_RDONLY);
printf("fdmjb %d fdmaury %dn", fdmjb, fdmaury);
setuid(uid);
printf("after setuid(%d): uid %d euid %dn", uid, getuid(), geteuid());
fdmjb = open("mjb", O_RDONLY);
fdmaury = open("maury", O_RDONLY);
printf("fdmjb %d fdmaury %dn", fdmjb, fdmaury);
setuid(uid);
printf("after setuid(%d): uid %d euid %dn", euid, getuid(), geteuid());
}
Рисунок 7.25. Пример выполнения программы setuid
Во время выполнения программы пользователем „maury“ на печать выводится следующая информация:
uid 8319 euid 8319
fdmjb -1 fdmaury 3
after setuid(8319): uid 8319 euid 8319
fdmjb -1 fdmaury 4
after setuid(8319): uid 8319 euid 8319
Реальный и исполнительный коды идентификации пользователя во время выполнения программы остаются равны 8319: процесс может открыть файл „maury“, но не может открыть файл „mjb“. Исполнительный код, хранящийся в пространстве процесса, занесен туда в результате последнего исполнения функции или программы setuid; только его значением определяются права доступа процесса к файлу. С помощью функции setuid исполнительному коду может быть присвоено значение сохраненного кода (из таблицы процессов), т. е. то значение, которое исполнительный код имел в самом начале.