简体   繁体   English

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

[英]Strange Output Buffering Behavior in C

I am trying out a C shell implementation from an open course, yet there is something intriguing about the behavior of the output buffering. 我正在从开放课程中尝试C外壳实现,但是关于输出缓冲的行为有些有趣。

The code goes like this (note the line where I use pid = waitpid(-1, &r, WNOHANG) ): 代码是这样的(请注意我使用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);
}

The runcmd function is like this (note that in pipe handling I create 2 child processes and wait for them to terminate): 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);
}

The wierd thing is, when I execute "ls | sort" in the terminal, I constantly get the following output 奇怪的是,当我在终端中执行“ ls | sort”时,我不断得到以下输出

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

This indicates that before the next command prompt "6828$" is printed, the output from the child process is still not flushed to terminal. 这表明在打印下一个命令提示符“ 6828 $”之前,子进程的输出仍未刷新到终端。

However, if I don't use pid = waitpid(-1, &r, WNOHANG)) and use pid = waitpid(-1, &r, 0)) (or wait() ), the output would be normal like: 但是,如果我不使用pid = waitpid(-1,&r,WNOHANG))并使用pid = waitpid(-1,&r,0)) (或wait() ),则输出将是正常的,例如:

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

I have been thinking about the cause of the problem for a long time but did not come up with a possible reason. 我已经考虑了问题的原因很长时间了,但是没有提出可能的原因。 Can anyone suggest some possible reason? 有人可以提出一些可能的原因吗?

Thanks a lot! 非常感谢!

This code does not have well-defined behaviour: 此代码没有明确定义的行为:

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

The while loop breaks immediately if waitpid returns -1, which is precisely what it returns in the case of an error. 如果waitpid返回-1,则while循环会立即中断,这恰恰是发生错误时返回的结果。 So if the body of the loop is entered, waitpid returned some non-negative value: either 0 -- indicating that the child is still executing -- or the pid of a child which had exited. 因此,如果输入了循环的主体,则waitpid返回一些非负值:0-指示子代仍在执行-或退出子代的pid。 Those are not error conditions, so the value of errno is not meaningful. 这些不是错误条件,因此errno的值没有意义。 It might be ECHILD , in which case the loop will incorrectly break. 可能是ECHILD ,在这种情况下,循环将错误地中断。

You must only check the value of errno in cases where the value is meaningful. 只有在有意义的情况下,才必须检查errno的值。 Or, to be more precise, quoting the Posix standard : 或者,更准确地说,引用Posix标准

The value of errno shall be defined only after a call to a function for which it is explicitly stated to be set and until it is changed by the next function call or if the application assigns it a value. errno的值应仅在调用明确声明要为其设置的函数之后,直到下一次函数调用对其进行更改或由应用程序为其分配值之后才定义。 The value of errno should only be examined when it is indicated to be valid by a function's return value. 仅当函数的返回值指示errno值有效时,才应检查errno的值。

But I'm puzzled why you feel it necessary to busy loop using WNOHANG . 但是我很困惑,为什么您觉得有必要使用WNOHANG忙循环。 That's a massive waste of resources, since your parent process will repeatedly execute the system call until the child actually terminates. 这将浪费大量资源,因为您的父进程将反复执行系统调用,直到子进程实际终止为止。 Since you really intend to wait until the child terminates, it would make much more sense to just call wait or to specify 0 as a flag value to waitpid . 因为您确实打算等到子项终止,所以仅调用wait或将0指定为waitpid的标志值会更有意义。

On the other hand, you might want to repeat the wait (or waitpid ) if it returns -1 with errno set to EINTR . 另一方面,如果errno设置为EINTR则返回-1,则可能要重复wait (或waitpid )。 And if it returns -1 and errno is neither EINTR nor ECHILD , then some hard error has occurred which you might want to log. 如果它返回-1并且errno既不是EINTR也不是ECHILD ,则发生了一些您可能想要记录的硬错误。 But that's not related to your problem, afaics. 但这与您的问题无关,狂热。

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

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