首页 > 代码库 > 【Linux驱动】文件描述符以及相关知识
【Linux驱动】文件描述符以及相关知识
1、文件描述符
Linux操作系统中,几乎所有的设备都被抽象成为设备文件。因此,当我们想对设备进行操作的时候可以直接去操作其相应的设备文件。设备文件即是文件,要想对文件进行操作,无非就是:打开文件、关闭文件、写入数据、读出数据等,它们分别对应的函数有open(),close(),write(),read(),就以其中的open()函数做一个分析。open函数的作用是打开一个文件。
(1)它的定义:int open( const char * pathname, int flags);
int open( const char * pathname,int flags, mode_t mode);
对于 open 函数来说,第三个参数(...)仅当创建新文件时才使用,用于指定文件的访问权限。
pathname 是待打开/创建文件的路径名;
oflag 用于指定文件的打开/创建模式,这个参数可由以下常量(定义于 fcntl.h)通过逻辑或构成。
常用的三种模式:
O_RDONLY 只读模式
O_WRONLY 只写模式
O_RDWR 读写模式
这三种模式是不可以同时使用的。
(2)返回值:成功后就返回文件描述符(整型变量0~255),不成功就返回-1。
那么到底什么是文件描述符??它的作用是什么?
文件描述符其实就是一个整数值,这个值的范围 在0~255之间。当程序新建或者打开一个文件的时候,内核就会向该进程返回一个文件描述符。内核可以通过文件描述符找到相应的文件。
2、文件描述符表
那么内核是怎样通过文件描述符找到相应文件的呢??
在Linux内核中,每个进程有一个task_struct结构体来维护相关的进程,被称为进程描述符。它定义在linux源码包跟目录下的/include/linux/sched.h文件中。每个进程PCB(Process Control Block)即进程控制块中都保存着一份文件描述符表。在task_struct结构体中的代码为struct files_struct *files,截取部分task_struct结构体的代码:
其中蓝色部分为 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; /*文件描述符的初值集合*/ <span style="color:#ff0000;">struct file * fd_array[32];/* 文件对象指针的初始化数组*/</span> };在files_struct结构体的定义中,可以看到struct file *fd_array[32];这句代码的作用是用来指向file结构体的。还可以看到指向文件对象的指针是一个数组。文件描述符就是文件描述符表的索引或者是该数组的下标,文件描述表中每个表项都有一个指向已打开文件的指针,已打开的文件在内核中用file结构体表示,文件描述符表中的指针指向file结构体。
3、file结构体
struct file { struct list_head f_list; /*所有打开的文件形成一个链表*/ struct dentry *f_dentry; /*指向相关目录项的指针*/ struct vfsmount *f_vfsmnt; /*指向VFS安装点的指针*/ <span style="color:#ff0000;"> struct file_operations *f_op; /*指向文件操作表的指针*/ </span> 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结构体,从file结构体的定义中有可以知道它指向一个
file_operations
结构体,这个结构体的成员都是函数指针,指向实现各种文件操作的内核函数。file_operations
结构体定义如下:(头文件 linux/fs.h中定义)struct file_operations { struct module *owner; loff_t(*llseek) (struct file *, loff_t, int); ssize_t(*read) (struct file *, char __user *, size_t, loff_t *); ssize_t(*aio_read) (struct kiocb *, char __user *, size_t, loff_t); ssize_t(*write) (struct file *, const char __user *, size_t, loff_t *); ssize_t(*aio_write) (struct kiocb *, const char __user *, size_t, loff_t); int (*readdir) (struct file *, void *, filldir_t); unsigned int (*poll) (struct file *, struct poll_table_struct *); int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); int (*mmap) (struct file *, struct vm_area_struct *); int (*open) (struct inode *, struct file *); int (*flush) (struct file *); int (*release) (struct inode *, struct file *); 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 *); ssize_t(*readv) (struct file *, const struct iovec *, unsigned long, loff_t *); ssize_t(*writev) (struct file *, const struct iovec *, unsigned long, loff_t *); ssize_t(*sendfile) (struct file *, loff_t *, size_t, read_actor_t, void __user *); 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); };比如在用户程序中read一个文件描述符,read通过系统调用进入内核,然后找到这个文件描述符所指向的file结构体,找到file结构体所指向的file_operations结构体,调用它的read成员所指向的内核函数以完成用户请求。在用户程序中调用lseek、read、write、ioctl、open等函数,最终都由内核调用file_operations的各成员所指向的内核函数完成用户请求。
它们的大体过程应该:
4、ioctl()函数
ioctl的用处:一些没办法归类的函数就统一放在ioctl这个函数操作中,通过指定的命令来实现对应的操作。所以,ioctl函数里面都实现了多个的对硬件的操作,通过应用层传入的命令来调用相应的操作。一般在这个函数中会有一个switch……case函数进行选择。