Как правило, очередь на выполнение не одна. Например, SCO UNIX имеет 127 очередей — по одной на каждый приоритет. BSD UNIX использует 32 очереди, каждая из которых обслуживает диапазон приоритетов, например 0–3, 4–7 и т.д. При выборе следующего процесса на выполнение из одной очереди, т. е. из нескольких процессов с одинаковым текущим приоритетом, используется механизм кругового чередования (round robin).[38] Этот механизм запускается ядром через каждый временной квант для наиболее приоритетной очереди. Однако если в системе появляется готовый к запуску процесс с более высоким приоритетом, чем текущий, он будет запущен, не дожидаясь прошествия временного кванта. С другой стороны, если все процессы, готовые к запуску, находятся в низкоприоритетных по отношению к текущему процессу очередях, последний будет продолжать выполняться и в течение следующего временного кванта.
Создание процесса
Как уже обсуждалось, в 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.
38
Round robin (англ.) означает петицию, подписи под которой располагаются по кругу — чтобы нельзя было определить, кто подписался первым. Отсюда и название схемы выбора процессов.
39
Естественно, речь здесь идет о выполнении в режиме задачи, в режиме ядра процесс выполняет инструкции ядра операционной системы.