Операционная система UNIX - Андрей Робачевский
Шрифт:
Интервал:
Закладка:
Когда процесс пробуждается, ядро устанавливает значение текущего приоритета процесса равным приоритету сна. Поскольку приоритет такого процесса находится в системном диапазоне и выше, чем приоритет режима задачи, вероятность предоставления процессу вычислительных ресурсов весьма велика. Такой подход позволяет, в частности, быстро завершить системный вызов, выполнение которого, в свою очередь, может блокировать некоторые системные ресурсы.
После завершения системного вызова перед возвращением в режим задачи ядро восстанавливает приоритет режима задачи, сохраненный перед выполнением системного вызова. Это может привести к понижению приоритета, что, в свою очередь, вызовет переключение контекста.
Текущий приоритет процесса в режиме задачи p_priuser зависит от двух факторов: значения nice number и степени использования вычислительных ресурсов p_cpu:
p_priuser = a*p_nice - b*p_cpu,
где p_nice — постоянная составляющая, зависящая от параметра nice.[36]
Задача планировщика разделения времени — справедливо распределить вычислительный ресурс между конкурирующими процессами. Для принятия решения о выборе следующего запускаемого процесса планировщику необходима информация об использовании процессора. Эта составляющая приоритета уменьшается обработчиком прерываний таймера каждый тик. Таким образом, пока процесс выполняется в режиме задачи, его текущий приоритет линейно уменьшается.
Каждую секунду ядро пересчитывает текущие приоритеты процессов, готовых к запуску (приоритеты которых меньше 65), последовательно увеличивая их.[37] Это перемещает процессы в более приоритетные очереди и повышает вероятность их последующего запуска.
Например, UNIX версии SVR3, использует следующую формулу:
p_cpu = p_cpu/2
Эта простая схема проявляет недостаток нивелирования приоритетов при повышении загрузки системы. Это происходит потому, что в этом случае каждый процесс получает незначительный объем вычислительных ресурсов и следовательно имеет малую составляющую p_cpu, которая еще более уменьшается благодаря формуле пересчета p_cpu. В результате степень использования процессора перестает оказывать заметное влияние на приоритет, и низкоприоритетные процессы (т.е. процессы с высоким nice number) практически "отлучаются" от вычислительных ресурсов системы.
В 4.3BSD UNIX для пересчета p_cpu используется другая формула:
p_cpu = p_cpu*(2*load)/(2*load+1)
Здесь параметр load равен среднему числу процессов, находившихся в очереди на выполнение за последнюю секунду, и характеризует среднюю загрузку системы за этот период времени. Этот алгоритм позволяет частично избавиться от недостатка планирования SVR3, поскольку при значительной загрузке системы уменьшение p_cpu при пересчете будет происходить медленнее.
Описанные алгоритмы планирования позволяют учесть интересы низкоприоритетных процессов, т.к. в результате длительного ожидания очереди на запуск приоритет таких процессов увеличивается, соответственно увеличивается и вероятность запуска. Представленные алгоритмы также обеспечивают более вероятный выбор планировщиком интерактивных процессов по отношению к вычислительным (фоновым). Такие задачи, как командный интерпретатор или редактор, большую часть времени проводят в ожидании ввода, имея, таким образом, высокий приоритет (приоритет сна). При наступлении ожидаемого события (например, пользователь осуществил ввод данных) им сразу же предоставляются вычислительные ресурсы. Фоновые процессы, потребляющие значительные ресурсы процессора, имеют высокую составляющую p_cpu и, как следствие, более низкий приоритет.
Как правило, очередь на выполнение не одна. Например, 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) выполняет следующие действия: