16-bo‘lim

Signallar

Signal turlari, sigaction, async-signal-safe funksiyalar, signal niqobi, SIGCHLD va zombi jarayonlar, xizmatni to'g'ri to'xtatish.

🕑 19 daqiqa o‘qish 📄 982 so‘z 👁 1 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. Asosiy signallar
  2. Signal ishlovchisi
  3. Ishlovchi ichida nima qilish mumkin?
  4. Signal niqobi
  5. SIGCHLD va zombi jarayonlar
  6. SIGKILL va SIGSTOP ni tutib bo'lmaydi
  7. Muloyim to'xtatish
  8. Taymer signali
  9. Signal yuborish
  10. Xulosa

Signal - jarayonga yuboriladigan dasturiy uzilish. U kutilgan joyda emas, istalgan buyruq orasida yetib keladi.

Asosiy signallar #

SignalRaqamOdatiy ta'sirSabab
SIGHUP1TugatishTerminal yopildi / qayta o'qish
SIGINT2TugatishCtrl+C
SIGQUIT3Tugatish + coreCtrl+\
SIGKILL9To'xtatib bo'lmaydiMajburiy o'ldirish
SIGSEGV11Tugatish + coreXotira buzilishi
SIGPIPE13TugatishYopiq quvurga yozish
SIGALRM14TugatishTaymer ishladi
SIGTERM15TugatishMuloyim to'xtatish so'rovi
SIGCHLD17E'tiborsizBola jarayon holati o'zgardi
SIGSTOP19To'xtatib bo'lmaydiJarayonni muzlatish
SIGCONT18Davom ettirishMuzlagan jarayonni tiklash
Terminal
kill -l
Natija
 1) SIGHUP     2) SIGINT     3) SIGQUIT     4) SIGILL
 5) SIGTRAP     6) SIGABRT     7) SIGBUS     8) SIGFPE
 9) SIGKILL    10) SIGUSR1    11) SIGSEGV    12) SIGUSR2
13) SIGPIPE    14) SIGALRM    15) SIGTERM    17) SIGCHLD
18) SIGCONT    19) SIGSTOP    20) SIGTSTP    21) SIGTTIN

Signal ishlovchisi #

C
#include <stdio.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>

static volatile sig_atomic_t keldi = 0;

static void ishlovchi(int raqam) {
    keldi = raqam;
}

int main(void) {
    struct sigaction sa;

    memset(&sa, 0, sizeof sa);
    sa.sa_handler = ishlovchi;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_RESTART;

    if (sigaction(SIGUSR1, &sa, NULL) < 0) {
        return 1;
    }

    printf("SIGUSR1 uchun ishlovchi o'rnatildi\n");

    /* O'zimizga signal yuboramiz */
    raise(SIGUSR1);

    printf("Signal keldimi?     : %s\n", keldi != 0 ? "ha" : "yo'q");
    printf("Qaysi signal        : %d\n", (int) keldi);
    printf("SIGUSR1 mi?         : %s\n", keldi == SIGUSR1 ? "ha" : "yo'q");

    /* kill bilan ham yuborish mumkin */
    keldi = 0;
    kill(getpid(), SIGUSR1);

    printf("kill orqali keldimi?: %s\n", keldi == SIGUSR1 ? "ha" : "yo'q");

    return 0;
}
Natija
SIGUSR1 uchun ishlovchi o'rnatildi
Signal keldimi?     : ha
Qaysi signal        : 10
SIGUSR1 mi?         : ha
kill orqali keldimi?: ha
signal emas, sigaction ishlating

Eski signal() funksiyasi turli tizimlarda turlicha ishlaydi:

Xususiyatsignal (BSD)signal (System V)
Ishlovchi qayta o'rnatiladimiYo'qHa (har chaqiruvdan keyin)
Uzilgan tizim chaqiruviQayta boshlanadiXato qaytaradi

System V xatti-harakatida signal ikki marta tez kelsa, ikkinchisi odatiy ta'sirni bajaradi - dastur o'ladi.

sigaction esa har joyda bir xil ishlaydi va aniq bayroqlarni beradi:

BayroqMa'nosi
SA_RESTARTUzilgan tizim chaqiruvini qayta boshlash
SA_NOCLDWAITBola zombi bo'lmasin
SA_SIGINFOIshlovchiga qo'shimcha ma'lumot berish

Yangi kodda doim sigaction ishlating.

Ishlovchi ichida nima qilish mumkin? #

C
#include <stdio.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>

static volatile sig_atomic_t bayroq = 0;

/* TO'G'RI: faqat bayroq qo'yadi va write ishlatadi */
static void yaxshi_ishlovchi(int raqam) {
    (void) raqam;

    bayroq = 1;

    const char *m = "[ishlovchi: write xavfsiz]\n";
    ssize_t n = write(STDOUT_FILENO, m, strlen(m));
    (void) n;
}

int main(void) {
    struct sigaction sa;

    memset(&sa, 0, sizeof sa);
    sa.sa_handler = yaxshi_ishlovchi;
    sigemptyset(&sa.sa_mask);

    sigaction(SIGUSR1, &sa, NULL);

    printf("Signal yuborishdan oldin\n");
    fflush(stdout);

    raise(SIGUSR1);

    printf("Bayroq qiymati: %d\n", (int) bayroq);
    printf("Asosiy ish shu yerda bajariladi\n");

    return 0;
}
Natija
Signal yuborishdan oldin
[ishlovchi: write xavfsiz]
Bayroq qiymati: 1
Asosiy ish shu yerda bajariladi
Ishlovchi ichida printf ishlatmang

Signal istalgan payt kelishi mumkin - shu jumladan printf ning o'rtasida.

Agar ishlovchi ham printf chaqirsa, stdio buferi buziladi: natija yo'qoladi, ikki marta chiqadi yoki dastur qotib qoladi (malloc mutexi tutilib qolsa).

Ishlovchi ichida faqat async-signal-safe funksiyalar mumkin:

RuxsatTaqiqlangan
writeprintf, fprintf
_exitexit
kill, raisemalloc, free
signal, sigactionstrerror
read, closeKo'pchilik libc funksiyalari

To'liq ro'yxat:

Terminal
man 7 signal-safety

Amaliy qoida: ishlovchi faqat bayroq qo'ysin, qolgan ishni asosiy sikl bajarsin:

C
while (ishlaymiz) {
    if (bayroq) {
        bayroq = 0;
        haqiqiy_ishni_bajarish();      /* bu yerda hammasi mumkin */
    }
}

Bayroq turi albatta volatile sig_atomic_t bo'lsin.

Signal niqobi #

C
#include <stdio.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>

static volatile sig_atomic_t sanoq = 0;

static void ishlovchi(int raqam) {
    (void) raqam;
    sanoq++;
}

int main(void) {
    struct sigaction sa;

    memset(&sa, 0, sizeof sa);
    sa.sa_handler = ishlovchi;
    sigemptyset(&sa.sa_mask);
    sigaction(SIGUSR1, &sa, NULL);

    /* SIGUSR1 ni bloklaymiz */
    sigset_t niqob, eski;
    sigemptyset(&niqob);
    sigaddset(&niqob, SIGUSR1);
    sigprocmask(SIG_BLOCK, &niqob, &eski);

    raise(SIGUSR1);

    printf("Bloklangan holatda sanoq: %d\n", (int) sanoq);

    /* Kutilayotgan signallarni tekshiramiz */
    sigset_t kutayotgan;
    sigpending(&kutayotgan);

    printf("SIGUSR1 kutayaptimi?    : %s\n",
           sigismember(&kutayotgan, SIGUSR1) ? "ha" : "yo'q");

    /* Blokni olamiz - signal darhol yetkaziladi */
    sigprocmask(SIG_SETMASK, &eski, NULL);

    printf("Blok olingandan keyin   : %d\n", (int) sanoq);

    return 0;
}
Natija
Bloklangan holatda sanoq: 0
SIGUSR1 kutayaptimi?    : ha
Blok olingandan keyin   : 1
Oddiy signallar to'planmaydi

Bloklangan paytda bir xil signal 100 marta kelsa ham, blok olingach faqat bir marta yetkaziladi.

Sabab: kutayotgan signallar bitli niqob (sigset_t) da saqlanadi - har signal uchun bitta bit.

Bu quyidagi xatoga olib keladi:

C
/* NOTO'G'RI: har SIGCHLD uchun bitta wait */
static void sigchld_ishlovchi(int r) {
    waitpid(-1, NULL, WNOHANG);        /* faqat bittasini yig'adi */
}

10 ta bola bir vaqtda tugasa, siz 1-2 ta signal olasiz va qolgan bolalar zombi bo'lib qoladi.

To'g'ri usul - hammasini yig'ish:

C
static void sigchld_ishlovchi(int r) {
    (void) r;
    while (waitpid(-1, NULL, WNOHANG) > 0) {
        /* barcha tugaganlarni yig'amiz */
    }
}

Real-time signallar (SIGRTMIN..SIGRTMAX) esa navbatga qo'yiladi va yo'qolmaydi.

SIGCHLD va zombi jarayonlar #

C
#include <stdio.h>
#include <signal.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/wait.h>

static volatile sig_atomic_t yigildi = 0;

static void sigchld_ishlovchi(int raqam) {
    (void) raqam;

    int saqlangan = errno;

    /* while - chunki signallar to'planmaydi */
    while (waitpid(-1, NULL, WNOHANG) > 0) {
        yigildi++;
    }

    errno = saqlangan;
}

int main(void) {
    struct sigaction sa;

    memset(&sa, 0, sizeof sa);
    sa.sa_handler = sigchld_ishlovchi;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_RESTART | SA_NOCLDSTOP;

    sigaction(SIGCHLD, &sa, NULL);

    /* Signalni bloklaymiz - poyga holatidan himoya */
    sigset_t niqob, bosh;
    sigemptyset(&niqob);
    sigaddset(&niqob, SIGCHLD);
    sigprocmask(SIG_BLOCK, &niqob, &bosh);

    fflush(stdout);

    /* Beshta bola yaratamiz */
    for (int i = 0; i < 5; i++) {
        pid_t b = fork();

        if (b == 0) {
            _exit(0);
        }
    }

    /* sigsuspend - niqobni ochish va kutish atomar bajariladi */
    while (yigildi < 5) {
        sigsuspend(&bosh);
    }

    sigprocmask(SIG_SETMASK, &bosh, NULL);

    printf("Yaratilgan bolalar : 5\n");
    printf("Yig'ilgan bolalar  : %d\n", (int) yigildi);
    printf("Zombi qoldimi?     : %s\n", yigildi == 5 ? "yo'q" : "ha");

    return 0;
}
Natija
Yaratilgan bolalar : 5
Yig'ilgan bolalar  : 5
Zombi qoldimi?     : yo'q
Ishlovchi ichida errno ni saqlang

Signal read ning o'rtasida kelishi mumkin. Agar ishlovchi waitpid chaqirib errno ni o'zgartirsa, asosiy kod noto'g'ri xato kodini ko'radi.

Shuning uchun ishlovchi boshida saqlab, oxirida tiklang:

C
int saqlangan = errno;
/* ... ish ... */
errno = saqlangan;

Bu kichik detal, lekin uni unutish juda chalkash xatolarga olib keladi.

Zombi umuman kerak bo'lmasa, eng oddiy yechim:

C
signal(SIGCHLD, SIG_IGN);

Bu holda yadro bolalarni avtomatik yig'adi. Lekin siz ularning chiqish kodini bila olmaysiz.

SIGKILL va SIGSTOP ni tutib bo'lmaydi #

C
#include <stdio.h>
#include <signal.h>
#include <string.h>
#include <errno.h>

static void ishlovchi(int raqam) {
    (void) raqam;
}

int main(void) {
    struct sigaction sa;

    memset(&sa, 0, sizeof sa);
    sa.sa_handler = ishlovchi;
    sigemptyset(&sa.sa_mask);

    int term = sigaction(SIGTERM, &sa, NULL);
    printf("SIGTERM ni tutish: %s\n", term == 0 ? "mumkin" : "mumkin emas");

    errno = 0;
    int kill_natija = sigaction(SIGKILL, &sa, NULL);
    printf("SIGKILL ni tutish: %s\n",
           kill_natija == 0 ? "mumkin" : "mumkin emas");
    printf("  Xato kodi EINVAL mi? %s\n", errno == EINVAL ? "ha" : "yo'q");

    errno = 0;
    int stop_natija = sigaction(SIGSTOP, &sa, NULL);
    printf("SIGSTOP ni tutish: %s\n",
           stop_natija == 0 ? "mumkin" : "mumkin emas");

    /* Bloklashga ham urinib ko'ramiz */
    sigset_t niqob;
    sigemptyset(&niqob);
    sigaddset(&niqob, SIGKILL);
    sigprocmask(SIG_BLOCK, &niqob, NULL);

    sigset_t joriy;
    sigprocmask(SIG_BLOCK, NULL, &joriy);

    printf("\nSIGKILL bloklandimi? %s\n",
           sigismember(&joriy, SIGKILL) ? "ha" : "yo'q - yadro e'tiborsiz qoldirdi");

    return 0;
}
Natija
SIGTERM ni tutish: mumkin
SIGKILL ni tutish: mumkin emas
  Xato kodi EINVAL mi? ha
SIGSTOP ni tutish: mumkin emas

SIGKILL bloklandimi? yo'q - yadro e'tiborsiz qoldirdi
Nima uchun SIGKILL tutilmaydi

Agar dastur SIGKILL ni tuta olsa, uni hech qachon to'xtatib bo'lmasdi. Administrator uchun oxirgi chora qolmasdi.

Shuning uchun yadro bu ikki signalni maxsus ko'radi: ular ishlovchiga umuman yetib bormaydi, yadro to'g'ridan-to'g'ri harakat qiladi.

Lekin bitta istisno bor: D holatidagi (uninterruptible sleep) jarayonni SIGKILL ham o'ldirmaydi.

Terminal
ps -eo pid,stat,comm | awk '$2 ~ /D/'
Natija
  4521 D    nfsd

Bu jarayon drayver ichida qotib qolgan - odatda buzilgan disk yoki javob bermayotgan NFS server sababli. Yagona yechim - muammoni manbada hal qilish yoki tizimni qayta yuklash.

Muloyim to'xtatish #

Xizmatni to'g'ri to'xtatish tartibi 1. SIGTERM "iltimos tugat" dastur o'zi tozalaydi 2. Kutish 30-90 soniya ulanishlar tugasin 3. SIGKILL majburiy o'ldirish ma'lumot yo'qolishi mumkin SIGTERM kelganda dastur nima qilishi kerak Yangi so'rovlarni qabul qilishni to'xtatish Boshlangan so'rovlarni tugatish Buferlarni diskka yozish, ulanishlarni yopish
Docker va systemd aynan shu tartibda ishlaydi
C
#include <stdio.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>

static volatile sig_atomic_t toxtash = 0;

static void toxtatish_signali(int raqam) {
    (void) raqam;
    toxtash = 1;
}

int main(void) {
    struct sigaction sa;

    memset(&sa, 0, sizeof sa);
    sa.sa_handler = toxtatish_signali;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_RESTART;

    sigaction(SIGTERM, &sa, NULL);
    sigaction(SIGINT, &sa, NULL);

    printf("Xizmat ishga tushdi\n");

    int qayta_ishlangan = 0;

    /* Asosiy sikl */
    while (!toxtash) {
        qayta_ishlangan++;

        /* Ish simulyatsiyasi: 100 ta so'rovdan keyin signal olamiz */
        if (qayta_ishlangan == 100) {
            raise(SIGTERM);
        }

        if (qayta_ishlangan > 200) {
            break;                     /* xavfsizlik chegarasi */
        }
    }

    printf("To'xtatish signali keldi\n");
    printf("Qayta ishlangan so'rovlar: %d\n", qayta_ishlangan);
    printf("Tozalash bajarilmoqda...\n");
    printf("Xizmat to'g'ri to'xtadi\n");

    return 0;
}
Natija
Xizmat ishga tushdi
To'xtatish signali keldi
Qayta ishlangan so'rovlar: 100
Tozalash bajarilmoqda...
Xizmat to'g'ri to'xtadi

Taymer signali #

C
#include <stdio.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>

static volatile sig_atomic_t taymer = 0;

static void alarm_ishlovchi(int raqam) {
    (void) raqam;
    taymer = 1;
}

int main(void) {
    struct sigaction sa;

    memset(&sa, 0, sizeof sa);
    sa.sa_handler = alarm_ishlovchi;
    sigemptyset(&sa.sa_mask);

    sigaction(SIGALRM, &sa, NULL);

    alarm(1);                          /* 1 soniyadan keyin */

    printf("Taymer o'rnatildi\n");

    /* Signal kelguncha kutamiz */
    pause();

    printf("Taymer ishladimi? %s\n", taymer ? "ha" : "yo'q");
    printf("pause() signal kelganda qaytadi\n");

    /* Qolgan vaqtni bekor qilish */
    unsigned qolgan = alarm(0);
    printf("Bekor qilingan taymerda qolgan: %u soniya\n", qolgan);

    return 0;
}
Natija
Taymer o'rnatildi
Taymer ishladimi? ha
pause() signal kelganda qaytadi
Bekor qilingan taymerda qolgan: 0 soniya
pause va poyga holati

Bu naqsh xavfli:

C
if (!bayroq) {
    pause();               /* signal SHU YERDA kelsa - abadiy kutish */
}

Signal if va pause orasida kelsa, pause uni o'tkazib yuboradi va dastur abadiy qotadi.

To'g'ri yechim - sigsuspend bilan atomar kutish:

C
sigset_t niqob, bosh;
sigemptyset(&niqob);
sigaddset(&niqob, SIGUSR1);
sigprocmask(SIG_BLOCK, &niqob, &bosh);

while (!bayroq) {
    sigsuspend(&bosh);     /* niqobni ochish va kutish - atomar */
}

sigprocmask(SIG_SETMASK, &bosh, NULL);

Zamonaviy alternativa - signalfd: signalni fayl deskriptori orqali epoll bilan birga kutish mumkin bo'ladi.

Signal yuborish #

Terminal
kill -TERM 4521
kill -9 4521
killall nginx
pkill -f "python skript.py"
BuyruqTa'siri
kill <pid>SIGTERM yuboradi (odatiy)
kill -9 <pid>SIGKILL - majburiy
kill -HUP <pid>Ko'p xizmatda sozlamani qayta o'qish
kill -0 <pid>Signal yubormaydi, faqat mavjudligini tekshiradi
kill -- -<pgid>Butun jarayonlar guruhiga
Terminal
kill -0 4521 && echo "jarayon ishlayapti"
SIGHUP - sozlamani qayta o'qish an'anasi

Tarixan SIGHUP terminal uzilganini bildirardi. Fon xizmatlarida terminal yo'q, shuning uchun bu signal bo'sh qoldi va "sozlamalarni qayta o'qi" ma'nosini oldi.

Terminal
sudo kill -HUP $(pidof nginx)
sudo nginx -s reload      # ichida aynan shu qiladi

Bu xizmatni to'xtatmasdan sozlamani yangilash imkonini beradi - mavjud ulanishlar uzilmaydi.

Dasturingizda shuni qo'llash oson:

C
if (sozlama_qayta_oqish_bayrogi) {
    sozlama_qayta_oqish_bayrogi = 0;
    sozlamani_yuklash();
}
Amaliy topshiriq
  1. sigaction bilan SIGUSR1 uchun ishlovchi o'rnating.
  2. signal va sigaction farqini tushuntiring.
  3. Ishlovchi ichida faqat write ishlatib xabar chiqaring.
  4. sigprocmask bilan signalni bloklab, sigpending bilan ko'ring.
  5. Bir xil signal ikki marta kelsa nima bo'lishini sinang.
  6. Beshta bola yaratib, SIGCHLD bilan hammasini yig'ing.
  7. SIGKILL ni tutishga urinib, EINVAL ni oling.
  8. SIGTERM bilan muloyim to'xtatishni amalga oshiring.
  9. alarm va pause bilan taymer quring.
  10. kill -0 bilan jarayon mavjudligini tekshiring.

Xulosa #

  • Signal - dasturiy uzilish, istalgan buyruq orasida keladi.
  • sigaction ishlating, signal emas - u portativ emas.
  • Ishlovchi ichida faqat async-signal-safe funksiyalar.
  • Ishlovchi bayroq qo'ysin, ishni asosiy sikl bajarsin.
  • Bayroq turi volatile sig_atomic_t bo'lishi shart.
  • Ishlovchi ichida errno ni saqlang va tiklang.
  • Oddiy signallar to'planmaydi - SIGCHLD da while ishlating.
  • SIGKILL va SIGSTOP ni tutib ham, bloklab ham bo'lmaydi.
  • D holatidagi jarayonni SIGKILL ham o'ldirmaydi.
  • Muloyim to'xtatish: SIGTERM → kutish → SIGKILL.
  • pause o'rniga sigsuspend yoki signalfd ishlating.
  • SIGHUP an'anaviy ravishda sozlamani qayta o'qishni bildiradi.

Keyingi bo'limda foydalanuvchilar, huquqlar va xavfsizlikni ko'ramiz.

Xatolik topdingizmi?

Imlo xatosi, ishlamaydigan kod yoki noto‘g‘ri ma‘lumotni ko‘rsangiz - bizga xabar bering. Har bir xabar administrator tomonidan ko‘rib chiqiladi.