Открыть список
Как стать автором
Обновить

Передача файла сигналами

Ненормальное программирование
Добрый день, хабражители. Наверняка все знают что такое сигналы в Linux и для чего они нужны. Но сегодня, я хотел бы рассказать о, как мне кажется, нетрадиционном их применении.

Задача очень надуманная и направленна на тренировку своих навыков работы с сигналами и, немножко, побитовыми операциями. В общем, задача:
Программа должна порождать процесс, который посредством только сигналов передавал родителю файл, указанный в качестве аргумента командной строки. Родитель выводит полученный файл в 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);
}

Исходный код программы можно скачать тут.

Вот так можно передавать файлы от одной программы другой, с помощью одних лишь сигналов. Правда такой подход мало эффективен и я не думаю, что ему есть применение в реальных проектах.

Спасибо за внимание!
Теги:cненормальное программированиесинхронизацияСи
Хабы: Ненормальное программирование
Всего голосов 49: ↑46 и ↓3 +43
Просмотры10.1K

Комментарии 34

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

Похожие публикации

Лучшие публикации за сутки