【问题标题】:Strange Output Buffering Behavior in CC中奇怪的输出缓冲行为
【发布时间】:2017-05-17 01:42:33
【问题描述】:

我正在从一门开放课程中尝试 C shell 实现,但输出缓冲的行为有些有趣。

代码是这样的(注意我使用 pid = waitpid(-1, &r, WNOHANG) 的那一行):

int
main(void)
{
  static char buf[100];
  int fd, r;
  pid_t pid = 0;

  // Read and run input commands.
  while(getcmd(buf, sizeof(buf)) >= 0){
    if(buf[0] == 'c' && buf[1] == 'd' && buf[2] == ' '){
      buf[strlen(buf)-1] = 0;  // chop \n
      if(chdir(buf+3) < 0)
        fprintf(stderr, "cannot cd %s\n", buf+3);
      continue;
    }
    if((pid = fork1()) == 0)
      runcmd(parsecmd(buf));
    while ((pid = waitpid(-1, &r, WNOHANG)) >= 0) {
      if (errno == ECHILD) {
          break;
      }
    }
  }
  exit(0);
}

runcmd函数是这样的(注意在管道处理中我创建了2个子进程并等待它们终止):

void
runcmd(struct cmd *cmd)
{
  int p[2], r;
  struct execcmd *ecmd;
  struct pipecmd *pcmd;
  struct redircmd *rcmd;

  if(cmd == 0)
    exit(0);

  switch(cmd->type){
  case ' ':
    ecmd = (struct execcmd*)cmd;
    if(ecmd->argv[0] == 0) {
      exit(0);
    }
    // Your code here ...
    // fprintf(stderr, "starting to run cmd: %s\n", ecmd->argv[0]);
    execvp(ecmd->argv[0], ecmd->argv);
    fprintf(stderr, "exec error !\n");
    exit(-1);

    break;

  case '>':
  case '<':
    rcmd = (struct redircmd*)cmd;
    // fprintf(stderr, "starting to run <> cmd: %s\n", rcmd->file);
    // Your code here ...
    mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
    if (rcmd->type == '<') {
      // input
      close(0);
      if (open(rcmd->file, O_RDONLY, mode) != 0) {
        fprintf(stderr, "Opening file error !\n");
        exit(-1);
      }
    } else {
      // output
      close(1);
      if (open(rcmd->file, O_WRONLY|O_CREAT|O_TRUNC, mode) != 1) {
        fprintf(stderr, "Opening file error !\n");
        exit(-1);
      }
    }

    runcmd(rcmd->cmd);
    break;

  case '|':
    pcmd = (struct pipecmd*)cmd;
    // fprintf(stderr, "starting to run pcmd\n");
    // Your code here ...
    pipe(p);

    if (fork1() == 0) {
      // child for read, right side command
      close(0);
      if (dup(p[0]) != 0) {
        fprintf(stderr, "error when dup !\n");
        exit(-1);
      }
      close(p[0]);
      close(p[1]);
      runcmd(pcmd->right);
      fprintf(stderr, "exec error !\n");
    } 
    if (fork1() == 0) {
      // left side command for writing
      close(1);
      if (dup(p[1]) != 1) {
        fprintf(stderr, "dup error !\n");
        exit(-1);
      }
      close(p[0]);
      close(p[1]);
      runcmd(pcmd->left);
      fprintf(stderr, "exec error !\n");
    }
    close(p[0]);
    close(p[1]);
    int stat;
    wait(&stat);
    wait(&stat);

    break;

  default:
    fprintf(stderr, "unknown runcmd\n");
    exit(-1);
  }    
  exit(0);
}

奇怪的是,当我在终端中执行“ls | sort”时,我不断得到以下输出

6.828$ ls | sort
6.828$ a.out
sh.c
t.sh

这表示在打印下一个命令提示符“6828$”之前,子进程的输出仍未刷新到终端。

但是,如果我不使用 pid = waitpid(-1, &r, WNOHANG)) 并使用 pid = waitpid(-1, &r, 0)) (或wait()),输出会是正常的,如:

6.828$ ls | sort
a.out
sh.c
t.sh

我一直在思考问题的原因,但没有想出可能的原因。谁能提出一些可能的原因?

非常感谢!

【问题讨论】:

    标签: c linux shell buffer


    【解决方案1】:

    此代码没有明确定义的行为:

    while ((pid = waitpid(-1, &r, WNOHANG)) >= 0) {
      if (errno == ECHILD) {
          break;
      }
    }
    

    如果waitpid 返回-1,while 循环将立即中断,这正是它在发生错误时返回的值。因此,如果进入循环体,waitpid 返回一些非负值:要么是 0——表示子进程仍在执行——要么是已经退出的子进程的 pid。这些不是错误条件,因此errno 的值没有意义。可能是ECHILD,在这种情况下循环会错误地中断。

    必须仅在值有意义的情况下检查 errno 的值。或者,更准确地说,引用Posix standard

    errno 的值应仅在调用明确声明为其设置的函数之后定义,直到它被下一个函数调用更改或应用程序为其分配值。 errno 的值只有在函数的返回值表明它是有效的时候才应该被检查。

    但我很困惑为什么你觉得有必要使用WNOHANG 忙循环。这是对资源的巨大浪费,因为您的父进程将重复执行系统调用,直到子进程实际终止。由于您确实打算等到孩子终止,因此只调用wait 或将0 指定为waitpid 的标志值会更有意义。

    另一方面,如果wait(或waitpid)返回-1,而errno 设置为EINTR,您可能希望重复此操作。如果它返回-1 并且errno 既不是EINTR 也不是ECHILD,那么您可能想要记录一些硬错误。但这与您的问题无关,afaics。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-03-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-07-05
      • 1970-01-01
      相关资源
      最近更新 更多