Операционная система UNIX
Шрифт:
Реальный (RGID) и эффективный (EGID) идентификаторы группы
Реальный идентификатор группы равен идентификатору первичной или текущей группы пользователя, запустившего процесс. Эффективный идентификатор служит для определения прав доступа к системным ресурсам по классу доступа группы. Так же как и для эффективного идентификатора пользователя, возможна его установка равным идентификатору группы владельца исполняемого файла (флаг SGID).
Команда ps(1) (process status) позволяет вывести список процессов, выполняющихся в системе, и их атрибуты:
Более
Жизненный путь процесса
Процесс в UNIX создается системным вызовом fork(2). Процесс, сделавший вызов fork(2) называется родительским, а вновь созданный процесс — дочерним. Новый процесс является точной копией породившего его процесса. Как это ни удивительно, но новый процесс имеет те же инструкции и данные, что и его родитель. Более того, выполнение родительского и дочернего процесса начнется с одной и той же инструкции, следующей за вызовом fork(2). Единственно, чем они различаются — это идентификатором процесса PID. Каждый процесс имеет одного родителя, но может иметь несколько дочерних процессов.
Для запуска задачи, т.е. для загрузки новой программы, процесс должен выполнить системный вызов exec(2). При этом новый процесс не порождается, а исполняемый код процесса полностью замещается кодом запускаемой программы. Тем не менее окружение новой программы во многом сохраняется, в частности сохраняются значения переменных окружения, назначения стандартных потоков ввода/вывода, вывода сообщений об ошибках, а также приоритет процесса.
В UNIX запуск на выполнение новой программы часто связан с порождением нового процесса, таким образом сначала процесс выполняет вызов fork(2), порождая дочерний процесс, который затем выполняет exec(2), полностью замещаясь новой программой.
Рассмотрим эту схему на примере.
Допустим, пользователь, работая в командном режиме (в командном интерпретаторе shell) запускает команду ls(1). Текущий процесс (shell) делает вызов fork(2), порождая вторую копию shell. В свою очередь, порожденный shell вызывает exec(2), указывая в качестве параметра имя исполняемого файла, образ которого необходимо загрузить в память вместо кода shell. Код ls(1) замещает код порожденного shell, и утилита ls(1) начинает выполняться. По завершении работы ls(1) созданный процесс "умирает". Пользователь вновь возвращается в командный режим. Описанный процесс представлен на рис. 1.5. Мы также проиллюстрируем работу командного интерпретатора в примере, приведенном в главе 2.
Рис. 1.5. Создание процесса и запуск программы
Если сделать "отпечаток" выполняемых процессов, например командой ps(1), между указанными стадиями, результат был бы следующим:
Пользователь работает в командном режиме:
Пользователь запустил команду ls(1), и shell произвел вызов fork(2):
Порожденный shell
произвел вызов exec(2):
Процесс ls(1) завершил работу:
Описанная процедура запуска новой программы называется fork-and-exec.
Однако бывают ситуации, когда достаточно одного вызова fork(2) без последующего exec(2). В этом случае исполняемый код родительского процесса должен содержать логическое ветвление для родительского и дочернего процессов [9] .
9
Такое ветвление можно организовать на основании значения, возвращаемого системным вызовом fork(2). Для родительского процесса fork возвращает идентификатор созданного дочернего процесса, а дочерний процесс получает значение, равное 0. Подробнее эти вопросы будут рассмотрены в главе 2.
Все процессы в UNIX создаются посредством вызова fork(2). Запуск на выполнение новых задач осуществляется либо по схеме fork-and-exec, либо с помощью exec(2). "Прародителем" всех процессов является процесс init(1М), называемый также распределителем процессов. Если построить граф "родственных отношений" между процессами, то получится дерево, корнем которого является init(1M). Показанные на рис. 1.6 процессы sched и vhand являются системными и формально не входят в иерархию (они будут рассматриваться в следующих главах).
Рис. 1.6. Типичное "дерево" процессов в UNIX
Сигналы
Сигналы являются способом передачи от одного процесса другому или от ядра операционной системы какому-либо процессу уведомления о возникновении определенного события. Сигналы можно рассматривать как простейшую форму межпроцессного взаимодействия. В то же время сигналы больше напоминают программные прерывания, — средство, с помощью которого нормальное выполнение процесса может быть прервано. Например, если процесс производит деление на 0, ядро посылает ему сигнал
Для отправления сигнала служит команда kill(1):
где
10
Точнее, с реальным и эффективным идентификаторами процесса, посылающего сигнал. Если вы посылаете сигнал командой kill(1), работая в shell, то речь идет о командном интерпретаторе.