Идентификатор процесса, породившего данный процесс.
Приоритет процесса (Nice Number)
Относительный приоритет процесса, учитываемый планировщиком при определении очередности запуска. Фактическое же распределение процессорных ресурсов определяется приоритетом выполнения, зависящим от нескольких факторов, в частности от заданного относительного приоритета. Относительный приоритет не изменяется системой на всем протяжении жизни процесса (хотя может быть изменен пользователем или администратором) в отличие от приоритета выполнения, динамически обновляемого ядром.
Терминальная линия (TTY)
Терминал или псевдотерминал, ассоциированный с процессом, если такой существует. Процессы-демоны не имеют ассоциированного терминала.
Реальный (RID) и эффективный (EUID) идентификаторы пользователя
Реальным идентификатором пользователя данного процесса является идентификатор пользователя, запустившего процесс. Эффективный идентификатор служит для определения прав доступа процесса к системным ресурсам (в первую очередь к ресурсам файловой системы). Обычно реальный и эффективный идентификаторы эквивалентны, т.е. процесс имеет в системе те же права, что и пользователь, запустивший его. Однако существует возможность задать процессу более широкие права, чем права пользователя путем установки флага SUID, когда эффективному идентификатору присваивается значение идентификатора владельца исполняемого файла (например, администратора).
Реальный (RGID) и эффективный (EGID) идентификаторы группы
Реальный идентификатор группы равен идентификатору первичной или текущей группы пользователя, запустившего процесс. Эффективный идентификатор служит для определения прав доступа к системным ресурсам по классу доступа группы. Так же как и для эффективного идентификатора пользователя, возможна его установка равным идентификатору группы владельца исполняемого файла (флаг SGID).
Команда ps(1) (process status) позволяет вывести список процессов, выполняющихся в системе, и их атрибуты:
$ ps -ef | head -20
UID PID PPID С STIME TTY TIME CMD
root 0 0 0 Dec 17 ? 0:00 sched
root 1 0 0 Dec 17 ? 0:01 /etc/init -
root 2 0 0 Dec 17 ? 0:00 pageout
root 3 0 0 Dec 17 ? 7:00 fsflush
root 164 1 0 Dec 17 ? 0:01 /usr/lib/sendmail -bd -q1h
fed 627 311 0 Dec 17 pts/3 0:27 emiclock
fed 314 304 0 Dec 17 pts/4 0:00 /usr/local/bin/bash
fed 3521 512 0 0:01 <defunct>
Более подробное описание полей вывода команды ps(1) приведено далее в разделе "Основные утилиты UNIX".
Жизненный путь процесса
Процесс в 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), между указанными стадиями, результат был бы следующим:
Пользователь работает в командном режиме:
UID PID PPID С STIME TTY TIME CMD
user1 745 1 10 10:11:34 ttyp4 0:01 sh
Пользователь запустил команду ls(1), и shell произвел вызов fork(2):
UID PID PPID С STIME TTY TIME CMD
user1 745 1 10 10:11:34 ttyp4 0:01 sh
user1 802 745 14 11:00:00 ttyp4 0:00 sh
Порожденный shell произвел вызов exec(2):
UID PID PPID С STIME TTY TIME CMD
user1 745 1 10 10:11:34 ttyp4 0:01 sh
user1 802 745 12 11:00:00 ttyp4 0:00 ls
Процесс ls(1) завершил работу:
UID PID PPID С STIME TTY TIME CMD
user1 745 1 10 10:11:34 ttyp4 0:01 sh
Описанная процедура запуска новой программы называется fork-and-exec.
Однако бывают ситуации, когда достаточно одного вызова fork(2) без последующего exec(2). В этом случае исполняемый код родительского процесса должен содержать логическое ветвление для родительского и дочернего процессов[9].
Все процессы в UNIX создаются посредством вызова fork(2). Запуск на выполнение новых задач осуществляется либо по схеме fork-and-exec, либо с помощью exec(2). "Прародителем" всех процессов является процесс init(1М), называемый также распределителем процессов. Если построить граф "родственных отношений" между процессами, то получится дерево, корнем которого является init(1M). Показанные на рис. 1.6 процессы sched и vhand являются системными и формально не входят в иерархию (они будут рассматриваться в следующих главах).
Рис. 1.6. Типичное "дерево" процессов в UNIX
Сигналы
Сигналы являются способом передачи от одного процесса другому или от ядра операционной системы какому-либо процессу уведомления о возникновении определенного события. Сигналы можно рассматривать как простейшую форму межпроцессного взаимодействия. В то же время сигналы больше напоминают программные прерывания, — средство, с помощью которого нормальное выполнение процесса может быть прервано. Например, если процесс производит деление на 0, ядро посылает ему сигнал SIGFPE, а при нажатии клавиш прерывания, обычно <Del> или <Ctrl>+<C>, текущему процессу посылается сигнал SIGINT.
9
Такое ветвление можно организовать на основании значения, возвращаемого системным вызовом