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

    Добрый день, хабражители. Наверняка все знают что такое сигналы в 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);
    }
    

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

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

    Спасибо за внимание!
    Реклама
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее

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

      +17
      Это реально ненормальное программирование! :)
        +2
        А какая, примерно, скорость передачи получается?)
          0
          Ну у меня получилось за 4 секунды 800 КБайт, то есть 200 КБайт в секунду.
            0
            Прошу прощения. Не секунды, а минуты. 200КБайт в минуту!
          +14
          --Теперь нужно рассмотреть случаи непредвиденной смерти родителя или ребёнка. Если умрет ребёнок, то всё --просто — родитель получит SIGCHLD.

          --С ребёнком будет немного сложнее, нет гарантии что ребёнка как либо известят о смерти его родителя.

          Мсье знает толк в маньячестве)
            +4
            Нет, у меня-таки ощущение, что всю программу физтеха по информатике решили на хабр запостить.
            Где тройной хендшейк? Никто ж не гарантирует, что другие процессы в системе не будут посылать эти сигналы.
              +2
              Тсс, иначе скоро бывшие первокурсники завалят хабр эмуляторами PDP-11 :)
                0
                … Написаными на .cmd скриптах, да…
                +1
                Интересно, как будет выглядеть хендшейк на азбуке Морзе… Может кто-нибудь реализует? )
                  0
                  Ну, предположим,
                  Сервер: Железисто, железисто, разделите-ка
                  Клиент: разделите-ка
                  Сервер: Попер данные
                  0
                  А процессы других пользователей и не могут посылать сигналы, у них на это нет прав.
                  0
                  Под 0x10000000 Вы имели в виду 100000002?
                  • НЛО прилетело и опубликовало эту надпись здесь
                    • НЛО прилетело и опубликовало эту надпись здесь
                      0
                      Да.
                      • НЛО прилетело и опубликовало эту надпись здесь
                          0
                          Спасибо за замечание, подправил.
                      0
                      В древней книжке «секреты суперхакера» был похожий метод обмена информацией. Только там они создавали пустой файл/каталог в качестве флага.
                        +1
                        <grammar-nazi> побитого => побитово </grammar-nazi>
                          0
                          Британские ученые снова в деле :-)
                            +1
                            Порядок доставки обычных сигналов не гарантирован. Под линуксом последовательная доставка работает из-за особенностей реализации, но, насколько я знаю, её никто не гарантирует.

                            Для Real Time сигналов всё ещё хитрее — сначала доставляются сигналы с наименьшим номером, т.е. они самые срочные.

                            Чтобы сделать доставку надёжной и переносимой предлагаю осуществить двустороннюю передачу данных с «подтверждением» каждого сигнала :)
                              0
                              Так подтверждение вроде и так есть, но используется для гарантии что принимающий процесс не завершился.
                                0
                                И вправду, я недоглядел, извиняюсь. Но тут возникают потенциальные гонки :) глобальные переменные не atomic_t, лихой компилятор может сделать обращения к ним неатомарными.
                              +1
                              Осталось реализовать передачу файлов на семафорах: взять 8 под «шину данных» и девятый для синхронизации!
                                0
                                Интересная идея, надо будет попробовать :)
                                –1
                                Это надо в блог ИБ. Это же информационный поток между приложениями с разными приоритетами доступа.
                                  0
                                  Нет, процессу другого пользователя посылать сигнал нельзя.
                                  0
                                  Ходят слухи, что i/=2 работает медленее чем i=i>>1, да и в данном случае так логичней и понятней написать на мой взгляд.
                                    0
                                    Нормальный компилятор будет оптимизировать любую арифметику с константами и выберет (ок, должен выбрать) наиболее быстрый вариант.

                                    Изо всех доступных мне компиляторов подобные вещи не делал ЕМНИП только компилятор Delphi.
                                      +1
                                      [sarcasm]В сравнении со скоростью передачи сигналов это очень существенная оптимизация[/sarcasm]
                                      0
                                      В linuxе есть штатный способ: Можно через prctl(PR_SET_PDEATHSIG,sig_num); назначить сигнал который будет присылаться при завершении существования родительского процесса.
                                        0
                                        А разве не может возникнуть ситуации, когда prctl в ребёнке ещё не вызвалась, а родитель уже умер? Ведь тогда на момент вызова prctl у ребёнка будет уже новый родитель?
                                          0
                                          Может. Но даже если это так, то это тоже детектируется, условием (getppid()==1)
                                        0
                                        Весело зато! =)

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

                                        Самое читаемое