美文网首页我爱编程
Linux 字符设备驱动之一

Linux 字符设备驱动之一

作者: 守拙圆 | 来源:发表于2018-04-13 17:09 被阅读41次

    字符设备、字符设备驱动与用户空间访问该设备的程序三者之间的关系

    1 字符设备的初步认识

    1.1 与普通文件差异

    在 Linux 中一切皆文件,字符设备也是以文件的形式存在于系统中,下面来通过 ls -l 命令比较普通文件与字符文件差异:
    字符设备

    crw-rw-rw-   1 root    tty       5,   0 4月  13 08:13 tty
    crw--w----   1 root    tty       4,   0 4月  12 16:58 tty0
    crw--w----   1 root    tty       4,   1 4月  12 16:58 tty1
    

    普通文件

    -rw-r--r--  1 root root  3106 10月 23  2015 .bashrc
    drwx------  2 root root  4096 2月  16  2017 .cache/
    drwx------  3 root root  4096 3月  12  2017 .gnupg/
    

    两者最大的差异在日期前两列,字符设备前两列分别表示主设备号和次设备号。另外,字符设备不像普通文件,在硬盘上占有正在的数据区。

    1.2 与块设备的差异

    • 字符设备顺序读写、块设备随机读取。
    • 字符设备按字符逐个读写,而块设备按数据片(chunk)随机读取。
    • 字符设备直接读取、块设备上面还有一层文件系统。

    1.3 设备文件的创建命令

    mknod 用于创建设备文件。

    2 设备号

    设备号分为主设备号和次设备号,主设备号与驱动程序对应,一个不同类型的驱动对应一个独有的主设备号;此设备号与具体设备对应,同一类设备下,每个都对应一个独有的次设备号。
    -设备号类型
    dev_t是无符号长整型号,

    typedef u_long dev_t;
    typeddef unsigned long u_long;
    
    • 设备号计算
    (1)主设备号 = MAJOR(dev_t dev)
    (2)次设备号 = MINOR(dev_t dev)
    (3)设备编号 = MKDEV(int major,int minor)
    

    3 字符设备及其驱动函数

    3.1 chrdevs 散列表

    在 linux 内核中有一个记录所有已分配的字符设备编号,该散列表中每一个元素都是一个 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];
    

    内核并不是每一个字符设备定义一个 char_device_struct 结构体,而是为一组对应同一个字符设备驱动的设备编号范围定义一个(也就是说与主设备号对应)。chrdevs 散列表的大小是 255,散列算法是把每组字符设备编号范围的主设备号以 255 取模插入相应的散列桶中,同一个散列桶中的字符设备编号范围是按照次设备号递增排序的。

    3.2 驱动注册

    字符设备注册函数有以下三种原型:

    //动态分配一个主设备号
    extern int alloc_chrdev_region(dev_t *, unsigned, unsigned, const char *);
    //已有主设备号注册
    extern int register_chrdev_region(dev_t, unsigned, const char *);
    //注册主设备号的的次设备范围为 0 -250,早期接口,同时会注册一个设备
    static inline int register_chrdev(unsigned int major, const char *name,
                      const struct file_operations *fops)
    

    均调用此函数实现

    extern int __register_chrdev(unsigned int major, unsigned int baseminor,
                     unsigned int count, const char *name,
                     const struct file_operations *fops);
    

    其源码为:

    /*
     * Register a single major with a specified minor range.
     *
     * If major == 0 this functions will dynamically allocate a major and return
     * its number.
     *
     * If major > 0 this function will attempt to reserve the passed range of
     * minors and will return zero on success.
     *
     * Returns a -ve errno on failure.
     */
    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(&chrdevs_lock);
          //若major 为 0,则系统自动分配一个主设备号
        if (major == 0) {
            ret = find_dynamic_major();
            if (ret < 0) {
                pr_err("CHRDEV \"%s\" dynamic allocation region is full\n",
                       name);
                goto out;
            }
            major = ret;
        }
      //主设备号不能大于最大设备
        if (major >= CHRDEV_MAJOR_MAX) {
            pr_err("CHRDEV \"%s\" major requested (%d) is greater than the maximum (%d)\n",
                   name, major, CHRDEV_MAJOR_MAX);
            ret = -EINVAL;
            goto out;
        }
    
        cd->major = major;
        cd->baseminor = baseminor;
        cd->minorct = minorct;
        strlcpy(cd->name, name, sizeof(cd->name));
      //将major转换为散列表的 索引
        i = major_to_index(major);
    //查找该节点应该插入散列的位置,由于是hash表,故不同key(major)可能对应相同节点位置。
        for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next)
            if ((*cp)->major > major ||
                ((*cp)->major == major &&
                 (((*cp)->baseminor >= baseminor) ||
                  ((*cp)->baseminor + (*cp)->minorct > baseminor))))
                break;
         //若该major已经存在,则判断设备范围是否在已有节点范围内,若重合,则返回错误
        /* Check for overlapping minor ranges.  */
        if (*cp && (*cp)->major == major) {
            int old_min = (*cp)->baseminor;
            int old_max = (*cp)->baseminor + (*cp)->minorct - 1;
            int new_min = baseminor;
            int new_max = baseminor + minorct - 1;
    
            /* New driver overlaps from the left.  */
            if (new_max >= old_min && new_max <= old_max) {
                ret = -EBUSY;
                goto out;
            }
    
            /* New driver overlaps from the right.  */
            if (new_min <= old_max && new_min >= old_min) {
                ret = -EBUSY;
                goto out;
            }
        }
    //将新节点插入散列表中,返回该节点
        cd->next = *cp;
        *cp = cd;
        mutex_unlock(&chrdevs_lock);
        return cd;
    out:
        mutex_unlock(&chrdevs_lock);
        kfree(cd);
        return ERR_PTR(ret);
    }
    

    3.3 设备注册

    内核中每个字符设备都对应一个 cdev 结构的变量,下面是它的定义:

    <include/linux/cdev.h>  
      
    struct cdev {   
        struct kobject kobj;                  //内嵌的内核对象.  
        struct module *owner;                 //该字符设备所在的内核模块的对象指针.  
        const struct file_operations *ops;    //该结构描述了字符设备所能实现的方法,是极为关键的一个结构体.  
        struct list_head list;                //用来将已经向内核注册的所有字符设备形成链表.  
        dev_t dev;                            //字符设备的设备号,由主设备号和次设备号构成.  
        unsigned int count;                   //隶属于同一主设备号的次设备号的个数.  
    }; 
    

    设备初始化:

    void cdev_init(struct cdev *cdev, const struct file_operations *fops)  
    {  
        memset(cdev, 0, sizeof *cdev);  
        INIT_LIST_HEAD(&cdev->list);  
        kobject_init(&cdev->kobj, &ktype_cdev_default);  
        cdev->ops = fops;  
    }  
    

    向内核注册一个设备

    int cdev_add(struct cdev *p, dev_t dev, unsigned count)
    {
        int error;
    
        p->dev = dev;
        p->count = count;
    
        error = kobj_map(cdev_map, dev, count, NULL,
                 exact_match, exact_lock, p);
        if (error)
            return error;
    
        kobject_get(p->kobj.parent);
    
        return 0;
    }
    

    相关文章

      网友评论

        本文标题:Linux 字符设备驱动之一

        本文链接:https://www.haomeiwen.com/subject/uqbqkftx.html