字符设备驱动模块

来源:互联网 发布:淘宝手机贷款 编辑:程序博客网 时间:2024/06/05 20:22
项目要做的是HPI接口的驱动模块,是属于字符设备驱动模块。

当然,首先需要有对字符设备驱动的基础知识有所了解。

设备驱动分为三类:字符设备驱动、块设备驱动、网络设备驱动。

字符设备:是指那些必须以串行顺序依次访问的设备,如触摸屏、磁带驱动器、鼠标等。

块设备:可以用任意顺序访问,以块为单位进行操作,如硬盘、软驱等。不同于字符设备的是,块设备经过系统的快速缓冲。

网络设备:网络设备面向数据包的接受和发送而设计,它并不对应于文件系统的节点。因而内核和网络设备的通信与内核和字符设备、块设备的通信方式完全不同。

 

我们需要具体了解字符设备驱动的结构,在Linux2.6内核中,使用cdev结构体来描述一个字符设备。

1、cdev结构体

1 struct cdev {2     struct kobject     kobj;                    //内嵌的kobject对象3     struct module     *owner;                //所属模块4     struct file_operations     *ops;        //文件操作结构体5     struct list_head    list;                    6     dev_t     dev;                                //设备号7     unsigned int    count;                    8 };  

其中cdev结构体的dev_t成员定义了设备号,为32位,其中主设备号12位,次设备号20位。使用下列宏可以从dev_t获得主设备号和次设备号:

MAJOR(dev_t dev)MINOR(dev_t dev)

而使用下列宏可以通过主设备号和次设备号生成dev_t:

MKDEV(int major , int minor)

2、Linux2.6内核提供了一组函数用于操作cdev结构体:

void cdev_init(struct cdev * , struct file_operations *);struct cdev *cdev_alloc(void);void cdev_put(struct cdev *p);int cdev_add(struct cdev * , dev_t unsigned);void cdev_del(struct cdev *);

cdev_init()函数用于初始化cdev的成员,并建立cdev和file_operations之间的连接,file_operations定义了字符设备驱动提供给虚拟文件系统的接口函数。

1 void cdev_init(struct cdev *cdev , struct file_operations *fops)2 {3     memset(cdev, 0, sizeof *cdev);4     INIT_LIST_HEAD(&cdev->list);5     kobject_init(&cdev->kobj, &ktype_cdev_default);6     cdev->ops = fops;   //将传入的文件操作结构体指针赋值给cdev的ops7 }

cdev_alloc()函数用于动态申请一个cdev内存。

1 struct cdev *cdev_alloc(void)2 {3     struct cdev *p = kzalloc(sizeof(struct cdev), GFP_KERNEL);4     if(p){5         INIT_LIST_HEAD(&p->list);6         kobject_init(&p->kobj, &ktype_cdev_dynamic);7     }8     return p;9 }

cdev_add()函数可以向系统添加一个cdev。

1 int cdev_add(struct cdev *p, dev_t dev, unsigned count)2 {3     p->dev = dev;4     p->count = count;5     return kobj_map(cdev_map, dev, count, NULL, exact_match,    exact_lock, p);6 }

cdev_del()函数可以向系统删除一个cdev。

1 void cdev_del(struct cdev *p)2 {3     cdev_unmap(p->dev, p->count);4     kobject_put(&p->kobj);5 }

3、分配和释放设备号

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,用于已知起始设备的设备号的情况。如果分配成功进行,register_chrdev_region 的返回值是 0;出错的情况下, 返回一个负的错误码,你不能存取请求的区域。

其中:

from是要分配设备编号范围的起始值。

count是所请求连续设备编号的个数。不能太大。

name是设备名,(要与该设备编号范围关联)。

alloc_chrdev_region,用于未知设备号,向系统动态申请未被占用的设备号的情况。与register_chrdev_region相比,它的优点是避开了设备号的冲突

其中:

*dev是一个输出参数,当设备成功完成调用后,它将保存已分配范围的第一个编号。

baseminor是要请求的第一个次设备号。一般为0。

count同样是所请求连续设备编号的次数。

*name同样是相应和设备名。

void unregister_chrdev_region(dev_t from, unsigned count);

注销字符设备,用于释放原先申请的设备号。

4、字符设备驱动模块的加载和卸载函数模版

//设备结构体struct xxx_dev_t {    struct cdev *cdev;    ...} xxx_dev;//设备驱动模块加载函数static int __init xxx_init(void){    ...    cdev_init(&xxx_dev.cdev, &xxx_fops);   //初始化cdev    xxx_dev.cdev.owner = THIS_MODULE;    //获取字符设备号    if(xxx_major)    {        register_chrdev_region(xxx_dev_no, 1, DEV_NAME);    }else{        alloc_chrdev_region(&xxx_dev_no, 1, DEV_NAME);    }    ret = cdev_add(&xxx_dev.cdev, xxx_dev_no, 1);   //注册设备    ...}//设备驱动模块卸载函数static void __exit xxx_exit(void){    unregister_chrdev_region(xxx_dev_no, 1);   //释放占用的设备号    cdev_del(&xxx_dev.cdev);   //注销设备}

 

这里也需要了解一下,关于file_operations结构体及一些常用的函数。

1、file_operations结构体

具体解释,此处就不细写了。可以看《Linux设备驱动开发详解》、也可以移步到file_operations, file 和inode、file_operations结构体介绍、file_operations结构体详细分析

如对.open,.release这样用法有困惑的,请移步解密file_operations结构体中的一些细节

2、字符设备驱动模块常用的一些函数:open、release(close)、read、write、ioctl等等,需要掌握。

linux3.0 已经移除了ioctl函数,而用了两个函数来代替,具体可以度娘。

 

最后,还要提一下字符设备驱动一些要注意的点:设备名和设备号、信号量的同步与互斥、中断与异常、阻塞等

 

原创粉丝点击