Жизненный цикл процесса в 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.