Linux设备驱动--字符设备驱动程序2

时间:2022-12-21 11:21:04

设备驱动中重要的数据结构

一、文件操作 file_operations

在大部分的驱动程序中,通常涉及到三个重要的内核数据结构,分别是file_operations 、file、inode

file_operations是建立设备驱动程序和设备编号连接的数据结构,定义在<linux/fs.h>中(kernel/include/linux/fs.h)

通常,file_operations结构或者指向这类结构的指针称为fops,其内部的每一个字段都必须指向驱动程序中实现特定操作的函数

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);

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 *);

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 *);

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 (*dir_notify)(struct file *filp, unsigned long arg);

int (*flock) (struct file *, int, struct file_lock *);
};

1.struct module *owner

  第一个file_operations字段并不是一个操作,相反它是指向“拥有”该结构模块的指针。内核使用这个字段以避免在模块的操作正在被使用时卸载该模块,几乎在所有情况下,该成员都会被初始化为THIS_MODULE,这是一个定义在<linux/module.h>中的一个宏

extern struct module __this_module;
#define THIS_MODULE (&__this_module)


2.loff_t (*llseek) (struct file *,loff_t, int)

指针llseek用来修改文件的当前读写位置,并将新位置作为(正的)返回值返回,参数loff_t是一个偏移量,即使在32平台上也至少占用64位的数据宽度,出错时返回一个负的返回值,如果这个函数指针是NULL,对seek的调用将会以不可预料的方式修改file结构中的位置计数器


3.ssize_t (*read) (struct file *,char __user *,size_t,loff_t *)

__user只是一种形式的文档,表明指针是一个用户空间的地址,因此不能直接被引用,对通常编译来讲,__user没有任何效果,但是可由外部检查软件使用,用来寻找对用户空间地址的错误使用

此函数字段用来从设备中读取数据,该函数指针被赋为NULL值时,将导致read系统调用出错并返回-EINVAL(Invalid argument,非法参数),函数返回非负值表示成功读取的字节数(返回值类型为 signed size,通常就是目标平台上的固有整数类型)


4.ssize_t  (*aio_read)(struct kiocb *,char __user *,size_t,loff_t)

初始化一个异步的读取操作---即在函数返回之前可能不会完成的读取操作,该函数指针被赋为NULL值时,所有操作将通过read(同步处理)


5.ssize_t (*write) (struct file *,char __user *,size_t,loff_t *)

向设备发送数据,如果没有这个函数,write系统调用会向程序返回-EINVAL(Invalid argument,非法参数),函数返回非负值表示成功写入的字节数


6.ssize_t  (*aio_write)(struct kiocb *,char __user *,size_t,loff_t)

初始化设备上的异步写入操作


7.int (*readdir) (struct file *, void *,filldir_t);

对于设备文件来说,这个字段应该为NULL,它仅用于读取目录,只对文件系统有用


8.unsigned int (*poll) (struct file *, struct poll_table)struct*)

poll方法是poll、epoll和select这三个系统调用的后端实现,这三个系统调用可用来查询某个或多个文件描述符上的读取或写入是否会被阻塞,poll方法应该返回一个位掩码,用来指出非阻塞的读取或写入是否可能,并且也会向内核提供将调用进程置于休眠状态直到I/O变为可能时的信息,如果驱动程序将poll方法定义为NULL,则设备会被认为即可读又可写,并且不会被阻塞


9.int  (*ioctl) (struct inode *,struct file *,unsigned int,unsigned long)

系统调用ioctl提供了一种执行设备特定命令的方法( 如格式化软盘的某个磁道,这既不是读操作也不是写操作)。另外,内核还能识别一部分ioctl命令,而不必调用fops表中的ioctl,如果设备不提供ioctl入口点,则对于任何内核未预先定义的请求,ioctl系统调用将返回错误(-ENOTTY,"No sunch ioctl for device,该设备无此ioctl命令")


10.int (*mmap) (struct file *,struct vm_area_struct*)

mmap用于请求将设备内存映射到进程地址空间,如果设备没有实现这个方法,那么mmap系统调用将返回-ENODEV


11.(*open) (struct inode *,struct file *)

尽管这是对设备文件执行的第一个操作,然而却并不要求驱动程序一定要声明一个相应的方法,如果这个入口为NULL,设备的打开操作永远成功,但系统不会通知驱动程序


12.int (*flush) (struct file *)

对flush操作的调用发生在进程关闭设备文件描述符副本的时候,它应该执行( 并等待)设备上尚未为完结的操作,请不要将它同用户程序使用的fsync操作相混淆,目前flush仅仅用于少数几个驱动程序,比如,SCSI磁带驱动程序用它来确保设备被关闭之前所有的数据都被写入到磁带中,如果flush被设置为NULL,内核将简单地忽略用户应用程序的请求


13.(*release) (struct inode *,struct file *)

当file结构被释放时,将调用这个操作,与open相仿,也可以将release设置为NULL


14.int (*fsync) (struct file *,struct dentry *,int)

该方法是fsync系统调用的后端实现,用户调用它来刷新待处理的数据,如果驱动程序没有实现这一方法,fsync系统调用返回-EINVAL


15.int (*aio_fsync)(struct kiocb *,int)

这是fsync方法的异步版本


16.int (*fsync) (int,struct file *,int)

这个操作用来通知设备其FSYNC标志发生了变化,如果设备不支持异步通知,该字段可以是NULL


17.int (*lock) (struct file *,int,struct file_lock *)

lock字段用来实现文件锁定,锁定是常规文件不可缺少的特性,但是设备驱动程序几乎不会使用这个字段


18.ssize_t (*readv) (struct file *,const struct iovec *,unsigned long,loff_t *)

     ssize_t (*writev) (struct file *,const struct iovec *,unsigned long,loff_t *)

这些方法用来实现分散/聚集型的读写操作,应用程序有时需要进行涉及多个内存区域的单次读或写操作,利用上面这些系统调用可完成这类工作,而不必强加额外的数据拷贝操作,如果这些函数指针被设置为NULL,就会调用read和write方法( 可能是多次)


19.ssize_t (*sendfile) (struct file *,loff_t *,size_t,read_actor_t,void *)

这个方法实现sendfile系统调用的的读取部分,sendfile系统调用以最小的复制操作将数据从一个文件描述符移动到另一个,例如,Web服务器可以利用这个方法将某个文件的内容发送到网络连接,设备驱动程序通常将sendfile设置为NULL


20.ssize_t (*sendpage) (struct file *,struct page *,int,size_t,loff_t *,int)

sendpage是sendfile系统调用的另外一半,它由内核调用以将数据发送到对应的文件,每次是一个数据页,设备驱动程序通常也不需要实现sendpage


21.unsigned long (*get_unmapped_area) (struct file *,unsigned long,unsigned long,unsigned long,unsigned long)

该方法的目的是在进程的地址空间找到一个合适的位置,以便将底层设备中的内存段映射到该位置,该任务通常由内存管理代码完成,但该方法的存在可允许驱动程序强制满足特定设备需要的任何对齐要求,大部分驱动程序可设置改方法为NULL


22.int (*check_flags) (int)

该方法允许模块检查传递给fcntl(F_SETFL...)调用的标志


23.int (*dir_notify) (struct file *,unsigned long)

当应用程序使用fcntl来请求目录改变通知时,改方法将被调用,该方法仅对文件系统有用,驱动程序不必实现dir_notify


scull设备驱动程序所实现的只是最重要的设备方法,它的file_operations结构被初始化为如下

struct file_operations scull_fops =

{

.owner = THIS_MODULE,

.llseek = scull_llseek,

.read = scull_read,

.write = scull_write,

.ioctl = scull_ioctl,

.open = scull_open,

.release = scull_release,

};


二、文件结构 file

struct file 是一个内核结构,它不会出现在用户程序中。file结构代表一个打开的文件(它并不仅仅限定于设备驱动程序,系统中每个打开的文件在内核空间都有一个对应的file结构),它由内核在open时创建,并传递给在该文件上进行操作的所有函数,直到最后的close函数,在文件的所有实例都被关闭之后,内核会释放这个数据结构

在内核源码中,指向struct file的指针通常被称为file或filp(文件指针),为了区分该结构和该结构的指针,一致将指针称为filp,这样file指的是结构本身,filp则是指向该结构的指针

struct file {

struct list_headf_list;

struct dentry *f_dentry;

struct vfsmount         *f_vfsmnt;

struct file_operations*f_op;

atomic_t f_count;

unsigned int f_flags;

mode_t f_mode;

int f_error;

loff_t f_pos;

struct fown_structf_owner;

unsigned int f_uid, f_gid;

struct file_ra_statef_ra;

size_t f_maxcount;

unsigned long f_version;

void *f_security;

void *private_data;

#ifdef CONFIG_EPOLL

struct list_headf_ep_links;

spinlock_t f_ep_lock;
#endif /* #ifdef CONFIG_EPOLL */

struct address_space*f_mapping;
};


1.mode-t f_mode

文件模式,它通过FMODE_READ和FMODE_WRITE位来标识文件是否可读或可写(可读可写)


2.loff_t f_ops

当前的读/写位置,loff_t是一个64位的数(long long)如果驱动程序需要知道文件中的当前位置,可以读取这个值,但是不要去修改它,read/write会使用它们接收到的最后那个指针参数来更新这一位置,而不是直接对filep->f_pos进行操作


3.unsigned int f_flags

文件标志,如O_RDONLY,O_NONBLOCK和O_SYNC


4.struct file_operations *f_op

与文件相关的操作,内核在执行open操作时对这个指针赋值,以后需要处理这些操作时就读取这个指针.filp->f_op中的值绝不会为方便而保存起来也就是说,我们可以在任何需要的时候修改文件的关联操作,在返回给调用者之后,新的操作会立即生效。例如,对于主设备号1(/dev/null\/dev/zero等等)的open代码根据要打开的次设备号替换filp->f_op中的操作,这种技巧允许相同主设备号下的设备实现多种操作行为,而不会增加系统调用的负担,这种替换文件操作的能力在面向对象编程技术中称为“方法重载”


5.void *private_data

open系统调用在调用驱动程序的open方法前会将这个指针置为NULL,驱动程序可以将这个字段用于任何目的或者忽略这个字段,驱动程序可以用这个字段指向已分配的数据,但是一定要在内核销毁file结构前在release方法中释放内存,private_data是跨系统调用时保存状态信息的非常有用的资源


6.struct dentry *f_dentry

文件对应的目录项(dentry)结构,除了filp->f_dentry->d_inode的方法来访问索引节点结构之外,设备驱动程序的开发者们一般无需关心dentry结构

实际上结构里还有一些字段,但它们对于设备驱动程序并没有多大用处,由于驱动程序从不填写file结构,而只是对别处创建的file结构进行访问,所以忽略这些字段是安全的



三、inode 结构

内核用inode结构在内部表示文件,因此它和file结构不同,后者表示打开的文件描述符,对单个文件,可能会有许多个打开的文件描述符的file结构,但1它们指向单个的inode结构

inode结构中包含了大量有关文件的信息,作为常规,只有下面两个字段对编写驱动程序代码有用


dev_t i_rdev

对表示设备文件的inode结构,该字段包含了真正的设备编号


struct cdev *i_cdev

struct cdev是表示字符设备的内核的内部结构,当inode指向一个字符设备文件时,该字段包含了指向struct cdev的结构的指针

获取inode中的主设备号和次设备号

unsigned int major(struct inode *inode)

unsigned int imajor(struct inode *inode)