在linux中使用命名管道和信号量

时间:2011-12-20 22:09:50

标签: c linux pipe semaphore

我一直在尝试让我的程序工作几个小时,而我无法弄清楚我的代码有什么问题。它是关于使用管道在进程之间传递变量。每个过程增加M次。当我使用共享内存时,该程序工作正常,但当我将其更改为使用管道时,这是一场灾难。创建或使用命名管道似乎根本不起作用,或者我想我只是以错误的方式进行操作。这是源代码:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/mman.h>
#include <unistd.h>
#include <memory.h>
#include <fcntl.h>
#include <sys/stat.h>

#define PIPE_NAME   "MY_PIPE"
#define N 5
#define M 10

struct sembuf operations;   
int semid;          
key_t key;          
int marker;

void semWait(int semid, int sempos) {
    operations.sem_num = sempos;        
    operations.sem_op = -1;         
    operations.sem_flg = 0;         

    if (semop(semid, &operations, 1) < 0) {
        perror("ERROR: semop wait\n");
        exit(-1);
    }
}


void semPost(int semid, int sempos) {
    operations.sem_num = sempos;        
    operations.sem_op = 1;          
    operations.sem_flg = IPC_NOWAIT;    

    if (semop(semid, &operations, 1) < 0) {
        perror("ERROR: semop post\n");
        exit(-1);
    }
}


void worker(int id) {
    int j, nmarker;
    int fd = open(PIPE_NAME, O_RDWR);
    read(fd, &nmarker, sizeof(int));

    for (j = 0 ; j < M; j++) {
        semWait(semid, id);
        nmarker = nmarker + 1 ;
        printf("%d ", marker);
        semPost(semid, N);              
    }

    write(fd, &nmarker, sizeof(nmarker));
    close(fd);
}

main() {
    int i, tempPID;
    int sarray[N+1] = {0};
    key = 23;
    marker = 0;


    if ((semid = semget(key , N+1, 0666 | IPC_CREAT)) == -1) {
        perror("ERROR: semget\n");
        exit(-1);   
    }


    if ((semctl(semid, N+1, SETALL, sarray)) < 0) {     
        perror("ERROR: semctl - val\n");
        exit(-1);
    }

    if(mkfifo(PIPE_NAME, S_IFIFO | 0666) < 0) {     
        perror("ERROR:pipe\n");
        exit(-1);
    }


    int fd;
    if( fd = open(PIPE_NAME, O_WRONLY) < 0 ){       
        perror("ERROR:open\n");
        exit(-1);
    } 

    write(fd, &marker, sizeof(marker));
    close(fd);


    for(i = 0; i < N; i++) {
        tempPID = fork();
        if (tempPID < 0) {
            perror("ERROR: fork\n");
            exit(-1);
        }
        else if (tempPID == 0) {    // if child
            worker(i);
            exit(0);
        }
    }


    for (i = 0 ; i < (M*N); i++) {
        semPost(semid, i%N);        
        semWait(semid, N);          
    }

    printf("Marker = %d\n", marker);

    if (semctl( semid, 1, IPC_RMID ) == -1) {
        perror("ERROR: semctl free\n");
        exit(-1);
    }

    unlinc(PIPE_NAME);
}

我创建了N个工作进程,每个进程必须将标记值增加M次。我必须创建一个“休眠”进程池并使用信号量逐个唤醒它们,但这一切都很模糊所以当前的源代码就是我想出来的......:\

这是同一程序的一个版本,但是使用共享内存而不是管道:

       #include <stdio.h>
       #include <stdlib.h>
       #include <sys/types.h>
           #include <sys/ipc.h>
         #include <sys/sem.h>
       #include <sys/mman.h>

       #define N 5
     #define M 10

        struct sembuf operations;   
     int semid;         
       key_t key;           
      int *sharedmem;


     void semWait(int semid, int sempos) {
operations.sem_num = sempos;        
operations.sem_op = -1;         
operations.sem_flg = 0;         

if (semop(semid, &operations, 1) < 0) {
    perror("ERROR: semop wait\n");
    exit(-1);
}
       }

      void semPost(int semid, int sempos) {
operations.sem_num = sempos;        
operations.sem_op = 1;          
operations.sem_flg = IPC_NOWAIT;    

if (semop(semid, &operations, 1) < 0) {
    perror("ERROR: semop post\n");
    exit(-1);
}
   }

       void worker(int id) {
int j;

for (j = 0 ; j < M; j++) {
    semWait(semid, id);
    (*sharedmem)++;
    semPost(semid, N);              
}
     }

     main() {
int i, tempPID;
int sarray[N+1] = {0};
int protect = PROT_READ | PROT_WRITE;
int flags = MAP_SHARED | MAP_ANONYMOUS;


if ((key = ftok("/dev/null", 4343)) == -1) {
    perror("ERROR: ftok\n");
    exit(-1);
}


if ((semid = semget(key , N+1, 0666 | IPC_CREAT)) == -1) {
    perror("ERROR: semget\n");
    exit(-1);   
}


if ((semctl(semid, N+1, SETALL, sarray)) < 0) {     
    perror("ERROR: semctl - val\n");
    exit(-1);
}


sharedmem = (int*)mmap(NULL, sizeof(int), protect, flags, 0, 0);
*(sharedmem) = 0;


    for(i = 0; i < N; i++) {
            tempPID = fork();
    if (tempPID < 0) {
        perror("ERROR: fork\n");
        exit(-1);
    }
            else if (tempPID == 0) {    // if child
                    worker(i);
                    exit(0);
            }
    }

for (i = 0 ; i < (M*N); i++) {
    semPost(semid, i%N);            
    semWait(semid, N);          
}

printf("Marker = %d\n", *sharedmem);

if (semctl( semid, 1, IPC_RMID ) == -1) {
    perror("ERROR: semctl free\n");
    exit(-1);
}


munmap(sharedmem, sizeof(int));
 }

1 个答案:

答案 0 :(得分:2)

你的一些问题出现在工人代码中 - 这两行:

int fd = open(PIPE_NAME, O_RDWR);
read(fd, &nmarker, sizeof(int));
  1. 如果您打开管道进行读写,则会遇到麻烦(IMNSHO)。打开它只读,读它,关闭它。然后打开它只写,写入它,关闭它。现在您必须考虑信号量操作应该发生的位置。在尝试打开管道进行写入之前,您实际上需要唤醒下一个进程,因为写入的打开将阻塞,直到有可用于从中读取的进程。同样,打开读取的进程将阻塞,直到有一个可用于写入的进程。因此,内核将协调进程。

  2. 您不检查open()的返回值,因此您不知道是否有有效的文件描述符。请务必检查open()的返回状态。

  3. 您不会检查read()的返回值,因此您不知道是否有任何有效的管道。请务必检查read()的返回状态。

  4. (如果写入失败没有有意义的错误恢复,您可以决定忽略write()的返回状态,但检查它是否正常工作并不是一个坏主意。您可以决定忽略close()的返回状态出于类似的原因,但在执行close()之前,您可能无法了解问题。)

    继续使用工人代码:

    for (j = 0 ; j < M; j++) {
        semWait(semid, id);
        nmarker = nmarker + 1 ;
        printf("%d ", marker);
        semPost(semid, N);              
    }
    

    令人惊讶的是,您打印marker而不是nmarker;当然,基本诊断技术在读取时会打印nmarker的值。您可能会或可能不会在每次迭代时打印jnmarker。请注意,由于此代码中的任何内容均不会增加marker,因此打印的值不会更改。

    这里的逻辑序列很有意思......它最奇怪地与main()中的循环相结合。父进程将一个值写入FIFO。只有一个孩子可以阅读该值 - 其余孩子立即获得EOF,或无限期挂起(取决于您是否在孩子中使用O_RDONLYO_RDWR)。每个孩子都会发出信号以增加其值,这样做,然后再回到睡眠状态,直到再次醒来。没有任何东西可以将递增的值发送​​给下一个孩子。因此,每个孩子都会独立递增所选择的任何值 - 这可能是垃圾。对于共享内存,如果你有一个指向共享值的指针,那么所有进程都会立即看到增量 - 这就是为什么它被称为共享内存。但是这里没有共享内存,因此您必须明确地进行通信以使其工作。 (我想知道你的FIFO和共享内存实现是否有效,因为通信是通过共享内存 - 换句话说,意外?)

    因此,如果孩子要增加每次读取的变量,它必须同时读取当前值并在循环周围每次写入新值。当然,这将是错误检查的读数。由于信号量的原因,你可能对O_RDWR没问题,但我个人对于单独的读写开放感到更高兴 - 如果需要,可以在每次迭代时使用。但是我没有实现这个来检查它确实遇到了问题;在FIFO上使用O_RDWR只是一种常规做法。

    在您的孩子将其值增加N次后,它会将结果写入管道。

    write(fd, &nmarker, sizeof(nmarker));
    close(fd);
    

    主程序然后:

    printf("Marker = %d\n", marker);
    
    if (semctl( semid, 1, IPC_RMID ) == -1) {
        perror("ERROR: semctl free\n");
        exit(-1);
    }
    
    unlinc(PIPE_NAME);
    

    由于它未修改marker,因此打印的值将为0.您应该让主进程读取每个子进程的回复。

    取消链接FIFO的正确功能是unlink()remove()


    讨论

    正如评论中所指出的,一个问题是打开FIFO是阻塞的 - 没有读者。然而,这远非唯一的问题。

    以下代码运行。我还没有确认数字正在增加(但它正在递增)。我没有检查过每个过程是否正在转变。我修改了错误处理(每次调用一行而不是3或4行),并添加了一个包含输出中PID的打印功能。我错误检查了每个系统调用(但没有打印语句)。我修复了问题if (fd = open(...) < 0)。据我所知,在主进程中关闭FIFO会丢弃写入它的内容 - 因此父进程不再立即关闭FIFO。但主要是我将FIFO的读写写入工作循环 - 在外部打开和关闭。该代码还带有诊断打印功能,因此我可以在出错时看到它出错的地方。我还没有完成标头最小化或任何其他应该发生的清理。但是,除main()之外的所有内容都是静态的,因此不必预先声明。它编译清洁:

    /usr/bin/gcc -O3 -g -std=c99 -Wall -Wextra fifocircle.c -o fifocircle 
    

    代码

    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/sem.h>
    #include <sys/mman.h>
    #include <unistd.h>
    #include <memory.h>
    #include <fcntl.h>
    #include <sys/stat.h>
    #include <stdarg.h>
    #include <errno.h>
    #include <string.h>
    
    static const char *arg0 = "undefined";
    
    static void err_error(const char *fmt, ...)
    {
        int errnum = errno;
        va_list args;
        fflush(0);
        fprintf(stderr, "%s: pid %d:", arg0, (int)getpid());
        va_start(args, fmt);
        vfprintf(stderr, fmt, args);
        va_end(args);
        if (errnum != 0)
            fprintf(stderr, "(%d: %s)", errnum, strerror(errnum));
        fputc('\n', stderr);
        exit(1);
    }
    
    static void print(const char *fmt, ...)
    {
        va_list args;
        printf("pid %d: ", (int)getpid());
        va_start(args, fmt);
        vfprintf(stdout, fmt, args);
        va_end(args);
        fflush(0);
    }
    
    #define PIPE_NAME   "MY_PIPE"
    #define N 5
    #define M 10
    
    static struct sembuf operations;
    static int semid;
    static key_t key;
    static int marker;
    
    static void semWait(int semid, int sempos)
    {
        operations.sem_num = sempos;
        operations.sem_op = -1;
        operations.sem_flg = 0;
    
        if (semop(semid, &operations, 1) < 0)
            err_error("semop wait");
    }
    
    static void semPost(int semid, int sempos)
    {
        operations.sem_num = sempos;
        operations.sem_op = 1;
        operations.sem_flg = IPC_NOWAIT;
    
        if (semop(semid, &operations, 1) < 0)
            err_error("semop post");
    }
    
    static void worker(int id)
    {
        int j;
        int fd = open(PIPE_NAME, O_RDWR);
        if (fd < 0)
            err_error("failed to open FIFO %s for read & write", PIPE_NAME);
        print("Worker %d: fd %d\n", id, fd);
    
        for (j = 0 ; j < M; j++)
        {
            int nmarker;
            print("waiting for %d\n", id);
            semWait(semid, id);
            if (read(fd, &nmarker, sizeof(int)) != sizeof(int))
                err_error("short read from FIFO");
            print("Got %d from FIFO\n", nmarker);
            nmarker = nmarker + 1 ;
            if (write(fd, &nmarker, sizeof(nmarker)) != sizeof(nmarker))
                err_error("short write to FIFO");
            print("Wrote %d to FIFO\n", nmarker);
            print("posting %d\n", id);
            semPost(semid, N);
        }
    
        if (close(fd) != 0)
            err_error("failed to close FIFO");
    
        print("done\n");
    }
    
    int main(int argc, char **argv)
    {
        int i;
        int sarray[N+1] = {0};
        key = 23;
        marker = 0;
        arg0 = argv[0];
    
        if (argc != 1)
            err_error("Usage: %s\n", arg0);
    
        if ((semid = semget(key , N+1, 0666 | IPC_CREAT)) == -1)
            err_error("semget");
    
        if ((semctl(semid, N+1, SETALL, sarray)) < 0)
        {
            perror("ERROR: semctl - val\n");
            exit(-1);
        }
    
        if (mkfifo(PIPE_NAME, S_IFIFO | 0666) < 0)
            err_error("failed to create FIFO %s\n", PIPE_NAME);
        print("FIFO created\n");
    
        int fd;
        if ((fd = open(PIPE_NAME, O_RDWR)) < 0 )
            err_error("failed to open FIFO %s\n", PIPE_NAME);
        print("FIFO opened\n");
    
        if (write(fd, &marker, sizeof(marker)) != sizeof(marker))
            err_error("short write to FIFO");
        print("FIFO loaded\n");
    
        print("Master: about to fork\n");
        for (i = 0; i < N; i++)
        {
            pid_t pid = fork();
            if (pid < 0)
                err_error("failed to fork");
            else if (pid == 0)
            {
                worker(i);
                exit(0);
            }
        }
    
        print("Master: about to loop\n");
        for (i = 0 ; i < (M*N); i++)
        {
            print("posting to %d\n", i%N);
            semPost(semid, i%N);
            print("waiting for %d\n", N);
            semWait(semid, N);
        }
    
        if (close(fd) != 0)
            err_error("failed to close FIFO");
    
        print("Marker = %d\n", marker);
    
        if (semctl( semid, 1, IPC_RMID ) == -1)
            err_error("semctl remove");
    
        if (unlink(PIPE_NAME) != 0)
            err_error("failed to remove FIFO %s", PIPE_NAME);
    
        return(0);
    }