Linux进程间通信

4

我需要实现一个测试程序(测验),除了显示问题并读取答案,还要在每过一分钟时显示剩余时间。当完成考试时间、完成问题或用尽时间时,程序必须从开始重新进入,在开始之前,我们输入候选人的姓名。这个实现必须使用进程来完成。以下是我迄今为止编写的代码。问题在于我不确定是否在进程和子进程之间建立了良好的通信,特别是因为我没有使用管道。有什么意见吗?

#include<stdio.h>
#include<sys/types.h>
#include<signal.h>
#include<unistd.h>
#include<sys/wait.h>

#define T 180

void firstChildAction(){
    static const char filename[] = "/home/osystems01/laura/text";
    char question[100];
    char answer[100];

    FILE *file = fopen(filename,"r");
    if(file != NULL){
        while(fgets(question,sizeof question,file) != NULL){
            fputs(question, stdout);
            scanf("%s",&answer);
        }
        fclose(file);
    }
    else{
        perror(filename);
    }
}

void secondChildAction(){
    int i;
    for(i = T; i >= 0; i-=60){
        if( i/60 != 0){
            printf("You have %d %s left.\n", i/60,(i/60 >   1)?"minutes":"minute");
            sleep(60);
        }
        else{
            printf("The time is over\n");
            break;
        }
    }
}

int main() {
    pid_t pidA;
    pid_t pidB;
    pid_t wPid;
    char name[20];
    while(1){
        printf("Enter the candidate name or Quit to exit: \n");
        scanf("%s",&name);
        if(strcmp(name,"Quit") == 0 || strcmp(name,"quit") == 0){
            printf("The program is terminating.....\n");
            break;
        }
        else{
            pidA = fork();
            if(pidA == 0){
                firstChildAction();
                exit(0);
            }
            else{
                pidB = fork();
                if(pidB == 0){
                    secondChildAction();
                    exit(0);
                }
            }
            int status;
            while(wPid = wait(&status)) > 0 ){
                if(WIFEXITED(status)){
                    int result = WEXITSTATUS(status);
                    printf("Exit status of %d is %d\n", wPid, result);
                    if(wPid == pidA){
                        kill(pidB,SIGTERM);
                        kill(pidA,SIGTERM);
                    }
                    else if(wPid == pidB){
                        kill(pidA,SIGTERM);
                        kill(pidB,SIGTERM);
                    }
                }
            }
        }
    }
    return 0;
}

建议:
  • 使用锁(在您的情况下是文件锁)
  • 或者使用管道 ;) :https://dev59.com/yHE85IYBdhLWcg3wbS1h
- Sam
我是新手,有些地方不太明白。我需要使用管道来从文件中读取(由子进程执行的操作),并显示问题(由父进程执行的操作)吗?我感到困惑。 - laura
移至答案以进行编辑。 - Sam
使用FIFO进行进程间通信:http://linux.die.net/man/3/mkfifo - ceving
1个回答

1

管道本身不需要提供常规文件,但它们可以有一个独特的、全局可见的名称,这个名称由你必须指定的(未使用的)文件名提供。如果有的话,文件的内容由库处理。

在相关进程之间(例如同一进程层次结构中的子进程和父进程)进行通信的(简单)管道,其中管道句柄可以轻松地传递给其他进程。

另一种类型的管道称为“命名管道”,用于任何关系的进程,其中可以使用全局名称查找管道句柄(如我链接的问题的答案中所解释的)。你可以把管道看作是直接连接的说话管道,允许两个进程使用读写函数随意聊天。在Linux上,管道是单向的(一次只能有一个人说话,另一个人听)。在这种情况下,需要两个管道进行双向异步IO(https://unix.stackexchange.com/questions/53641/how-to-make-bidirectional-pipe-between-two-programs)。输入和输出的即时缓冲区被抽象化了。就像网络套接字一样。

我建议编译接受答案中的漂亮示例以进行操作:https://dev59.com/yHE85IYBdhLWcg3wbS1h#2789967 编辑
带有错误处理的示例代码。将pipe.h和pipe.c视为库(链接NamedPipeReader和NamedPipeWriter)。 此代码需要进一步测试,但该代码能够以任何顺序(重新)打开命名管道。


pipe.h

#ifndef PIPE_H_
#define PIPE_H_

//C headers
#include <errno.h>
#include <assert.h>

//Linux headers
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#ifdef __cplusplus
extern "C"
{
#endif

int open_named_pipe(const char* const name, const int permissions, const int mode, int* pipe_created);

#ifdef __cplusplus
}
#endif

#endif /* PIPE_H_ */


pipe.c

#include "pipe.h"

#include <stdio.h>

int open_named_pipe(const char* const name, const int permissions, const int mode, int* pipe_created)
{
    int fd;

    assert(name);
    assert(permissions);
    assert(pipe_created);

    //Create or use an existing pipe special file
    if (0 == mkfifo(name, permissions))
    {
        *pipe_created = 1;
        printf("Successfully created named pipe '%s'\n", name);
    }
    else
    {
        switch (errno)
        {
        case EEXIST:
            //this is OK, as the other process might already has created the special file
            printf("Opened existing named pipe '%s'\n", name);
            break;
        default:
            fprintf(stderr, "Failed to create or access named pipe '%s'\n", name);
            perror("    ");
            return -1;
        };
    }

    fd = open(name, mode);
    if (fd < 0)
    {
        perror("Could not open pipe for writing");
        if (*pipe_created)
        {
            if (0 == unlink(name))
            {
                *pipe_created = 0;
            }
            else
            {
                perror("Failed to unlink named pipe");
            }
        }
    }

    return fd;
}

NamedPipeReader.c

#include <stdlib.h>
#include <stdio.h>
#include <signal.h>

#include "pipe.h"

//Globals
const char* const pipe_name = "/tmp/myfifo";
const int pipe_permissions = 0600;
const size_t read_buffer_size = 1024; //[bytes]
const size_t read_retry_delay = 25000; //[us]

int fd = -1;
int pipe_created = 0;
char* read_buffer = NULL;

//Handles EPIPE signal
void signal_handler(int signal)
{
    fprintf(stderr, "cought signal %d\n", signal);
}

//Handles cleanup on exit
void exit_handler(void)
{
    if (read_buffer)
        free(read_buffer);

    if (fd >= 0)
        close(fd);

    //if this process created the FIFO, we unlink it
    if (pipe_created == 0)
        unlink(pipe_name);
}

int main()
{
    //Locals
    int run = 1;
    int received = 0;

    //Install the exit handler
    atexit(&exit_handler);
    signal(EPIPE, signal_handler);
    signal(EACCES, signal_handler);

    //Allocate the buffer
    read_buffer = (char*) malloc(read_buffer_size);
    if (!read_buffer)
    {
        perror("Failed to allocate buffer");
        return EXIT_FAILURE;
    }

    restart: ;

    //Close if already open
    if(fd >= 0)
        close(fd);
    //Create or use an existing pipe special file
    fd = open_named_pipe(pipe_name, pipe_permissions, O_RDONLY, &pipe_created);
    if (fd < 0)
    {
        return EXIT_FAILURE;
    }

    while (run)
    {
        assert(fd >= 0);
        assert(read_buffer_size > 1);

        received = read(fd, read_buffer, read_buffer_size - 1);

        if (received > 0)
        {
            //add a NUL char for string termination
            read_buffer[received] = '0';
            printf("local process %llu received: %s\n", (unsigned long long) getpid(), read_buffer);
        }
        else if (received == 0)
        {
            //EOF reached, this happens in case the writer has closed its handle.
            //Perform a delayed restart and recreate the named pipe
            usleep(read_retry_delay);
            printf("Restarting...\n");
            goto restart;
        }
        else
        {
            switch (errno)
            {
            case EAGAIN:
                //Wait, if the pipe is empty,
                //happens when opened with the O_NONBLOCK flag
                usleep(read_retry_delay);
                break;
            case EPIPE:
            case EBADF:
            case EBADFD:
                perror("Pipe error");
                printf("Restarting...\n");
                goto restart;
            default:
                perror("Pipe error");
                return EXIT_FAILURE;
            };
        }
    }

    return EXIT_SUCCESS;
}

NamedPipeWriter.c

#include <stdlib.h>
#include <stdio.h>
#include <signal.h>

#include "pipe.h"

//Globals
const char* const pipe_name = "/tmp/myfifo";
const int pipe_permissions = 0600;
const size_t write_buffer_size = 1024; //[bytes]
const size_t write_retry_delay = 25000; //[us]
const size_t write_interval = 1000000;

int fd = -1;
int pipe_created = 0;
char* write_buffer = NULL;

//Handles EPIPE signal
void signal_handler(int signal)
{
    fprintf(stderr, "cought signal %d\n", signal);
}

//Handles cleanup on exit
void exit_handler(void)
{
    if (write_buffer)
        free(write_buffer);

    if (fd >= 0)
        close(fd);

    //if this process created the FIFO, we unlink it
    if (pipe_created == 0)
        unlink(pipe_name);
}

//Main Function
int main()
{
    //Locals
    int run = 1;
    int sent = 0;
    int msg_len = 0;

    //Install the exit handler
    atexit(&exit_handler);
    signal(EPIPE, signal_handler);
    signal(EACCES, signal_handler);

    //Allocate the buffer
    write_buffer = (char*) malloc(write_buffer_size);
    if (!write_buffer)
    {
        perror("Failed to allocate buffer");
        return EXIT_FAILURE;
    }

    restart: ;

    //Close if already open
    if(fd >= 0)
        close(fd);
    //Create or use an existing pipe special file
    fd = open_named_pipe(pipe_name, pipe_permissions, O_WRONLY, &pipe_created);
    if (fd < 0)
    {
        return EXIT_FAILURE;
    }

    while (run)
    {
        //Print message into the buffer
        msg_len = snprintf(write_buffer, write_buffer_size, "Greetings from process %llu\n", (unsigned long long) getpid());

        {
            char* msg_ptr = write_buffer;
            char* msg_end = write_buffer + msg_len;
            while (msg_ptr != msg_end)
            {
                assert(fd >= 0);
                assert(msg_ptr < msg_end);
                sent = write(fd, msg_ptr, msg_end - msg_ptr);
                if (sent > 0)
                {
                    msg_ptr += sent;
                }
                else if (sent == 0)
                {
                    //retry delay for nonblocking writes
                    usleep(write_retry_delay);
                }
                else
                {
                    switch (errno)
                    {
                    case EAGAIN:
                        //Wait, if the pipe is full,
                        //happens when opened with the O_NONBLOCK flag
                        usleep(write_retry_delay);
                        break;
                    case EPIPE:
                    case EBADF:
                    case EBADFD:
                        perror("Pipe error");
                        printf("Restarting...\n");
                        goto restart;
                    default:
                        perror("Pipe error");
                        return EXIT_FAILURE;
                    };
                }
            }

            printf("Written: %s\n", write_buffer);
            usleep(write_interval);
        }
    }

    return EXIT_SUCCESS;
}

我不知道我的哪里出了问题,但那些示例中没有一个能够工作。我编译并运行,然后什么都没有发生。我没有收到任何错误消息,它就是什么也没做。 - laura
抱歉,那个示例实现是相反的,先写入再读取。但我将提供一个例子,其中顺序无关紧要(因为进程应该能够在启动顺序上进行通信)。 - Sam
糟糕...正如我在已删除的评论中提到的那样,不要使用另一个管道将进程连接起来。对于这两个示例,请使用单独的终端。 - Sam

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接