我正在尝试一些用于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;
}
谢谢!
> 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
请注意孩子的lseek(0,-14,SEEK_CUR)(pid 29488)
....
if (read(0, buf, nbuf) == 0) {
....
while(getcmd(buf, 7, pid) >= 0) {
....
和程序按预期运行(三行和EOF)
我找到了使用fgets()
的this thread解决同样问题的解决方案,tldr:
exit刷新子进程中的stdio缓冲区。 ...有关详细信息,请参阅第2.5.1节中与POSIX参考相对应的链接:
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); }