csignal

У операційних системах типу Unix (Linux, macOS) сигнали — це форма міжпроцесної взаємодії (IPC). Хоча більшість сигналів мають конкретне призначення (як-от SIGKILL для зупинки процесу), існують спеціальні сигнали, зарезервовані саме для користувацьких потреб.

#NAMEC/C++htop
1HUPSIGHUPSIGHUP“Hangup” (відбій). Раніше означав розрив зв’язку з терміналом. Зараз часто використовується для того, щоб змусити фонові програми (демони) перечитати конфігураційні файли.
2INTSIGINTSIGINT“Interrupt” (переривання). Виникає, коли ви натискаєте Ctrl+C.
3QUITSIGQUITSIGQUITСхожий на INT (натискання Ctrl+\), але змушує програму створити “core dump” (знімок пам’яті) для налагодження.
4ILLSIGILLSIGILL“Illegal Instruction”. Програма намагається виконати команду, яку процесор не розуміє.
5TRAPSIGTRAPSIGTRAPВикористовується налагоджувачами (debugger) для встановлення точок зупинки (breakpoints).
6ABRTSIGSIG“Abort”. Посилається самою програмою через функцію abort(), зазвичай при критичній помилці в коді.
7BUS-SIGBUSПомилка шини. Програма намагається отримати доступ за неправильною адресою пам’яті.
8FPESIGFPESIGFPE“Floating Point Exception”. Виникає при помилковій математичній операції, наприклад, при діленні на нуль.
9KILLSIGKILLSIGKILLНайпотужніший сигнал. Він миттєво вбиває процес. Його неможливо проігнорувати або перехопити через код.
10USR1-SIGUSR1Ці сигнали не мають визначеного системою значення.
11SEGVSIGSEGVSIGSEGV“Segmentation Fault”. Найвідоміша помилка програміста — спроба звернутися до пам’яті, яка програмі не належить.
12USR2-SIGUSR2Ці сигнали не мають визначеного системою значення.
13PIPESIGPIPESIGPIPEВиникає, коли програма намагається писати в “трубу” (pipe), інший кінець якої вже закритий.
14ALRMSIGALRMSIGALRMСигнал-будильник. Використовується для таймерів.
15TERMSIGTERMSIGTERM“Terminate”. Стандартний сигнал для ввічливого прохання до програми завершити роботу. На відміну від KILL, програма може його “спіймати” і встигнути зберегти дані.
16STKFLT-SIGSTKFLTПомилка стека на співпроцесорі (зараз майже не використовується).
17CHLD-SIGCHLD“Child”. Надсилається батьківському процесу, коли його дочірній процес завершується або зупиняється.
18CONT-SIGCONT“Continue”. Наказує зупиненому процесу продовжити роботу.
19STOP-SIGSTOPЗупиняє (ставить на паузу) процес. Як і KILL, його не можна перехопити.
20TSTP-SIGTSTP“Terminal Stop”. Виникає при натисканні Ctrl+Z. Зупиняє процес, але його можна перехопити.
21SIGTTIN-SIGTTINФоновий процес хоче read() з термінала.
22SIGTTOU-SIGTTOUФоновий процес хоче write() в термінал (якщо увімкнено tostop)
23URG-SIGURGТермінові дані в мережевому сокеті.
24XCPU-SIGXCPUПрограма перевищила ліміт часу роботи процесора.
25XFSZ-SIGXFSZПрограма намагається створити файл, більший за дозволений ліміт.
26VTALRM-SIGVTALRMВіртуальний таймер (враховує тільки час, коли процес працював).
27PROF-SIGPROFВикористовується для профайлінгу (вимірювання швидкості роботи коду).
28WINCH-SIGWINCHЗміна розміру вікна термінала.
29POLL-SIGPOLLПодія введення-виведення.
30PWR-SIGPWRПомилка живлення (наприклад, коли ДБЖ повідомляє про розряд батареї).
31SYS-SIGSYSНеправильний системний виклик.
33reserved-SIGCANCELВикористовуються NPTL (Native POSIX Thread Library). Використовується бібліотекою для скасування потоків.
33reserved-SIGSETXIDВикористовуються NPTL (Native POSIX Thread Library).Використовується для синхронізації змін ідентифікаторів користувача/групи (UID/GID) між усіма потоками одного процесу.
34-SIGRTMINSIGRTMIN-
35--SIGRTMIN+1-
..--SIGRTMIN+..-
64-SIGRTMAXSIGRTMIN+30-

CMakeLists.txt

project(sig)

add_executable(alpha alpha.cpp)
add_executable(betta betta.cpp)

file(WRITE ${CMAKE_BINARY_DIR}/.gitignore "*\n*.*")

alpha.cpp

#include <csignal>
#include <fstream>
#include <iostream>
#include <unistd.h>

// Обробник сигналів з параметром
void handle_signal(int sig, siginfo_t *info, void *context) {
  std::cout << "\n[Альфа] Отримано сигнал з параметром: "
            << info->si_value.sival_int << std::endl;
}

int main() {
  pid_t my_pid = getpid();
  std::cout << "Альфа запущена. PID: " << my_pid << std::endl;

  // 1. Записуємо свій PID
  std::ofstream out("alpha.pid");
  out << my_pid;
  out.close();

  // 2. Налаштовуємо обробник
  struct sigaction sa;
  sa.sa_sigaction = handle_signal;
  sigemptyset(&sa.sa_mask);
  sa.sa_flags = SA_SIGINFO;
  sigaction(SIGRTMIN, &sa, NULL);

  std::cout << "Чекаю на файл beta.pid та сигнали..." << std::endl;

  // 3. Цикл взаємодії
  while (true) {
    std::ifstream in("beta.pid");
    pid_t target_pid;
    if (in >> target_pid) {
      int val;
      std::cout << "Введіть число для надсилання Беті (або 0 для виходу): ";
      std::cin >> val;
      if (val == 0)
        break;

      sigval sv;
      sv.sival_int = val;
      sigqueue(target_pid, SIGRTMIN, sv);
    }
    in.close();
    sleep(1);
  }

  unlink("alpha.pid"); // Видаляємо файл при виході
  return 0;
}

betta.cpp

#include <csignal>
#include <fstream>
#include <iostream>
#include <unistd.h>

void handle_signal(int sig, siginfo_t *info, void *context) {
  std::cout << "\n[Бета] Отримано сигнал з параметром: "
            << info->si_value.sival_int << std::endl;
}

int main() {
  pid_t my_pid = getpid();
  std::cout << "Бета запущена. PID: " << my_pid << std::endl;

  // 1. Записуємо свій PID
  std::ofstream out("beta.pid");
  out << my_pid;
  out.close();

  // 2. Налаштовуємо обробник
  struct sigaction sa;
  sa.sa_sigaction = handle_signal;
  sigemptyset(&sa.sa_mask);
  sa.sa_flags = SA_SIGINFO;
  sigaction(SIGRTMIN, &sa, NULL);

  // 3. Читаємо PID Альфи та надсилаємо вітання
  std::cout << "Шукаю Альфу..." << std::endl;
  while (true) {
    std::ifstream in("alpha.pid");
    pid_t target_pid;
    if (in >> target_pid) {
      std::cout << "Альфу знайдено (PID: " << target_pid
                << "). Насилаю '777'..." << std::endl;
      sigval sv;
      sv.sival_int = 777;
      sigqueue(target_pid, SIGRTMIN, sv);
      break;
    }
    sleep(1);
  }

  // Залишаємося працювати для прийому сигналів
  while (true)
    pause();

  unlink("beta.pid");
  return 0;
}

systemctl

systemctl є головним інструментом для керування системою ініціалізації systemd. Коли ви віддаєте команду systemctl, вона не просто “вбиває” процес, а намагається керувати ним цивілізовано.

systemctl stop \[service\]

Це найчастіша операція. Systemd діє за певним алгоритмом:

  1. SIGTERM (15). Це “ввічливе” прохання завершити роботу. Програма отримує сигнал і має час, щоб зберегти файли, закрити з’єднання з базою даних і вийти самостійно.
  2. Очікування (Timeout). Systemd чекає (за замовчуванням 90 секунд).
  3. SIGKILL (9). Якщо програма не закрилася за відведений час, systemd надсилає “контрольний постріл”, який завершує процес миттєво без збереження даних.

systemctl restart \[service\]

Працює точно так само, як stop, а після повного завершення процесу запускає його знову.

systemctl reload \[service\]

Ця команда не зупиняє програму. Вона зазвичай надсилає сигнал SIGHUP (1). Більшість серверів (як-от Nginx або Apache) налаштовані так, що при отриманні SIGHUP вони перечитують свої конфігураційні файли, не перериваючи обслуговування користувачів.

Команда systemctlСигнал за замовчуваннямМета
stopSIGTERMБезпечне завершення.
killSIGTERM(або інший вказаний)Негайне надсилання сигналу всій групі процесів.
reloadSIGHUPОновлення конфігурації без перезапуску.
(якщо завис)SIGKILLПримусове завершення після таймауту.

Налаштування сигналів у .service файлі

  • KillSignal= Вказує, який сигнал надіслати першим при зупинці (systemctl stop). За замовчуванням це SIGTERM. Ви можете змінити його, наприклад, на SIGQUIT або SIGUSR1.
  • RestartKillSignal= Який сигнал використовувати при перезавантаженні.
  • FinalKillSignal= Який сигнал надіслати, якщо програма не закрилася вчасно (за замовчуванням SIGKILL). Його неможливо змінити на щось, що можна ігнорувати.
  • ExecReload= Тут ви можете вказати команду, яка виконується при systemctl reload. Часто туди пишуть /bin/kill -HUP $MAINPID.
[Unit]
Description=Мій кастомний C++ сервіс

[Service]
ExecStart=/usr/local/bin/my_app
# Змінюємо сигнал зупинки на SIGINT (як Ctrl+C)
KillSignal=SIGINT
# Даємо програмі 20 секунд на завершення перед примусовим вбивством
TimeoutStopSec=20
# Якщо натиснули reload, надіслати сигнал реального часу 34
ExecReload=/bin/kill -34 $MAINPID

[Install]
WantedBy=multi-user.target

Як перевірити, що програма отримала сигнал від systemd?

Коли ви зміните файл сервісу, не забудьте оновити конфігурацію systemd:

sudo systemctl daemon-reload

sudo systemctl restart my_service

Тепер ви можете перевірити логи вашої програми в реальному часі:

journalctl -u my_service -f