Добрый день, хабражители. Наверняка все знают что такое сигналы в Linux и для чего они нужны. Но сегодня, я хотел бы рассказать о, как мне кажется, нетрадиционном их применении.
Задача очень надуманная и направленна на тренировку своих навыков работы с сигналами и, немножко, побитовыми операциями. В общем, задача:
Программа должна порождать процесс, который посредством только сигналов передавал родителю файл, указанный в качестве аргумента командной строки. Родитель выводит полученный файл в stdout.
Решение очень простое: будем пользоваться сигналами, как азбукой морзе, только использоваться будт SIGUSR1 и SIGUSR2 вместо «Точка» и «Тире».
Будем передавать файл побитово, с помощью сигналов SIGUSR2 и SIGUSR1
Пускай SIGUSR2 — бит равный нулю, SIGUSR1 — бит равный единице.
Читаем байт из файла в переменную с.
Создаем переменную counter равную 0b10000000 или 128.
Если c И (имеется в виду «побитовое и») counter равно единице, то старший бит равен единице, посылаем SIGUSR1, иначе посылаем SIGUSR2.
Делим counter пополам (получаем 0b01000000 или 64), то есть переходим ко второму слева биту.
Повторяем до тех пор, пока counter не станет равен нулю. Затем читаем новый байт из файла.
На языке Си это выглядит следующим образом:
Принимать мы будем в переменную out_char, изначально равную нулю.
Пока counter не равен нулю обрабатываем сигналы следующим образом:
Если пришел SIGUSR1, то out_char += counter, затем counter /= 2.
Если пришел SIGUSR1, то counter /= 2.
Напишем обработчики для сигналов:
Теперь нужно рассмотреть случаи непредвиденной смерти родителя или ребёнка. Если умрет ребёнок, то всё просто — родитель получит SIGCHLD.
С ребёнком будет немного сложнее, нет гарантии что ребёнка как либо известят о смерти его родителя. Поэтому попросим ядро отправить нам SIGALRM, если никаких других сигналов нам не отправят через заданный промежуток времени. Добавим это в отправляющий цикл:
Добавим механизм подтверждения получения сигнала от ребёнка родителем. То есть, пока родитель не подтвердит получение бита, ребёнок передавать следующий не будет.
Делается это просто, в функции one и zero надо добавить отправку ответа. Будем отвечать сигналом SIGUSR1. После изменений функции будут выглядеть следующим образом:
А для ожидания ребёнком подтверждения добавим sigsuspend(&set):
Функция выполняемая при приходе от родителя сигнала подтверждения хоть и пустая, но должна быть, иначе будет выполняться действие для сигнала по-умолчанию — Выход.
Собственно функция и set:
В родителе же маска сигналов должна быть следующей:
Так как в процессе работы программы произойдет порождение нового процесса, который сразу же начнет слать нам сигналы (данные), sigprocmask(SIG_BLOCK, &set, NULL ) нужно сделать обязательно до fork'а, иначе есть шанс получить ошибку из-за эффекта гонки (race condition).
В итоге программа будет выглядеть так:
Исходный код программы можно скачать тут.
Вот так можно передавать файлы от одной программы другой, с помощью одних лишь сигналов. Правда такой подход мало эффективен и я не думаю, что ему есть применение в реальных проектах.
Спасибо за внимание!
Задача очень надуманная и направленна на тренировку своих навыков работы с сигналами и, немножко, побитовыми операциями. В общем, задача:
Программа должна порождать процесс, который посредством только сигналов передавал родителю файл, указанный в качестве аргумента командной строки. Родитель выводит полученный файл в stdout.
Решение очень простое: будем пользоваться сигналами, как азбукой морзе, только использоваться будт SIGUSR1 и SIGUSR2 вместо «Точка» и «Тире».
Решение
Будем передавать файл побитово, с помощью сигналов SIGUSR2 и SIGUSR1
Пускай SIGUSR2 — бит равный нулю, SIGUSR1 — бит равный единице.
Отправка данных
Читаем байт из файла в переменную с.
Создаем переменную counter равную 0b10000000 или 128.
Если c И (имеется в виду «побитовое и») counter равно единице, то старший бит равен единице, посылаем SIGUSR1, иначе посылаем SIGUSR2.
Делим counter пополам (получаем 0b01000000 или 64), то есть переходим ко второму слева биту.
Повторяем до тех пор, пока counter не станет равен нулю. Затем читаем новый байт из файла.
На языке Си это выглядит следующим образом:
while (read(fd, &c, 1) > 0) { for ( i = 128; i >= 1; i /= 2) { if ( i & c ) // 1 kill(ppid, SIGUSR1); else // 0 kill(ppid, SIGUSR2); }
Прием данных
Принимать мы будем в переменную out_char, изначально равную нулю.
Пока counter не равен нулю обрабатываем сигналы следующим образом:
Если пришел SIGUSR1, то out_char += counter, затем counter /= 2.
Если пришел SIGUSR1, то counter /= 2.
Напишем обработчики для сигналов:
// SIGUSR1 void one(int signo) { out_char += counter; counter /= 2; } // SIGUSR2 void zero(int signo) { counter/=2; }
Рабочий вариант
Теперь нужно рассмотреть случаи непредвиденной смерти родителя или ребёнка. Если умрет ребёнок, то всё просто — родитель получит SIGCHLD.
// SIGCHLD void childexit(int signo) { exit(EXIT_SUCCESS); }
С ребёнком будет немного сложнее, нет гарантии что ребёнка как либо известят о смерти его родителя. Поэтому попросим ядро отправить нам SIGALRM, если никаких других сигналов нам не отправят через заданный промежуток времени. Добавим это в отправляющий цикл:
while (read(fd, &c, 1) > 0) { // SIGALRM Будет получен если родитель не успеет ответить за секунду alarm(1); // Побитовые операции for ( i = 128; i >= 1; i /= 2) { if ( i & c ) // 1 kill(ppid, SIGUSR1); else // 0 kill(ppid, SIGUSR2); } }
Добавим механизм подтверждения получения сигнала от ребёнка родителем. То есть, пока родитель не подтвердит получение бита, ребёнок передавать следующий не будет.
Делается это просто, в функции one и zero надо добавить отправку ответа. Будем отвечать сигналом SIGUSR1. После изменений функции будут выглядеть следующим образом:
// SIGUSR1 void one(int signo) { out_char += counter; counter /= 2; kill(pid, SIGUSR1); } // SIGUSR2 void zero(int signo) { counter/=2; kill(pid, SIGUSR1); }
А для ожидания ребёнком подтверждения добавим sigsuspend(&set):
while (read(fd, &c, 1) > 0){ // SIGALRM Будет получен если родитель не успеет ответить за секунду alarm(1); // Побитовые операции for ( i = 128; i >= 1; i /= 2){ if ( i & c ) // 1 kill(ppid, SIGUSR1); else // 0 kill(ppid, SIGUSR2); // приостанавливает процесс до получения сигнала // Ждём подтверждения от родителя sigsuspend(&set); }
Функция выполняемая при приходе от родителя сигнала подтверждения хоть и пустая, но должна быть, иначе будет выполняться действие для сигнала по-умолчанию — Выход.
Собственно функция и set:
// Nothing void empty(int signo) { } // SET sigemptyset(&set); // очищает набор сигналов // SIGUSR1 - empty() struct sigaction act_empty; memset(&act_empty, 0, sizeof(act_empty)); act_empty.sa_handler = empty; sigfillset(&act_empty.sa_mask); sigaction(SIGUSR1, &act_empty, NULL); // SIGALRM - parentexit() struct sigaction act_alarm; memset(&act_alarm, 0, sizeof(act_alarm)); act_alarm.sa_handler = parentexit; sigfillset(&act_alarm.sa_mask); sigaction(SIGALRM, &act_alarm, NULL);
В родителе же маска сигналов должна быть следующей:
// SIGCHLD - exit struct sigaction act_exit; memset(&act_exit, 0, sizeof(act_exit)); act_exit.sa_handler = childexit; sigfillset(&act_exit.sa_mask); sigaction(SIGCHLD, &act_exit, NULL); // SIGUSR1 - one() struct sigaction act_one; memset(&act_one, 0, sizeof(act_one)); act_one.sa_handler = one; sigfillset(&act_one.sa_mask); sigaction(SIGUSR1, &act_one, NULL); // SIGUSR2 - zero() struct sigaction act_zero; memset(&act_zero, 0, sizeof(act_zero)); act_zero.sa_handler = zero; sigfillset(&act_zero.sa_mask); sigaction(SIGUSR2, &act_zero, NULL); // Добавляем блокировки sigaddset(&set, SIGUSR1); sigaddset(&set, SIGUSR2); sigaddset(&set, SIGCHLD); sigprocmask(SIG_BLOCK, &set, NULL );
Так как в процессе работы программы произойдет порождение нового процесса, который сразу же начнет слать нам сигналы (данные), sigprocmask(SIG_BLOCK, &set, NULL ) нужно сделать обязательно до fork'а, иначе есть шанс получить ошибку из-за эффекта гонки (race condition).
В итоге программа будет выглядеть так:
int out_char = 0, counter = 128; pid_t pid; // Объявляем функции выполняемые по сигналам // SIGCHLD void childexit(int signo) { exit(EXIT_SUCCESS); } // SIGALRM void parentexit(int signo) { exit(EXIT_SUCCESS); } // Nothing void empty(int signo) { } // SIGUSR1 void one(int signo) { out_char += counter; counter /= 2; kill(pid, SIGUSR1); } // SIGUSR2 void zero(int signo) { counter/=2; kill(pid, SIGUSR1); } int main(int argc, char ** argv){ if (argc != 2) { fprintf(stderr, "Use: %s [source]\n", argv[0]); exit(EXIT_FAILURE); } pid_t ppid = getpid(); // Запонимаем пид родителя, то есть приёмника sigset_t set; // Изменяем набор блокированых сигналов // при SIGCHLD - выходим struct sigaction act_exit; memset(&act_exit, 0, sizeof(act_exit)); act_exit.sa_handler = childexit; sigfillset(&act_exit.sa_mask); sigaction(SIGCHLD, &act_exit, NULL); // SIGUSR1 - one() struct sigaction act_one; memset(&act_one, 0, sizeof(act_one)); act_one.sa_handler = one; sigfillset(&act_one.sa_mask); sigaction(SIGUSR1, &act_one, NULL); // SIGUSR2 - zero() struct sigaction act_zero; memset(&act_zero, 0, sizeof(act_zero)); act_zero.sa_handler = zero; sigfillset(&act_zero.sa_mask); sigaction(SIGUSR2, &act_zero, NULL); //sigemptyset(&set); // Добавляем блокировки sigaddset(&set, SIGUSR1); sigaddset(&set, SIGUSR2); sigaddset(&set, SIGCHLD); sigprocmask(SIG_BLOCK, &set, NULL ); sigemptyset(&set); // Ветвимся pid = fork(); // Ребёнок (Передатчик) if (pid == 0) { unsigned int fd = 0; char c = 0; sigemptyset(&set); // очищает набор сигналов // SIGUSR1 - empty() struct sigaction act_empty; memset(&act_empty, 0, sizeof(act_empty)); act_empty.sa_handler = empty; sigfillset(&act_empty.sa_mask); sigaction(SIGUSR1, &act_empty, NULL); // SIGALRM - parentexit() struct sigaction act_alarm; memset(&act_alarm, 0, sizeof(act_alarm)); act_alarm.sa_handler = parentexit; sigfillset(&act_alarm.sa_mask); sigaction(SIGALRM, &act_alarm, NULL); if ((fd = open(argv[1], O_RDONLY)) < 0 ){ perror("Can't open file"); exit(EXIT_FAILURE); } int i; while (read(fd, &c, 1) > 0){ // SIGALRM Будет получен если родитель не успеет ответить за секунду alarm(1); // Побитовые операции for ( i = 128; i >= 1; i /= 2){ if ( i & c ) // 1 kill(ppid, SIGUSR1); else // 0 kill(ppid, SIGUSR2); // Ждём подтверждения от родителя // приостанавливает процесс до получения сигнала sigsuspend(&set); } } // Файл кончился exit(EXIT_SUCCESS); } errno = 0; // Получаем пока ребёнок не умрёт do { if(counter == 0){ // Whole byte write(STDOUT_FILENO, &out_char, 1); // fflush(stdout); counter=128; out_char = 0; } sigsuspend(&set); // Ждём сигнал от ребёнка } while (1); exit(EXIT_SUCCESS); }
Исходный код программы можно скачать тут.
Вот так можно передавать файлы от одной программы другой, с помощью одних лишь сигналов. Правда такой подход мало эффективен и я не думаю, что ему есть применение в реальных проектах.
Спасибо за внимание!
