打开的设备在内核内部由设备文件结构标识,内核使用file_operations(文件操作)结构访问驱动程序的函数。每个文件都与自己的函数集相关联(通过包含在设备中指向file_operations结构的指针实现),这些操作主要负责系统调用的实现。用户进程利用系统调用对设备文件进行操作时,系统调用通过设备的主设备号找到相应的设备驱动程序,然后读取这个数据结构相应的函数指针,接着把控制权交给该函数,这就是Linux的设备驱动程序工作的基本原理。由此可见,编写设备驱动程序的主要工作就是编写file_operations中的子函数,完成对设备的操作。下面给出2.4系统内核中file_operations结构中所包括的操作以及相关的解释[17],具体的定义可以参看<linux/fs.h>文件。
struct file_operations
loff_t (*llseek)(struct file*, loff_t, int);
方法llseek用来修改文件的当前读写位置,并将新位置(正的)作为返回值返回。参数loff_t是一个“长偏移量”,即使在32位平台上也至少占用64位的数据宽度。出错时返回一个负的返回值。如果驱动程序没有设置这个函数,那么相对于文件末尾的定位操作就会失败,而其他的定位操作就会修改file结构中的位置计数器并正确返回。
ssize_t (*read)(struct file*,char *, size_t, loff_t *);
用来从设备中读取数据。当该函数指针被赋为NULL值时,将导致read系统调用出错并返回-EINVAl(“Invalid argument,非法参数”)。函数返回非负值表示成功读取的字节数。
ssize_t (*write)(struct file*,char *, size_t, loff_t *);
向设备发送数据,如果没有这个函数,write函数的调用会向调用程序返回-EINVAL,如果返回非负值,则表示成功写入的字节数。
int(*readdir)(struct file*,void *,filldir_t);
对于设备文件来说,这个字段应该为NULL。它仅用于读取目录,并且只对文件系统有用。
unsigned int(*poll)(struct file *,struct poll_table_struct *);
poll方法是poll和select这两个系统调用的后端实现。这两个系统调用可用来查询设备是否可读或者可写,或者出于某种特殊状态。这两个系统调用是可阻塞的,直至系统可读、可写或者到某种特殊状态为止。如果驱动程序没有定义它的poll方法,它所驱动的设备就会被认为既可读又可写,并且不会出于某种特殊状态,返回值是一个描述设备状态的位掩码。
int(*ioctl)(struct inode *, struct file *,unsigned int ,unsigned long);
系统调用ioctl提供了一种执行设备特定命令的方法(如格式化某个硬盘分区)。另外,内核还能识别部分的ioctl命令,而不必调用fops中的ioctl。如果设备不提供ioctl入口点,则对于任何内核未预先定义的请求,ioctl系统调用将返回错误(-ENOTTY,“No such ioctl for device,该设备无此ioctl命令”)。如果该设备方法返回一个非负值,那么相同的值会被返回给调用程序以表示调用成功。
int(*mmap)(struct file *,struct vm_area_struct *);
mmap用于请求将设备内存映射到进程地址空间。如果设备没有实现这个方法,那么mmap系统调用将返回-ENODEV。
int(*open)(struct inode *,struct file *);
尽管这个始终是设备文件执行的第一个操作,但是却不要求驱动程序一定要声明这个方法,如果这个入口点为NULL,那么设备的打开操作永远成功,但是系统不会通知驱动程序。
int(*flush)(struct file *);
对flush操作的调用发生在进程关闭设备文件描述符副本的时候,它应该执行设备上尚未完成的操作。目前flush仅仅用于网络文件系统代码中,如果flush被置为NULL,则它只是简单的不被调用。
int(*release)(struct inode *,struct file *);
当file结构被释放时,将调用这个操作。与open相仿,也可以没有release。
int(*fsync)(struct inode *,struct dentry *,int);
该方法是fsync系统调用的后端实现,用户调用它来刷新待处理的数据。如果驱动程序没有实现这一方法,fsync系统调用就返回-EINVAL。
int(*fasync)(int,struct file *,int);
这个操作用来通知设备,它的异步标志发生了变化。如果设备不支持异步通知,那么该字段可以为NULL。