使用fgets读取输入会返回C

时间:2017-05-17 06:05:52

标签: c shell process io fgets

我正在尝试一些用于shell实现的C代码,发现fgets()在我分叉一个进程后返回重复的行,这是我无法理解的,我非常感谢任何帮助。

我的问题是:分叉是否更改了父进程中任何打开文件的偏移量?这似乎发生在我的程序中。

来自下面的回答@Vadim Ponomarev和我的理解: fgets()不是线程安全的(或严格来说,它是,但是分支进程导致stdin以某种方式初始化,导致共享文件偏移的更改)。

代码如下:

int main() {

  char buf[200];
  int r;
  pid_t pid = 0;

  while(getcmd(buf, 200, pid) >= 0) {
    fprintf(stderr, "current pid: %d\n", getpid());
    pid = fork();
    // Without forking the fgets() reads all lines normally
    if(pid == 0)
      exit(0);

    wait(&r);
  }

  return 0;
}

getcmd()函数只是一个包装器:

int
getcmd(char *buf, int nbuf, pid_t pid)
{
  memset(buf, 0, nbuf);
  if (fgets(buf, nbuf, stdin) == NULL) {
    fprintf(stderr, "EOF !!!\n");
    return -1;
  }
  fprintf(stderr, "pid: %d -- getcmd buf ======= --> %s\n", getpid(), buf);
  return 0;
}

我还有一个带有一些随机文本的输入文件 temp

line 1
line 2
line 3

编译完成后,我运行 a.out< temp ,输出显示打印了6行,通常会复制一些行。但如果我删除该行

pid = fork()
...

然后输出变得正常(只是逐个显示所有行,这意味着fgets()被调用3次)。

知道出了什么问题吗?

输出(这是得到的):

pid: 10361 -- getcmd buf ======= --> line1

current pid: 10361
pid: 10361 -- getcmd buf ======= --> line2

current pid: 10361
pid: 10361 -- getcmd buf ======= --> line3

current pid: 10361
pid: 10361 -- getcmd buf ======= --> line2

current pid: 10361
pid: 10361 -- getcmd buf ======= --> line3

current pid: 10361
pid: 10361 -- getcmd buf ======= --> line3

current pid: 10361
EOF !!!

我希望看到这个:

current pid: 10361
pid: 10361 -- getcmd buf ======= --> line1

current pid: 10361
pid: 10361 -- getcmd buf ======= --> line2

current pid: 10361
pid: 10361 -- getcmd buf ======= --> line3

EOF

可编辑的版本供参考:

#include <stdio.h>
#include <stdlib.h>
#include <wait.h>
#include <zconf.h>
#include <unistd.h>
#include <memory.h>

int
getcmd(char *buf, int nbuf, pid_t pid)
{
  memset(buf, 0, nbuf);
  if (fgets(buf, nbuf, stdin) == NULL) {
    fprintf(stderr, "EOF !!!\n");
    return -1;
  }
  fprintf(stderr, "pid: %d -- getcmd buf ======= --> %s\n", getpid(), buf);
  return 0;
}

int main() {

  char buf[200];
  int r;
  pid_t pid = 0;

  while(getcmd(buf, 200, pid) >= 0) {
    fprintf(stderr, "current pid: %d\n", getpid());
    pid = fork();
    // Without forking the fgets() reads all lines normally
    if(pid == 0)
      exit(0);

    wait(&r);
  }

  return 0;
}

谢谢!

2 个答案:

答案 0 :(得分:4)

  1. 已经提到父和子共享文件描述符0(stdin)的当前位置
  2. 似乎流的libc运行时初始化(stdin,stdout,stderr)包含一些改变当前stdin位置的东西:

    > strace -f ./a.out < temp 2>&1 | less
    ....
    write(2, "pid: 29487 -- getcmd buf ======="..., 45pid: 29487 -- getcmd buf ======= --> line 1
    clone(child_stack=0,flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD,child_tidptr=0x7f34940f19d0) = 29488
    Process 29488 attached
    [pid 29487] wait4(-1,  <unfinished ...>
    [pid 29488] lseek(0, -14, SEEK_CUR)     = 7
    [pid 29488] exit_group(0)               = ?
    [pid 29488] +++ exited with 0 +++
    <... wait4 resumed> [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = 29488
    
  3. 请注意孩子的lseek(0,-14,SEEK_CUR)(pid 29488)

    1. 因此,在我的环境中(openSUSE Leap 42.2,glibc-2.22-4.3.1),程序无限循环,根本没有EOF

    2. 在示例

      中将fgets()更改为read()
      ....
      if (read(0, buf, nbuf) == 0) {
      ....
      while(getcmd(buf, 7, pid) >= 0) {
      ....
      
    3. 并且程序按预期运行(三行和EOF)

      1. 再次运行strace -f - 在孩子中不再有lseek()!!

      2. 结论 - 似乎必须在多进程环境中非常谨慎地使用流函数(在stdio.h中声明),因为有很多副作用(如本例所示)

答案 1 :(得分:0)

我找到了使用this thread中的fgets()的解决方案,该解决方案讨论了相同的问题tldr:

  

exit刷新子代中的stdio缓冲区。   ...   有关更多详细信息,这里是与POSIX对应的链接   参考,第2.5.1章:

     

http://pubs.opengroup.org/onlinepubs/007904875/functions/xsh_chap02_05.html

     

因此,行为是不确定的,因此可以更改   在glibc 2.19和2.24之间。

解决方法:

  

如上面的链接所述,可以使用两种解决方案来修复代码:

     

if(fork()== 0){fclose(fd);出口(1); }

     

     

if(fork()== 0){_exit(1); }