文件描述符(fd)
对于linux而言,所有对设备(对于linux而言,一切皆文件)和文件的操作都使用文件描述符来进行的。
文件描述符是一个非负的整数,它是一个索引值,指向内核中每个进程打开文件的记录表。
当打开一个现存文件或创建一个新文件时,内核就向进程返回一个文件描述符用于后续对文件的读写操作;当需要读写文件时,也需要把文件描述符作为参数传递给相应的函数。
通常情况下,将一个程序从硬盘加载到内存后,这个程序就化身为了一个进程,这时系统会默认打开三个文件:
标准输入(stdin)、标准输出(stdout)、标准错误(stderr)。
这三个文件相对应的三个文件描述符分别为0、1、2。所以后面如果创建新文件,那么此时这个新文件的文件描述符就是3啦。。。
这是因为在Linux中,文件的描述符分配是从小到大逐个查询文件描述符是否已经使用,然后再分配。
相应的,如果你提前关闭了文件描述符1,那么新建的文件的描述符就是1。
一个进程的文件描述符与对应的文件的关系简图:
FILE结构体
1.先来看看FILE结构体的定义:
FILE结构体中最重要的两个成员变量是:
文件描述符和缓冲区的大小
//C语言文件指针域文件描述符之间可以相互转换
int fileno(FILE * stream)
FILE * fdopen(int fd, const char * mode)
struct _iobuf {
char *_ptr; //缓冲区当前指针
int _cnt;
char *_base; //缓冲区基址
int _flag; //文件读写模式
int _file; //文件描述符
int _charbuf; //缓冲区剩余自己个数
int _bufsiz; //缓冲区大小
char *_tmpfname;
};
typedef struct _iobuf FILE;
FILE结构体与文件描述符之间的关系图示:
对上图内容的简要介绍:
1.进程打开一个文件的过程:
进程通过系统调用open()来打开一个文件,实质上是获得一个文件描述符,以便于进程通过文件描述符来读写该文件、
进程打开文件时,会为该文件创建一个file对象,并将一个指向该file对象的指针存入进程描述符表(进程描述符数组),进而确定了打开文件的文件描述符(数组下标)。open()系统调用是在内核里通过sys_open()实现的,
sys_open()将创建文件的dentry、inode和file对象。
创建file对象时,将file对象f_op指向了所属文件系统的操作函数集file_operations
,而该函数集又来自具体文件的i节点,于是虚拟文件系统就与实际文件系统的操作就衔接起来了。并在file_struct结构体的进程打开文件表fd_array[NR_OPEN_DEFAULT]中查找一个空闲表项(也就是此时数组中最小的未被占用的表项),然后返回该表项的下标(文件描述符)。
2.上述描述中提到的结构体的定义:
描述符数组存放在进程打开的文件表files_struct结构中。
文件描述符数组中存放了一个进程所打开的所有文件.
files_struct结构体定义:
struct files_struct { atomic_t count; /* 共享该表的进程数 */
rwlock_t file_lock; /* 保护以下的所有域,以免在tsk->alloc_lock中的嵌套*/
int max_fds; /*当前文件对象的最大数*/
int max_fdset;/*当前文件描述符的最大数*/
int next_fd; /*已分配的文件描述符加1*/
struct file ** fd; /* 指向文件描述符数组的指针 */
fd_set *close_on_exec; /*指向执行exec( )时需要关闭的文件描述符*/
fd_set *open_fds; /*指向打开文件描述符的指针*/
fd_set close_on_exec_init;/* 执行exec( )时需要关闭的文件描述符的初 值集合*/
fd_set open_fds_init; /*文件描述符的初值集合*/
struct file * fd_array[32];/* 文件对象指针的初始化数组*/};
file结构体:
struct file
{
struct list_head f_list; /*所有打开的文件形成一个链表*/
struct dentry *f_dentry; /*指向相关目录项的指针*/
struct vfsmount *f_vfsmnt; /*指向VFS安装点的指针*/
struct file_operations *f_op; /*指向文件操作表的指针*/
mode_t f_mode; /*文件的打开模式*/
loff_t f_pos; /*文件的当前位置*/
unsigned short f_flags; /*打开文件时所指定的标志*/
unsigned short f_count; /*使用该结构的进程数*/
unsigned long f_reada, f_ramax, f_raend, f_ralen, f_rawin;
/*预读标志、要预读的最多页面数、上次预读后的文件指针、预读的字节数以及
预读的页面数*/
int f_owner; /* 通过信号进行异步I/O数据的传送*/
unsigned int f_uid, f_gid; /*用户的UID和GID*/
int f_error; /*网络写操作的错误码*/
unsigned long f_version; /*版本号*/
void *private_data; /* tty驱动程序所需 */
};
file结构体的几个重要的成员变量:
f_flags:表示打开文件的权限 。
f_pos:表示当前读写文件的位置。
f_count:表示打开文件的引用计数,如果有多个文件指针指向它,就会增加f_count的值。
f_mode:设置对文件的访问模式,例如:只读,只写、可读可写等。
③file_operations结构体:
file结构体中的f_op指针指向file_operations结构体,这个结构体中的成员除了struct module* owner 其余都是函数指针。
file_operation就是把系统调用和驱动程序关联起来的关键数据结构。
这个结构的每一个成员都对应着一个系统调用。
读取file_operation中相应的函数指针,接着把控制权转交给函数,从而完成了Linux设备驱动程序的工作。
struct file_operations {
struct module *owner;
//指向拥有该模块的指针;
loff_t (*llseek) (struct file *, loff_t, int);
//llseek 方法用作改变文件中的当前读/写位置, 并且新位置作为(正的)返回值.
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
//用来从设备中获取数据. 在这个位置的一个空指针导致 read 系统调用以 -EINVAL("Invalid argument") 失败. 一个非负返回值代表了成功读取的字节数( 返回值是一个 "signed size" 类型, 常常是目标平台本地的整数类型).
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
//发送数据给设备. 如果 NULL, -EINVAL 返回给调用 write 系统调用的程序. 如果非负, 返回值代表成功写的字节数.
ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
//初始化一个异步读 -- 可能在函数返回前不结束的读操作.
ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
//初始化设备上的一个异步写.
int (*readdir) (struct file *, void *, filldir_t);
//对于设备文件这个成员应当为 NULL; 它用来读取目录, 并且仅对**文件系统**有用.
unsigned int (*poll) (struct file *, struct poll_table_struct *);
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
//mmap 用来请求将设备内存映射到进程的地址空间. 如果这个方法是 NULL, mmap 系统调用返回 -ENODEV.
int (*open) (struct inode *, struct file *);
//打开一个文件
int (*flush) (struct file *, fl_owner_t id);
//flush 操作在进程关闭它的设备文件描述符的拷贝时调用;
int (*release) (struct inode *, struct file *);
//在文件结构被释放时引用这个操作. 如同 open, release 可以为 NULL.
int (*fsync) (struct file *, struct dentry *, int datasync);
//用户调用来刷新任何挂着的数据.
int (*aio_fsync) (struct kiocb *, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
//lock 方法用来实现文件加锁; 加锁对常规文件是必不可少的特性, 但是设备驱动几乎从不实现它.
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
int (*check_flags)(int);
int (*flock) (struct file *, int, struct file_lock *);
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
int (*setlease)(struct file *, long, struct file_lock **);
};
小结:
file_struct是操作系统用来管理文件的数据结构,
当我们创建一个进程时,会创建文件描述符表,
进程控制块PCB中的fs指针指向文件描述符表,
当我们创建文件时,会为指向该文件的指针FILE*关联一个文件描述符并添加在文件描述符表中。
在文件描述符表中fd相当于数组的索引,FILE*相当于数组的内容,指向一个文件结构体。