Linux字符驱动框架相比初学还是比较难记的,在学了一阵子字符驱动的开发后对于框架的搭建总结出了几个字 。
对于框架来讲主要要完成两步。
申请设备号,注册字符驱动
其关键代码就两句
~
int alloc_chrdev_region(dev_t *, unsigned, unsigned, const char *);//动态申请设备号
int cdev_add(struct cdev *, dev_t, unsigned); //注册字符驱动
~
执行完次就可以将我们的驱动程序加载到内核里了
首先我们搭建主程序,字符驱动的名字就叫做"main"
首先先写下将要用到的头文件,以及一个宏定义,指明了我们驱动的名称,当然名称可以任意这里就取"main" 作为名字
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/coda.h>
#include <linux/slab.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#define MUDULE_NAME "main"
驱动由于需要加载到内核里,所以我们需要声明一下我们驱动所遵循的协议,如果没有申明,那么加载内核的时候系统会提示一段信息。我们按照内核的风格来,就加一个GPL协议吧
MODULE_LICENSE("GPL");
我们要想将我们的驱动注册到内核里,就必须将我们的驱动本身作为一个抽象,抽象成一个struct cdev的结构体。因为我们系统内部有许多中字符驱动,为了将这些不同种类的驱动都能使用同一个函数进行注册,内核声明了一个结构体,不同的驱动通过这个结构体--变成了一个抽象的驱动供系统调用。这段有点罗嗦,我们来看一下cdev这个结构体吧。
//这段不属于主程序
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
};
这个结构体就包含了一个驱动所应有的东西其中 kobj 不需要管它,我也没有仔细研究,owner指向模块的所有者,常常使用THIS_MODULE这个宏来赋值,ops是我们主要做的工作,其中定义了各种操作的接口。
下面我们定义了我们程序的抽象体mydev,以及他所需要的接口
struct cdev mydev;
struct file_operations ops;
struct file_operations这个结构有点庞大。
//不属于本程序
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 *, loff_t, loff_t, 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 **);
long (*fallocate)(struct file *file, int mode, loff_t offset,
loff_t len);
};
上面看到,这个结构内部都是一些函数指针,相当与这个结构本身就是一个接口,在c语言中没有接口这个概念,使用这种方式来定义也是一种巧妙的用法。不过有所不同的是我们可以不完全实现其中的接口。