一、设备描述结构
在任何一种驱动模型中,设备都会用内核中的一种结构来描述,字符设备在内核中使用struct cdev结构体来描述。
1 struct cdev { 2 struct kobject kobj; 3 struct module *owner; 4 const struct file_operations *ops; 5 struct list_head list; 6 dev_t dev; 7 unsigned int count; 8 };
这里关注三个成员:
1、count:设备数目
2、dev:设备号
使用dev_t dev来定义设备号,dev_t类型为unsigned int的别名。变量dev高12位为主设备号,低20位为次设备号。合并及分解有如下函数:
合并:dev_t dev = MKDEV(主设备号,次设备号)
分解:
主设备号 = MAJOR(设备号)
次设备号 = MINOR(设备号)
为设备分配设备号有两种方法:
a、静态申请:自己选一个数字向内核申请
缺点:若所申请的设备号已被占用,则申请失败
实现:int register_chrdev_region(dev_t from, unsigned count, const char *name)
参数:from:第一个设备号
count:设备数目
name:设备名称
返回值:0说明成功,负数说明失败。
b、动态分配:由内核动态地分配设备号,不会出现申请失败的情况
实现:int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name)
参数:dev:第一个设备号的地址(输出参数)
baseminor:次设备号的起始数字
count:设备数目
name:设备名称
驱动退出时要使用void unregister_chrdev_region(dev_t from, unsigned count)来释放设备号,设备号是一种资源,若只获取不释放会用完的。
参数:from:获取到的设备号
count:设备数
3、file_operations *ops:函数映射关系表,其结构成员如下:
1 struct file_operations { 2 struct module *owner; 3 loff_t (*llseek) (struct file *, loff_t, int); 4 ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); 5 ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); 6 ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t); 7 ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t); 8 int (*readdir) (struct file *, void *, filldir_t); 9 unsigned int (*poll) (struct file *, struct poll_table_struct *); 10 long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); 11 long (*compat_ioctl) (struct file *, unsigned int, unsigned long); 12 int (*mmap) (struct file *, struct vm_area_struct *); 13 int (*open) (struct inode *, struct file *); 14 int (*flush) (struct file *, fl_owner_t id); 15 int (*release) (struct inode *, struct file *); 16 int (*fsync) (struct file *, int datasync); 17 int (*aio_fsync) (struct kiocb *, int datasync); 18 int (*fasync) (int, struct file *, int); 19 int (*lock) (struct file *, int, struct file_lock *); 20 ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int); 21 unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long); 22 int (*check_flags)(int); 23 int (*flock) (struct file *, int, struct file_lock *); 24 ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int); 25 ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int); 26 int (*setlease)(struct file *, long, struct file_lock **); 27 long (*fallocate)(struct file *file, int mode, loff_t offset, 28 loff_t len); 29 };
这个成员主要用来进行应用程序函数和内核驱动函数之间的映射。
二、驱动模型
1、驱动初始化
a、分配设备描述结构(字符设备为cdev)
两种方式:
静态分配:struct cdev 变量,即定义一个结构体变量;
动态分配:struct cdev *变量 = cdev_alloc();
b、初始化cdev
作用:初始化设备描述结构和函数映射关系表,使其可被注册
使用cdev_init函数
函数原型:void cdev_init(struct cdev *cdev, const struct file_operations *fops)
参数:
cdev:分配到设备描述结构的地址
fops:这个驱动的函数映射关系表的地址
c、注册cdev
作用:向linux内核注册p指向的设备,使其立即生效
使用cdev_add函数
函数原型:int cdev_add(struct cdev *p, dev_t dev, unsigned count)
参数:
p:分配到设备描述结构的地址
dev:设备号
count:设备数目
2、实现设备操作:
主要实现内核驱动的函数,对应于用户程序的函数。
两个很重要的结构体:
a、struct file
每一个打开的文件都会与之对应一个struct file结构体
重要成员:
1、loff_t f_pos,文件读写指针
2、struct file_operations *ops,对应操作
3、void *private_data,该成员是系统调用时保存状态信息非常有用的资源
b、struct inode
每一个文件都会关联一个struct inode结构体
重要成员:
dev_t i_rdev,设备号
3、驱动注销
当卸载驱动时被调用,使用cdev_del函数来删除设备。
函数原型:void cdev_del(struct cdev *p)
参数:要删除的设备描述结构的地址。
总结:
信息量真心有点大了,今天一天的时间都搭在这节课上了。捋一捋思路吧。它主要做这几件事情:
1、初始化驱动,使它能够被加进系统内核并可以使用;
2、加入 这个设备驱动总得要对他做点什么操作吧,要不加入它做什么?所以要在驱动程序中实现一些对文件进行操作的函数(应用程序通过操作字符设备文件来操作字符设 备),由于调用内核的系统调用函数,所以要使用内核指定的函数接口,因此需要一个函数映射关系表来指明用户程序中的函数和驱动程序中的函数之间的对应关 系,也就是
struct file_operations做的事情,实际应用是这样来实现的:
/*文件操作结构体*/ static const struct file_operations mem_fops = { .llseek = mem_llseek, .read = mem_read, .write = mem_write, .open = mem_open, .release = mem_release, };
3、接下来就是处理后事了, 当卸载驱动时要删除设备,调用cdev_del函数。这样看看思路还是挺清晰的嘛。