Жизненный цикл процесса в UNIX и основные системные вызовы

Жизненный цикл процесса в операционной системе UNIX может быть разбит на несколько состояний. Переход из одного состояния в другое происходит в зависимости от наступления определенных событий в системе.

Возможны следующие состояния процесса:

1. Процесс выполняется в пользовательском режиме. При этом процессором выполняются прикладные инструкции данного процесса.

2. Процесс выполняется в режиме ядра. При этом процессором выполняются системные инструкции ядра от имени процесса.

3. Процесс не выполняется, но готов к запуску, как только планировщик выберет его, то есть процесс находиться в очереди на выполнение и обладает всеми необходимыми ему ресурсами, кроме процессора.

4. Процесс находиться в состоянии сна, ожидая недоступного в данный момент ресурса, например завершения операции ввода-вывода.

5. Процесс возвращается из режима ядра в режим задачи, но ядро прерывает его и производит переключение контекста для запуска более приоритетного процесса.

6. Процесс только что создан системным вызовом fork и находится в переходном состоянии: он существует, но не готов к запуску и не находиться в состоянии сна.

7. Процесс выполнил системный вызов exit и перешел в состояние зомби. Как такового процесса не существует, но остаются записи, содержащие код возврата и временную статистику его выполнения, доступную для родительского процесса. Это состояние является конечным в жизненном цикле процесса.

Процесс начинает свой жизненный путь с состояния 6, когда родительский процесс выполняет системный вызов fork. После того как создание процесса полностью завершено, процесс завершает "дочернюю часть" вызова fork и переходит в состояние 3 готовности к запуску, ожидая своей очереди на выполнение. Когда планировщик выбирает процесс для выполнения, он переходит в состояние 1 и выполняется в пользовательском режиме.

Выполнение в пользовательском режиме завершается в результате системного вызова или прерывания, и процесс переходит в режим ядра, в котором выполняется код системного вызова или прерывания. После этого процесс опять может вернуться в пользовательский режим. Однако во время выполнения системного вызова процесса в режиме ядра процессу может понадобиться недоступный в данный момент ресурс. Для ожидания доступа к такому ресурсу, процесс делает системный вызов sleep и переходит в состояние 4 – сна. При этом процесс добровольно освобождает вычислительные ресурсы, которые предоставляются следующему наиболее приоритетному процессу. Когда ресурс становиться доступным, ядро "пробуждает процесс", используя вызов wakeup, помещает его в очередь на выполнение, и процесс переходит в состояние 3 готовности к запуску.

Планирование процессов в UNIX основано на приоритете процесса. Планировщик всегда выбирает процесс с наивысшим приоритетом. Приоритет процесса не является фиксированным и динамически изменяется системой в зависимости от использования вычислительных ресурсов, времени ожидания запуска и текущего состояния процесса. Если процесс готов к запуску и имеет наивысший приоритет, планировщик приостановит выполнение текущего процесса (с более низким приоритетом), даже если последний не "выработал" свой временной квант.

Ядро UNIX является непрерываемым (nonpreemptive). Это означает, что процесс, находящийся в режиме ядра (в результате системного вызова или прерывания) и выполняющий системные инструкции, не может быть прерван системой, а вычислительные ресурсы переданы другому высокоприоритетному процессу. В этом состоянии выполняющийся процесс не может освободить процессор "по собственному желанию", в результате недоступности какого-либо ресурса, перейдя в состояние сна. В противном случае система может прервать выполнение процесса только при переходе из режима ядра в пользовательский режим. Такой подход значительно упрощает решение задач синхронизации и поддержки целостности структур данных ядра.

Новый процесс создается в UNIX только путем системного вызова fork. Процесс, сделавший вызов fork, называется родительским, а вновь созданный процесс – порожденным. Новый процесс является точной копией родительского. При порождении (разветвлении) процесса проверяется, достаточно ли памяти и места в таблице процессов для данного процесса. Если да, то образ текущего процесса копируется в новый образ процесса, и в таблице процессов возникает новый элемент. Новому процессу присваивается новый уникальный идентификатор (PID). Когда изменение таблицы процессов ядра завершается, процесс добавляется к списку процессов, доступных для выполнения и ожидающих в очереди планировщика подобно другим процессам.

Порожденный процесс отличается от родительского процесса следующими основными характеристиками:

· Порожденный процесс имеет свой уникальный идентификатор.

· Порожденный процесс имеет другой идентификатор родительского процесса, равный идентификатору породившего процесса.

· Порожденный процесс имеет свои собственные копии дескрипторов файлов (в частности, стандартных потоков), открытых родительским процессом. Каждый дескриптор файла порожденного процесса имеет первоначально такое же значение текущей позиции в файле, что и соответствующий родительский.

· У порожденного процесса обнуляются счетчики времени, потраченного системой для его обслуживания.

Обычно после порождения порожденный процесс выполняет системный вызов exec, перекрывающий сегменты текста и данных процесса новыми сегментами текста и данных, взятыми из указанного выполняемого файла. При этом аппаратный контекст процесса инициализируется заново.

Новый процесс наследует у процесса, вызвавшего exec, его основные характеристики:

· Значение поправки приоритета.

· Идентификатор процесса.

· Идентификатор родительского процесса.

· Идентификатор группы процессов.

· Терминальную линию.

· Текущий каталог.

· Корневой каталог.

· Маску создания файлов.

· Ограничения ресурсов.

· Счетчики времени, потраченного системой на обслуживание этого процесса.

· Блокировки доступа к сегментам файлов.

Процесс завершает работу в системе при выполнении системного вызова exit. Процесс может сам завершить свою работу, в соответствии с алгоритмом, либо может быть прекращен ядром.

Для получения информации о состоянии процессов используется команда ps. Команду ps может выполнять любой пользователь. Динамически отслеживать состояние всех процессов в системе можно при помощи утилиты top.

Сигналы

Сигналы обеспечивают механизм вызова определенной процедуры при наступлении некоторого события (аналогично прерываниям). Сигнал отправляется, когда происходит определенное событие, о наступлении которого должен быть уведомлен процесс.

Сигнал может посылаться одним процессом другому (с помощью системного вызоваkill) и будет доставлен, если оба процесса – одного пользователя или сигнал послан от имени пользователя root. Сигналы посылаются также ядром.

Ядро генерирует и посылает процессу сигнал в ответ на ряд событий, которые могут быть вызваны самим процессом, другим процессом, прерыванием или каким либо внешним событием.

Следует заметить, что любая обработка сигнала, в том числе и обработка по умолчанию, подразумевает, что процесс выполняется. На системах с высокой загрузкой это может привести к задержкам между отправлением и доставкой сигнала, т.к. процесс не может получить сигнал, пока не будет выбран планировщиком, и ему не будут предоставлены вычислительные ресурсы.

Детальная информация о сигналах представлена на страницах справочного руководства signal(вызываемого командой man signal).

Для посылки сигналов из командного интерпретатора используется команда kill. Она имеет следующий синтаксис:

kill [ -сигнал ] pid ...

Эта команда посылает указанный сигнал (по умолчанию сигнал SIGTERM– завершение процесса) всем процессам с указанными идентификаторами. Посылать сигнал можно и не существующему процессу – выдается предупреждение, но другим процессам сигнал посылается. Посылаемый сигнал задается по имени без префикса SIG или по номеру.

Два сигнала– 9 (KILL) и 19 (STOP) – всегда обрабатывает система. Первый из них нужен для того, чтобы убить процесснаверняка (отсюда и название). Сигнал STOP приостанавливает процесс: в таком состоянии процесс не удаляется из таблицы процессов, но и не выполняется до тех пор, пока не получит сигнал 18 (CONT) – после чего продолжит работу. В Linux сигналSTOP можно передать активному процессу с помощью управляющего символа Ctrl+z.

Наши рекомендации