Несмотря на очевидные различия, историю возникновения и развития, нити и процессы объединяет общее назначение они являются примитивами выполнения некоторого набора последовательных инструкций. Откровенно говоря, нити, в общем, появились в операционных системах раньше, чем изолированные UNIX-процессы, в которые со временем вернулись UNIX-нити…
Процессы выполняют или разные последовательные программы целиком, или ветви одной параллельной программы, но в изолированном окружении со своим «частным» (private) набором ресурсов. Нити, наоборот, выполняют ветви одной параллельной программы в одном окружении с «общим» (shared) набором ресурсов.
В многозадачном ядре Linux вообще используется универсальное понятие «задача», которая может иметь как общие ресурсы (память, открытые файлы и т. д.) с другими задачами, так и частные ресурсы для своего собственного использования.
Порождение нового процесса реализуется при помощи системного вызова fork, в результате которого ядро, операционной системы создает новый дочерний (child) процесс PID2 — полную копию (COPY) процесса-родителя (parent) PID1.
Вся (за небольшими исключениями) память процесса — состояние, свойства, атрибуты (кроме идентификатора PID) и даже содержимое (программа с ее библиотеками) — наследуется дочерним процессом. Даже выполнение порожденного и порождающего процесса продолжится с одной и той же инструкции их одинаковой программы. Такое клонирование обычно использует параллельные программы с ветвями, выполняющимися в дочерних процессах.
Уничтожение процесса (например, при штатном, окончании программы) производится с помощью системного вызова exit. При этом родительскому процессу доставляется сигнал SIGCHILD, оповещающий о завершении дочернего процесса.
Статус завершения status, переданный дочерним процессом через аргументы exit, будет сохраняться ядром до момента его востребования родительским процессом при помощи системного вызова wait, а весь этот промежуток времени дочерний процесс будет находиться в состоянии Z (zombie) (см. столбец STAT).
Родительский процесс может завершиться раньше своих дочерних процессов, тогда логично предположить, что все «осиротевшие» процессы окажутся зомби по завершении, потому как просто некому будет востребовать их статус завершения.
На самом деле этого не происходит, потому что «осиротевшим» процессам назначается приемный родитель, в качестве которого выступает прародитель всех процессов init с идентификатором PID=1.
Запуск новой программы реализуется при помощи системного вызова ехес, в результате которого содержимое процесса PID1 полностью замещается запускаемой программой и библиотеками, от которых она зависит, а свойства и атрибуты (включая идентификатор PID) остаются неизменными.
Такое замещение обычно используется программами, устанавливающими нужные значения свойств и атрибутов процесса и подготавливающими ресурсы процесса к выполнению запускаемой программы.
Например, обработчик терминального доступа getty открывает заданный терминал, устанавливает режимы работы порта терминала, перенаправляет на терминал стандартные потоки ввода-вывода, а затем замещает себя программой аутентификации login.
Для запуска новой программы в новом процессе используются оба системных вызова fork и ехес, согласно принципу fork-and-exec «раздвоиться и запустить». В примере из листинга в статье дерево процессов сформировано именно на основе дочерне-родительских отношений между процессами, формирующимися при использовании принципа fork-and-exec.
Напримtр, командный интерпретатор bash по командам ps fx или man ps порождает дочерние процессы и замещает их программами ps и man. Тем же образом действует графический эмулятор терминала gnome-terminal — запуская новый сеанс пользователя на каждой из своих вкладок, он замещает свои дочерние процессы программой интерпретатора bash.
Листинг ниже иллюстрирует команду интерпретатора, запущенную в «фоновом» режиме при помощи конструкции асинхронного списка. Аналогично всем предыдущим командам, интерпретатор использует fork-and-exec для запуска программы в дочернем процессе с идентификатором 23228, но не дожидается его завершения при помощи системного вызова wait, как обычно, а немедленно продолжает интерактивное взаимодействие с пользователем, сообщив ему PID по-рожденного процесса и «номер задания» команды «заднего фона».
Оповещение о завершении своего дочернего процесса интерпретатор получит позже, при помощи сигнала SICCHLD, и отреагирует соответствующим сообщением об окончании команды «заднего фона».
Фоновое выполнение программ
fitz@ubuntu:~$ dd ifs/dev/dvd of=plan9.iso
[1] 23228
fitz@ubuntu:~$ ps f
PID TTY STAT TIME COMMAND
23625 pts/1 S 0:00 -bash
23228 pts/1 R 1:23 \_ dd if=/dev/dvd of=plan9.iso
23236 pts/1 R+ 0:00 \_ ps f
fitz@ubuntu:~$
fitz@ubuntu:~$ 586896+0 записей получено
586896+6 записей отправлено
скопировано 366496752 байта (300 МВ), 14,6916 с, 26,5 МВ/с
[1]+ Готово dd xf=/dev/dvd of=plan9.iso
В листинге ниже показана конвейерная конструкция интерпретатора, при помощи которой осуществляется поиск самого большого файла с суффиксом .html вниз по дереву каталогов, начиная с /usr/share/doc.
Эта конструкция реализуется при помощи fork-and-exec четырьмя параллельно порожденными дочерними процессами интерпретатора, в каждом из которых запущена программа соответствующей части конвейера, при этом дочерние процессы связаны неименованным каналом pipe — простейшим средством межпроцессного взаимодействия.
Встроенная команда интерпретатора wait реализует одноименный системный вызов wait и используется для ожидания окончания всех дочерних процессов конвейера, целиком запущенного в «фоновом» режиме.
Параллельный запуск взаимодействующих программ
fitz@ubuntu:~$ find /usr/share/doc -type f -name ‘*.html’ | xargs -n1 wc -l | sort -k 1 -nr | head -1 &
[1] 12827
fitz@ubuntu:~$ ps fj
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
11715 11716 11716 9184 pts/0 14699 S 1006 0:01 -bash
11716 12824 12824 9184 pts/0 14699 S 1006 0:00 \_ find … -type f -name *.html
11716 12825 12824 9184 pts/0 14699 S 1006 0:00 \_ xargs -n1 wc -l
11716 12826 12824 9184 pts/0 14699 S 1006 0:00 \_ sort -к 1 -nr
11716 12827 12824 9184 pts/0 14699 S 1006 0:00 \_ head -1
11716 14699 14699 9184 pts/0 14699 R+ 1006 0:00 \_ ps fj
fitz@ubuntu:~$ wait
41235 /usr/share/doc/libxll-dev/libX11/libX11. html
[1]+ Готово find … -type f -name ‘*.html’ | xargs -n1 wc -l | sort -k 1 -nr | head -1