人,生在床上,死在床上;欲生欲死,还是在床上.这句话非常有道理.有人说它有点俗,但,我并不这么认为.我因为经常坐在床上一边看A片一边看代码,所以对这句话体会颇深,事实上它形象的描述了我坐在床上看代码时复杂的心情,说欲生欲死,一点也不夸张,尤其是当我看到add_disk()这个无比变态的函数的时候.我不禁感慨,上帝欲使人灭亡,必先使其疯狂;上帝欲使人疯狂,必先使其看Linux内核代码.
175 /**
176 * add_disk - add partitioning information to kernel list
177 * @disk: per-device partitioning information
178 *
179 * This function registers the partitioning information in @disk
180 * with the kernel.
181 */
182 void add_disk(struct gendisk *disk)
183 {
184 disk->flags |= GENHD_FL_UP;
185 blk_register_region(MKDEV(disk->major, disk->first_minor),
186 disk->minors, NULL, exact_match, exact_lock, disk);
187 register_disk(disk);
188 blk_register_queue(disk);
189 }
老实说当我一开始看到这个函数只有四行代码的时候,我几乎喜极而泣.但很快我就发现自己的想法Too Simple, Sometimes Naive了.这个函数虽然只有四行代码,可是超级复杂,旗下三个函数,一个比一个拽,我渐渐困惑,写代码的哥们儿有必要写这种浓缩版的函数么?要黑赵丽华老师也不至于这么表现吧?
头一个,blk_register_region,来自block/genhd.c:
139 /*
140 * Register device numbers dev..(dev+range-1)
141 * range must be nonzero
142 * The hash chain is sorted on range, so that subranges can override.
143 */
144 void blk_register_region(dev_t dev, unsigned long range, struct module *module,
145 struct kobject *(*probe)(dev_t, int *, void *),
146 int (*lock)(dev_t, void *), void *data)
147 {
148 kobj_map(bdev_map, dev, range, module, probe, lock, data);
149 }
这里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;
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 }
结合我们的sd_probe函数来看,我们在sd_probe()中说了,first_minor无非就是0,16,32,48这样一系列的数,而minors总是16,换言之按照这里我们的上下文range就是16,这种情况下n只能是1.
Domain就是 bdev_map,于是我们即便不看代码也能猜到,这个函数的主要目的就是为bdev_map的probes这个指针数组赋值,假设我们的major是 8,那么这里就是为probes[8]赋值.对比形参实参可以看到,我们为get指针赋的是exact_match().这个函数同样来自于 block/genhd.c:
160 static struct kobject *exact_match(dev_t dev, int *part, void *data)
161 {
162 struct gendisk *p = data;
163 return &p->kobj;
164 }
即,比如说我们的index或者说major number是8的话,那么这之后,bdev_map->probes[8]所对应的get指针就指向了exact_match.
同时,data指针赋上了disk,即struct gendisk指针disk.
老实说,现在我们完全看不出这么做的意义,或者说blk_register_region这个函数究竟有什么价值现在完全体现不出来.但是其实这是Linux中实现的一种管理设备号的机制,这里利用了传说中的哈希表来管理设备号,哈希表的优点大家知道,便于查找,而我们的目的是为了通过给定的一个设备号就能迅速得到它所对应的 kobject指针,对于块设备来说,得到kobject是为了得到其对应的gendisk.
那么什么时候会需要这样做呢?Ok,比如你执行fdisk –l /dev/sda,从而open系统调用或者说函数sys_open会被执行,如果你一路跟踪,你会发现到后来会有一个叫做get_gendisk()的函数被调用.这个函数实际上也是我们这边定义的,来自block/genhd.c:
203 /**
204 * get_gendisk - get partitioning information for a given device
205 * @dev: device to get partitioning information for
206 *
207 * This function gets the structure containing partitioning
208 * information for the given device @dev.
209 */
210 struct gendisk *get_gendisk(dev_t dev, int *part)
211 {
212 struct kobject *kobj = kobj_lookup(bdev_map, dev, part);
213 return kobj ? to_disk(kobj) : NULL;
214 }
于是我们来看kobj_lookup().来自drivers/base/map.c:
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 }
现在我们隐隐约约的感觉到,kobj_map_init()和kobj_map()以及kobj_lookup()是一个系列的,它们都是为Linux设备号管理服务的,就好比舒淇,李丽珍,钟丽缇是一个系列的,她们都是为三级片市场服务的.首先,kobj_map_init提供的是一次性服务,它的使命是建立了 bdev_map这个struct kobj_map.然后kobj_map是每次在blk_register_region中被调用的,然而,在这个五彩缤纷的世界中,调用 blk_register_region()的地方可真不少,随便一搜索就是一大把,而我们这个在add_disk中调用只是其中之一,其它的比如 RAID驱动那边,软驱驱动那边,都会有调用这个blk_register_region的需求,而kobj_lookup()发生在什么情况下呢?它提供的其实是售后服务.当块设备驱动完成了初始化工作,当它在内核中站稳了脚跟,会有一个设备文件和它相对应,这个文件会出现在/dev目录下.在不久的将来,当open系统调用试图打开块设备文件的时候就会调用它,更准确地说,sys_open经由filp_open然后是dentry_open(),最终会找到blkdev_open,blkdev_open会调用do_open,do_open()会调用get_gendisk(),要想明白这个理儿,得先看一下dev_t这个结构.dev_t实际上就是u32,也即就是32个bits.前面咱们看到的MKDEV,MAJOR,都来自 include/linux/kdev_t.h:
4 #define MINORBITS 20
5 #define MINORMASK ((1U << MINORBITS) - 1)
6
7 #define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
8 #define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
9 #define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
通过这几个宏,我们不难看出 dev_t的意义了,32个bits,其中高12位被用来记录设备的主设备号,低20位用来记录设备的次设备号.而MKDEV就是建立一个设备号.ma代表主设备号,mi代表次设备号,ma左移20位再和mi相或,反过来,MAJOR就是从dev中取主设备号,MINOR就是从dev中取次设备号.不多说了,杭州西湖畔拉皮条的都知道怎么回事了.
当一个设备闯入Linux的内心时,首先它会有一个居住证号,这就是dev_t,很显然,每个人的居住证号不一样,它是唯一的.(为什么不说是身份证号?因为居住证意味着当设备离开 Linux系统的时候就可以销毁,所以它更能体现设备的流动性.)建立一个设备文件的时候,其设备号是确定的,而我们每次建立一个文件都会建立一个结构体变量,它就是struct inode,而struct inode拥有成员dev_t i_dev,所以日后我们从struct inode就可以得到其设备号dev_t,而这里kobj_map这一系列函数使得我们可以从dev_t找到对应的kobject,然后进一步作为磁盘驱动,我们不可避免的需要访问磁盘对应的gendisk结构体指针,而get_gendisk()就是在这时候应运而生并粉墨登场的.咱们看到 get_gendisk()的两个参数,dev_t dev和int *part,前者就是设备号,而后者传递的是一个指针,这表示什么呢?这表示,
1. 如果这个设备号对应的是一个分区,那么part变量就用来保存分区的编号.
2. 如果这个设备号对应的是整个设备而不是某个分区,那么part就只要设置成0就ok了.
那么得到gendisk的目的又是什么呢?我们注意到struct gendisk有一个成员,struct block_device_operations *fops,而这个指针才是用来真正执行操作的,每一个块设备驱动都准备了这么一个结构体,比如咱们在sd中定义的那个:
872 static struct block_device_operations sd_fops = {
873 .owner = THIS_MODULE,
874 .open = sd_open,
875 .release = sd_release,
876 .ioctl = sd_ioctl,
877 .getgeo = sd_getgeo,
878 #ifdef CONFIG_COMPAT
879 .compat_ioctl = sd_compat_ioctl,
880 #endif
881 .media_changed = sd_media_changed,
882 .revalidate_disk = sd_revalidate_disk,
883 };
正是因为有这种种暧昧关系,我们才能一步一步从sys_open最终走到sd_open,也才能从用户层一步一步走到块设备驱动层,如同董卿姐姐能够从上海一步步走向央视.