在开始编写驱动前,还需要学习一下驱动程序操作时一个重要的数据结构,file_operations。
在获取了一些设备编号后,我们还没有将任何驱动程序操作连接到这些编号,file_operations结构就是用来建立这种连接的。
这个结构定义在
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 (*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
int (*iterate) (struct file *, struct dir_context *);//
int (*iterate_shared) (struct file *, struct dir_context *);//
unsigned int (*poll) (struct file *, struct poll_table_struct *);
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 *);
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, loff_t, loff_t, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_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 **, void **);
long (*fallocate)(struct file *file, int mode, loff_t offset,
loff_t len);
void (*show_fdinfo)(struct seq_file *m, struct file *f);
#ifndef CONFIG_MMU
unsigned (*mmap_capabilities)(struct file *);
#endif
ssize_t (*copy_file_range)(struct file *, loff_t, struct file *,
loff_t, size_t, unsigned int);
int (*clone_file_range)(struct file *, loff_t, struct file *, loff_t,
u64);
ssize_t (*dedupe_file_range)(struct file *, u64, u64, struct file *,
u64);
};
这里面有很多地方都用到了__user字符。
它其实是一种形式的文档而已,表明指针是一个用户空间地址,因此不能被直接引用。对通常的编译来将,__user没有任何效果,但是可由外部检查软件使用,用来寻找对用户空间的错误使用。
第一个file_operations字段并不是一个操作,相反,它是一个指向拥有该结构的模块的指针。内核使用这个字段以避免在模块的操作正在被使用时卸载该模块。几乎在所有情况下,该成员都会被初始化为THIS_MODULE,它是定义在
llseek 方法用作改变文件中的当前读/写位置, 并且新位置作为(正的)返回值。
(指针参数struct file *为进行读取信息的目标文件结构体指针;参数 loff_t 为文件定位的目标偏移量;参数int为对文件定位的起始地址,这个值可以为文件开头(SEEK_SET,0,当前位置(SEEK_CUR,1),文件末尾(SEEK_END,2))
loff_t 参数是一个"long offset", 并且就算在 32位平台上也至少 64 位宽. 错误由一个负返回值指示;如果这个函数指针是 NULL, seek 调用会以潜在地无法预知的方式修改 file结构中的位置计数器。
用来从设备中读取数据。该函数指针被赋值为NULL值时,将导致read系统调用出错并返回-EINVAL("Invalid argument,非法参数")。函数返回非负值表示成功读取的字节数(返回值为"signed size"数据类型,通常就是目标平台上的固有整数类型)。
向设备发送数据。如果没有这个函数,write系统调用会向程序返回一个-EINVAL。如果返回值非负,则表示成功写入的字节数。
异步的读取操作,在函数返回之前可能不会完成的读取操作。如果该函数为NULL,所有的操作将通过read(同步)处理。
linux 4.5版本前为:
ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
异步写入操作。
linux 4.5版本前为:
ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
poll方法是poll、epoll和select这三个系统调用的后端实现。这三个系统调用可用来查询某个或多个文件描述符上的读取或写入是否被阻塞。poll方法应该返回一个位掩码,用来指出非阻塞的读取或写入是否可能,并且也会向内核提供将调用进程置于休眠状态直到I/O变为可能时的信息。如果驱动程序将poll方法定义为NULL,则设备会被认为即可读也可写,并且不会被阻塞。
ioctl提供了一种执行设备特定命令的方法(如格式化软盘的某个磁道,这即不是读操作也不是写操作)。另外,内核还能识别一部分ioctl命令,而不必调用fops表中的ioctl。如果设备不提供ioctl入口点。这对于任何内核未预先定义的请求,ioctl系统调用将返回错误(-ENOTTY,“No such ioctl for device,该设备无此ioctl命令”)。
1、compat_ioctl:支持64bit的driver必须要实现的ioctl,当有32bit的userspace application call 64bit kernel的IOCTL的时候,这个callback会被调用到。如果没有实现compat_ioctl,那么32位的用户程序在64位的kernel上执行ioctl时会返回错误:Not a typewriter
2、如果是64位的用户程序运行在64位的kernel上,调用的是unlocked_ioctl,如果是32位的APP运行在32位的kernel上,调用的也是unlocked_ioctl
mmap用于请求将设备内存映射到进程地址空间。如果设备没有实现这个方法,那么mmap系统调用将返回-ENODEV。
尽管这始终是对设备文件执行的第一个操作,然而却并不要求驱动程序一定要声明一个相应的方法。如果这个入口为NULL,设备的打开操作永远成功,但系统不会通知驱动程序。
对flush操作的调用发生在进程关闭设备文件描述符的时候,它应该执行(并等待)设备上尚未完结的操作。请不要将它同用户程序使用的fsync操作相混淆。目前,flush仅仅用于少数几个驱动程序,比如SCSI磁带驱动程序用它来确保设备被关闭之前所有的数据都被写入磁带中。如果flush被置为NULL,内核将简单地忽略用户应用程序的请求。
当file结构被释放时,将调用这个操作。与open相仿,也可以将release置为NULL。
注意:release并不是在进程每次调用close时都会被调用。只要file结构被共享(如在fork或dup调用之后),release就会等到所有的副本都关闭之后才会得到调动。如果需要在关闭任意一个副本时刷新那些待处理的数据,则实现flush方法。
该方法是fsync系统调用的后端实现,用户调用它来刷新待处理的数据。如果驱动程序没有实现这一方法,fsync系统调用返回-EINVAL。
这个操作用来通知设备起FASYNC标志位发生了变化。如果设备不支持异步通知,该字段可以是NULL。
lock方法用于实现文件锁定,锁定是常规文件不可缺少的特性,但设备驱动程序几乎从来不会实现这个方法。
由内核调用,将数据发送到对应的文件,每次一个数据页。设备驱动程序通常也不需要实现sendpage。
该方法的目的是在进程的地址空间中找到一个合适的位置,以便将底层驱动设备中的内存段映射到该位置。该任务通常由内存管理代码完成,但该方法的存在可允许驱动程序强制满足特定设备需要的任何对齐需求。大部分驱动程序可设置该方法为NULL。
该方法允许模块检查传递给fcntl(F_SETFL...)调用的标志。
...