Linux驱动学习----字符设备驱动(二)
写在前面的话:
上一次讲到了cdev的注册和销毁,将一个字符设备驱动的程序的整个轮廓已经建立起来了。但是,我们的驱动程序不可能什么都不做吧,那下面我们就来看看驱动程序的操作是怎么去定义的。
必不可少的,我们要涉及到一些数据结构,我们先来看看有哪些是我们需要去注意的。
首先我们来看看我们最熟悉的文件操作相关的数据结构---file_operations。
该结构的作用是将驱动程操作连接到我们申请的一些设备编号上。该结构定义在<linux/fs.h>中:
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 (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t); ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t); int (*readdir) (struct file *, void *, filldir_t); 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 *, 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 (*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 **); };
我们可以看见,除了第一项之外的其他项都是函数指针。对的,这个结构中的每一个字段都必须指向驱动程序中实现特定操作的函数,对于不支持的操作,对应的字段可置为NULL值。
下面,我们就简单的介绍一下,我们常用的一些项。
struct module *owner; 我们可以看出这一项并不是一个函数指针;它是指向“拥有”该结构模块的指针。几乎在所有的情况下,该成员会被初始化为THIS_MODULE。
loff_t (*llseek) (struct file *, loff_t, int); 修改文件的当前读写位置,并将新位置作为返回值返回。
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); 从设备中读取数据。该函数指针被赋为NULL时,将导致read系统调用出错并返回-EINVAL,函数返回非负值表示成功读取的字节数
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); 向设备发送数据。该函数指针被赋为NULL时,将导致read系统调用出错并返回-EINVAL,函数返回非负值表示成功写入的字节数
ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t); 异步操作的初始化函数,即在函数返回之前是不会完成的读取/写入操作。
unsigned int (*poll) (struct file *, struct poll_table_struct *); poll方法是poll,epoll,select这三个系统调用的后端实现。这三个系统调用可用来查询某个或多个文件描述符上的读取或写入是否会被阻 塞。这个我们会在后面阻塞的之处讲到。
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 *); 这始终是对设备文件执行的第一个操作,但是却并不要求驱动程序一定要声明一个相应的方法。如果这个入口为NULL,设备的打开永远都能成功。
int (*release) (struct inode *, struct file *); 当file结构被释放时,将调用这个操作。与open相仿,也可以将release设置为NULL。
上面的这些项是我们一般写驱动会经常用到的。
下面要介绍的是第二个数据结构----file结构。
file结构代表一个打开的文件(并不仅仅限定于设备驱动程序,系统每个打开的文件在内核空间都会有一个对应的file结构)。
同样,该结构被定义在<linux/fs.h>中:
struct file { /* * fu_list becomes invalid after file_free is called and queued via * fu_rcuhead for RCU freeing */ union { struct list_head fu_list; struct rcu_head fu_rcuhead; } f_u; struct path f_path; #define f_dentry f_path.dentry #define f_vfsmnt f_path.mnt const struct file_operations *f_op; /* * Protects f_ep_links, f_flags, f_pos vs i_size in lseek SEEK_CUR. * Must not be taken from IRQ context. */ spinlock_t f_lock; #ifdef CONFIG_SMP int f_sb_list_cpu; #endif atomic_long_t f_count; unsigned int f_flags; fmode_t f_mode; loff_t f_pos; struct fown_struct f_owner; const struct cred *f_cred; struct file_ra_state f_ra; u64 f_version; #ifdef CONFIG_SECURITY void *f_security; #endif /* needed for tty driver, and maybe others */ void *private_data; #ifdef CONFIG_EPOLL /* Used by fs/eventpoll.c to link all the hooks to this file */ struct list_head f_ep_links; struct list_head f_tfile_llink; #endif /* #ifdef CONFIG_EPOLL */ struct address_space *f_mapping; #ifdef CONFIG_DEBUG_WRITECOUNT unsigned long f_mnt_write_state; #endif };
我们挑几个来介绍一下:
fmode_t f_mode; 文件模式。它通过FMODE_READ和FMODE_WRITE位来标识文件是否可读或可写(或可读写)。该标志各位的定义在include/linux/fs.h。
loff_t f_ops; 当前的读写位置
unsigned int f_flags; 文件标志,如O_RDONLY,O_NONBLOCK,O_SYNC。一般这里是用来检查用户请求是否是阻塞式的操作,即检查O_NONBLOCK。注意:检查读写权限应查看f_mode,而不是f_flags。
const struct file_operations *f_op; 与文件相关的操作。内核在执行open操作时对这个指针赋值,以后需要处理这些操作时就读取这个指针。同时,我们也可以在任何需要的时候修改文件的关联操 作,在返回给调用者之前新的操作方法就会立即生效。这种技巧允许相同主设备号下的设备实现多种操作行为,而不会增加系统调用的负担。
void *private_data; 跨系统调用时保存状态信息的非常有用的资源。具体的使用可以在程序中见识到。
最后,让我们来看看inode结构:
同样的,它被定义在<linux/fs.h>。由于篇幅较长,我就不将它黏贴出来了。
inode结构在内核内部表示文件。由此,我们可以知道它和file结构不同,后者表示打开的文件描述符。对单个文件,可能会有许多个表示文件描述符的file结构,但它们都指向一个inode结构。
inode结构中包含了大量有关文件的信息。下面只介绍两个于我们 编程有直接关系的:
dev_t i_rdev; 表示设备文件的inode结构,这里包含了真正的设备编号
struct cdev *i_cdev; cdev表示子都设备的内核的内部结构。
好了,至此,我们将在驱动编程中最关键的三个数据结构进行了简单的叙述。
下面,我们将上一篇中的程序进行补全,让它成为一个全的简单设备驱动程序。
这里,我们简单的实现read和write操作。
先来看看file_operations结构体的初始化:
static const struct file_operations module_framework_fops = { .owner = THIS_MODULE, .read = module_framework_read, .write = module_framework_write, .open = module_framework_open, .release = module_framework_release, };
这就是file_operations典型的初始化形式,以后其他方法实现之后,只要在这里添加调用就可以了。
下面的我的read和write方法对内存进行了拷贝。
1 ssize_t module_framework_read(struct file *filp, char __user *buf, size_t count, loff_t *offp) 2 { 3 unsigned long p = *offp; 4 unsigned int mem_size = count; 5 int ret = 0; 6 struct module_framework_dev *dev = filp->private_data; 7 8 if (p >= TESTMEM_SIZE) 9 return mem_size ? -ENXIO: 0; 10 if (mem_size > TESTMEM_SIZE - p) 11 mem_size = TESTMEM_SIZE - p; 12 if (copy_to_user(buf,(void*)(dev->memory + p), mem_size)) { 13 ret = -EFAULT; 14 } else { 15 *offp += mem_size; 16 ret = mem_size; 17 printk(KERN_INFO "ok! you read %d bytes from %ld\n", mem_size, p); 18 } 19 20 return ret; 21 } 22 23 static ssize_t module_framework_write(struct file *filp, const char __user *buf, size_t count, loff_t *offp) 24 { 25 unsigned long p = *offp; 26 unsigned int mem_size = count; 27 int ret = 0; 28 struct module_framework_dev *dev = filp->private_data; 29 30 if (p >= TESTMEM_SIZE) 31 return mem_size ? -ENXIO: 0; 32 if (count > TESTMEM_SIZE - p) 33 mem_size = TESTMEM_SIZE - p; 34 if (copy_from_user(dev->memory+p, buf, mem_size)) { 35 ret = -EFAULT; 36 } else { 37 *offp += mem_size; 38 ret = mem_size; 39 printk(KERN_INFO "ok! you written %d byte from %ld\n", mem_size, p); 40 } 41 return ret; 42 }
最后,我将程序和Makefile文件一起上传,供大家参考。module_framework.zip