由此,提出了一个疑问,为什么不把fork和exec合并成一个调用?
猜测:一个是保留父进程,一个是终止父进程,肯定不一样啊。
getcmd
获取用户输入,若不是cd命令,则:从逻辑上可看出这样的代码非常浪费时间和空间,因为fork会将父进程全部拷贝,然后再用exec替换。为了防止这样的浪费,有了copy-on-write(要在4.6进行讨论)
read(fd1, buf, n)
从fd1
读最多n个字节,将它们拷贝到buf
中,然后返回读出的字节数;write(fd2, buf, n)
写buf
中的n个字节到fd2
并返回实际写出的字节数cat
的实现逻辑:从fd1
读取,然后再写到f2
中。下面代码中和其类似,而分别将二者设为0和1,代表从标准输入读取,然后再输出到标准输出中(即从键盘读取,再输出到屏幕上)char buf[512];
int n;
for(;;){
n = read(0, buf, sizeof buf);
if(n == 0)
break;
if(n < 0){
fprintf(2, "read error\n");
exit();
}
if(write(1, buf, n) != n){
fprintf(2, "write error\n");
exit();
}
}
close
系统调用释放一个文件描述符,让其以后可以被其他系统调用使用(如open
、pipe
、dup
)fork
一个进程->关闭当前的标准输入->打开指定文件(默认成为标准输入)->执行新的程序cat<input.txt
的原理:char *argv[] = {"cat", nullptr};
pid = fork();
if (pid==0){
close(0); // 子进程关闭文件描述符0后,可以保证接下来的open会使用0作为心打开的文件input.txt的文件描述符,之后cat就会在标准输入指向input.txt的情况下运行
open("input.txt", O_RDONLY);
exec("cat", argv);
}
if (pid!=0){
wait(NULL);
}
dup
复制一个已有的文件描述符,返回一个指向同一个输入/输出对象的新描述符。这两个描述符共享同一个文件偏移文件描述符是一个强大的抽象,因为它们将所连接的细节隐藏起来了:一个进程向描述符1写出,它有可能是写到一份文件、一个设备(如控制台)、或一个管道
int p[2];
char *argv[2];
argv[0] = "wc";
argv[1] = 0;
pipe(p);
if(fork() == 0) { // 此时子进程的文件描述符表为:0->标准读;1->标准写;2->标准错误;3->p[0];4->p[1]
close(0); // 此时子进程的文件描述符表为:1->标准写;2->标准错误;3->p[0];4->p[1]
dup(p[0]); // 此时子进程的文件描述符表为:0->p[0];1->标准写;2->标准错误;3->p[0];4->p[1]
close(p[0]); // 此时子进程的文件描述符表为:0->p[0];1->标准写;2->标准错误;4->p[1]
close(p[1]); // 此时子进程的文件描述符表为:0->p[0];1->标准写;2->标准错误;
exec("/bin/wc", argv);
} else {
write(p[1], "hello world\n", 12);
close(p[0]);
close(p[1]);
}
即,调用pipe()、fork()后,父子进程都有了指向管道的文件描述符。子进程将管道的读端口拷贝在描述符0上,再关闭p中的描述符(新管道的读写描述符被记录在数组p中),然后执行wc。当wc从标准输入读取时,实际上是从管道读取的。父进程向管道的写端口写入然后关闭它的两个文件描述符
3. 如命令echo hello too | xargs echo bye
,背后的原理是:先执行echo hello too
,导致将"hello too"输出到标准输出,并通过管道“|”传递给xargs
命令。而xargs的实现代码中,有这么一段:
while(read(0, &ReadChar, 1))
{
if(ReadChar != '\n')
{
*Ptr = ReadChar;
++Ptr;
}
else
{
// 一旦遇到结尾,给字符串末尾一个空字符,执行此命令行即可
*Ptr = 0;
execCommand(CmdPath, Argv);
Ptr = Argv[StartIndex];
}
}
就是在之前设置好xargs后面的参数后(存放在Argv中),又读入标准输入中的内容(即通过管道传递过来的之前echo的内容)并存放在Ptr(也就是CmdPath)中