字符设备驱动
Linux字符设备驱动结构
1 .cdev结构体
struct cdev
{
struct kobject kobj; //内嵌的kojbect对象
struct module *owner; //所属模块
struct file_operations *ops; //文件操作结构体
struct list_head list;
dev_t dev; //设备号
unsigned int count;
};
2 . 主次设备号的获得
高12为主设备号,低20为次设备号,下面两个宏可以从dev_t中获得主设备号和次设备号
MAJOR(dev_t dev);
MINOR(dev_t dev);
//下面是MAJOR和MINOR两个宏的定义
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS)) // 4#define MINORBITS 20
5#define MINORMASK ((1U << MINORBITS) - 1)
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
2.1 dev_t的生成
MKDEV(int major,int minor);
下面这个宏可以通过主设备号和次设备号生成dev_t
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
Linux2.6内核提供了一组函数用于操作cdev结构体,如下所示
void cdev_init(struct cdev *,struct file_operations *);
struct cdev *cdev_alloc(void); //初始化cdev的成员
void cdev_put(struct cdev *p); //减少计数
int cdev_add(struct cdev *,dev_t,unsigned);//添加一个字符设备
void cdev_del(struct cdev *); //删除字符设备
3 . 初始化cdev成员
cdev_init()函数用来初始化cdev的成员,并建立cdev和file_operations之间的连接,其源代码如下:
void cdev_init(struct cdev *cdev,struct file_operations *fops)
{
memset(cdev,0,sizeof *cdev);//对字符设备进行清零操作
INIT_LIST_HEAD(&cdev->list); //这个宏定义的功能是使得结构体list_head的next和prev都指向自身
cdev->kobj.ktype=&ktype_cdev_default;
kobject_init(&cdev->kobj); //进行kobject的初始化,在今后的驱动程序设计中,这是一个必不可少的工作。
cdev->ops=fops; //将传入的文件操作结构体指针赋值给cdev的ops
}
/*
下面是INIT_LIST_HEAD(struct list_head *list)的源代码
static inline void INIT_LIST_HEAD(struct list_head *list) //这个操作使得双向链表节点指向自己,常常用来初始化链表
{
list->next = list;
list->prev = list;
}
下面是list_head结构体的源代码
struct list_head
{
struct list_head *next, *prev; //声明链表结构
};
*/
/*
static struct kobj_type ktype_cdev_default = {
.release = cdev_default_release,
};
//在下面的赋值操作中使用了结构体ktype_cdev_default来对ktype赋值
上面这个赋值操作中使用的ktype_cdev_default结构体中存在函数
static void cdev_purge(struct cdev *cdev) //这个函数是为了清除对应操作的字符设备cdev。
415{
416 spin_lock(&cdev_lock);
417 while (!list_empty(&cdev->list)) {
418 struct inode *inode;
419 inode = container_of(cdev->list.next, struct inode, i_devices); //指针ptr指向结构体type中的成员member;
//通过指针ptr,返回结构体type的起始地址。
420 list_del_init(&inode->i_devices);
421 inode->i_cdev = NULL;
422 }
423 spin_unlock(&cdev_lock);
424}
425
//////////////当被创建时, 每个 kobject 被给定一套缺省属性.
////////////// 这些属性通过 kobj_type 结构来指定
此处使用ktype_cdev_default来清除缺省属性,使kobject的缺省属性为空,这样的话可以避免属性污染。
*/
/*
void kobject_init(struct kobject * kobj)
{
if (!kobj)
return;
kref_init(&kobj->kref);
INIT_LIST_HEAD(&kobj->entry);
kobj->kset = kset_get(kobj->kset);
}
*/
/*
memset(void* s, int c, size_t n)
{
int i;
char *ss = (char*)s;
for (i=0;i
}
*/
4 .动态申请一个cdev内存
下面cdev_alloc()函数用于动态申请一个cdev内存,其源代码如下:
struct cdev *cdev_alloc(void)
{
struct cdev *p=kmalloc(sizeof(struct cdev),GFP_KERNEL);//分配cdev的内存;给 kmalloc 的第一个参数是要分配的块的大小.
//第 2 个参数, 分配标志, 非常有趣, 因为它以几个方式控制 kmalloc 的行为.
if(p){
memset(p,0,sizeof(struct cdev));
p->kobj.ktype=&ktype_cdev_dynamic;
/*
static struct kobj_type ktype_cdev_dynamic = {
.release = cdev_dynamic_release,
//这里的函数使用的是动态操作来释放存储空间。
//这个函数同上面的函数cdev_default_release有着微弱的区别,前者使用的静态操作,后者使用的是动态操作。
};
/*
struct kobj_type {
109 void (*release)(struct kobject *);
110 struct sysfs_ops * sysfs_ops;
111 struct attribute ** default_attrs;
112};
*/
cdev_add()函数和cdev_del()函数分别向系统添加和删除一个cdev,完成字符设备的注册和注销。
对cdev_add()函数的调用通常发生在字符设备驱动模块加载函数中,而对cdev_del()函数的调用则通常发生在字符设备驱动模块卸载函数中。
下面是cdev_add()函数的定义:
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
458{
459 p->dev = dev;
460 p->count = count;
461 return kobj_map(cdev_map, dev, count, NULL, exact_match, exact_lock, p);//这个函数实现的是添加一个对应的cdev对象.
462}
下面死cdev_del()函数的定义:
476void cdev_del(struct cdev *p)
477{
478 cdev_unmap(p->dev, p->count); //这个函数主要实现的是删除一个cdev的功能
479 kobject_put(&p->kobj); //减少计数量
480}
5 . 分配和释放设备号
int register_chrdev_region(dev_t from,unsigned count,const char *name);
int alloc_chrdev_region(dev_t *dev,unsigned baseminor,unsigned count,const char *name);
register_chrdev_region用于设备号已知的情况,而alloc_chrdev_region用于设备号未知的情况。
int register_chrdev_region(dev_t from, unsigned count, const char *name) //此处的count为主设备号范围
198{
199 struct char_device_struct *cd; //定义char_device_struct变量
200 dev_t to = from + count; //由于设备号是递增顺序进行排序的,所以这里使用设备号加运算来进行设备号的计算
201 dev_t n, next; //声明两个变量在后面使用
202
203 for (n = from; n < to; n = next) { //此处使用循环来进行设备号的获取,因为这里的counter值为设备号范围,所以使用循环来进行设备号的获取
204 next = MKDEV(MAJOR(n)+1, 0); //一般情况下,次设备号设置为0,当然,也可以不为0
205 if (next > to) //
206 next = to;
207 cd = __register_chrdev_region(MAJOR(n), MINOR(n), //
208 next - n, name);
209 if (IS_ERR(cd))
210 goto fail;
211 }
212 return 0;
213 fail:
214 to = n;
215 for (n = from; n < to; n = next) {
216 next = MKDEV(MAJOR(n)+1, 0);
217 kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n));
218 }
219 return PTR_ERR(cd);
220}
//下面是对char_device_struct结构的解析:
/*
static struct char_device_struct {
struct char_device_struct *next; //指向散列冲突链表中下一个元素的指针
unsigned int major; //设备号范围内的主设备号
unsigned int baseminor; //设备号范围内的初始化的初始次设备号
int minorct; //设备号范围的大小
char name[64]; //处理设备号范围内的设备驱动程序的名称
struct file_operations *fops; //没有使用
struct cdev *cdev; //指向字符设备驱动程序描述符的指针
} *chrdevs[CHRDEV_MAJOR_HASH_SIZE]; //
上面的代码中调用函数__register_chrdev_region(),这个函数实现的功能为:
1、分配一个新的char_device_struct结构,并用0填充
2、如果设备号范围内的主设备号为0,那么设备驱动程序请求动态分配一个主设备号
3、初始化char_device_struct结构中的初始设备号,范围大小以及设备驱动程序名称
4、执行散列函数计算与主设备号对应的散列表索引
5、遍历冲突链表,为新的char_device_struct结构寻找正确的位置,同时,如果找到与请求的设备号范围重叠的一个范围,则返回一个错误码
6、将新的char_device_struct描述符插入冲突链表中
7、返回新的char_device_struct描述符的地址
*/
221
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
234 const char *name)
235{
236 struct char_device_struct *cd;
237 cd = __register_chrdev_region(0, baseminor, count, name);
238 if (IS_ERR(cd))
239 return PTR_ERR(cd);
240 *dev = MKDEV(cd->major, cd->baseminor);
241 return 0;
242}
相反地,在调用cdev_del()函数从系统注销字符设备之后,unregister_chrdev_region()应该被调用以释放原先申请的设备好,原型如下:
void unregister_chrdev_region(dev_t from,unsigned count);
阅读(2685) | 评论(1) | 转发(0) |