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