2

首先来看一段代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main()
{
    int fd1,fd2;
    
    fd2 = open("p1.py",O_RDONLY,0);
    dup2(fd2,0);
    char c;
    while( read(0,&c,1) > 0)
        printf("%c",c);
    close(fd2);

    fd1 = open("/dev/stdin",O_RDONLY);

    printf("%d\n",fd1);
    while( read(fd1, &c, 1) > 0)
        printf("%c",c);

    return 0;
}

先讲一下文件描述符是什么。
linux进程每打开一个文件都会返回一个文件描述符(整数)。
这个描述符实际是打开的文件在该进程的描述符表上的偏移值。
比如说p是描述符表,1是描述符,那么p[1]就能够索引到1描述符对应的打开文件。
有了这个偏移值(文件描述符)就能够快速的找到并操作文件。

(当然实际的情况是这个文件描述符能够索引到打开文件表表项,然后再通过打开文件表表项索引到对应的V-node节点表表项,而这个v-node节点表表项才代表真正的文件。不过只从逻辑上来看不需要理解这个括号里的说明。)

解释一下这段程序:
1、首先打开了一个叫做p1.py的文件。
2、然后用dup2这个函数使得文件描述符0这个位置的指针指向文件描述符fd2这个位置的指针指向的文件。
也就是说本来是这样:p[0] = &fiel1,p[fd2] = &file2,现在p[0] = p[fd2] = &file2。
而我们都知道文件描述符0这个位置对应的文件file1是标准输入文件/dev/stdin。
那么这个函数的意义就是把标准输入重定向到了p1.py这个文件里了。

之后再用标准输入比如说scanf(),来读,那么都是从p1.py这个文件里读了。
3、然后把这个文件里的东西读出来并输出到屏幕上。
4、打开/dev/stdin这个文件,这个文件是标准输入。
5、把这个文件里的东西读出来并打印到屏幕上。

预期结果是什么?
执行到第5步应该停下来,等待键盘输入。
然后把输入的东西打印到屏幕。

实际结果?
没有等待键盘输入,它直接把p1.py的文件内容输出到屏幕上,也就是说和上面的输出是一样的!!!

why???how???
我打开了一个文件,应该读取文件里的内容。而这个文件是标准输入,那么既然是标准输入(从键盘输入),我还没输入呢,怎么就输出结果了呢???而且结果还很奇特。。。

这是因为,标准输入这个文件/dev/stdin是个链接文件!!!
存放的是别的文件的地址!!!
如果文件描述符指向的文件是个普通文件,那么把这个文件描述符指向别的文件,就是真的指向了别的文件。
而这里的文件描述符指向的是个链接文件,那么把这个文件描述符指向别的文件,意味着什么???意味着它
把这个链接文件里的内容(地址)更改了,而它仍然指向这个文件。只不过它知道这是个链接文件,因此它会访问的是这个链接文件中的地址对应的文件。

理解了上面的操作就可以说得通了,dup2对于链接文件只是修改了文件中的地址,它并没有真正指向别的文件,这也导致了一个问题,那就是它把这个链接文件给修改了,如果进程再次打开这个链接文件,那么链接文件之前存的地址就没有了,因此执行

fd1 = open("/dev/stdin",O_RDONLY);

这个的时候,实际上是又一次打开了p1.py这个文件!!!因为/dev/stdin这个文件里存放的地址已经是p1.py这个文件的地址了。。。


Linus脱袜子
183 声望63 粉丝

硕士研究生在读。