您的位置:首页 > 运维架构 > Linux

Linux那些事儿之我是Block层(4)浓缩就是精华?(一)

2008-01-31 09:11 211 查看
人, 生在床上,死在床上;欲生欲死,还是在床上.这句话非常有道理.有人说它有点俗,但,我并不这么认为.我因为经常坐在床上一边看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,也才能从用户层一步一步走到块设备驱动层,如同董卿姐姐能够从上海一步步走向央视.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: