繁体   English   中英

C语言中奇怪的输出缓冲行为

[英]Strange Output Buffering Behavior in C

我正在从开放课程中尝试C外壳实现,但是关于输出缓冲的行为有些有趣。

代码是这样的(请注意我使用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函数是这样的(请注意,在管道处理中,我创建了两个子进程并等待它们终止):

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

我已经考虑了问题的原因很长时间了,但是没有提出可能的原因。 有人可以提出一些可能的原因吗?

非常感谢!

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

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

如果waitpid返回-1,则while循环会立即中断,这恰恰是发生错误时返回的结果。 因此,如果输入了循环的主体,则waitpid返回一些非负值:0-指示子代仍在执行-或退出子代的pid。 这些不是错误条件,因此errno的值没有意义。 可能是ECHILD ,在这种情况下,循环将错误地中断。

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

errno的值应仅在调用明确声明要为其设置的函数之后,直到下一次函数调用对其进行更改或由应用程序为其分配值之后才定义。 仅当函数的返回值指示errno值有效时,才应检查errno的值。

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

另一方面,如果errno设置为EINTR则返回-1,则可能要重复wait (或waitpid )。 如果它返回-1并且errno既不是EINTR也不是ECHILD ,则发生了一些您可能想要记录的硬错误。 但这与您的问题无关,狂热。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM