Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1014675
  • 博文数量: 123
  • 博客积分: 5051
  • 博客等级: 大校
  • 技术积分: 1356
  • 用 户 组: 普通用户
  • 注册时间: 2008-07-14 10:56
文章分类
文章存档

2012年(1)

2011年(21)

2010年(13)

2009年(55)

2008年(33)

分类: LINUX

2008-07-28 21:07:51

字符设备驱动
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);
 
阅读(2643) | 评论(1) | 转发(0) |
给主人留下些什么吧!~~

chinaunix网友2008-07-29 10:12:26

int cdev_add(struct cdev *,dev_t,unsigned);//增加计数 这样的说法不太准确吧?