1

我有一个编码任务,其中我要使用 fork() 设置一个进程环,然后通过环传递一条消息。现在,此时明显的问题是我无法将消息从初始进程传递给其直接连接的子进程。(只是先做 1 条消息传递作为测试)但是,我意识到环也可能无法正常运行。如果是这种情况,我不会感到惊讶,因为我使用的消息传递测试本质上是逐字示例代码,但话又说回来,形成环的代码也是如此......

所以,我的问题是:谁能帮我弄清楚我的代码出了什么问题?

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

int msg[2];
const int MAX = 100;

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

    int master, i, child, num, pid, ppid, counter, loops;
    char buffer[MAX];
    num = atoi(argv[1]);
    loops = atoi(argv[2]);
    counter = 0;

    master = (int)getpid();

    //check num arguments
    if (argc != 4) {
        fprintf(stderr, "%s\n", "incorrect # arguments");
    }
    pipe(msg);                      //create pipe
    dup2(msg[0], STDIN_FILENO);     //duplicate pipes
    dup2(msg[1], STDOUT_FILENO);
    close(msg[0]);                  //close ends of pipe
    close(msg[1]);

    //create other processes
    for(i=1; i<num; i++) {
        pipe(msg);                  //create new pipe

        //create new process
        child = fork();             //parent has child id
        pid = (int)getpid();        //has own pid
        ppid = (int)getppid();      //has parent pid

        //if parent, fix output
        if(child > 0){
            dup2(msg[1], STDOUT_FILENO);
        } else {
            dup2(msg[0], STDIN_FILENO);
        }
        close(msg[0]);
        close(msg[1]);
        if(child){
            break;
        }
    }

    //simple output
    fprintf(stderr, "process %d with id %d and parent id %d\n", i, pid, ppid);

    //message passing
    //if master, establish trasnfer
    if (pid == master) {
        //parent
        close(msg[0]);  //closes its read end
        char buffer[MAX];
        fprintf(stderr, "Parent: Waiting for input\n");
        while(1) {
            scanf("%s", buffer);
            if (strcmp(buffer, "exit")==0) {
               break;
            }
            write(msg[1], buffer, MAX);
        }
        close(msg[1]);  //closes it write end
    } else {
        //child
        close(msg[1]);  //closes its write end
        char buffer[MAX];
        fprintf(stderr, "Child: Waiting for pipe\n");
        while(read(msg[0], buffer, MAX) > 0) {
            fprintf(stderr, "Received: %s\n", buffer);
            buffer[0] = '\0';
        }
        close(msg[0]);  //closes its read end        
    }

    //special stuff for master node
    if(master == pid){
        fprintf(stderr, "%s\n", "i am the master");
        //special stuff
    } else {
        fprintf(stderr, "%s\n", "i am a child");
        //nothing really?
    }

    wait(2); //let all processes finish.
    exit(0);
}

我会问我的导师或助教,但他们都决定出城,远离电子邮件,直到作业到期。如果我不能让这个工作,它不是世界末日,但我想避免以不完整的编码作业开始课程。

4

4 回答 4

2

恕我直言,您最好在引用任何命令行参数之前验证它们(即代码应该像

if (argc != 4) {
    ...
}

int num = atol(argv[1]);

)

我不清楚你的代码,很抱歉我的不耐烦。以下是我的解决方案,其中包含要验证的代码片段。为了建立环,首先注意进程分为两种类型:主进程,谁初始化IPC,和从属。最初,我们需要两个管道,一个供 master 读取,另一个供 master 写入。初始化后,两个进程通过这样的管道连接(P 代表父,C 代表子)。

C:+----------+-----------+
  |          |           |
  | Read End | Write End |
  |          |           |
  +----------+-----------+
         |\            \
           \            \
            \            \
             \            \
              \            \
P:+----------+-----------+  \
  |          |           |   \
  | Read End | Write End |    \
  |          |           |     \
  +----------+-----------+      |
       /|                       |
        |                       |
        +-----------------------+

递归地,当新进程分叉时,我们希望连接演变为:

   C2:+----------+-----------+
      |          |           |
      | Read End | Write End |
      |          |           |
      +----------+-----------+
             |\                \
               \                \
                \                \
                 \                \
                  \                \
   C1:+----------+-----------+      |
      |          |           |      |
      | Read End | Write End |      |
      |          |           |      |
      +----------+-----------+      |
             |\                     |
               \                    |
                \                   |
                 \                  |
                  \                 |
    P:+----------+-----------+      |
      |          |           |      |
      | Read End | Write End |      |
      |          |           |      |
      +----------+-----------+      |
           /|                       |
            |                       |
            +-----------------------+

所以我们需要在 C1 和 C2 之间建立另一个管道,然后重定向 I/O。以下 ASCII 艺术说明了这一点。

                                  C2:+----------+-----------+
                                     |          |           |
                                     | Read End | WriteEnd3 |
                                     |          |           |
                                     +----------+-----------+
                                             |\           \
                                               \           \
                                                \           \
                                                 \           \
   C1:+----------+-----------+       +----------+-----------+ |
      |          |           |       |          |           | |
      | Read End | WriteEnd1 |       | Read End | WriteEnd2 | |
      |          |           |       |          |           | |
      +----------+-----------+       +----------+-----------+ |
             |\            \              /|                  |
               \            \              |                  |
                \            \             +------------------+
                 \            \
                  \            \
    P:+----------+-----------+  \
      |          |           |   \
      | Read End | Write End |    \
      |          |           |     \
      +----------+-----------+      |
           /|                       |
            |                       |
            +-----------------------+

要形成环,应该将 WriteEnd2 复制到WriteEnd1 因此 child2 可以从 child1 读取。WriteEnd1也应该代替WriteEnd3写入主控。

我包含了示例代码,但是,您最好了解完成作业的过程,而不仅仅是复制和粘贴:)。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define BUF_LEN 32

int  input_pipe[2];
int output_pipe[2];

char buf[BUF_LEN];
const char msg[] = "hello world.\n";
int str_len = sizeof(msg);

int status;

int main(int argc, char **argv) {
    /* master input channel */
    int master_input;
    int ring_pid = 0;

    if (argc != 2) {
        fprintf(stdout, "Argument error!\n");
        fflush(stdout);

        exit(-1);
    }

    int total_processes = atoi(argv[1]);

    if (pipe(input_pipe) || pipe(output_pipe)) {
        fprintf(stdout, "Cannot create pipes at master. Program exits.\n");
        fflush(stdout);

        exit(-1);
    } else {
        /* fork child process */
        pid_t pid = fork();
        if (pid) {
            /* dup read end of input pipe and write end of output pipe. */
            int input_fd  = dup(input_pipe[0]);
            int output_fd = dup(output_pipe[1]);

            close(input_pipe[0]);
            close(input_pipe[1]);
            close(output_pipe[0]);
            close(output_pipe[1]);

            /* parent process */
            fprintf(stdout, "Send message to downstream: %s", msg);
            fflush(stdout);
            write(output_fd, msg, str_len + 1);

            read(input_fd, buf, BUF_LEN);
            fprintf(stdout, "Recieve message from upstream: %s", buf);
            fflush(stdout);

            waitpid(pid, &status, 0);
        } else {
            /* child process */
            /* write end of master's input pipe */
            master_input  = dup(input_pipe[1]);

            int input_fd  = dup(output_pipe[0]);
            int output_fd = -1;

            /* increase ring number */
            while (++ring_pid < total_processes) {
                int tmp_pipe[2];

                if (pipe(tmp_pipe)) {
                    fprintf(stdout, "Cannot create pipes at master. Program exits.\n");
                    fflush(stdout);

                    exit(-1);
                } else {
                    /* pid of sub-process's child */
                    pid_t spid = fork();
                    if (spid) {
                        /* drop read end of the pipe, we read from parent process */
                        close(tmp_pipe[0]);
                        /* output of last process directs to master's input */
                        if (ring_pid == total_processes - 1) {
                            output_fd = dup(master_input);
                        } else {
                            output_fd = dup(tmp_pipe[1]);
                        }
                        close(tmp_pipe[1]);
                        /* receive and send message */
                        read(input_fd, buf, BUF_LEN);
                        fprintf(stdout, "Read from upstream: %s", buf);

                        fprintf(stdout, "Write to downstream: %s", buf);
                        write(output_fd, buf, BUF_LEN);

                        waitpid(spid, &status, 0);
                        break;
                    } else {
                        /* for child process, the input is read end of the new pipe. */
                        input_fd = dup(tmp_pipe[0]);
                        close(tmp_pipe[0]);
                    }
                }
            }
        }
    }
}
于 2012-09-24T17:51:37.873 回答
0

您的算法需要更改。这里的逻辑是,如果你有 n 个孩子,那么你需要有 n + 1 个管道。

现在您需要将管道的末端存储在合适的数据结构中。我使用了一个二维数组,其中每一行表示一个管道,并且说第 0 列是读取端,第 1 列是特定管道的写入端。(请考虑使这个数组动态化)

现在对于每个孩子,我们需要两端,一个是读端,另一个是写端。现在,这些末端将来自两个不同的管道。这就是逻辑。

这意味着假设我们考虑 child1,他从 pipe1 读取结束并从 pipe2 写入结束。现在 child0 应该有来自 pipe1 的写端,而 child2 应该有来自 pipe2 的读端。希望这个想法很清楚。

(现在另一个标准是进程需要充当过滤器,即它们从标准输入读取并写入标准输出。这是使用 dup2 函数完成的,该函数将复制各自的管道描述符以进行读取和写入,到标准输入和输出)

我在下面提供了一个带有一些注释的示例代码,这将帮助您理解这个概念。

(有硬编码的值和非常少的错误处理。你应该添加这个。)

#define MAXCHILD 50
#define NO_OF_CHILDREN 20
#define MAXBUF 100

void child(int readend, int writeend);
int main()
{
int pipefd[MAXCHILD + 1][2];
int noofchildren = 0;
int nchild = NO_OF_CHILDREN;
int ret;
int i ;
char buf[MAXBUF];
for(i = 0; i <= nchild; i++)
{
    ret = pipe(pipefd[i]);
    if (0 > ret)
    {
        printf("Pipe Creation failed\n");
        exit(1);
    }
}
printf("Pipes created\n");

for(i = 0; i < nchild; i++)
{

    ret = fork();
    if (0 == ret)
    {
        child(pipefd[i][0], pipefd[i + 1][1]);
    }
    //These ends are no longer needed in parent.
    close (pipefd[i][0]);
    close(pipefd[i + 1][1]);
}

printf("%d Children  Created\n", nchild);

while(1) 
{
    printf("Enter Some data\n");
    //Now the parent/master reads from the standard input
    fgets(buf, MAXBUF, stdin);
    //Write to the pipe which is connected to the first child.
    write(pipefd[0][1],buf, strlen(buf) + 1);
    //Read from the pipe, which is attached to the last child created
    read(pipefd[nchild][0], buf, MAXBUF);
    printf("Master Read from the pipe - %s\n", buf);
    if (strcmp(buf, "exit\n") == 0)
    {
        break;
    }
} 
//By this time all other children, would have exited
//Let's wait to see this 
while(1)
{
    ret = wait(NULL);
    if (0 > ret) //Returns -ve when no more child is left.
    {
        break;
    }
    noofchildren++;
}
printf("My %d children terminated! Now I am exiting", noofchildren);
exit(0);
}

void child(int readend, int writeend)
{
char buf[MAXBUF];
int ret;
//set up environment
dup2(readend, 0);
dup2(writeend,1);   
while(1)
{
    ret = read(0, buf, MAXBUF );
    if (strcmp(buf, "exit\n") == 0)
    {
        write(1, buf, ret);
        exit(0);
    }
    write(1, buf, ret);
}
}
于 2012-09-24T20:43:37.600 回答
0

我可以建议的一件事是在分叉进程时使用这种风格:

child_pid = fork();
if (child_pid==0) {
  childProcess(); // isolate the child process logic
  _exit(0); // make sure the execution doesn't escape the if statement
}

我认为这将帮助您查看代码中的一些逻辑问题。

于 2012-09-24T14:40:26.170 回答
0

您正在尝试使用scanf从标准输入中读取的 读取您的输入。但是,您所做的第一件事就是通过在原始标准输入上复制第一个管道的读取端来关闭原始标准输入。因此,您正试图从管道中读取输入。要解决此问题,您应该将第一个管道的读取端保存在一个变量中,以便主控器稍后读取,但不要将其复制到标准输入中。

此外,当您尝试读取或写入管道时,您正在使用存储在 中的描述符msg,但您在创建链时关闭了这些描述符。您需要读取/写入复制管道的标准输入/输出描述符。您还(尝试)在读/写之前关闭管道链的未使用端,但是当您开始使用将不再使用的整个链时。

例如,一个包含 4 个进程的循环可能如下所示:

 input(stdin)
   v
+> A -+
|     v
D     B
^     |
+- C <+

创建所有进程后,每个连接要么是标准输入,要么是标准输出,除了返回到进程 A(主)的链。进程 A 从标准输入读取并写入标准输出,标准输出连接到进程 B 的标准输入。进程 B 然后读取它的标准输入并写入它的标准输出,将消息传递给进程 C。进程 C 和 D 做同样的事情,将消息传递到整个循环并返回到读取端口上的进程 A的原始管道。由于每个进程都负责读取和写入,因此在完全完成之前不应关闭其管道。

于 2012-09-24T14:41:29.587 回答