1. file_operations结构体的概述
Linux驱动程序中最重要的涉及3个重要的内核数据结构,分别为file_operations,file和inode。在linux中inode结构用于表示文件,而file结构则表示打开的文件的描述,因为对于单个文件而言可能会有许多个表示打开的文件的描述符,因而就可能会的对应有多个file结构,但是都指向单个inode结构。
在系统内部,I/O设备的存取操作通过特定的的入口来进行,而这组特定的入口由驱动程序来提供的。通常这组设备驱动的接口是由结构体file_operations向系统说明的,结构体file_operations是定义在include/linux/fs.h中的。
structfile_operations {
struct module *owner; //防止模块在使用的时候被卸载
loff_t (*llseek) (struct file *, loff_t,int);
ssize_t (*read) (struct file *, char *,size_t, loff_t *);
ssize_t (*write) (struct file *, const char*, size_t, loff_t *);
int (*readdir) (struct file *, void *,filldir_t);
unsigned int (*poll) (struct file *, structpoll_table_struct *);
int (*ioctl) (struct inode *, struct file*, unsigned int, unsigned long);
int (*mmap) (struct file *, structvm_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 (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, structfile_lock *);
ssize_t (*readv) (struct file *, conststruct iovec *, unsigned long, loff_t *);
ssize_t (*writev) (struct file *, conststruct iovec *, unsigned long, loff_t *);
ssize_t (*sendpage) (struct file *, structpage *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(structfile *, unsigned long, unsigned long, unsigned long, unsigned long);
};
详细分析结构体中比较重要的几个方法:
struct module *owner;
它是一个指向拥有这个结构的模块的指针.这个成员用来在它的操作还在被使用时阻止模块被卸载。
loff_t (*llseek) (struct file *, loff_t, int);
llseek方法用作改变文件中的当前读/写位置, 并且新位置作为(正的)返回值。指针参数filp为进行读取信息的目标文件结构体指针; loff_t 参数是一个"long offset", 并且就算在 32位平台上也至少 64 位宽. 错误由一个负返回值指示。
ssize_t (*read) (struct file * filp, char * , size_t,loff_t * );
这个函数用来从设备中获取数据. 在这个位置的一个空指针导致 read 系统调用以 -EINVAL("Invalid argument") 失败.一个非负返回值代表了成功读取的字节数( 返回值是一个"signed size" 类型, 常常是目标平台本地的整数类型)。
ssize_t (*write) (struct file*, const char * ,size_t,loff_t *);
发送数据给设备. 如果 NULL,-EINVAL 返回给调用 write 系统调用的程序. 如果非负, 返回值代表成功写的字节数.
(注:这个操作和上面的对文件进行读的操作均为阻塞操作)
int (*readdir) (struct file * ,void *, filldir_t);
对于设备文件这个成员应当为 NULL; 它用来读取目录, 并且仅对文件系统有用。
int (*ioctl) (struct inode*, struct file*, unsignedint, unsigned long );
ioctl 系统调用提供了发出设备特定命令的方法(例如格式化软盘的一个磁道, 这不是读也不是写). 另外, 几个 ioctl 命令被内核识别而不必引用 fops 表.如果设备不提供 ioctl 方法, 对于任何未事先定义的请求(-ENOTTY,"设备无这样的ioctl"), 系统调用返回一个错误.
int (*open) (struct inode * inode , struct file * filp) ;
这常常是对设备文件进行的第一个操作, 不要求驱动声明一个对应的方法. 如果这个项是 NULL, 设备打开一直成功, 但是你的驱动不会得到通知。与open()函数对应的是release()函数。
int (*release) (struct inode *, struct file *);
release ()函数当最后一个打开设备的用户进程执行close()系统调用的时候,内核将调用驱动程序release()函数:
void release(struct inode inode,struct file*file),release函数的主要任务是清理未结束的输入输出操作,释放资源,用户自定义排他标志的复位等。在文件结构被释放时引用这个操作. 如同 open, release 可以为 NULL.
2.驱动编写的框架
设备驱动程序的主体一般是写好了的。嵌入式开发人员要把驱动程序嵌入内核。驱动程序可以按照两种方式编译。一种是编译进kernel,另一种是编译成模块(modules),如果编译进内核的话,会增加内核的大小,还要改动内核的源文件,而且不能动态的卸载,不利于调试,所以推荐使用模块方式,也可以理解为静态加载与动态加载的方式。
在这里可大致将驱动编写分为以下几个部分:
u 驱动程序的注册和注销;
u 设备的打开和释放;
u 设备的读写操作;
u 设备的控制操作;
u 设备的中断和轮询处理。
3.模块的概念
模块:内核本身是很庞大的一个结构,需要的组件很多。编译内核时,用户 可以把所有的代码编译进内核,但是这样会引起两个问题:一是内核过大;二是 当需要添加或者删除内核时,需要重新再编译内核,而重新编译内核是一个是否花费时间的工作,所以有了内核模块的概念。 模块并不编译到内核中,编译后存放在指定的目录,当需要使用时动态加载。