Перейти к содержанию

Состояния процессов, wait и sleep

Состояния процесса в Linux

Каждый процесс в Linux в каждый момент времени находится в одном из нескольких состояний. Состояние отражает, что процесс делает сейчас: выполняется, ждёт, остановлен или завершён.

Код Состояние Описание
R Running Процесс выполняется на CPU или находится в очереди готовых к выполнению
S Interruptible Sleep Процесс ждёт наступления события (I/O, таймер, сигнал); может быть разбужен сигналом
D Uninterruptible Sleep Процесс ждёт завершения операции (как правило, блочный I/O); не может быть прерван сигналом
T Stopped Процесс приостановлен (получил SIGSTOP или SIGTSTP)
Z Zombie Процесс завершился, но родитель ещё не прочитал его статус через wait()

Помимо основного состояния, ps может показывать дополнительные флаги:

Флаг Значение
< Высокий приоритет (отрицательный nice)
N Низкий приоритет (положительный nice)
I Idle — процесс спит более 20 секунд
s Лидер сессии
+ Процесс работает на переднем плане (foreground)
l Многопоточный процесс
w Процесс выгружен в swap
stateDiagram-v2
    [*] --> Runnable: fork() / clone()
    Runnable --> Running: получил CPU
    Running --> Runnable: вытеснён планировщиком
    Running --> Sleep: блокирующий I/O или syscall
    Running --> Stopped: SIGSTOP / SIGTSTP
    Running --> Zombie: exit() или смертельный сигнал
    Stopped --> Runnable: SIGCONT
    Sleep --> Runnable: событие наступило / SIGCONT / I/O готов / таймер
    Zombie --> [*]: wait() / waitpid() — reaped

S (interruptible) — ожидание I/O, таймера, блокировки; сигнал прерывает. D (uninterruptible) — ожидание блочного I/O; сигнал не прерывает.

Просмотр состояний:

ps aux                      # столбец STAT показывает состояние
ps -eo pid,state,comm       # только PID, состояние, имя

# Состояние конкретного процесса:
cat /proc/<pid>/status | grep State

Управление выполнением: foreground и background

Приостановка и возобновление процесса

Из терминала, где работает процесс:

Ctrl+Z          # отправляет SIGTSTP, переводит процесс в состояние Stopped

Из другого терминала:

kill -STOP <pid>    # эквивалент SIGSTOP (нельзя игнорировать)
kill -19 <pid>      # SIGSTOP = сигнал номер 19

Возобновить остановленный процесс:

kill -CONT <pid>    # отправить SIGCONT
kill -18 <pid>      # SIGCONT = сигнал номер 18

Команды fg и bg

fg (foreground) — перевести фоновое или остановленное задание на передний план. Процесс получает доступ к терминалу.

./prog &            # запустить в фоне (shell выводит [job_id] PID)
fg %1               # привести задание с job_id=1 на передний план
fg %prog            # привести задание по имени

bg (background) — возобновить остановленное задание в фоновом режиме.

./long_prog         # запустить интерактивно
Ctrl+Z              # остановить
bg                  # возобновить в фоне
# или явно:
bg %1

Посмотреть все фоновые задания текущей оболочки:

jobs -l             # список заданий с PID и статусом

Orphan-процессы

Orphan (осиротевший) — процесс, чей родитель завершился раньше него. Ядро автоматически «усыновляет» осиротевший процесс: его новым родителем становится init/systemd (PID 1). Это гарантирует, что когда orphan завершится, его статус будет корректно прочитан и запись в таблице процессов — убрана.

Orphan-процессы используются намеренно для создания демонов: процесс вызывает fork(), родитель немедленно завершается, дочерний (теперь orphan) продолжает работать в фоне под init.

Процессы-зомби

Зомби (zombie process) — процесс, который завершил своё выполнение, но запись о нём в таблице процессов ядра ещё не была удалена, потому что родитель не прочитал его статус выхода.

Механизм возникновения:

  1. дочерний процесс вызывает exit() или получает смертельный сигнал;
  2. ядро сохраняет статус завершения в структуре дескриптора процесса;
  3. если родитель не вызывает wait() / waitpid(), запись остаётся в таблице со статусом Z;
  4. зомби «живёт» до тех пор, пока родитель не прочитает статус или пока родитель не завершится (тогда зомби усыновляет init/systemd и немедленно убирает).

Пример кода, создающего зомби:

#include <unistd.h>
#include <stdio.h>

int main() {
    pid_t child = fork();

    if (child == 0) {
        printf("Child: завершаюсь\n");
        return 42;  // дочерний процесс завершился
    } else {
        // Родитель не вызывает wait() — дочерний становится зомби
        printf("Parent: жду ввода...\n");
        getchar();
    }
    return 0;
}

Найти зомби в системе:

ps aux | grep ' Z '
ps -eo pid,state,ppid,comm | awk '$2 == "Z"'

Устранение зомби:

  • вызывать wait() или waitpid() в родительском процессе;
  • установить обработчик SIGCHLD для автоматического сбора завершившихся дочерних процессов;
  • игнорировать SIGCHLD: signal(SIGCHLD, SIG_IGN) — ядро само будет убирать дочерние процессы.

Системный вызов wait

wait() и waitpid() блокируют родительский процесс до завершения одного из дочерних и позволяют получить статус его завершения.

wait

#include <sys/wait.h>

int status;
pid_t child_pid = wait(&status);  // ждать любого дочернего

waitpid

#include <sys/wait.h>

int status;

// Ждать любого дочернего:
pid_t child_pid = waitpid(-1, &status, 0);

// Ждать конкретного дочернего:
waitpid(12345, &status, 0);

// Неблокирующая проверка (вернёт 0, если дочерний ещё жив):
waitpid(-1, &status, WNOHANG);

Макросы для анализа статуса

if (WIFEXITED(status)) {
    int code = WEXITSTATUS(status);  // код возврата
    printf("Завершился с кодом %d\n", code);
}

if (WIFSIGNALED(status)) {
    int sig = WTERMSIG(status);  // номер сигнала
    printf("Убит сигналом %d\n", sig);
}

if (WIFSTOPPED(status)) {
    // Процесс был остановлен (при использовании WUNTRACED)
    int sig = WSTOPSIG(status);
    printf("Остановлен сигналом %d\n", sig);
}

Функции sleep

Для приостановки выполнения процесса на заданное время используются несколько функций.

sleep(n) — спать n секунд:

#include <unistd.h>

sleep(5);     // приостановить выполнение на 5 секунд

Функция может вернуться раньше, если процесс получит сигнал. В этом случае возвращается количество оставшихся секунд.

usleep(us) — спать заданное количество микросекунд (устарела):

#include <unistd.h>

usleep(500000);  // 500 000 микросекунд = 0.5 секунды

nanosleep() — наиболее точный вариант с разрешением до наносекунды:

#include <time.h>

struct timespec req = {
    .tv_sec  = 1,        // 1 секунда
    .tv_nsec = 500000000 // 500 миллисекунд
};
struct timespec rem;

if (nanosleep(&req, &rem) == -1) {
    // Прерван сигналом; rem содержит оставшееся время
}

Связанные темы

  • fork и exec — как создаётся дочерний процесс и почему появляются зомби
  • СигналыSIGCHLD, SIGSTOP, SIGCONT и их роль в управлении процессами
  • rlimit — ограничения на количество процессов (RLIMIT_NPROC)

Источники

  • man 2 wait — wait, waitpid
  • man 2 kill — отправка сигналов
  • man 1 ps — просмотр состояния процессов
  • man 1 jobs — управление заданиями оболочки
  • man 1 fg, man 1 bg — foreground и background
  • man 3 sleep — функция sleep
  • man 2 nanosleep — наносекундный sleep