Страницы

Поиск по вопросам

понедельник, 24 февраля 2020 г.

Совместное использование сигналов и каналов

#c


Никак не могу разобраться как решить задачу на совместное использование сигналов
и каналов (пинг-понг) .
Вот условие: 


  Написать программу игры в "пинг-понг" двух процессов через один канал.
  Первый процесс посылает второму 1, второй первому – 2, первый второму
  – 3, второй первому – 4 и т.д. Для синхронизации использовать сигнал.
  Игра завершается при нажатии клавиш Ctrl+C.


Вот мой код:

#include 
#include 
#include 
#include 
#include 

int cnt=0;

int main(){
    int fd[2];
    pipe(fd);
    if(fork()==0){
        write(fd[1],&cnt,sizeof(int));
        while(read(fd[0],&cnt,sizeof(int))){//Дочерний процесс начинает пинг-понг
            printf("%d\n",cnt);
            cnt++;
            write(fd[1],&cnt,sizeof(int));
        }
        close(fd[1]);
        close(fd[0]);
        exit(1);
    }
    while(read(fd[0],&cnt,sizeof(int))){
        printf("%d\n",cnt);
        write(fd[1],&cnt,sizeof(int));
    }
    close(fd[1]);
    close(fd[0]);
    return 0;
}

    


Ответы

Ответ 1



Поскольку процессы должны общаться через один канал, а канал имеет некоторый буфер, позволяющий писать несколько байт без читающего, процесс вызывающий read после write может прочитать собственные данные, в чём легко убедиться запустив немного исправленную вашу программу: #include #include #include #include #include int cnt=0; int main(){ int fd[2]; pipe(fd); if(fork()==0){ printf("2 w%d\n",cnt); write(fd[1],&cnt,sizeof(int)); while(cnt<100 && read(fd[0],&cnt,sizeof(int))){//Дочерний процесс начинает пинг-понг printf("2 r%d\n",cnt); cnt++; printf("2 w%d\n",cnt); write(fd[1],&cnt,sizeof(int)); } close(fd[1]); close(fd[0]); exit(1); } while(cnt<100 && read(fd[0],&cnt,sizeof(int))){ printf("1 r%d\n",cnt); cnt++; printf("1 w%d\n",cnt); write(fd[1],&cnt,sizeof(int)); } close(fd[1]); close(fd[0]); return 0; } Результаты: $ ./a.out 2 w0 1 r0 1 w1 1 r1 1 w2 1 r2 1 w3 1 r3 1 w4 1 r4 1 w5 1 r5 1 w6 1 r6 1 w7 1 r7 1 w8 1 r8 1 w9 1 r9 1 w10 Первый процесс сам справляется, а второй ему изредка помогает. Необходимо чтобы каждый процесс после записи очередного числа ожидал готовности данных для него, блокирующее чтение тут не поможет, так как данные в канале уже есть. Для ожидания можно бы использовать функцию pause(), но если сигнал придёт до входа в неё, это приведёт к бесконечному ожиданию, поэтому лучше sleep(1) в цикле с проверкой флага, который изменяется в функции-перехватчике сигнала. Программа с обменом сигналами. #include #include #include #include #include int cnt=0; int signaled=0; void intr(int n) { // обработчик сигнала signaled=1; } int main(){ int fd[2]; pipe(fd); int pid; signal(SIGUSR1, intr); if((pid=fork())==0){ // Дочерний процесс начинает пинг-понг pid= getppid(); printf("%d w%d\n",pid,cnt); kill(pid, SIGUSR1); write(fd[1],&cnt,sizeof(int)); } while(1) { while(!signaled) sleep(1); if(read(fd[0],&cnt,sizeof(int))<=0) break; printf("%d r%d\n",pid,cnt); cnt++; printf("%d w%d\n",pid,cnt); signaled=0; // после kill в любом месте может прийти ответ, поэтому здесь обнуляем флаг signal(SIGUSR1, intr); kill(pid, SIGUSR1); write(fd[1],&cnt,sizeof(int)); } close(fd[1]); close(fd[0]); return 0; } Примечание. Иногда процессы приостанавливаются на одну секунду, это значит сигнал пришёл после проверки условия, но перед ожиданием сигнала внутри sleep().

Ответ 2



Вот честное решение с синхронизацией только обменом сигналами, без sleep-ов, глобальных переменных и потенциальных гонок (надеюсь (строго доказывать не собираюсь)). Идея состоит в том, что мы получаем сигнал (т.е. разрешаем вызвать обработчик (на самом деле фиктивный, он ничего не делает)) в строго определенном месте кода (а именно в вызове sigsuspend). Это и есть точка синхронизации, в ней мы ждем "шарик от партнера". Все остальное время получение сигнала SIGUSR1, используемого для синхронизации блокируется. Для блокировки используется вызов sigprocmask с маской, блокирующей SIGUSR1. В самом начале вызовом sigprocmask мы запоминаем текущую маску сигналов (она разрешает обработку SIGUSR1) и далее используем ее в sigsuspend. Маска сигналов наследуется новым процессом (после fork). Впрочем, хватит разговоров, вот код: #include #include #include #include #include #include #include void usr1 (int sig) { } int main (int ac, char *av[]) { signal(SIGUSR1, usr1); sigset_t old, blocked; if (sigprocmask(SIG_SETMASK, 0, &old)) err(-1, "sigprocmask get old"); // exit memcpy(&blocked, &old, sizeof(old)); sigaddset(&blocked, SIGUSR1); if (sigprocmask(SIG_SETMASK, &blocked, 0)) err(-1, "sigprocmask set SIGUSR1"); // exit int fd[2], cnt = 0; pid_t pong; pipe(fd); if (pong = fork()) { // parent if (pong == -1) err(-1, "fork"); // exit // wait for a partner (child) while (kill(pong, 0)) { if (errno != ESRCH) err(-1, "kill start"); // exit usleep(1000); cnt++; if (cnt > 1000) err(0, "no partner"); // exit } printf("parent starts ping-pong (%d aditional attempts to see partner)\n", cnt); fflush(stdout); // important, if stdout redirect to file cnt = 1; for (;;) { write(fd[1], &cnt, sizeof(cnt)); // sleep(1); kill(pong, SIGUSR1); sigsuspend(&old); read(fd[0], &cnt, sizeof(cnt)); printf("parent %d\n", cnt++); fflush(stdout); } exit(0); // not reached } puts("child ready"); for (;;) { sigsuspend(&old); read(fd[0], &cnt, sizeof(cnt)); printf("child %d\n", cnt++); fflush(stdout); write(fd[1], &cnt, sizeof(cnt)); // sleep(1); kill(getppid(), SIGUSR1); } } Что непонятно, спрашивайте.

Комментариев нет:

Отправить комментарий