Лабораторна робота №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з назвою роботи та командами компіляції.
Питання для захисту
- Що повертає
forkбатьківському процесу, а що — нащадкові? - Чому після успішного
execкод, що йде далі, не виконується? - Що повертає
waitpidі що містить зміннаstatus? - Чим відрізняється нормальне завершення процесу від завершення сигналом і як це визначити макросами
WIFEXITED/WIFSIGNALED?
