1. linux
文件描述符
对于 linux 而言,所有对设备和文件的操作都使用文件描述符来进行的。文件描述符是一个非负的整数,它是一个索引值,指向内核中每个进程打开文件的记录表。当打开一个现存文件或创建一个新文件时,内核就向进程返回一个文件描述符;当需要读写文件时,也需要把文件描述符作为参数传递给相应的函数。
通常,一个进程启动时,都会打开 3 个文件:标准输入、标准输出和标准出错处理。这 3 个文件分别对应文件描述符为 0 、 1 和 2 (宏 STD_FILENO 、 STDOUT_FILENO 和 STDERR_FILENO )。
2. 内核中的实现细节:
linux 用一个数组来管理进程打开的文件的 file 对象,数组中的每一个元素都存放了一个指向进程所打开的文件的 file 对象。数组的下标用来访问文件。 linux 把数组元素的下标叫做该数组元素所对应文件的文件描述符,这个描述符就是系统对文件的标识,也叫做文件描述符数组。(注:内核可以通过系统调用函数 dup( ) 、 dup2( ) 和 fctl( ) 将数组中的多个元素指向同一个文件的 file 对象。即同一个文件可以有多个文件描述符。)文件描述符表在 files_struct 结构中定义,为 structfile*fd_array[NR_OPEN_DEFAULT] ;进程与文件系统及其所打开文件的关系如图所示:
open( ) 操作在内核里通过 sys_open( ) 实现的, sys_open( ) 将创建文件的 dentry 、 inode 和 file 对象,并在 file_struct 结构体的进程打开文件表 fd_array[NR_OPEN_DEFAULT] 中寻找一个空闲表项,然后返回这个表项的下标(索引),即文件描述符。创建文件的 file 对象时,将 file 对象的 f_op 指向了所属文件系统的操作函数集 file_operations ,而该函数集又来自具体文件的 i 节点,于是虚拟文件系统就与实际文件系统的操作衔接起来了。
当然,上面的结构体太复杂,我们只关心需要的部分 - 文件描述符,但是在上面的结构体中,我们并没有发现与文件描述符相关的诸如 fd 成员变量。此时,类型为 int 的 _fileno 结构体成员引起了我们的注意,但是不能确定其为文件描述符。因此写个程序测试是最好的办法,可以用以下的代码测试:
因此, _fileno 成员即为操作系统打开文件返回的句柄( windows 系统)或文件描述符。深入学习可以阅读人民邮电出版社《 C 标准库》。当然还可以阅读 /glibc-2.9/manual/io.txti 文件。 Linux 中,文件的描述符分配是从小到大逐个查询文件描述符是否已经使用,然后再分配,也可以写程序测试。
file 在 /include/linux/fs.h 中定义,从进程的角度来看,文件就是一个 file 结构的实例,也常常叫 file 对象。 file 对象是进程调用 open 打开一个文件, linux 虚拟文件系统根据进程请求创建的一个 file 对象。进程通过该文件的 file 结构了解和操作文件。
对于 linux 而言,所有对设备和文件的操作都使用文件描述符来进行的。文件描述符是一个非负的整数,它是一个索引值,指向内核中每个进程打开文件的记录表。当打开一个现存文件或创建一个新文件时,内核就向进程返回一个文件描述符;当需要读写文件时,也需要把文件描述符作为参数传递给相应的函数。
通常,一个进程启动时,都会打开 3 个文件:标准输入、标准输出和标准出错处理。这 3 个文件分别对应文件描述符为 0 、 1 和 2 (宏 STD_FILENO 、 STDOUT_FILENO 和 STDERR_FILENO )。
例如下面的程序,打开/home/shenlan/hello.c文件,如果此目录下没有hello.c文件,程序自动创建,程序中返回的文件描述符为3。因为进程启动时,打开了标准输入、标准输出和标准出错处理三个文件,因此返回的文件描述符为3。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
int main()
{
int fd;
if((fd = open("/home/shenlan/hello.c", O_CREAT|O_WRONLY|O_TRUNC, 0611)) < 0){
perror("openfile hello.c error!\n");
exit(1);
} else {
printf("openfile hello.c success:%d\n",fd);
}
if(close(fd) < 0){
perror("closefile hello.c error!\n");
exit(1);
}
else
printf("closefile hello.c success!\n");
exit(0);
}
执行结果如下图所示:
2. 内核中的实现细节:
linux 用一个数组来管理进程打开的文件的 file 对象,数组中的每一个元素都存放了一个指向进程所打开的文件的 file 对象。数组的下标用来访问文件。 linux 把数组元素的下标叫做该数组元素所对应文件的文件描述符,这个描述符就是系统对文件的标识,也叫做文件描述符数组。(注:内核可以通过系统调用函数 dup( ) 、 dup2( ) 和 fctl( ) 将数组中的多个元素指向同一个文件的 file 对象。即同一个文件可以有多个文件描述符。)文件描述符表在 files_struct 结构中定义,为 structfile*fd_array[NR_OPEN_DEFAULT] ;进程与文件系统及其所打开文件的关系如图所示:
3.进程打开一个文件的具体流程
进程通过系统调用 open( ) 来打开一个文件,实质上是获得一个文件描述符,以便进程通过文件描述符为连接对文件进行其他操作。进程打开文件时,会为该文件创建一个 file 对象,并把该 file 对象存入进程打开文件表中(文件描述符数组),进而确定了所打开文件的文件描述符。open( ) 操作在内核里通过 sys_open( ) 实现的, sys_open( ) 将创建文件的 dentry 、 inode 和 file 对象,并在 file_struct 结构体的进程打开文件表 fd_array[NR_OPEN_DEFAULT] 中寻找一个空闲表项,然后返回这个表项的下标(索引),即文件描述符。创建文件的 file 对象时,将 file 对象的 f_op 指向了所属文件系统的操作函数集 file_operations ,而该函数集又来自具体文件的 i 节点,于是虚拟文件系统就与实际文件系统的操作衔接起来了。
4. C标准库中的FILE结构和文件描述符、files_struct和file结构之间的关系
早期的 C 标准库中, FILE 在 stdio.h 中定义 ; Turbo C 中,参见谭浩强的《 C 程序设计》, FILE 结构体中包含成员 fd ,即文件描述符。在 glibc-2.9中,stdio.h中定义FILE为_IO_FILE类型(typedef struct _IO_FILE FILE;),而_IO_FILE在/glibc-2.9/libio/libio.h 中定义,如下所示,亦可以在安装的 Ubuntu 系统的 /usr/include/stdio.h 中找到:
struct _IO_FILE {
int _flags; /* High-order word is _IO_MAGIC; restis flags. */
#define _IO_file_flags _flags
/* The following pointerscorrespond to the C++ streambuf protocol. */
/* Note: Tk uses the _IO_read_ptr and _IO_read_endfields directly. */
char* _IO_read_ptr; /* Current read pointer */
char* _IO_read_end; /* End of get area. */
char* _IO_read_base; /* Start of putback+get area. */
char* _IO_write_base; /* Start of put area. */
char* _IO_write_ptr; /* Current put pointer. */
char* _IO_write_end; /* End of put area. */
char* _IO_buf_base; /* Start of reserve area. */
char* _IO_buf_end; /* End of reserve area. */
/* The following fields areused to support backing up and undo. */
char *_IO_save_base; /*Pointer to start of non-current get area. */
char *_IO_backup_base; /* Pointer to first valid character of backuparea */
char *_IO_save_end; /*Pointer to end of non-current get area. */
struct _IO_marker *_markers;
struct _IO_FILE *_chain;
int _fileno;
#if 0
int _blksize;
#else
int _flags2;
#endif
_IO_off_t _old_offset; /*This used to be _offset but it's too small. */
#define __HAVE_COLUMN /* temporary */
/* 1+column number ofpbase(); 0 is unknown. */
unsigned short _cur_column;
signed char _vtable_offset;
char _shortbuf[1];
/* char* _save_gptr; char* _save_egptr; */
_IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};
当然,上面的结构体太复杂,我们只关心需要的部分 - 文件描述符,但是在上面的结构体中,我们并没有发现与文件描述符相关的诸如 fd 成员变量。此时,类型为 int 的 _fileno 结构体成员引起了我们的注意,但是不能确定其为文件描述符。因此写个程序测试是最好的办法,可以用以下的代码测试:
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
int main( )
{
char buf[50] = {"ILOVE this game!"};
FILE *myfile;
myfile = fopen("2.txt","w+");
if(!myfile){
printf("error:openfile failed!\n");
}
printf("The openedfile's descriptor is %d\n",myfile->_fileno);
if(write(myfile->_fileno,buf,50) < 0){
perror("error:writefile failed!\n");
exit(1);
}else{
printf("writefile successed!\n");
}
exit(0);
}
程序中,使用
fopen
函数以读写打开
2.txt
文件,如果不存在
2.txt
文件,则创建此文件。并将其返回的
FILE
指针
myfile
。使用
printf
向标准终端打印出
myfile->_fileno
的值,并将
myfile->_fileno
作为文件描述符传递给
write
系统调用,向打开的文件写入缓冲区数据。然后使用
cat
命令查看
2.txt
的内容。执行的结果如图所示。
_fileno
的值为
3
,因为标准输入、输出、出错为
0
、
1
、
2
因此, _fileno 成员即为操作系统打开文件返回的句柄( windows 系统)或文件描述符。深入学习可以阅读人民邮电出版社《 C 标准库》。当然还可以阅读 /glibc-2.9/manual/io.txti 文件。 Linux 中,文件的描述符分配是从小到大逐个查询文件描述符是否已经使用,然后再分配,也可以写程序测试。
file 在 /include/linux/fs.h 中定义,从进程的角度来看,文件就是一个 file 结构的实例,也常常叫 file 对象。 file 对象是进程调用 open 打开一个文件, linux 虚拟文件系统根据进程请求创建的一个 file 对象。进程通过该文件的 file 结构了解和操作文件。
struct file {
/*
* fu_list becomes invalid after file_free iscalled and queued via
* fu_rcuhead for RCU freeing
*/
union{
struct list_head fu_list;
struct rcu_head fu_rcuhead;
}f_u;
struct path f_path;
#define f_dentry f_path.dentry
#define f_vfsmnt f_path.mnt
const struct file_operations *f_op;
atomic_t f_count;
unsigned int f_flags;
mode_t f_mode;
loff_t f_pos;
struct fown_struct f_owner;
unsigned int f_uid, f_gid;
struct file_ra_state f_ra;
u64 f_version;
#ifdef CONFIG_SECURITY
void *f_security;
#endif
/*needed for tty driver, and maybe others */
void *private_data;
#ifdef CONFIG_EPOLL
/* Used by fs/eventpoll.c to link all thehooks to this file */
struct list_head f_ep_links;
spinlock_t f_ep_lock;
#endif /* #ifdef CONFIG_EPOLL */
struct address_space *f_mapping;
#ifdef CONFIG_DEBUG_WRITECOUNT
unsigned long f_mnt_write_state;
#endif
};
文件描述符数组中存放了一个进程所打开的所有文件。文件描述符数组包含在进程打开的文件表
files_struct
结构中。在
/include/linux/fdtable.h
中定义,为一个指向
file
类型的指针数组
---fd_array[NR_OPEN_DEFAULT]
,其中
NR_OPEN_DEFAULT
也在
fdtable.h
中定义,这是一个和具体的
CPU
体系结构有关的变量,
#define NR_OPEN_DEFAULTBITS_PER_LONG
。以
x86
体系为例,
BITS_PER_LONG
在
/include/asm-x86/types.h
中定义,
32
位
CPU
为
32,64
位
CPU
为
64
struct files_struct {
/*
* read mostly part
*/
atomic_t count;
struct fdtable *fdt;
struct fdtable fdtab;
/*
* written part on aseparate cache line in SMP
*/
spinlock_t file_lock____cacheline_aligned_in_smp;
int next_fd;
struct embedded_fd_setclose_on_exec_init;
struct embedded_fd_setopen_fds_init;
struct file * fd_array[NR_OPEN_DEFAULT];
};
FILE
结构和文件描述符、
file
结构之间的关系可以用下图来表示: