C/C++:文件描述符与进程之间的关系

98 篇文章 2 订阅
97 篇文章 7 订阅

C/C++:文件描述符与进程之间的关系

在Unix中,文件在进程中通常抽象化为文件描述符(File Descriptor)。

文件描述符是一个非负整数,可以理解为一个句柄。

我们可以通过open一个磁盘文件,获取一个文件描述符,后续我们对这个文件的所有操作,都要通过这个描述符进行操作。

我们思考:

1.文件描述符如果脱离了进程,是否有意义?

2.文件描述符在内核中是如何表示的?

3.同一进程两次打开同一磁盘文件,获取的是同一个句柄—文件描述符吗?

在操作系统的内核中有专门的数据结构来描述一个进程相关的信息。

即,任何进程的任何信息,例如申请了多少内存、起始地址等都被内核所记录。

上面说的“任何信息”,也包含了一张关于文件描述符的“表”(可以简单理解为一张表)。

test1280

文件描述符不可脱离进程而存在,文件描述符的作用范围是进程,脱离某进程讨论FD是无意义的。

如上图所示,每个进程都有一张表,这个表中记录了每个文件描述符的标识信息以及文件指针。

文件描述符不能脱离进程而存在,作用域是进程,但“文件表”却不是,其是脱离进程存在,被内核所记录,作用域是内核全局级别。

当你在执行open系统调用时,内核将新创建一个文件表对象,并在当前进程中创建一个新的FD,并将FD与文件表对象进行关联。

我们可以看到,文件描述符在进程中,不仅仅有文件指针,指向某文件表对象,还有FD标识信息。

每一个FD都有自己的一套文件描述符标识信息。例如,FD_CLOEXEC。

文件表包含哪些我们关心的信息呢?

文件偏移量表示,如果对这个文件进行写、读操作,将会从距离文件起始位置的多少字节后进行操作。

V节点指针指明了文件在物理磁盘上的位置信息。

请看两个示例。

Case 1:

在同一进程中两次调用open,打开同一磁盘文件,并写入不同数据。

Code:

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

char buf1[] = "abcdefg\n";
char buf2[] = "ABCDEFG\n";

int main()
{
    int fd = -1;
    if ( (fd = open("./newfile", O_WRONLY | O_CREAT)) < 0 )
    {
        printf("ERROR: open(%d)=%s\n", errno, strerror(errno));
        return 1;
    }

    int fd2 = -1;
    if ( (fd2 = open("./newfile", O_WRONLY | O_CREAT)) < 0 )
    {
        printf("ERROR: open(%d)=%s\n", errno, strerror(errno));
        return 1;
    }

    if ( write(fd, buf1, strlen(buf1)) != strlen(buf1) )
    {
        printf("ERROR: write(%d)=%s\n", errno, strerror(errno));
        return 1;
    }

    if ( write(fd2, buf2, strlen(buf2)) != strlen(buf2) )
    {
        printf("ERROR: write(%d)=%s\n", errno, strerror(errno));
        return 1;
    }

    close(fd);
    close(fd2);
    return 0;
}

编译 & 运行:

[jiang@localhost 01]$ ./main
[jiang@localhost 01]$ ll
total 16
-rwxrwxr-x. 1 jiang jiang 7716 May 12 09:49 main
-rw-rw-r--. 1 jiang jiang  832 May 12 08:55 main.c
-rwxrwx--T. 1 jiang jiang    8 May 12 09:49 newfile

查看newfile内容:

[jiang@localhost 01]$ cat newfile 
ABCDEFG

在进程执行中发生如下过程:

1)

在执行第一次open之后:

test1280

文件描述符3指向在内核中新创建的文件表1对象,此时偏移量为0.

2)

在执行第二次open之后:

test1280

内核创建了新的文件表2对象,并将进程中的文件描述符4与之关联。此时,文件表2对象的偏移量为0.

我们应当注意,文件表1和文件表2都是指向同一个V节点表,因为物理文件只有一份。

3)

在执行第一次write之后:

test1280

文件表1的偏移量更新为8.

也就是说,下次在write并且通过FD=3时,将会从文件起始位置偏移8字节处继续写入文件。

此时,物理磁盘上的文件内容为“abcdefg\n”.

但是,由于文件表1和文件表2并不是同一对象,所以文件表2对象的文件偏移量还是0.

4)

在执行第二次write之后:

test1280

文件表2的偏移量更新为8.

此时,物理磁盘上的文件内容为“ABCDEFG\n”.

这是因为,在write时,传入的FD=4,write系统调用一看文件表2的偏移量为0,直接从0起始位置开始写入数据,将上次写入的,旧有的数据“覆盖”咯!

最后结果自然是被覆盖之后的文本信息啦!

Case 2:

在同一进程中先open一个文件,然后通过dup系统调用“复制一份”,并通过write对两个不同的FD写入不同数据。

Code:

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

char buf1[] = "abcdefg\n";
char buf2[] = "ABCDEFG\n";

int main()
{
    int fd = open("./newfile", O_WRONLY | O_CREAT);
    if (fd < 0)
    {
        printf("ERROR: open(%d)=%s\n", errno, strerror(errno));
        return 1;
    }

    int fd2 = dup(fd);
    if (fd2 < 0)
    {
        printf("ERROR: dup(%d)=%s\n", errno, strerror(errno));
        return 1;
    }

    int wbytes = write(fd, buf1, strlen(buf1));
    if (wbytes != strlen(buf1))
    {
        printf("ERROR: write(%d)=%s\n", errno, strerror(errno));
        return 1;
    }

    int wbytes2 = write(fd2, buf2, strlen(buf2));
    if (wbytes2 != strlen(buf2))
    {
        printf("ERROR: write(%d)=%s\n", errno, strerror(errno));
        return 1;
    }

    close(fd);
    close(fd2);
    return 0;
}

编译 & 运行:

[jiang@localhost 02]$ gcc -o main main.c
[jiang@localhost 02]$ ./main
[jiang@localhost 02]$ ll
total 16
-rwxrwxr-x. 1 jiang jiang 7853 May 12 10:11 main
-rw-rw-r--. 1 jiang jiang  825 May 12 09:04 main.c
-rwxr-x--T. 1 jiang jiang   16 May 12 10:11 newfile

查看newfile内容:

[jiang@localhost 02]$ cat newfile 
abcdefg
ABCDEFG

在进程执行中发生如下过程:

1)在执行第一次open之后:

test1280

文件描述符3指向在内核中新创建的文件表1对象,此时偏移量为0.

2)在执行dup复制操作之后:

test1280

dup仅仅是“duplicate a file descriptor”,内核不会创建新的文件表对象出来,而是将新的FD和旧有的文件表对象关联。

此时,在同一进程中,有两个(多个)FD指向同一文件表对象。

3)在执行第一个write之后:

test1280

文件表1的偏移量更新为8.

由于FD=3以及FD=4关联的是同一个文件表对象,所以,对FD=3的write的偏移量更新,对于FD=4来说是“可见的”

此时物理文件内容为:”abcdefg\n”.

4)在执行第二个write之后:

test1280

在write FD=4操作执行时,内核发现文件表偏移量为8,所以不会从0位置开始写,而是先定位到8字节偏移处,然后写数据。

此时的文件表1的偏移量更新为16,这一更新同样对FD=3是可见的。

Case 2中第二次write不会将第一次write的内容覆盖,因为他们的偏移量对于不同FD来说是共享可见的。

总结思考:

1.文件描述符脱离进程是无意义的。文件描述符一定是在某个进程空间内才具有特定意义,指代某个物理文件。

2.文件描述符在内核中,或者说在内核维护的某进程数据结构中,可以理解为一条表记录。这条记录包含了这个文件描述符的标志信息以及文件指针信息(等)。

3.同一进程打开两次执行open打开同一文件,拿到的当然是不同的FD啦!而且都是不同的文件表对象。如果是通过dup的方式,拿到的也是不同的FD,但是指向的是同一文件表对象。

另:

当进程执行fork时,子进程会复制父进程的所有信息,并共享一些数据。

例如子进程会复制父进程的所有FD的表记录,包括FD标志信息、文件指针。这也就意味着,由父进程通过open创建出来的文件表,对于子进程来说是可见的。子进程、父进程共享同一文件表对象!

  • 5
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值