通常而言,主设备号标识设备对应的驱动,次设备号由内核使用,用于正确确定设备文件所指的设备文件所指的设备,我们可通过次设备号获得指向内核设备的直接指针,也可将次设备号当作设备本地数组的引索。
同一类设备使用相同的主设备号,不同类的设备使用不同的主设备号,用次设备号来描述使用该驱动的设备的序号,序号一般从0开始。
在调用cdev_add()函数向系统注册字符设备之前,该首先分配设备号可通过命令:cat /proc/devices 来查看系统已经被使用的设备号。
file_operations结构详解:
struct file_operations { //文件操作
struct module *owner;
/*owner 它并不是一个操作,它指向“拥有”该结构的模块的指针,内核使用这个字段以避免在模块
* 操作正在被使用时卸载该模块,几乎所有的情况下,该成员被初始化为THIS_MODULE,它是
* 定义在<linux/module.h>中的一个宏。
*/
loff_t (*llseek) (struct file *file, loff_t offset, int origin);
/*
* llseek用来修改文件的当前读写位置,并将新位置(正的)作为返回值返回,参数loff_t是一个长偏移量,即使在32位平台上也至少占用64位的数据宽度,出错返回负值,
* 如果这个函数指针为NULL,对seek的调用将会以某种不可预期的方式修改file结构中的位置计数器。
*/
ssize_t (*read) (struct file *filp, char __user *buf, size_t count, loff_t *f_pos);
/*
* 用来从设备读取数据,该函数指针被赋为NULL时,将导致read系统调用出错并返回-EINVAL,函数返回非负值表示成功读取
* 的字节数.
* 注意更新文件的位置f_pos,如果返回值传输的字节数小于count,则应该重新传输数据,返回0表示已经到达文件尾,
* 如果数据暂时没有到达则应该阻塞
*/
ssize_t (*write) (struct file *filp,const char __user *buf, size_t count, loff_t *f_pos);
/* 向设备发送数据,如果没有这个函数,将导致write系统调用出错并返回-EINVAL,函数返回非负值表示成功写入的字节数 */
ssize_t (*aio_read) (struct kiocb *iocb, const struct iovec *iov,unsigned long nr_segs, loff_t pos);
/* 初始化一个异步的读取操作---即在函数返回之前可能不会完成的读取操作,如果该方法为NULL,所有的操作将通过read(同步)处理 */
ssize_t (*aio_write) (struct kiocb *iocb, const struct iovec *iov,unsigned long nr_segs, loff_t pos);
/* 初始化一个异步的写入操作 */
unsigned int (*poll) (struct file *filp, poll_table *wait);
/* poll方法是poll、epoll、select这三个系统调用的后端实现,这三个系统调用可用查询某个或多个文件描述符上读写是否阻塞,
* poll方法应该返回一位掩码,用来指定非阻塞的读取或写入是否可能,并且也会向内核提供将调用进程至于休眠状态直至I/O变
* 可能时的信息,如果驱动程序将poll方法定义为NULL,则设备被认为即可读也可写,并且不会阻塞.
*/
int (*ioctl) (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg);
/* 系统调用ioctl提供了一钟执行设备特定命令的方法(如格式化磁盘,这个既不是读操作也不是写操作),另外,内核还能识别
* 一部分ioctl命令,而不必调用fops表中的ioctl,如果设备不提供ioctl入口点,对于任何内核未预先定义的请求,ioctl系统调
* 用将返回错误
*/
long (*unlocked_ioctl) (struct file *filp, unsigned int ioctl, unsigned long arg);
/* 不使用BLK的文件系统,将使用此种函数指针代替ioctl */
long (*compat_ioctl) (struct file *file, unsigned cmd, unsigned long arg);
/* 在64位系统上,32位的ioctl调用,将调用此函数指针代替 */
int (*mmap) (struct file *file, struct vm_area_struct *vma);
/* mmap用于请求将设备内存映射到进程空间,如果设备没有实现这个方法,那么mmap系统调用将返回-ENODEV */
int (*open) (struct inode *inode,struct file *filp);
/* 尽管这是对设备文件的第一个操作,然而却并不要求驱动程序一定要声明一个相应的方法,如果这个入口为NULL,
* 设备的打开操作永远成功,但系统不会通知驱动程序
*/
int (*flush) (struct file *filp, fl_owner_t id);
/* 对flush操作的调用发生在进程关闭设备文件描述符副本的时候,它应该执行(并等待)设备上尚未完结的操作,
* 目前,flush仅仅用于少数几个驱动程序,如果flush被设置为NULL,内核将简单低忽略用于的应用程序请求
*/
int (*release) (struct inode *inode, struct file *filp);
/* 当file结构被释放时即关闭设备文件,将调用这个操作,与open相仿,也可以将release设置为NULL */
int (*fsync) (struct file *filp, struct dentry *dentry, int datasync);
/* 该方法是fsync系统调用的后端实现,用户调用它来刷新待处理的数据,如果驱动程序没有实现这一方法,
* fsync系统调用将返回-EINVAL
*/
int (*aio_fsync) (struct kiocb *iocb, int datasync); //这是fsync方法的异步版本
int (*fasync) (int fd, struct file *filp, int mode);
/* 这个操作用来通知设备其FASYNC标志发生了变化,如果设备不支持异步通知,该字段可以使NULL */
int (*lock) (struct file *filp, int cmd, struct file_lock *fl);
/* lock方法用于实现文件锁定,锁定是常规文件不可缺少的特性,但设备驱动程序几乎不会实现这个方法 */
unsigned long (*get_unmapped_area)(struct file *file,unsigned long addr, unsigned long len,unsigned long pgoff,unsigned long flags);
//该方法的目的是在进程的地址空间找到一个合适的位置,以便将底层设备中的内存
//映射到该位置,大部分驱动程序可设置改方法为NULL
int (*check_flags)(int); //该方法允许模块检查传递给fcntl调用的标志
ssize_t (*splice_write)(struct pipe_inode_info *pipe, struct file *out,loff_t *ppos, size_t len, unsigned int flags);
//由VFS调用,将管道数据粘接到文件
ssize_t (*splice_read)(struct file *in, loff_t *ppos,struct pipe_inode_info *pipe, size_t len,unsigned int flags);
//由VFS调用,将文件数据粘接到管道
};
/* file_operations使用实例 */
struct file_operations xxx_fops = {
.owner = THIS_MODULE,
.open = xxx_open,
.read = xxx_read,
.write = xxx_write,
.ioctl = xxx_ioctl,
.poll = xxx_poll,
.release = xxx_close,
......
};