【问题标题】:n-pipeline producing EOF on endn 管道在末端产生 EOF
【发布时间】:2014-11-24 04:00:47
【问题描述】:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>

#include <string.h>

void tokenizer(char* input, char** output) { //My tokenizer
  char* input_dup = strdup(input);
  output[0] = strtok(input_dup, " ");
  int i = 1;
  while ((output[i] = strtok(NULL, " ")) != NULL) {
   i++; 
  }
}

void run_command(char** args, int* fd) { //no pipe
  pid_t pid = fork();

  if (pid < 0) {
   printf("Forking failed...\n"); 
  }
  else if (pid == 0) {
    close(fd[0]);
    if (fd[1] != 1)
      dup2(fd[1], 1);
    execvp(args[0], args);
    printf("Command failed...\n");
    exit(1);
  }
  else {
    close(fd[1]);
    wait(pid);
    char buff[1];

    while (read(fd[0], buff, 1) > 0) {
      if (buff[0] == EOF || buff[0] == '\0') {
    printf("Caught something, returning out...");
    return;
      }
      else {
    printf("%c", buff[0]);
      }      
    }
  }
}

//pipeline function
void run_pipe(char** args, int* fd) {
    pid_t pid = fork();

    if (pid < 0) {
   printf("Forking failed...\n"); 
  }
  else if (pid == 0) {
    if (fd[1] != 1) {
      dup2(fd[1], 1);
    }
    execvp(args[0], args);
    printf("Command failed...\n");
    exit(1);
  }
  else {
    close(fd[1]);
    if (fd[0] != 0) {
     dup2(fd[0], 0);
    }
    wait(pid);
  }         
}

int main(int argc, char** argv) {
  printf ("Starting myshell (mysh) \n..\n..\n");

  while (1) {
    char cwd[1024];

    printf ("mysh :: %s -> ", getcwd(cwd, sizeof(cwd)));

    char ch[1024];
    memset(ch, 0, 1023); //for cleanup
    char c = 0;
    int i = 0;

    while (c != '\n') {
      c = getchar();
      if (c == EOF) {
    printf ("EOF Received, exiting...\n");
    return 0;
     }
      if (c != '\n')
     ch[i] = c;
      i++;
    }

    if (ch[0] != '\0') {

      char* tokens[128];
      tokenizer(ch, tokens);

      //first check for keywords
      if (strcmp(tokens[0], "cd") == 0) {
    if (chdir(tokens[1]) < 0) {
      printf("ERROR: Directory %s does not exist\n", tokens[1]);
    }
      }
      else if (strcmp(tokens[0], "exit") == 0) {
      printf("Leaving shell...\n");
      return 0;
      }
      else {
    char* commands[50];
    memset(commands, 0, sizeof(commands));
    int j = 0;
    int k = 0;

    int fd[2];

    //try something different...

    while (tokens[j] != NULL) {
     if (strcmp(tokens[j], "|") == 0) {
       commands[k] = NULL;
       pipe(fd);
       run_pipe(commands, fd);
       j++;
       k = 0;
     }
     //more cases here
     else { //nothing special
       commands[k] = tokens[j];
       j++;
       k++;
     }
    }
    commands[k] = NULL;
    pipe(fd);
    run_command(commands, fd);
      }
    }
  }
}

上面的代码是为了模拟一个shell。它处理单个命令并正确处理流水线(即 ps | sort | wc 返回正确的输出),但是当流水线完成时,它返回一个 EOF,该 EOF 被 getchar() 循环中的条件捕获。如果我尝试忽略此 EOF,则会出现段错误。我是否在某处打开了管道并且标准输入被淹没了?任何帮助表示赞赏。

【问题讨论】:

  • gcc -Wall -Wextra -Werror -Wmissing-prototypes编译会发现wait()没有被声明;添加#include &lt;sys/wait.h&gt;,你会发现编译错误。这可能是也可能不是你所有麻烦的根源,但它会解释段错误——你在需要指针的地方传递一个整数。 FWIW:始终使用与建议的一样严格甚至更严格的选项进行编译(我通常使用一些额外的选项:-Wold-style-declaration -Wold-style-definition -Wstrict-prototypes,这需要对您的代码进行其他小的更改。)
  • 修复了对wait() 的调用,我得到了可理解的、不崩溃的结果。
  • @JonathanLeffler 你能发布一个固定等待的小sn-p吗?我已经修复了导入,但现在不知道如何修复等待/waitpids
  • @JonathanLeffler 我实现了等待更改,但这并没有真正解决任何问题,在调用类似 ps |排序

标签: c shell operating-system


【解决方案1】:

我能够解决这个问题。这可能不是正确的方法,但我保存了一份标准输入副本,并在流水线完成后使用 dup2 重置它。

int in_bak = dup(0);

//stuff

dup2(in_bak, 0);
close(in_bak);

【讨论】:

  • 是的,这有点粗略但足够有效。最终,您将希望让您的外壳摆脱与管道输出相呼应的业务,但这会让您摆脱直接的打嗝。我开始着手处理它,但要修复代码需要做的工作量适中,因此它可以正常工作。
  • @JonathanLeffler 是的......在这一点上,“黑客工作”对我来说已经足够了。
【解决方案2】:

编译修复

您需要添加#include &lt;sys/wait.h&gt;,然后修复对wait() 的调用。我用过(两次):

int status;
int corpse = wait(&status);
printf("PID %d status 0x%.4X\n", corpse, status);

可以说,这应该是一个寻找特定 PID 的循环,或者您应该改用 waitpid()。在调试 shell 时,您想了解每个退出的 PID 及其状态。

我跑了`ps | wc' 并得到:

Starting myshell (mysh) 
..
..
mysh :: /usr/local/google/home/jleffler/soq -> ps | wc
PID 25960 status 0x0000
PID 25961 status 0x0000
      4      16     117
mysh :: /usr/local/google/home/jleffler/soq -> EOF Received, exiting...

如果您的意思是“代码应该继续而不是获得 EOF”,那么还有一些工作要做。


切题

我注意到这条线:

if (buff[0] == EOF || buff[0] == '\0')

buff[0] 中的字符来自read() 调用。它永远不会是有意义的 EOF; EOF 不同于每个字符(因此getchar() 返回int)。这在以后变得很重要:

char c = 0;
while (c != '\n')
{
    c = getchar();
    if (c == EOF)

由于cchar,因此您无法可靠地将其与EOF 进行比较。您必须将getchar() 的结果存储在int 中。

我还不确定这些是造成问题的原因,但你必须小心。


可能的原因

我认为麻烦在于父代码中的run_pipe()(已修改):

    else
    {
        close(fd[1]);
        if (fd[0] != 0)
        {
            dup2(fd[0], 0);
        }
        int status;
        int corpse = wait(&status);
        printf("PID %d status 0x%.4X\n", corpse, status);
    }

fd[0] != 0 条件将始终为真(极不可能为假),因此您随后将 shell 的输入更改为从fd[0] 读取。您应该对此进行审查;这意味着您正在从管道的读取端向子级读取标准输入。那很糟;您丢失了原始输入!

您的代码似乎也让父母等待孩子死亡,然后读取管道并回显到标准输出。这不是一个好主意;最好让孩子(管道中的最后一个孩子)直接写入标准输出。这有两个原因:

  1. 子进程可能写入的数据多于管道的容量,因此它将阻塞等待读取其输出的内容,但读取器将阻塞等待子进程死亡,因此您将陷入死锁.

  2. 它会减慢速度,孩子的输出很可能会被缓冲,而不是及时出现在终端上。

我对如何处理由三部分组成的管道有点怀疑。在运行这三个的中间过程之前,您需要创建两个管道;我在你的代码中没有看到。

【讨论】:

  • 这就是我的意思...我不确定如何防止 EOF 在命令结束后吐出。
  • 关于您的编辑,第一个绝对是多余的,但 char c 工作正常/能够捕获 ctrl + d 或生成的 EOF。如果我只执行一个命令,shell 工作正常,如果我执行两个命令,但是 shell 执行上述操作并吐出 EOF。如果你在 main 中删除 EOF 捕获,它将出现段错误。
  • 注意:read() 从不返回 EOF 本身; EOF 是标准 I/O 包的人工制品,read() 调用不是标准 I/O 包的一部分。 read() 通过返回读取的 0 个字节表示“没有更多数据”。您可能会遇到两个问题(我想知道我多久输入一次):如果签署了普通的 char,那么 ÿ (y-umlaut, U+00FF, LATIN SMALL LETTER Y WITH DIAERESIS) 被误解为等同于 EOF (假设 8859-1、8859-15 代码集);如果普通的char 是无符号的,那么没有字符会匹配 EOF。这两种行为都是错误的。
  • 这部分是有道理的,所以我将一些东西改为整数而不是字符,但这本身并没有改变从管道中出来的 EOF。我认为这可能与 stdin 被 dup2'ed 有关,但我不确定它为什么会溢出。
  • 嗯...我认为这可能是问题所在,但它似乎一直到最后都可以正常工作。有没有办法解决结局还是整个事情都需要重做?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-09-17
  • 2012-02-04
  • 2013-05-08
  • 2020-01-25
  • 2016-07-15
  • 1970-01-01
相关资源
最近更新 更多