【问题标题】:Simple synchronization with C signals与 C 信号的简单同步
【发布时间】:2012-01-02 16:12:55
【问题描述】:

我正在尝试解决一个要求:“启动过程必须分叉两次。父亲和孩子必须同步写入,一个接一个,在一个临时文件的第一个位置读取写入的字符在三个不同的文件上(每个进程一个)。程序必须使用信号来实现同步机制。"

到目前为止,我已经尝试通过这样做来解决这个问题:

  • P1(父亲)首先开始读/写。在停止自己之前(通过调用 raise 函数),他发送一个 SIGCONT 信号唤醒 F2(第二个孩子)
  • F2 从他的文件中读取并写入临时文件。然后他也停下来,并发送一个 SIGCONT 信号唤醒 F1(第一个孩子)
  • F1 的作用与 F2 相同,但会唤醒 P1,依此类推...

但是,我无法使代码正常工作(在某些情况下,在更改读取和写入的顺序后,我在输出中得到了后者的大部分内容,但程序行为总是不稳定且从未终止)。

这是我的代码:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>
#include <string.h>
#include <signal.h>

#define TEMP_PATH "/tmp/mytempfile"

int main(int argc, char *argv[]){

FILE *writeFp;
FILE *rfpF1;
FILE *rfpF2;
FILE *rfpP1;
pid_t pid1, pid2;

char car;   
char sizeOfChar = sizeof(char);

if (argc != 4 || !strcmp(argv[1], "--help")){
    fprintf(stderr, "Usage : %s filePath1 filePath2 filePath3\n", argv[0]);
    exit(EXIT_FAILURE);
}

if (access(argv[1], F_OK)==-1){
    perror("access 1 error");
    exit(EXIT_FAILURE);
}

if (access(argv[2], F_OK)==-1){
    perror("access 2 error");
    exit(EXIT_FAILURE);
}

if (access(argv[3], F_OK)==-1){
    perror("access 3 error");
    exit(EXIT_FAILURE);
}

if((writeFp = fopen(TEMP_PATH, "w")) == NULL){
    fprintf(stderr, "Can't open temp file on writing.\n");
    exit(EXIT_FAILURE);
}

if ((rfpP1 = fopen(argv[3], "r")) == NULL){
    fprintf(stderr, "Can't open %s on reading.\n", argv[3]);
    exit(EXIT_FAILURE);
}   

switch(pid1 = fork()){
    case -1:
            perror("fork error");
            exit(EXIT_FAILURE);

    case 0:
            /* F1 : first child */

            if ((rfpF1 = fopen(argv[1], "r")) == NULL){
                fprintf(stderr, "Can't open %s on reading.\n", argv[1]);
                exit(EXIT_FAILURE);
            }

            raise(SIGSTOP);
            while(fscanf(rfpF1, "%c", &car) != EOF){

                if(fseek(writeFp, 0L, SEEK_SET) == -1){
                    perror("fseek error");
                    exit(EXIT_FAILURE);
                }
                if(fprintf(writeFp, "%c", car) != 1){
                    fprintf(stderr, "fprintf error. Terminating...\n");
                    exit(EXIT_FAILURE);
                }                   

                if(kill(getppid(), SIGCONT) == -1){
                    perror("F1 kill error");
                    exit(EXIT_FAILURE);
                }

                printf("F1 : i've written '%c'\n", car); fflush(stdout);

                // If with the next read EOF is reached, the process doesn't have to stop...
                if(fscanf(rfpF1, "%c", &car) == EOF)
                    break;
                else{
                    if(fseek(rfpF1, -sizeOfChar, SEEK_CUR)){
                        perror("fseek error");
                        exit(EXIT_FAILURE);
                    }
                    raise(SIGSTOP);
                }
            }

            fclose(rfpF1);              
            exit(EXIT_SUCCESS);


    default :
            break;  

}


switch(pid2 = fork()){
    case -1:
            perror("fork 2 error");
            exit(EXIT_FAILURE);

    case 0:
            /* F2 : second child */

            if ((rfpF2 = fopen(argv[2], "r")) == NULL){
                fprintf(stderr, "Can't open %s on reading.\n", argv[2]);
                exit(EXIT_FAILURE);
            }

            raise(SIGSTOP);
            while(fscanf(rfpF2, "%c", &car) != EOF){

                if(fseek(writeFp, 0L, SEEK_SET) == -1){
                    perror("fseek error");
                    exit(EXIT_FAILURE);
                }
                if(fprintf(writeFp, "%c", car) != 1){
                    fprintf(stderr, "fprintf error. Terminating...\n");
                    exit(EXIT_FAILURE);
                }               

                if(kill(pid1, SIGCONT) == -1){
                    perror("F2 kill error");
                    exit(EXIT_FAILURE);
                }

                printf("F2 : i've written '%c'\n", car); fflush(stdout);

                if(fscanf(rfpF2, "%c", &car) == EOF)
                    break;
                else{
                    if(fseek(rfpF2, -sizeOfChar, SEEK_CUR)){
                        perror("fseek error");
                        exit(EXIT_FAILURE);
                    }
                    raise(SIGSTOP);
                }
            }

            fclose(rfpF2);  
            exit(EXIT_SUCCESS);
    default:    
            /* P1 : Father */

            // Wait for the children to be interrupted by SIGSTOP (which changes their states)
            if(wait(NULL) == -1){
                perror("wait 1 error");
                exit(EXIT_FAILURE);
            }

            if(wait(NULL) == -1){
                perror("wait 2 error");
                exit(EXIT_FAILURE);
            }

            // P1 is the first to be reading and writing...
            while(fscanf(rfpP1, "%c", &car) != EOF){

                if(fseek(writeFp, 0L, SEEK_SET) == -1){
                    perror("fseek error");
                    exit(EXIT_FAILURE);
                }
                if(fprintf(writeFp, "%c", car) != 1){
                    fprintf(stderr, "fprintf error. Terminating...\n");
                    exit(EXIT_FAILURE);
                }

                if(kill(pid2, SIGCONT) == -1){
                    perror("P kill error");
                    exit(EXIT_FAILURE);
                }

                printf("P1 : i've written '%c'\n", car); fflush(stdout);

                if(fscanf(rfpP1, "%c", &car) == EOF)
                    break;
                else{
                    if(fseek(rfpP1, -sizeOfChar, SEEK_CUR)){
                        perror("fseek error");
                        exit(EXIT_FAILURE);
                    }
                    raise(SIGSTOP);
                }

            }

            fclose(rfpP1);  
            break;
}

// Wait for the children...
if(wait(NULL) == -1){
    perror("wait 1 error");
    exit(EXIT_FAILURE);
}

if(wait(NULL) == -1){
    perror("wait 2 error");
    exit(EXIT_FAILURE);
}

fclose(writeFp);
exit(EXIT_SUCCESS);
}

【问题讨论】:

  • 如果是这样,则讲师不称职,因为作业中的同步设计具有固有且无法修复的竞争条件(当另一个进程试图唤醒时,一个进程可能尚未完成为自己提升SIGSTOP SIGCONT,导致丢失唤醒)。基本上停止/继续信号应该从不用于同步。
  • 顺便说一句,如果讲师不是一个彻头彻尾的混蛋,那么您可能会因为证明他/她的设计无法工作而获得比实施它所获得的更多荣誉.. ;-)
  • 也许教练正在考虑其他信号,例如 SIGUSR1、SIGUSR2?
  • 我第一次编写这段代码时,实际上使用了 SIGUSR1(它的处理程序除了从暂停调用中释放进程之外什么都不做)和 pause() 而不是 SIGCONT 和 raise(SIGSTOP)。我改变了这一点,认为父亲应该在两个孩子进入暂停呼叫后才开始阅读和写作。因此,在父亲中,我在进入 while 循环之前等待它们,知道停止它们会导致它们的状态发生变化(这允许从等待中返回)。
  • @user996922:任何使用pause 的方法都具有完全相同的竞争条件。您必须使用像 sigwaitpselect (或使用自管道和 select 模拟 pselect)这样的方法,以原子方式取消屏蔽/屏蔽信号,同时等待它避免竞争条件。跨度>

标签: c posix signals


【解决方案1】:

更好的方法是使用sigwait() 函数,并将信号掩码设置为您要等待的信号。首先,在你可以使用sigwait()之前,你应该确保你正在等待的信号首先被阻塞在进程或线程的信号掩码中。然后,执行以下操作:

  1. 在父级中,为您将要从中读取和写入的文件打开所有适当的文件描述符
  2. 设置父进程的信号掩码,以便阻止将用于在父进程和子进程之间进行同步的信号。
  3. 分叉子进程。设置处理后,在 while 循环中,使用仅包含同步信号的信号掩码调用 sigwait()。当该子进程接收到同步信号时,它将继续执行while循环。在完成循环并重复对sigwait() 的阻塞调用之前,向下一个进程发送信号。
  4. 在父进程中,使用 while 循环执行第一个读/写序列,然后向下一个子进程发送信号。最后,在循环结束时调用sigwait()

所以最终你的子进程看起来像:

//child process

//...setup the child process

while (/* some condition for stopping */)
{
    int signal
    sigwait(&signal_mask, &signal)

    //check to make sure we're getting the right signal
    if (signal != synchronization_signal)
        continue;

    //...more code for reading/writing to files

    //send a signal to next process in-line
    //i.e., F1 will send a signal to F2, and F2 will signal P1    
}

你的父进程看起来像:

//...block the synchronization signal and fork the children

while (/* some condition for stopping */)
{
    //...perform the reading and writing to the files

    //send signal to F1

    //block waiting for the signal to arrive from F2
    while (true)
    {
        int signal;
        sigwait(&signal_mask, &signal);

        //check to make sure we're getting the right signal
        if (signal == synchronization_signal)
            break;
    }
}   

【讨论】:

  • +1 表示sigwait,这是进行基于信号的非竞争性同步的唯一方法之一
猜你喜欢
  • 1970-01-01
  • 2013-05-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多