Linux驱动—深入字符设备驱动详解

之前对于字符设备驱动只是停留在会用,知道步骤,怎么去改的层面上。

最近开始更为细致的重看,做一些总结,主要自己再梳理一遍。

看懂本篇需要一定的基础,很多都跟rtos内核相似。其次,先会用会改字符设备驱动之后,之前的文章里有相关字符驱动的编写,然后可以看这篇分析。

设备树下的字符设备驱动开发-led


在应用层,当我们用open对一个设备节点打开,发生了什么呢?

 过程大致如下:

  • 在虚拟文件系统VFS中查找对应与字符设备对应struct inode节点
  • 遍历散列表cdev_map,根据inod节点中的cdev_t设备号找到cdev对象
  • 创建struct file对象
  • 初始化struct file对象,将struct file对象中的file_operations成员指向struct cdev对象中的file_operation成员
  • 回调open函数

这样说,可能有点抽象,但可以先大致看一下。

下面,针对上图具体分析一下。

我们在使用open函数,发生系统调用进入内核,也就是对应的sys_open函数,sys_open函数又会调用do_sys_open函数。

do_sys_open函数首先调用get_unused_fd_flags来获取一个未被使用的文件描述符,同时这个描述符也是我们在应用层open得到的值。

具体看下图。

得到文件描述符之后,紧接着会调用do_filp_open函数,得到一个新的file结构体,同时该描述符是指向该结构体的。

来具体看一下do_dentry_open这个函数。

static int do_dentry_open(struct file *f,struct inode *inode,int␣
,(*open)(struct inode *, struct file *),const struct cred *cred)
{
   ……
   f->f_op = fops_get(inode->i_fop);//通过设备节点获取该成员变量,是最关键的一步
   ……
   if (!open)
   open = f->f_op->open;
   if (open) {
   error = open(inode, f);
   if (error)
   goto cleanup_all;
   }
 ……
}

这样要说明一点的是, inode上面的file_operation并不是自己构造的file_operation,而是字符设备通用的def_chr_fops,所以自己的构建的是在应用程序调用open之后,才会绑定在文件上。这样其实就解释了之前我们写open函数的时候、只是返回了一个0,但是文件也能正常打开是为什么了。

而inode->i_fop就是指向了def_chr_fops。

具体来看一下这个函数。

const struct file_operations def_chr_fops = {
 .open = chrdev_open,
 .llseek = noop_llseek,
};

同样也是一个file_operations结构体。

具体看chrdev_open干了什么。

static int chrdev_open(struct inode *inode, struct file *filp)
{
 const struct file_operations *fops;
 struct cdev *p;
 struct cdev *new = NULL;
 int ret = 0;
 spin_lock(&cdev_lock);
 p = inode->i_cdev;//1.获取结构体cdev是否为空
 if (!p) {
    struct kobject *kobj;
    int idx;
    spin_unlock(&cdev_lock);

    //2.由设备号来查找cdev结构体的kobj成员
    kobj = kobj_lookup(cdev_map, inode->i_rdev, &idx);
    if (!kobj)
      return -ENXIO;

    //3.通过container_of得到cdev
    new = container_of(kobj, struct cdev, kobj);
    spin_lock(&cdev_lock);
 /* Check i_cdev again in case somebody beat us to it whilewe dropped␣,the lock.*/
   p = inode->i_cdev;
   if (!p) {
      inode->i_cdev = p = new;//4.将得到的cdev给设备节点
      list_add(&inode->i_devices, &p->list);
      new = NULL;
   } else if (!cdev_get(p))
      ret = -ENXIO;
 } else if (!cdev_get(p))
    ret = -ENXIO;
 spin_unlock(&cdev_lock);
 cdev_put(new);
 if (ret)
    return ret;

 ret = -ENXIO;
 fops = fops_get(p->ops);
 if (!fops)
 goto out_cdev_put;

 replace_fops(filp, fops);//5.代替原结构体中的fops
 if (filp->f_op->open) {
    ret = filp->f_op->open(inode, filp);//6.调用自己构建的open
    if (ret)
    goto out_cdev_put;
 }

 return 0;

 out_cdev_put:
 cdev_put(p);
 return ret;
 }

见代码中的注释,如此,对于open究竟干了什么就十分清楚了。

ok,既然了解了这些,那么下面就对整个字符设备驱动的创建过程进行一个梳理。


在linux中,我们使用设备编号来表示设备,主设备号区分设备类别,次设备号标识具体的设备。cdev结构体被内核用来记录设备号。

使用设备的时候,我们通常会打开设备节点,通过设备节点的inode结构体、file结构体最终找到file_operations结构体。

在内核中,dev_t用来表示设备编号,是一个32位的数,其中高12位表示主设备号,低20位表示次设备号。

重点看的是cdev结构体。

内核通过一个哈希表来记录设备编号。

哈希表由数组和链表组成,吸收数组查找快,链表增删效率高,容易拓展等优点。

其实这么一看,是不是觉得有点眼熟!

在freertos里面,任务列表中也是这样的,一个优先级就是一个链表。

这里就是,一个主设备号就是一个链表,通过次设备号依次增加。

很好理解,这里就不解释了。

有了cdev结构体之后,就可以创建设备节点了。

设备节点在/dev下。

file结构体

内核中用file结构体表示每一个打开的文件,每打开一个文件,内核就会创建一个结构体,并且将对该文件的操作函数传递给该结构体的成员变量f_op。

在上面分析的时候,其实有提及。

VFS inode

它是linux管理文件系统的最基本单位。

inode结构体在内核内部表示一个文件,它与表示已经打开的文件描述符的结构体(file)是不同的。我们可以使用多个file文件结构表示同一个文件的多个文件描述符,但是所有的file文件结构全部必须只能指向一个inode结构体

它主要包括两个:

  • dev_t i_rdev:设备文件的节点,包含了设备号
  • struct cdev *i_cdev

通常使用设备号来定位cdev。

字符设备驱动框架

  1.  首先要有设备号,通常用动态分配
  2. 然后实现file_operation并保存到cdev中,实现cdev的初始化
  3. 使用cdev_add()注册cdev到内核中,告诉内核我们的工作
  4. 为了调用file_operations,我们还需要创建设备节点

这样梳理下来,非常清晰,每一步都是紧紧关联的。

有问题欢迎私信留言。


参考野火linux驱动资料

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值