Linux 字符设备驱动之一

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

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;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值