DIY编程器网

 找回密码
 注册

QQ登录

只需一步,快速开始

扫一扫,访问微社区

查看: 789|回复: 0
打印 上一主题 下一主题

[待整理] 字符设备驱动模型浅析

[复制链接]
跳转到指定楼层
楼主
发表于 2014-10-10 07:17:03 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
在linux系统中,很多驱动是字符型驱动,有些是直接编译集成在内核中,另一些是单独编译成“。ko”动态加载的。其实字符驱动只是个外壳,用于内核与应用程序间通信,无非是调用open,release,read,write和ioctl等例程。所以根据应用不同,字符驱动能会调用其他驱动模块,如i2c、spi和v4l2等,于是字符驱动还可分WDT驱动、RTC驱动和MTD驱动等。所以在分析其他驱动模块之前有必要好好分析下字符设备驱动模型。本篇文章要讲的就是字符设备驱动模型,也就是字符设备驱动是怎么注册和注销的,怎么生成设备节点的,怎么和应用程序关联的,例程调用具体如何实现的等等。
       
        一、字符设备驱动的注册和注销
       
        对于写过linux-2.6内核(本文采用linux-2.6.18内核)字符驱动的程序员来说,对下面这段程序的形式肯定不陌生。int result;
       
        /*
       
        * Register the driver in the kernel
       
        * Dynmically get the major number for the driver using
       
        * alloc_chrdev_region function
       
        */
       
        result = alloc_chrdev_region( 0, 1,“testchar”);
       
        /* if it fails return error */
       
        if (result dev = 1;
       
        base->range = ~0; /*初始的范围很大*/
       
        base->get = base_probe; /*保存函数指针*/
       
        for (i = 0; i probes = base; /*所有指针都指向同一个base */
       
        p->lock = lock;
       
        return p;
       
        }.
       
        复制代码该函数只是分配了一个结构体struct kobj_map,并做了初始化,保存了函数指针base_probe和全局锁lock。
       
        下面就按照驱动注册流程一个个解析这些例程调用吧。首先是alloc_chrdev_region()函数,解析它之前,先看看结构体(定义了255个结构体指针),static struct char_device_struct {
       
        /*被255整除后相同的设备号链成一个单向链表*/
       
        struct char_device_struct *next;
       
        unsigned int major; /*主设备号*/
       
        unsigned int baseminor; /*次设备起始号*/
       
        int minorct; /*次设备号范围*/
       
        char name[64]; /*驱动的名字*/
       
        struct file_operations *fops; /*保存文件操作指针,目前没有使用*/
       
        struct cdev *cdev; /* will die */ /*目前没有使用*/
       
        } *chrdevs[CHRDEV_MAJOR_HASH_SIZE]; /* CHRDEV_MAJOR_HASH_SIZE = 255 */
       
        复制代码它的作用仅仅是用于注册字符设备驱动,保存已经注册字符驱动的一些信息,如主次设备号,次设备号的数量,驱动的名字等,便于字符设备驱动注册时索引查找。
         
        alloc_chrdev_region()函数很简单,通过调用__register_chrdev_region()来实现,通过英语注释你也可以明白,这个函数有两个作用,一是,如果主设备号为0,则分配一个最近的主设备号,返回给调用者;二是,如果主设备号不为0,则占用好该主设备号对应的位置,返回给调用者。如下,static struct char_device_struct *
       
        __register_chrdev_region(unsigned int major, unsigned int baseminor,
       
        int minorct, const char *name)
       
        {
       
        struct char_device_struct *cd, **cp;
       
        int ret = 0;
       
        int i;
       
        cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL);
       
        if (cd == NULL)
       
        return ERR_PTR(-ENOMEM);
       
        mutex_lock( /*这下看到了吧,加锁,就允许你一个人进来*/
       
        /* temporary */
       
        if (major == 0) { /*如果主设备号为零,则找一个最近空闲的号码分配*/
       
        for (i = ARRAY_SIZE(chrdevs)-1; i > 0; i——) {
       
        if (chrdevs == NULL)
       
        break;
       
        }
       
        if (i == 0) {
       
        ret = -EBUSY;
       
        goto out;
       
        }
       
        major = i;
       
        ret = major;
       
        }
       
        /*这些不用说你懂的*/
       
        cd->major = major;
       
        cd->baseminor = baseminor;
       
        cd->minorct = minorct;
       
        strncpy(cd->name,name, 64);
       
        i = major_to_index(major);
       
        /*如果主设备号不为0,则占用好该主设备号对应的位置*/
       
        for (cp = *cp; cp =
       
        if ((*cp)->major > major ||
       
        ((*cp)->major == major (*cp)->baseminor >= baseminor))
       
        break;
       
        if (*cp (*cp)->major == major
       
        (*cp)->baseminor next = *cp;
       
        *cp = cd;
       
        mutex_unlock( /*开锁,队列里的下一个人可以进来了*/
       
        return cd;
       
        out:
       
        mutex_unlock(
       
        kfree(cd);
       
        return ERR_PTR(ret);
       
        }
       
        复制代码接着是cdev_init()函数,先说说cdev的结构体,struct cdev {
       
        struct kobject kobj; /*不多解释了,看看鄙人前面写的文章吧*/
       
        struct module *owner; /*模块锁定和加载时用得着*/
       
        const struct file_operations *ops; /*保存文件操作例程结构体*/
       
        struct list_head list; /* open时,会将其inode加到该链表中,方便判别是否空闲*/
       
        dev_t dev; /*设备号*/
       
        unsigned int count;
       
        };
       
        复制代码cdev结构体把字符设备驱动和文件系统相关联,后面解析字符设备驱动怎样运行的时候会详谈。
       
        cdev_init()函数如下,void cdev_init(struct cdev *cdev, const struct file_operations *fops)
       
        {
       
        memset(cdev, 0, sizeof *cdev);
       
        INIT_LIST_HEAD(
       
        cdev->kobj.ktype = /*卸载驱动时会用到,别急,后面详讲*/
       
        kobject_init(
       
        cdev->ops = fops; /*用户写的字符设备驱动fops就保存在这了*/
       
        }.
       
        复制代码你也看到了,该函数就是对变量做了初始化,关于kobject的解析,建议你看看鄙人博客上写的《Linux设备模型浅析之设备篇》和《Linux设备模型浅析之驱动篇》两篇文章,这里就不详谈了。
       
        用户的fops,在本文中是test_fops,一般形式是这样的,
       
        static const struct file_operations test_fops = {
       
        。owner = THIS_MODULE,
       
        。open = test_fops_open,
       
        。release = test_fops_release,
       
        。ioctl = test_fops_ioctl,
       
        。read = test_fops_read,
       
        。write = test_fops_write,
       
        };
       
        复制代码
       
        接着又调用了函数cdev_add(),这个函数又调用了kobj_map()函数,其作用就是分配一个struct probe结构体,填充该结构体中的变量并将其加入到全局的cdev_map中,说白了,就是分个一亩三分田给该字符设备驱动,并做好标记,放到主设备号对应的地方,等主人下次来找的时候能找到(使用kobj_lookup()函数,后面会讲到)。该函数是这样的,int kobj_map(struct kobj_map *domain, dev_t dev, unsigned long range,
       
        struct module *module, kobj_probe_t *probe,
       
        int (*lock)(dev_t, void *), void *data)
       
        {
       
        unsigned n = MAJOR(dev + range - 1) - MAJOR(dev) + 1;
       
        unsigned index = MAJOR(dev);
       
        unsigned i;
       
        struct probe *p;
       
        if (n > 255)
       
        n = 255;
       
        /*分配了一亩三分田*/
       
        p = kmalloc(sizeof(struct probe) * n, GFP_KERNEL);
       
        if (p == NULL)
       
        return -ENOMEM;
       
        /*填充些私有的东西*/
       
        for (i = 0; i owner = module;
       
        p->get = probe; /*是exact_match ()函数,获取cdev结构体的kobject指针*/
       
        p->lock = lock; /*是exact_lock()函数,增加引用*/
       
        p->dev = dev;
       
        p->range = range;
       
        p->data = data; /* cdev保存到p->data中*/
       
        }
       
        mutex_lock(domain->lock);
       
        /*将这一亩三分田加到主设备号对应的位置上去*/
       
        for (i = 0, p -= n; i probes[index % 255];
       
        while (*s (*s)->range next;
       
        p->next = *s;
       
        *s = p;
       
        }
       
        mutex_unlock(domain->lock);
       
        return 0;
       
        }
       
        复制代码接下来有class_create()函数和class_device_create()函数,前者生成一个名字为"testchar"的class,后者作用就是在/dev目录下生成设备节点,当然,需要uevent和UDEVD的支持,具体可见鄙人博客上的文章《Linux设备模型浅析之uevent篇》。
       
        顺带说下register_chrdev()函数,其也是注册字符设备驱动,只不过是封装好的,包含了所有前面讲的注册步骤——分配一个设备号,由一个主设备号和255个次设备号组成。如下,int register_chrdev(unsigned int major, const char *name,
       
        const struct file_operations *fops)
       
        {
       
        struct char_device_struct *cd;
       
        struct cdev *cdev;
       
        char *s;
       
        int err = -ENOMEM;
       
        cd = __register_chrdev_region(major, 0, 256, name);
       
        if (IS_ERR(cd))
       
        return PTR_ERR(cd);
       
        cdev = cdev_alloc(); /*这个有点不一样,动态分配的,不是调用者提供*/
       
        if (!cdev)
       
        goto out2;
       
        cdev->owner = fops->owner;
       
        cdev->ops = fops;
       
        kobject_set_name( "%s", name);
       
        for (s = strchr(kobject_name( s; s = strchr(s, '/'))
       
        *s = '!';
       
        err = cdev_add(cdev, MKDEV(cd->major, 0), 256);
       
        if (err)
       
        goto out;
       
        cd->cdev = cdev;
       
        return major ? 0 : cd->major;
       
        out:
       
        kobject_put(
       
        out2:
       
        kfree(__unregister_chrdev_region(cd->major, 0, 256));
       
        return err;
       
        }
分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友 微信微信
收藏收藏 分享分享 支持支持 反对反对
您需要登录后才可以回帖 登录 | 注册

本版积分规则

小黑屋|文字版|手机版|DIY编程器网 ( 桂ICP备14005565号-1 )

GMT+8, 2024-5-10 12:00 , 耗时 0.083819 秒, 18 个查询请求 , Gzip 开启.

各位嘉宾言论仅代表个人观点,非属DIY编程器网立场。

桂公网安备 45031202000115号

DIY编程器群(超员):41210778 DIY编程器

DIY编程器群1(满员):3044634 DIY编程器1

diy编程器群2:551025008 diy编程器群2

QQ:28000622;Email:libyoufer@sina.com

本站由桂林市临桂区技兴电子商务经营部独家赞助。旨在技术交流,请自觉遵守国家法律法规,一旦发现将做封号删号处理。

快速回复 返回顶部 返回列表