Как уже обсуждалось, в UNIX проведена четкая грань между программой и процессом . Каждый процесс в конкретный момент времени выполняет инструкции некоторой программы, которая может быть одной и той же для нескольких процессов. [39] Естественно, речь здесь идет о выполнении в режиме задачи, в режиме ядра процесс выполняет инструкции ядра операционной системы.
Примером может служить командный интерпретатор, с которым одновременно работают несколько пользователей, таким образом инструкции программы shell выполняют несколько различных процессов. Такие процессы могут совместно использовать один сегмент кода в памяти, но в остальном они являются изолированными друг от друга и имеют собственные сегменты данных и стека.
В любой момент процесс может запустить другую программу и начать выполнять ее инструкции; такую операцию он может сделать несколько раз.
В операционной системе UNIX имеются отдельные системные вызовы для создания (порождения) процесса, и для запуска новой программы. Системный вызов fork(2) создает новый процесс, который является точной копией родителя. После возвращения из системного вызова оба процесса выполняют инструкции одной и той же программы и имеют одинаковые сегменты данных и стека.
Тем не менее между родительским и дочерним процессом имеется ряд различий:
□ Дочернему процессу присваивается уникальный идентификатор PID, отличный от родительского.
□ Соответственно и идентификатор родительского процесса PPID для родителя и потомка различны.
□ Дочерний процесс получает собственную копию u-area и, в частности, собственные файловые дескрипторы, хотя он разделяет те же записи файловой таблицы.
□ Для дочернего процесса очищаются все ожидающие доставки сигналы.
□ Временная статистика выполнения процесса в режиме ядра и задачи для дочернего процесса обнуляется.
□ Блокировки памяти и записей, установленные родительским процессом, потомком не наследуются.
Более подробно наследуемые характеристики представлены в табл. 3.4.
Таблица 3.4. Наследование установок при создании процесса и запуске программы
Атрибут |
Наследование потомком ( fork(2) ) |
Сохранение при запуске программы ( exec(2) ) |
Сегмент кода (text) |
Да, разделяемый |
Нет |
Сегмент данных (data) |
Да, копируется при записи (copy-on-write) |
Нет |
Окружение |
Да |
Возможно |
Аргументы |
Да |
Возможно |
Идентификатор пользователя UID |
Да |
Да |
Идентификатор группы GID |
Да |
Да |
Эффективный идентификатор пользователя EUID |
Да |
Да (Нет, при вызове setuid(2) ) |
Эффективный идентификатор группы EGID |
Да |
Да (Нет, при вызове setgid(2) ) |
ID процесса (PID) |
Нет |
Да |
ID группы процессов |
Да |
Да |
ID родительского процесса (PPID) |
Нет |
Да |
Приоритет nice number |
Да |
Да |
Права доступа к создаваемому файлу |
Да |
Да |
Ограничение на размер файла |
Да |
Да |
Сигналы, обрабатываемые по умолчанию |
Да |
Да |
Игнорируемые сигналы |
Да |
Да |
Перехватываемые сигналы |
Да |
Нет |
Файловые дескрипторы |
Да |
Да, если для файлового дескриптора не установлен флаг FD_CLOEXEC (например, с помощью fcntl(2) ) |
Файловые указатели |
Да, разделяемые |
Да, если для файлового дескриптора не установлен флаг FD_CLOEXEC (например, с помощью fcntl(2) ) |
В общем случае вызов fork(2) выполняет следующие действия:
□ Резервирует место в области свопинга для сегмента данных и стека процесса.
□ Размещает новую запись proc
в таблице процессов и присваивает процессу уникальный идентификатор PID.
□ Инициализирует структуру proc
(поля структуры proc подробно рассматривались в разделе "Структуры данных процесса").
□ Размещает карты отображения, необходимые для трансляции адреса.
□ Размещает u-area процесса и копирует ее содержимое с родительского.
□ Создает соответствующие области процесса, часть из которых совпадает с родительскими.
□ Инициализирует аппаратный контекст процесса, копируя его с родительского.
□ Устанавливает в ноль возвращаемое дочернему процессу вызовом fork(2) значение.
□ Устанавливает возвращаемое родительскому процессу вызовом fork(2) значение равным PID потомка.
Читать дальше