字符设备注册:
杂项设备注册方法、早期经典方式注册方法、linux2.6版本注册方法
区别:
安装杂项设备会在/dev目录生成设备文件,早期经典标准字符设备驱动模型和linux2.6标准字符设备驱动不会自动生成设备文件。
杂项设备:
Linux杂项驱动出现的意义在于:有很多简单的外围字符设备,它们功能相对简单,一个设备占用一个主设备号对于内核资源来说太浪费。
所以对于这些简单的字符设备它们共用一个主设备号,不同的设备使用不同的次设备号。
杂项驱动特点:
主设备号相同,次设备号不同
在文件系统中自动生成设备节点
注册方法:
函数
原型注册函数:int misc_register(struct miscdevice *misc);
功能:向内核注册一个杂项字符设备
参数:misc 已经实现好minor,name,fops三个成员的struct miscdevice结构变量的地址
返回值:0:注册成功; <0 :失败,返回失败错误码
参数miscdevice 头文件#include
参数fop 头文件#include
minor表示次设备号 name表示节点名字在/dev/下可以查找到 fop表示指令集内部有许多函数可以使用户应用与内核相对应,如常用的open close read write lseek等函数
调用一次misc_register注册函数,只会占用一个次设备号。
杂项设备注册函数的主设备号为10
原型注销函数:int misc_deregister(struct miscdevice *misc)
功能:注销一个已经注册到内核中杂项字符设备
参数:misc已经注册好的struct miscdevice 结构体变量的地址
返回值:0:注册成功,<0 :失败,返回失败错误代码
早期经典方式注册方法:
主设备号:0~255(10除外)
次设备号:0~255全部占用(早期经典字符设备只能以主设备号区分)
设备特征:
1、 安装后不会在/dev目录下创建设备文件节点,需要手动mknod命令创建
2、 调用一个register_chrdev注册函数后,一个主设备号下面的256个次设备号都被占用完了,也就是说一个主设备号只能使用register_chrdev注册一次。
注册函数:int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops)
参数
major:主设备号,设备号为0时表示内核自动分配一个可用主设备号,这类设备注册的主设备号不能与其他驱动相同。
name:设备名,不需要与/dev对应设备名相同,但会出现在/proc/devices中,这个文档显示已注册的设备。
fops:文件操作集合结构体
- 返回值
当major==0时,成功:返回系统自动分配的主设备号 失败:返回负数
当major >0时,成功:返回0 失败:返回负数
注销函数:static inline void unregister_chrdev(unsigned int major, const char *name)
参数 :
major:主设备号
name:设备名,与register_chrdev名称相同
和杂项设备驱动模型相比:文件操作方法是相同的,不同的是注册/注销函数以及设备号分配上不同
小结:
1、早期模型注册时用到的name并不要求和最终/dev下的name相同,因为应用程序不是靠设备名寻找设备文件的,而是通过设备号来寻找设备文件的。
2、早期字符设备驱动文件,次设备号0~255都指向同一个驱动,即只要主设备号相同,无论次设备号是多少都能通过设备节点文件找到设备驱动,也就是一个主设备号下面的256个次设备号都被占用完了,无论次设备号是多少都是这个驱动的文件节点。一个早期字符设备占用整个主设备号及以下所有次设备号。
linux2.6版本注册方法:
linux2.6的版本中使用了dev_t dev 保存了设备号一共32位前12位代表了主设备号后20位代表了次设备号 可以使用宏MAJOR(dev)从完整设备号提取主设备号 宏MINOR(dev)从完整设备号提取次设备号 宏MKDEV(ma,mi)可以从已知次设备号合成完整设备号。
在2.6版本中必须首先申请设备号若已知某个主设备号与次设备号可以使用可以使用静态申请函数申请设备号 若未知可以使用动态申请
静态设备号申请int register_chrdev_region(dev_t from, unsigned count, const char *name) 头文件#include
动态设备号申请int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name) 头文件#include
功能:申请一个设备号范围
参数dev: 存放分配到的第一个设备(包括主次设备号)
参数baseminon:要分配起始次设备号(次设备号的起始值)
参数count:连续的次设备号数量
参数name:设备名,不需要和/dev的设备文件名相同
返回值:成功返回0 失败返回负数
设备号释放函数void unregister_chrdev_region(dev_t from, unsigned int count)
头文件 #include
主要流程:
申请完整设备号
动态申请(动态分配)或静态申请(直接指定)
注册添加设备 cdev_init -- cdev_add
自动/手动创建节点
核心结构初始化void cdev_init(struct cdev *cdev,const struct file_operations *fops) 头文件#include
功能:初始化核心结构,具体做的是清零核心结构,初始化核心结构的list,kobj,ops成员
参数cdev:需要初始化的核心结构指针
参数fops:文件操作方法结构指针集
无返回值
说明:写这种驱动模型的时候,不需要在定义struct cdev结构变量初始化,因为调用cdev_init函数的时候会把它清零0,定义时候的初始无效。
设备注册函数int cdev_add(struct cdev *p,dev_t dev,unsigned count) 头文件#include
功能注册一个cdev结构
参数p:已经初始化的核心结构指针
参数dev:起始设备号(包含主次设备号)
参数count:连续次设备号的数量
返回值:成功: 返回 0, 失败:返回负数
设备注销函数void cdev_del(struct cdev *p) 头文件#include
功能:注销一个cdev结构
参数p:前面注册的struct cdev结构指针
无返回值
核心结构体cdev可以有两种方法定义一种使普通定义一种是指针
指针式:
核心结构分配函数:
struct cdev *cdev_alloc(void)
头文件#include
功能:在堆空间中分配一个核心结构,注意,不适用的时候要用kfree函数释放
参数:无
返回值:返回分配到struct cdev 结构空间首地址
说明:用完记得使用kfree释放,否则会造成内存泄漏
linux2.6字符设备驱动编程步骤
模块加载函数中:
1:定义核心结构体,两种方法,第二中要使用cdev_alloc(void) 分配结构空间
2:申请设备号静态或者是动态 alloc_chrdev_region
3:初始化cdev结构cdev_init
4:注册已经初始化好的cdev结构
模块卸载函数中:
1:注销设备
2:释放设备号
3:释放cdev结构空间
早期经典方式注册方法、linux2.6版本注册方法由于不能自动的在/dev下创建设备节点 必须手动创建设备节点比较麻烦可以参考杂项引用类的思想自动创建设备节点
加载函数:
创建class类(class_create(ower,name)) 这个函数返回的是指针 头文件#include
owner:类的所有者,固定是THIS_MODULE
name;类名,随便,能有含义最好,不是/dev/下设备的名字,这个名字决定了/sys/class/name。 当创建一个class后会在/sys/class/目录生成一个名为name的目录.
创建设备节点(struct device *device_create(struct class *class,struct device *parent,dev_t devt,void *drvdata,const char *fmt,.....)) 头文件#include
参数class:已经创建好的类指针
参数parent:指向父设备的指针,如果没有可以为NULL
参数devt: 32位的设备号
参数drvdata:驱动数据,也可以是NULL,一般为NULL
参数:fmt,...可变参数,用来生成/dev/目录下的设备文件名
返回值 :返回的指针是否合法使用IS_ERR( )宏判断, 成功 有效struct device*指针;失败返回错误指针,可以使用ERR_PTR( )宏转换成错误代码
卸载函数:
摧毁class类void class_destroy(struct class *cls)
摧毁设备节点void device_destroy(struct class *class,dev_t devt)