我在Linux字符设备驱动框架一文中已经简单的介绍了字符设备驱动的基本的编程框架,这里我们来探讨一下Linux内核(以4.8.5内核为例)是怎么管理字符设备的,即当我们获得了设备号,分配了cdev结构,注册了驱动的操作方法集,最后进行cdev_add()的时候,究竟是将哪些内容告诉了内核,内核又是怎么管理我的cdev结构的,这就是本文要讨论的内容。我们知道,Linux内核对设备的管理是基于kobject的(参见Linux设备管理(一)_kobject_kset_kobj_type),这点从我们的cdev结构中就可以看出,所以,接下来,你将看到"fs/char_dev.c"中实现的操作字符设备的函数都是基于"lib/kobject.c"以及"drivers/base/map.c"中对kobject操作的函数。好,现在我们从cdev_add()开始一层层的扒。

cdev_map对象

//fs/char_dev.c

27 static struct kobj_map *cdev_map;

内核中关于字符设备的操作函数的实现放在"fs/char_dev.c"中,打开这个文件,首先注意到就是这个在内核中不常见的静态全局变量cdev_map(27),我们知道,为了提高软件的内聚性,Linux内核在设计的时候尽量避免使用全局变量作为函数间数据传递的方式,而建议多使用形参列表,而这个结构体变量在这个文件中到处被使用,所以它应该是描述了系统中所有字符设备的某种信息,带着这样的想法,我们可以在"drivers/base/map.c"中找到kobj_map结构的定义:

//drivers/base/map.c

19 struct kobj_map {

20 struct probe {

21 struct probe *next;

22 dev_t dev;

23 unsigned long range;

24 struct module *owner;

25 kobj_probe_t *get;

26 int (*lock)(dev_t, void *);

27 void *data;

28 } *probes[255];

29 struct mutex *lock;

30 };

从中可以看出,kobj_map的核心就是一个struct probe指针类型、大小为255的数组,而在这个probe结构中,第一个成员next(21)显然是将这些probe结构通过链表的形式连接起来,dev_t类型的成员dev显然是设备号,get(25)和lock(26)分别是两个函数接口,最后的重点来了,void*作为C语言中的万金油类型,在这里就是我们cdev结构(通过后面的分析可以看出),所以,这个cdev_map是一个struct kobj_map类型的指针,其中包含着一个struct probe指针类型、大小为255的数组,数组的每个元素指向的一个probe结构封装了一个设备号和相应的设备对象(这里就是cdev),可见,这个cdev_map封装了系统中的所有的cdev结构和对应的设备号,最多为255个字符设备。

cdev_add

了解了cdev_map的功能,我们就可以一探cdev_add()

//fs/char_dev.c

456 int cdev_add(struct cdev *p, dev_t dev, unsigned count)

457{

458 int error;

459

460 p->dev = dev;

461 p->count = count;

462

463 error = kobj_map(cdev_map, dev, count, NULL,

464 exact_match, exact_lock, p);

465 if (error)

466 return error;

467

468 kobject_get(p->kobj.parent);

469

470 return 0;

471 }

函数很短,(460-461)就是将我们之前获得设备号和设备号长度填充到cdev结构中,kobject_get()(468)也没做什么事:

//lib/kobject.c

591 struct kobject *kobject_get(struct kobject *kobj)

592{

593 if (kobj) {

...

598 kref_get(&kobj->kref);

599 }

600 return kobj;

601 }

所以,核心工作显然是交给了kobj_map()

kobj_map()

这个函数在内核的设备管理中占有重要的地位,这里我们只从字符设备的角度分析它的功能,先上实现

//drivers/base/map.c

32 int kobj_map(struct kobj_map *domain, dev_t dev, unsigned long range,

33 struct module *module, kobj_probe_t *probe,

34 int (*lock)(dev_t, void *), void *data)

35{

36 unsigned n = MAJOR(dev + range - 1) - MAJOR(dev) + 1;

37 unsigned index = MAJOR(dev);

38 unsigned i;

39 struct probe *p;

...

44 p = kmalloc_array(n, sizeof(struct probe), GFP_KERNEL);

...

48 for (i = 0; i < n; i++, p++) {

49 p->owner = module;

50 p->get = probe;

51 p->lock = lock;

52 p->dev = dev;

53 p->range = range;

54 p->data = data;

55 }

56 mutex_lock(domain->lock);

57 for (i = 0, p -= n; i < n; i++, p++, index++) {

58 struct probe **s = &domain->probes[index % 255];

59 while (*s && (*s)->range < range)

60 s = &(*s)->next;

61 p->next = *s;

62 *s = p;

63 }

64 mutex_unlock(domain->lock);

65 return 0;

66 }

这个函数的设计也很单纯,就是封装好一个probe结构并将它的地址放入probes数组进而封装进cdev_map,(48-55)j就是根据传入的设备号的个数,将设备号和cdev依次封装到kmalloc_array分配的n个probe结构中,(57-63)就是遍历probs数组,直到找到一个值为NULL的元素,再将probe的地址存入probes。至此,我们就将我们的cdev放入的内核的数据结构,当然,cdev中大量属性都是由内核帮我们填充的。

chrdev_open()

将设备放入的内核,我们再来看看内核是怎么找到一个特定的cdev的,对一个字符设备的访问流程大概是:文件路径=>inode=>chrdev_open=>cdev->fops->my_chr_open。所以只要通过VFS找到了inode,就可以找到chrdev_open(),这里我们就来关注一个chrdev_open()是怎么从内核的数据结构中找到我们的cdev并回调里满的my_chr_open()的。首先,chrdev_open()尝试将inode->i_cdev(一个cdev结构指针)保存在局部变量p中(359),如果p为空,即inode->i_cdev为空(360),我们就根据inode->i_rdev(设备号)通过kobj_lookup搜索cdev_map,并返回与之对应kobj(364),由于kobject是cdev的父类,我们根据container_of很容易找到相应的cdev结构并将其保存在inode->i_cdev中(367),找到了cdev,我们就可以将inode->devices挂接到inode->i_cdev的管理链表中,这样下次就不用重新搜索,直接cdev_get()即可(378)。找到了我们的cdev结构,我们就可以将其中的操作方法集inode->i_cdev->ops传递给filp->f_ops(386-390),这样,我们就可以回调我们的设备打开函数my_chr_open()(392);

//fs/char_dev.c

326 static struct kobject *cdev_get(struct cdev *p)

327{

328 struct module *owner = p->owner;

329 struct kobject *kobj;

330

331 if (owner && !try_module_get(owner))

332 return NULL;

333 kobj = kobject_get(&p->kobj);

...

336 return kobj;

337 }

351 static int chrdev_open(struct inode *inode, struct file *filp)

352{

353 const struct file_operations *fops;

354 struct cdev *p;

355 struct cdev *new = NULL;

356 int ret = 0;

...

359 p = inode->i_cdev;

360 if (!p) {

361 struct kobject *kobj;

362 int idx;

...

364 kobj = kobj_lookup(cdev_map, inode->i_rdev, &idx);

...

367 new = container_of(kobj, struct cdev, kobj);

369 /* Check i_cdev again in case somebody beat us to it while

370 we dropped the lock. */

371 p = inode->i_cdev;

372 if (!p) {

373 inode->i_cdev = p = new;

374 list_add(&inode->i_devices, &p->list);

375 new = NULL;

376 } else if (!cdev_get(p))

377 ret = -ENXIO;

378 } else if (!cdev_get(p))

379 ret = -ENXIO;

...

386 fops = fops_get(p->ops);

...

390 replace_fops(filp, fops);

391 if (filp->f_op->open) {

392 ret = filp->f_op->open(inode, filp);

...

395 }

396

397 return 0;

398

399 out_cdev_put:

400 cdev_put(p);

401 return ret;

402 }

cdev linux_Linux设备管理(二)_从cdev_add说起相关推荐

  1. QIIME2进阶二_元数据及数据导入QIIME2

    本节主要讲解如何将元数据与数据导入生物信息分析软件QIIME2,实现数据导入与检查. 本实战教程将使用来自人源化(humanized)小鼠的一组粪便样品,展示16S rRNA基因扩增子数据的" ...

  2. 2.4G无线麦克风领夹麦一拖二_全双工_杰理JL6976M单芯片方案

    目录 一.简介 二.详细说明 2.1 目前杰理的无线麦方案 2.2 主控芯片脚位图 2.3 系统框架图 2.4 方案参数低延时无线麦功能支持以下三种组合配置:(1)一发一收a)    无线麦(单收发) ...

  3. sqlserver 2005进程未能连接到分发服务器dis,SQLSERVER2005数据库镜像_二_

    SQLSERVER2005数据库镜像_二_ CIO C l u C I O Clubb 见图2. >当safety设置为FULL, 如果 主服务器无法和其他服务器组成 quorum, 它将不能提 ...

  4. struct cdev结构体和cdev_init和cdev_add函数

    在Linux内核2.6版本以前,注册一个字符设备的的经典方法是使用register_chrdev,相应的从设备中注销字符设备的方法是unregister_chrdev,而在2.6版本以后,字符设备函数 ...

  5. python基础笔记二_面向对象

    面向对象 Object Oriented 概述 面向过程 1.分析出解决问题的步骤,然后逐步实现.例如:婚礼筹办-- 发请柬(选照片.措词.制作)-- 宴席(场地.找厨师.准备桌椅餐具.计划菜品.购买 ...

  6. 大学计算机实验报五告,大学计算机实验报告二_相关文章专题_写写帮文库

    时间:2019-05-12 01:07:32 作者:admin 大学计算机实验报告一 长文档的组织.编辑与排版 姓名: 学号: 专业: 年级: 指导教师: 年月日1 实验目的 学会长文档的组织.编辑. ...

  7. BZOJ_2194_快速傅立叶之二_(FFT+卷积)

    描述 http://www.lydsy.com/JudgeOnline/problem.php?id=2194 给出序列\(a[0],a[1],...,a[n-1]\)和\(b[0],b[1],... ...

  8. 网络对抗技术_实验二_网络嗅探与欺骗

    中国人民公安大学 Chinese people' public security university 网络对抗技术 实验报告   实验二 网络嗅探与欺骗     学生姓名 岳庆伟 年级 2014级 ...

  9. Docker学习与和应用(二)_使用Docker

    在前一篇文章 Docker学习与和应用(一)_初步认识中,我们初步介绍了Docker解决了什么问题,Docker容器化技术与传统的虚拟化方式的区别,以及简要介绍了Docker的几大核心概念:镜像.容器 ...

最新文章

  1. 线段树 ---- H. AND = OR (或和与的性质之1的个数 + 线段树)
  2. ipmi重启_重启ipmi服务器
  3. Halcon算子学习:sample_object_model_3d
  4. 12-容器之间link
  5. 学生信息管理系统c语言课设,学生信息管理系统C语言课设.doc
  6. 丝蛋白行业调研报告 - 市场现状分析与发展前景预测
  7. Kubernetes详解(二十七)——Deployment控制器回滚
  8. Steinberg Cubase Elements 11 for Mac(音频处理软件)
  9. 阿里云播放器的官方文档
  10. easyui框架搭建
  11. 关于画法几何和机械制图有感
  12. SQL不同类型分组排序
  13. Spring Cloud与Dubbo怎么选择?
  14. Windows10动态壁纸Wallpaper软件
  15. 华三交换机升级的ipe文件_H3C 交换机升级说明
  16. 系统运维工程师面试的11大热门问题
  17. expr4j 表达式计算
  18. 《第十一堂棒球课》:MLB棒球创造营·棒球名人堂
  19. 【Netty】Netty零拷贝原理
  20. 给自己的优盘做个保护,再也不怕病毒了!

热门文章

  1. 前端怎么使用jsessionid_成都Web前端是干什么的?新手怎么入门
  2. python算法详解张玲玲电子版_算法之路该如何学习?
  3. php的array跟go的array,实现类似php的array_column方法
  4. java 数组group by_java Group by分组算法
  5. docker安装配置分布式elasticsearch、kibana、head、cerebro
  6. Python中最常用的 14 种数据可视化类型的概念与代码
  7. 老师吴恩达,身家又增20亿!
  8. vue 数字变星号 过滤器_Vue自定义过滤器格式化数字三位加一逗号
  9. 即时通讯软件测试方法,Linux系统环境下如何使用aMsn即时通讯
  10. 动态折线图 python_python 怎么做个动态折线