1、重要的数据结构
注册设备编号仅仅是驱动代码需要完成的任务之一,还有很多基础性的驱动操作需要驱动代码来完成,这里有3个重要的内核数据结构需要了解一下分别是:file_operations、file、inode。
1.1、文件操作
File_operation结构的功能是建立一个字符驱动与设备编号的连接。通常结构中的每个成员必须指向驱动中的函数,这些函数实现一个特别的操作,对于不支持的操作置为NULL。
__user这种注解是一种文档形式,表明一个指针是一个不能被直接解除引用的用户空间地址,对于正常的编译没有效果。
Structmodule*owner
指向拥有这个结构的模块的指针。
Loff_t(*llseek)(structfile*,loff_t,int);
Llseek用来改变文件中当前读/写位置,并且新位置作为返回值,错误返回负值。
Ssize_t(*read)(structfile*,char__user*,size_t,loff_t*);
用来从设备中获取数据,如果这个方法是NULL,将导致系统调用-EINVAL失败,返回非负值代笔成功读取的字节数(返回值是一个signedsize类型,通常是目标平台本地的整数类型)。
Ssize_t(*aio_read)(structkiocb*,char__user*,size_t,loff_t);
初始化一个异步读,肯恩在函数返回前不结束的读操作,如果这个方法是NULL,所有的操作将会由read代替进行(同步读)。
Ssize_t(*write)(structfile*,constchar__user*,size_t,loff_t*);
发送数据给设备,如果这个方法NULL,将返回-EINVAL给调用这个方法的程序,如果非负,返回值代表成功写的字节数。
Ssize_t(*aio_write)(structkiocb*,constchar__user*,size_t,loff_f*);
初始化设备上的一个异步写。
Int(*readdir)(structfile*,void*,filldir_t);
对于设备文件这个成员应当为NULL,它用来读取目录,并且仅对文件系统有用。
Unsignedint(*poll)(structfile*,structpoll_table_struct*);
Poll方法为3个系统调用的后端:pollepollselect,它们都用作查询一个或多个文件描述符的读和写是否会拥塞。Poll应当方法返回一个位掩码指示读或写是否拥塞,并提供给内核信息,使调用进程睡眠直到I/O变为可能,如果一个驱动的poll方法为NULL,则设备被假定为不阻塞可读可写。
Int(*ioctl)(structinode*,structfile*,unsignedint,unsignedlong);
Ioctl系统调用提供了发出设备特定命令的方法,如果设备不提供ioctl,对于任何事先未定义的请求,系统调用都会返回一个错误。
Int(*mmap)(structfile*,structvm_area_struct*);
Mmap用来请求将设备内存映射到进程的地址空间。如果这个方法是NULL,mmap系统调用返回-ENODEY。
Int(*open)(structinode*,structfile*);
这常常是对设备文件进行的第一个操作,不要求驱动声明一个对应的方法,如果这个方法是NULL,设备打开一只成功,但是你的驱动不会得到通知。
Int(*flush)(structfile*);
Flush操作在进程关闭它的设备文件描述符的拷贝时调用,它应当执行设备的任何未完成的操作。
Int(*release)(structinode*,structfile*);
在文件结构被释放时引用这个操作,如同open,release可以为NULL。
Int(*fsync)(structfile*,structdentry*,int);
这个方法是fsync系统调用的后端,用户调用来刷新任何挂着的数据,如果这个指针是NULL,系统返回-EINVAL。
Int(*aio_fsync)(structkiocb*,int);
Fsync方法的异步版本。
Int(*fasync)(int,structfile*,int);
这个操作用来通知设备它的FASYNC标志发生改变,如果驱动不支持异步通知,这个成员可以使NULL。
Int(*lock)(structfile*,int,structfile_lock*);
Lock发放用来实现文件加锁,加锁对常规文件时必不可少的特性,但是设备驱动几乎从不实现它。
Ssize_t(*readv)(structfile*,conststructiovec*,unsignedlong,loff_f*);
Ssize_t(*writev)(structfile*,conststructiovec*,unsignedlong,loff_t*);
这个方法实现发散/汇聚读和写操作,应用程序偶尔需要做一个包含多个内存区的单个读或写操作,这个系统调用允许他们这样做而不必对数据进行额外拷贝,如果这些函数指针为NULL,read和write方法被调用(可能多于一次)。
Ssize_t(*sendfile)(structfile*,loff_t*,size_t,read_actor_t,void*);
这个方法实现sendfile系统调用的读,使用最少的拷贝从一个文件描述符搬移数据到另一个,设备驱动常常使sendfile为NULL。
Ssize_t(*sendpage)(structfile*,structpage*,int,size_t,loff_t*,int);
Sendpage是sendfile的另一半,它由内核调用来发送数据,一次一页,到对应的文件,设备驱动通常不实现sendpage。
Unsignedlong(*get_unmapped_area)(structfile*,unsignedlong,unsignedlong,unsignedlong);
这个方法的目的是在进程的地址空间找一个合适的位置来映射在底层设备上的内存段中,大部分驱动可以置这个方法为NULL。
Int(*check_flags)(int);
这个方法允许模块检查传递给fnctl(F_SETEL...)调用的标志。
Int(*dir_notify)(structfile*,unsignedlong);
这个方法在应用程序使用fnctl来请求目录改变通知时调用,只对文件系统有用,驱动不需要实现dir_notify。
Scull设备驱动只实现最重要的设备方法,它的file_operations结构是如下初始化的:
Structfile_operationsscull_fops={.owner=THIS_MODULE,
.llseek=scull_llseek,
.read=scull_read,
.write=scull_write,
.ioctl=scull_ioctl,
.open=scull_open,
,release=scull_release,
};
1.2、文件结构
Structfile是设备驱动中第二个重要的数据结构,注意file与用户空间程序的FILE指针没有任何关系,一个FILE定义在C库中,从不出现在内核代码中,一个structfile是内核结构,从不出现在用户程序中。
Structfile代表一个打开的文件,在内核空间,系统中每个打开的文件都有一个关联的structfile,在内核open时创建,并传递给操作文件的函数,直到最后关闭,然后内核释放这个数据结构。
在内核源码中,structfile的指针常常称为file或者filp(filepointer),为避免混淆,以后file指结构,filp指结构指针。下面是file结构的一些重要成员:
Mode_tf_mode:文件模式
确定文件时可读或是可写,通过位FMODE_READ和FMODE_WRITE确定。
Loff_tf_pos:当前读写位置
Loff_t在所有平台都是64位的,驱动如果需要知道文件的当前位置,可以读这个值。
Unsignedintf_flags:文件标志
如O_RDONLYO_NONBLOCKO_SYNC,驱动应该检查O_NONBLOCK标志来查看是否是请求非阻塞操作,其他的很少使用。
Structfile_operations*f_op:和文件关联的操作
Void*private_data:open系统调用设置这个指针为NULL,在为驱动调用open方法之前,你可自由使用这个成员或者忽略它。
Structdentry*f_dentry:关联到文件的目录入口结构。
1.3、inode结构
Inode结构由内核在内部用来表示文件。
有用的成员:
Dev_ti_rdev:对于代表设备文件的节点,这个成员包含实际的设备编号。
Structcdev*i_cdev:
Structcdev是内核的内部结构,代表字符设备。
从一个inode中获取主次编号的宏:
Unsignedintiminor(structinode*inode);
Unsignedintimajor(structinode*inode);