Логотип коледжу
Оптико-механічний фаховий коледж

Лабораторна робота №3. Керування життєвим циклом процесів у C/Linux

Мета

Навчитися створювати процеси через fork, запускати програму через exec, очікувати завершення через waitpid і аналізувати коди завершення за допомогою макросів WIFEXITED, WEXITSTATUS, WIFSIGNALED та WTERMSIG.

Підготовка

Перед виконанням опрацюйте пов'язану лекцію:

Для роботи потрібне Linux-середовище з GCC. Перевірте:

gcc --version

Робота виконується в межах папки lab-03. Права адміністратора не потрібні; усі команди безпечні для запуску в особистому каталозі.

Завдання

Завдання 1. Відтворіть fork + exec + waitpid

У папці lab-03 створіть файл process_demo.c:

#include <stdio.h>
#include <sys/wait.h>
#include <unistd.h>

int main(void) {
    printf("Батько: pid=%d, ppid=%d\n", getpid(), getppid());
    fflush(NULL);

    pid_t pid = fork();
    if (pid == -1) {
        perror("fork");
        return 1;
    }

    if (pid == 0) {
        printf("Нащадок: pid=%d, ppid=%d\n", getpid(), getppid());
        fflush(NULL);
        execlp("echo", "echo", "Нащадок запустив команду echo", (char *)NULL);
        perror("execlp");
        _exit(127);
    }

    int status;
    if (waitpid(pid, &status, 0) == -1) {
        perror("waitpid");
        return 1;
    }

    if (WIFEXITED(status)) {
        printf("Батько: нащадок завершився з кодом %d\n", WEXITSTATUS(status));
    } else if (WIFSIGNALED(status)) {
        printf("Батько: нащадок завершився від сигналу %d\n", WTERMSIG(status));
    }
    return 0;
}

Скомпілюйте та запустіть кілька разів:

gcc -Wall -Wextra -pedantic -o process_demo process_demo.c
./process_demo

Поясніть: чому fork повертає різні значення батькові й нащадкові, що робить execlp, чому після успішного exec код після виклику не виконується, і що означає waitpid(pid, &status, 0). Зверніть увагу на fflush(NULL) перед fork і перед exec: без нього буферизовані рядки printf можуть бути втрачені, коли exec замінює образ процесу, або з'явитися в несподіваному порядку. Це класична пастка під час роботи з fork і exec.

Завдання 2. Замініть програму нащадка

Змініть рядок з execlp так, щоб нащадок запускав ls -l:

execlp("ls", "ls", "-l", (char *)NULL);

Скомпілюйте та запустіть. Зверніть увагу, який вивід з'являється між рядком «Нащадок: ...» і повідомленням батька про код завершення.

Потім зробіть навмисну помилку: замініть "echo" на ім'я програми, якої не існує, наприклад "no-such-program-xyz". Запустіть і поясніть, що вивела функція perror і чому код завершення дорівнює 127.

Завдання 3. Прикладний: кілька нащадків та аналіз статусів

Створіть файл worker_children.c:

#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>

#define CHILDREN 3

int main(void) {
    pid_t pids[CHILDREN];

    for (int i = 0; i < CHILDREN; i++) {
        pid_t pid = fork();
        if (pid == -1) {
            perror("fork");
            return 1;
        }
        if (pid == 0) {
            sleep(1);
            if (i == 1) {
                abort();
            }
            _exit(10 + i);
        }
        pids[i] = pid;
    }

    for (int i = 0; i < CHILDREN; i++) {
        int status;
        if (waitpid(pids[i], &status, 0) == -1) {
            perror("waitpid");
            return 1;
        }
        if (WIFEXITED(status)) {
            printf("Нащадок %d: код завершення %d\n", i, WEXITSTATUS(status));
        } else if (WIFSIGNALED(status)) {
            printf("Нащадок %d: сигнал %d\n", i, WTERMSIG(status));
        }
    }
    return 0;
}

Скомпілюйте та запустіть:

gcc -Wall -Wextra -pedantic -o worker_children worker_children.c
./worker_children

Поясніть результат: чому нащадок 1 завершився від сигналу, а решта — із звичайним кодом завершення. Запустіть програму кілька разів і перевірте, чи завжди звіт батька друкується в тому самому порядку; поясніть, чому.

Очікуваний результат

Для завдання 1 типовий вивід має вигляд:

Батько: pid=1234, ppid=...
Нащадок: pid=1235, ppid=1234
Нащадок запустив команду echo
Батько: нащадок завершився з кодом 0

Точні значення pid і ppid залежать від системи та запуску. Для завдання 3 очікується:

Нащадок 0: код завершення 10
Нащадок 1: сигнал 6
Нащадок 2: код завершення 12

Значення 6 — це номер сигналу SIGABRT, який викликала функція abort().

Структура репозиторію

operational-systems/
├── README.md
├── lab-01/
├── lab-02/
└── lab-03/
    ├── process_demo.c
    └── worker_children.c

Що здати

  • Посилання на ваш особистий репозиторій operational-systems, надіслане у формі здачі на поточній сторінці.
  • Папку lab-03 з файлами process_demo.c і worker_children.c.
  • Оновлений кореневий README.md з назвою роботи та командами компіляції.

Питання для захисту

  1. Що повертає fork батьківському процесу, а що — нащадкові?
  2. Чому після успішного exec код, що йде далі, не виконується?
  3. Що повертає waitpid і що містить змінна status?
  4. Чим відрізняється нормальне завершення процесу від завершення сигналом і як це визначити макросами WIFEXITED / WIFSIGNALED?

Пов'язані лекції

Ресурси

Здати роботу