Nikita Shirikov Ed blog

Signals

Сигналы и каналы

На лекции будем разбирать способы взаимодействия процессов.

Сигналы

Имеется список стандартных сигналов. С помощью них ядро может взаимодействовать с процессом.

man 7 signal - посмотреть список сигналов

Стандартные действия (диспозиции):

На большиство сигналов можем повесить обработчик:

#include <signal.h>
#include <unistd.h>

void sayhi(int signo) {
    write(STDOUT_FILENO, "hi guys\n", 8);
}

int main() {
    char c;
    signal(SIGINT, sayhi); // не самый современный и лучший механизм
    while (1) {
        pause(); // дожидаемся сигнала
        // Причем работает даже так:
        c++;
    }
}

Обработчик выполняется на нашем стеке. Когда ядру надо выполнить обработку сигнала. Отступает от rsp 128 байт. Записывает старые значения регистров, адрес возврата и т.д. И запускает нашу функцию. (Механизм как в корутинах).

Если обращаться к одной переменной из процесса и обработчика:

#include <signal.h>
#include <unistd.h>

int counter;

void sayhi(int signo) {
    if (++counter >= 3) {
        _exit(0);
    }
    write(STDOUT_FILENO, "hi guys\n", 8);
}

int main() {
    char c;
    signal(SIGINT, sayhi);
    while (1) {
        ++counter;
    }
}

То из-за того, что у нас закодироваться операции могут как угодно и прийти сигнал может когда угодно, то может все поломаться.

Стадарт языка C предлагает следующий механизм:

volatile sig_atomic_t counter;

Хотим что-то печатать:

#include <signal.h>
#include <unistd.h>

int counter;

void sayhi(int signo) {
    if (++counter >= 3) {
        _exit(0);
    }
    printf("hello world");
    write(STDOUT_FILENO, "hi guys\n", 8);
}

int main() {
    char c;
    signal(SIGINT, sayhi);
    while (1) {
        printf("hello world");
    }
}

Опять же все сломается: printf - это блокирующая функция => если мы попробуем использовать ее вместе с прерываниями, то просто рискуем получить deadlock.

Из обработчика сигналов, можем использовать только специальные функции: системные вызовы, функции стандартной библиотеки без глобального состояния. (Фукции с пометкой async singal sace - man signal-safety)

Исторические проблемы:

На разных системах signal() может работать по-разному.

Поэтому лучше использовать sigaction()

void sayhi(int signo) {
    if (++counter >= 3) {
        _exit(0);
    }
    printf("hello world");
}

int main() {
    char c;
    struct sigaction act = {
        .sa_handler = sayhi,
    };
    sigaction(SIGINT, &act, NULL);
    while (1) {
        ++c;
    }
}

При этом sigaction() намного более тонко настраивать обработчик. Особенно флаги.

По умолчанию, мы блокируем сигнал, пока его обрабатываем.

Флаг SA_RESETHAND позволяет эмулировать поведение старого Unix: если во время системного вызова приходит сигнал, то производится выход из вызова со специальной ошибкой и надо вручную перезапускать сискол. Сейчас это делается автоматически - флаг SA_RESTART.

Доставка сигналов

В ядре есть два ветктора, для отслеживания сигналов:

pending: bitvector 0000001000000000
blocked: bitvector 0000000000010000

Поэтому нельзя послать несколько одинаковых сигналов.

Существует системный вызов: sigprocmask() для управления блокирующей маской.

Заблокируем какой-то сигнал на время выполнения read():

while(1) {
    sigset_t sigint, oldmask;
    sigemptyset(&sigint);
    sigaddset(&sigint, SIGINT);
    sigprocmask(SIG_BLOCK, &sigint, &oldmask);
    read(STDIN_FILENO, &c, 1);
    sigprocmask(SIG_SETMASK, &oldmask, NULL);
    pause();
}

sigsuspend(&oldmask) повзоляет либо ждать прихода сигнала, либо сразу его выполнить, если он пришел и был заблокирован.

До этого мы говорили про асинхронные сигналы.

Если мы отправляем сигнал себе в команду или получаем сигнал от ядра в ожидаемые моменты, то это синхронные сигналы.

Есть сигналы, которые нельзя блокировать, по типу SIGKILL

Таймеры и другие

Есть сигнал Alarm.

А еще есть механизм таймеров (setitimer)

Каналы

Системный вызов pipe() создает 2 файловых дескриптора, которые соединены неименованным каналом, один конец - пишущий, второй - читающий.

Когда процесс пытается вписать в закртый канал, от ядра приходит сигнал SIGPIPE.

#Os