使用设备号的一个基本需求就是根据设备号索引能够找到设备,而在Linux设备模型中,所有的设备最后都归结到kobject,所以问题也就转变成 dev_t到kobject实例的mapping问题,内核中使用kobj_map来描述这种映射,具体地,内核创建了两个实例cdev_map和bdev_map来分别管理字符设备和块设备及其设备号的映射关系, 二者都是kobj_map类型, 可以用下图简要表示
本文主要讨论如下问题:
- kobj_map是如何实现这种映射的。
- kobj_map实现的这种映射是如何被使用的。
kobj_map
实际上, 这个kobj_map也是通过HASH来建立mapping的:以dev_t为HASH索引,通过每一个HASH表项(probe)上的链表来找到相应的kobject,和char_device_struct
或blk_major_name
完全不同,kobj_map有更大的自由度,它的每一条链表是完全根据range大小顺序排列的,链表中的每个probe对象负责的设备号区间可以重叠。此外,通过对大range的分割,保证了只要kobj_map的映射管理已经覆盖到了某个dev_t,那么就一定可以在相应的一条链表中找到,具体的表现是:HASH检索时用到的major与该链表中的某个MAJOR(dev_t)可能不一致。总的来说,相比于`char_device_struct`的”不重不漏”原则,kobj_map秉承**“优中取优,一次到位”**的原则,检索时会优先获取符合条件的最小range。接下来我们来分析一下这个结构。
//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 };
–20–>每一个HASH表项链表的节点,每一个节点都表示一个设备号区间及其映射的kobject
–21–>链表节点连接处
–22–>该节点的设备号区间的起始设备号
–23–>该节点的设备号区间长度
–25–>函数指针实例,原型为typedef struct kobject *kobj_probe_t(dev_t, int *, void *);
,保存根据dev_t获取kobject的方法
–26–>函数指针,保存锁定kobject实例,防止其被释放的方法。
–27–>与kobject关联的自定义结构,比如封装了kobject的cdev或gendisk实例
–29–>操作该结构使用的mutex
同样,内核也实现了针对该结构的操作方法.
kobj_map_init()
初始化kobj_map实例的函数,在相关子系统的初始化时期被调用,比如block subsys的__init genhd_device_init(void)
或者char subsys的__init chrdev_init(void)
//drivers/base/map.c 136 struct kobj_map *kobj_map_init(kobj_probe_t *base_probe, struct mutex *lock) 137 { 138 struct kobj_map *p = kmalloc(sizeof(struct kobj_map), GFP_KERNEL); 139 struct probe *base = kzalloc(sizeof(*base), GFP_KERNEL); 140 int i; 141 142 if ((p == NULL) || (base == NULL)) { 143 kfree(p); 144 kfree(base); 145 return NULL; 146 } 147 148 base->dev = 1; 149 base->range = ~0; 150 base->get = base_probe; 151 for (i = 0; i < 255; i++) 152 p->probes[i] = base; 153 p->lock = lock; 154 return p; 155 }
–139–>分配链表”尾”
–148–>初始化probe实例base,作为”泛化”项,base映射[1,~0]区间的dev_t,即所有的设备号!base用来匹配无法匹配到该链表中任何其他项的设备号,它有自己的kobj_probe_t方法,该方法最终会尝试通过__request_module()>call_modprobe()来加载ko,并返回NULL,这样,在下一次检索的时候就可以通过设备号真正地获取kobj。
–151–>将链表”尾”链入每一个HASH表项,作为其初始值
–154–>初始化整个HASH表使用的Mutex
kobj_map()/kobj_ummap()
该函数实现的功能是向HASH表中插入/删除表项, 这里仅分析插入的代码。既然是插入,那么正确初始化一个probe实例所必需的信息是一定要提供的,这点从传入的参数即可看出。
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; 40 41 if (n > 255) 42 n = 255; 43 44 p = kmalloc(sizeof(struct probe) * n, GFP_KERNEL); 45 46 if (p == NULL) 47 return -ENOMEM; 48 49 for (i = 0; i < n; i++, p++) { 50 p->owner = module; 51 p->get = probe; 52 p->lock = lock; 53 p->dev = dev; 54 p->range = range; 55 p->data = data; 56 } 57 mutex_lock(domain->lock); 58 for (i = 0, p -= n; i < n; i++, p++, index++) { 59 struct probe **s = &domain->probes[index % 255]; 60 while (*s && (*s)->range < range) 61 s = &(*s)->next; 62 p->next = *s; 63 *s = p; 64 } 65 mutex_unlock(domain->lock); 66 return 0; 67 }
–32–>要插入到哪个HASH表domain,需要建立映射的设备号区间dev和range,该HASH归属的模块,以及probe结构所需的其他成员…
–36–>为支持跨major做准备
–37–>根据major获取HASH索引index
–44–>有多少的major,就分配多少个probe对象
–49–>这些对象完全相同。–58–>遍历需要遍历的每一个链表。
–60–>每一个链表都按照range的大小,从前到后排列
–62–>找到合适的位置就插入probe对象。n>1时,会将probe对象分别根据range放在index和index+1两条链表中。如前文所述,对于一个dev_t,它被一个probe覆盖了,但这个probe的range是跨多个major的,那么怎么能保证无论这个dev_t属于哪个major链表,都能被一次遍历链表找到呢?就是将这个probe加入到每一个它覆盖到的major链表中!这种设计可以让lookup在一条链表中完成,是典型的空间换时间。
kobj_lookup()
这个函数是这组接口里被调用次数最多的一个函数,用于在kobj_map中检索需要的kobject实例。
96 struct kobject *kobj_lookup(struct kobj_map *domain, dev_t dev, int *index) 97 { 98 struct kobject *kobj; 99 struct probe *p; 100 unsigned long best = ~0UL; 101 102 retry: 103 mutex_lock(domain->lock); 104 for (p = domain->probes[MAJOR(dev) % 255]; p; p = p->next) { 105 struct kobject *(*probe)(dev_t, int *, void *); 106 struct module *owner; 107 void *data; 108 109 if (p->dev > dev || p->dev + p->range - 1 < dev) 110 continue; 111 if (p->range - 1 >= best) 112 break; 113 if (!try_module_get(p->owner)) 114 continue; 115 owner = p->owner; 116 data = p->data; 117 probe = p->get; 118 best = p->range - 1; 119 *index = dev - p->dev; 120 if (p->lock && p->lock(dev, data) < 0) { 121 module_put(owner); 122 continue; 123 } 124 mutex_unlock(domain->lock); 125 kobj = probe(dev, index, data); 126 /* Currently ->owner protects _only_ ->probe() itself. */ 127 module_put(owner); 128 if (kobj) 129 return kobj; 130 goto retry; 131 } 132 mutex_unlock(domain->lock); 133 return NULL; 134 }
–104–>遍历根据MAJOR(dev_t)获取到的链表,如前文所述,只要这个dev_t已经被覆盖,那么一定可以在这个链表中找到。
–109–>当前的probe对象的dev不合适,继续循环遍历当前链表
–111–>range已经大于”泛化”probe对象,结束循环
–113–>对module的引用计数-1,如果为0则不能返回该module的kobj。
–119–>index不是HASH的索引,而是probe对象中的dev_t与待检索dev_t的差值。
–125–>回调probe方法,获取kobject并返回之。
Example
在具体的设备中,通常还会对上述方法进行封装,这里以block device为例讨论这种映射的使用方式。当前版本的block device使用kobj_map以及IDR机制对gendisk对象进行管理,kobj_map主要是对常规分区的设备号进行管理,IDR主要用来管理超出gendisk->minors数量的分区的设备号,参见add_disk()->blk_alloc_devt。本文仅讨论其kobj_map部分,kobj_map部分的相关的实现多在“block/genhd.c”。
genhd_device_init()
900 static int __init genhd_device_init(void) 901 { 902 int error; 903 904 block_class.dev_kobj = sysfs_dev_block_kobj; 905 error = class_register(&block_class); 906 if (unlikely(error)) 907 return error; 908 bdev_map = kobj_map_init(base_probe, &block_class_lock); 909 blk_dev_init(); 910 911 register_blkdev(BLOCK_EXT_MAJOR, "blkext"); 912 913 /* create top-level block dir */ 914 if (!sysfs_deprecated) 915 block_depr = kobject_create_and_add("block", NULL); 916 return 0; 917 }
–900–>__init 可以看出,这个函数在系统初始化的时候就会被调用
–908–>构建bdev_map供系统中所有的gendisk使用
–909–>通过slab分配request_queue等结构
blk_register_region()
“注册”设备号,看起来是”获取”设备号(register_chrdev_region()),其实是建立设备号dev_t的映射。
//block/genhd.c 475 void blk_register_region(dev_t devt, unsigned long range, struct module *module, 476 struct kobject *(*probe)(dev_t, int *, void *), 477 int (*lock)(dev_t, void *), void *data) 478 { 479 kobj_map(bdev_map, devt, range, module, probe, lock, data); 480 }
get_gendisk()
681 struct gendisk *get_gendisk(dev_t devt, int *partno) 682 { 683 struct gendisk *disk = NULL; 684 685 if (MAJOR(devt) != BLOCK_EXT_MAJOR) { 686 struct kobject *kobj; 687 688 kobj = kobj_lookup(bdev_map, devt, partno); 689 if (kobj) 690 disk = dev_to_disk(kobj_to_dev(kobj)); 691 } else { ... 701 } 702 703 return disk; 704 } 705 EXPORT_SYMBOL(get_gendisk);
–685–>如果不是extended major,extended major用于分区,对应hd_struct而不是gendisk
–688–>使用kobj_lookup获取kobj
–690–>通过kobj获取的gendisk对象并返回