句柄
在我们日常编程中经常会遇到文件描述符(file descriptor)和文件句柄(file handler)这两个概念,特别是需要开发跨平台(跨windows和linux)项目的时候会被这两个概念搞得很头痛,所以下面来说说它们是什么东西及它们的区别与联系。
文件描述符
本质是一个索引号(非负整数),系统用户层可以根据它找到系统内核层的文件数据。这是一个POSIX标准下的概念,常见于类Unix系统,比如linux。windows也是声称遵循POSIX标准的,所以windows也有文件描述符这个概念,但不常用。
文件句柄
Windows下的概念。句柄是Windows下各种对象的标识符,比如文件(也许叫文档比较合适一点)、资源、菜单、光标、位图等。文件句柄和文件描述符类似,它也是一个非负整数,也用于定位文件数据在内存中的位置。
由于linux下所有东西都被看成是文件,比如文件(也许叫文档比较合适一点)、目录、进程、网络socket、各种硬件设备等,所以linux下的文件描述符其实就相当于windows下的句柄。文件句柄只是windows众多句柄中的一种类型而已。
下面用一张图来说明文件描述符的作用(windows下的句柄也是类似的,只是实现细节不同):
对上图进行说明之前,还要普及一个知识:一个操作系统对象(比如文件)在内存中的位置是不固定的。它有可能会在虚拟内存和物理内存中来回切换。
- 由于一个文件在内存中的位置是会变的,而文件管理是系统内核的事,那么对于用户程序来说,要怎么定位它呢?这就是上面那张图要说明的。
- 这里有三张表:进程文件表,文件概要表和文件节点表。其中进程文件表是每个进程都会有一张,而文件概要表和文件节点表是整个系统各有且只有一张。
- 文件节点表中每一项数据就是文件的实际数据。
- 文件概要表中的每一顼是文件的概要数据,它包含了文件的状态、当前文件读写位置、节点指针等。当文件节点表中的数据发生变化时(比如说位置变了),内核会相应地修改文件概要表中对应项的节点指针,让它指向最新的文件数据。
- 进程文件表里记录了该进程打开的所有文件的文件描述符,看图可以知道文件描述符其实只是一个数组的下标,根据它可以访问数组的数据,而这个数组的数据就是指向该文件对应的文件概要表。
- 所以,不管文件数据在内存中的哪个位置,只要我们有文件描述符,我们就可以找到该文件的概要数据,从而找到文件的数据来进行操作。
一些补充说明
- 每个进程创建的时候都会打开三个文件:stdin,stdout和stderr。它们的文件描述符对应为0,1,2。
- 系统分配文件描述符时总是分配最小可用的值。如果当前进程只使用了0,1,2这三个文件描述符,那下次分配的将是3。如果当前进程只使用了0,1,2,3,5这五个文件描述符,那下次分配的将是4。
- 不同的文件概要中的节点指针可以指向同一个文件节点,另外,文件描述符与文件概要是一一对应的,因此,不同的文件描述符可以对应相同的文件结点,即同一个文件可以在同一时间内被多次打开,每次打开都会分配一个文件描述符及生成一个文件概要。
- Windows一般不直接使用文件描述符,所以它提供了一个系统函数叫_get_osfhandle,它可以根据一个文件描述符来获得文件句柄。
- Windows创建一个文件一定会生成一个文件句柄,但并不一定分配一个文件描述符,如果你需要一个文件描述符,可以使用_open_osfhandle来分配一个。
- 在windows下不要混用文件描述符与文件句柄,最好就不要用文件描述符了。比如socket这个函数,它在linux下的返回值是一个文件描述符,而在windows下返回的是一个句柄,如果要写跨平台的代码,就得十分小心这些差异。