我有一个程序 A 执行以下操作:
- 从输入读取 2 个字节
- 打印读取的输入
exec
进入程序B。
程序B执行以下操作
- 从输入读取 2 个字节
- 打印输入
具体来说,以下是方案A、B:
A:
#include <unistd.h>
#include <stdio.h>
int main(){
char a[3];
fgets(a, 3, stdin);
printf("%s\n", a);
char* args[] = {NULL};
execv("./read2", args);
}
乙:
#include <stdio.h>
int main(){
char a[] = "hy";
fgets(a, 3, stdin);
printf("%s\n", a);
}
当我像这样执行它时echo 'abcd' | ./A
,我期望得到以下输出
ab
cd
但我得到了 ab hy
为什么不B
从其标准输入读取?
答案1
TLDR:如果后续进程需要从父进程停止的位置准确读取,则父进程必须使用无缓冲 IO。
对于非缓冲 IO,程序的行为正确:
#include <stdio.h>
#include <string.h>
#include <unistd.h>
char buf[3];
int main(int argc, char *argv[])
{
read(STDIN_FILENO, buf, 2);
printf("%s '%s'\n", *argv, buf);
if (strcmp(*argv, "./childtu") == 0) return 0;
execl("./readtwo", "./childtu", (char *) 0);
}
通过运行
$ make readtwo
cc readtwo.c -o readtwo
$ echo abcdefg | ./readtwo
./readtwo 'ab'
./childtu 'cd'
$
父级中的缓冲 IO(通过fgets
)是问题所在,因为如果输入比父级提前读取的输入多,则子级只能从标准输入读取:
#include <stdio.h>
#include <string.h>
#include <unistd.h>
char buf[3];
int main(int argc, char *argv[])
{
fgets(buf, 3, stdin);
printf("%s '%s'\n", *argv, buf);
if (strcmp(*argv, "./childtu") == 0) return 0;
execl("./readtwo", "./childtu", (char *) 0);
}
如果好奇,可以二进制搜索确切的缓冲区大小,或者查看内核中设置的内容:
$ perl -e 'print(("a")x99999)' | ./readtwo
./readtwo 'aa'
./childtu 'aa'
$
使用strace
(或类似的)我们可以观察父进程有多少read
来自标准输入(fd 0):
$ echo asdf | strace -o blah ./readtwo
./readtwo 'as'
./childtu ''
$ fgrep 'read(0' blah
read(0, "asdf\n", 4096) = 5
read(0, "", 4096) = 0
$
在这里,父进程想要 4096 字节(但只得到了 5 个字节),而exec
'd 进程得到了零,因为没有剩下任何东西。因此,如果这是一个问题,请不要在父进程中使用缓冲读取。