Операционная система UNIX
Шрифт:
Каждую секунду ядро пересчитывает текущие приоритеты процессов, готовых к запуску (приоритеты которых меньше 65), последовательно увеличивая их. [37] Это перемещает процессы в более приоритетные очереди и повышает вероятность их последующего запуска.
Например, UNIX версии SVR3, использует следующую формулу:
Эта простая схема проявляет недостаток нивелирования приоритетов при повышении загрузки системы. Это происходит потому, что в этом случае каждый процесс получает незначительный объем вычислительных ресурсов и следовательно имеет малую составляющую
37
Ядро последовательно уменьшает отрицательную компоненту времени использования процессора.
В 4.3BSD UNIX для пересчета p_cpu используется другая формула:
Здесь параметр
Описанные алгоритмы планирования позволяют учесть интересы низкоприоритетных процессов, т.к. в результате длительного ожидания очереди на запуск приоритет таких процессов увеличивается, соответственно увеличивается и вероятность запуска. Представленные алгоритмы также обеспечивают более вероятный выбор планировщиком интерактивных процессов по отношению к вычислительным (фоновым). Такие задачи, как командный интерпретатор или редактор, большую часть времени проводят в ожидании ввода, имея, таким образом, высокий приоритет (приоритет сна). При наступлении ожидаемого события (например, пользователь осуществил ввод данных) им сразу же предоставляются вычислительные ресурсы. Фоновые процессы, потребляющие значительные ресурсы процессора, имеют высокую составляющую
Как правило, очередь на выполнение не одна. Например, SCO UNIX имеет 127 очередей — по одной на каждый приоритет. BSD UNIX использует 32 очереди, каждая из которых обслуживает диапазон приоритетов, например 0–3, 4–7 и т.д. При выборе следующего процесса на выполнение из одной очереди, т. е. из нескольких процессов с одинаковым текущим приоритетом, используется механизм кругового чередования (round robin). [38] Этот механизм запускается ядром через каждый временной квант для наиболее приоритетной очереди. Однако если в системе появляется готовый к запуску процесс с более высоким приоритетом, чем текущий, он будет запущен, не дожидаясь прошествия временного кванта. С другой стороны, если все процессы, готовые к запуску, находятся в низкоприоритетных по отношению к текущему процессу очередях, последний будет продолжать выполняться и в течение следующего временного кванта.
38
Round robin (англ.) означает петицию, подписи под которой располагаются по кругу — чтобы нельзя было определить, кто подписался первым. Отсюда и название схемы выбора процессов.
Создание процесса
Как уже обсуждалось, в UNIX проведена четкая грань между программой и процессом. Каждый процесс в конкретный момент времени выполняет инструкции некоторой программы, которая может быть одной и той же для нескольких процессов. [39] Примером может служить командный интерпретатор, с которым одновременно работают несколько пользователей, таким образом инструкции программы shell выполняют несколько различных процессов. Такие процессы могут совместно использовать один сегмент кода в памяти, но в остальном они являются изолированными друг от друга и имеют собственные сегменты данных и стека.
39
Естественно, речь здесь идет о выполнении в режиме задачи, в режиме ядра процесс выполняет инструкции ядра операционной системы.
В любой момент процесс может запустить другую программу и начать выполнять ее инструкции; такую операцию он может сделать несколько раз.
В операционной системе UNIX имеются отдельные системные вызовы для создания (порождения) процесса, и для запуска новой программы. Системный вызов fork(2) создает новый процесс, который является точной копией родителя. После возвращения из системного вызова оба процесса выполняют инструкции одной и той же программы и имеют одинаковые сегменты данных и стека.
Тем не менее между родительским и дочерним процессом имеется ряд различий:
Дочернему процессу
Соответственно и идентификатор родительского процесса 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) выполняет следующие действия:
Резервирует место в области свопинга для сегмента данных и стека процесса.
Размещает новую запись
Инициализирует структуру
Размещает карты отображения, необходимые для трансляции адреса.
Размещает u-area процесса и копирует ее содержимое с родительского.
Создает соответствующие области процесса, часть из которых совпадает с родительскими.
Инициализирует аппаратный контекст процесса, копируя его с родительского.
Устанавливает в ноль возвращаемое дочернему процессу вызовом fork(2) значение.
Устанавливает возвращаемое родительскому процессу вызовом fork(2) значение равным PID потомка.
Помечает процесс готовым к запуску и помещает его в очередь на выполнение.