Linux那些事儿之我是Block层(1)Block子系统的初始化

于是我们从genhd_device_init()开始看起.

    350 static int __init genhd_device_init(void)

    351 {

    352         int err;

    353

    354         bdev_map = kobj_map_init(base_probe, &block_subsys_lock);

    355         blk_dev_init();

    356         err = subsystem_register(&block_subsys);

    357         if (err < 0)

    358                 printk(KERN_WARNING "%s: subsystem_register error: %d/n",

    359                         __FUNCTION__, err);

    360         return err;

    361 }

这个初始化函数看起来粉简单,然而,正如电影<<十分爱>>里面说的一样,有时候看到的不一定是真的,真的不一定看的到.早在我还没断奶的时候,我就听说了Block子系统是如何如何的复杂,赫赫有名的ll_rw_blk.c是如何如何的深奥,也许那时候,我是个天才,可是,后来经过二十多年的社会主义教育后,终于成功的被培育成了庸才!所以现在的我要想看懂这代码可真不是件容易的事儿.

首先关注来自block/ll_rw_blk.c中的blk_dev_init().

   3700 int __init blk_dev_init(void)

   3701 {

   3702         int i;

   3703

   3704         kblockd_workqueue = create_workqueue("kblockd");

   3705         if (!kblockd_workqueue)

   3706                 panic("Failed to create kblockd/n");

   3707

   3708         request_cachep = kmem_cache_create("blkdev_requests",

   3709                         sizeof(struct request), 0, SLAB_PANIC, NULL, NULL);

   3710

   3711         requestq_cachep = kmem_cache_create("blkdev_queue",

   3712                         sizeof(request_queue_t), 0, SLAB_PANIC, NULL, NULL);

   3713

   3714         iocontext_cachep = kmem_cache_create("blkdev_ioc",

   3715                         sizeof(struct io_context), 0, SLAB_PANIC, NULL, NULL);

   3716

   3717         for_each_possible_cpu(i)

   3718                 INIT_LIST_HEAD(&per_cpu(blk_cpu_done, i));

   3719

   3720         open_softirq(BLOCK_SOFTIRQ, blk_done_softirq, NULL);

   3721         register_hotcpu_notifier(&blk_cpu_notifier);

   3722

   3723         blk_max_low_pfn = max_low_pfn - 1;

   3724         blk_max_pfn = max_pfn - 1;

   3725

   3726         return 0;

   3727 }

这个函数虽然不长,但是如果你能轻轻松松看懂这个函数,那么你完全有资格在简历里面写上自己精通Linux,当然就算你啥也不懂多写几个精通也很正常,我就这么干的,这就是江湖.

首先第一个函数,create_workqueue()干的什么事情你也许不是很清楚,但是你不要忘了每次你用ps命令看进程的时候你都能看到一个叫做kblockd的玩意儿.比如:

[root@localhost ~]# ps -el | grep kblockd

1 S     0    80     2  0  70  -5 -     0 worker ?        00:00:00 kblockd/0

1 S     0    81     2  0  70  -5 -     0 worker ?        00:00:00 kblockd/1

1 S     0    82     2  0  70  -5 -     0 worker ?        00:00:00 kblockd/2

1 S     0    83     2  0  70  -5 -     0 worker ?        00:00:00 kblockd/3

1 S     0    84     2  0  70  -5 -     0 worker ?        00:00:00 kblockd/4

1 S     0    85     2  0  70  -5 -     0 worker ?        00:00:00 kblockd/5

1 S     0    86     2  0  70  -5 -     0 worker ?        00:00:00 kblockd/6

1 S     0    87     2  0  70  -5 -     0 worker ?        00:00:00 kblockd/7

以上这个kblockd之所以有8个,是因为我的机器里有8个处理器.

这里返回值赋给了kblocked_workqueue:

     64 static struct workqueue_struct *kblockd_workqueue;

接下来,三个kmem_cache_create()咱们当然不再是初次见面了,不过这里我们不妨看一下效果.我推荐给你的方法是使用cat /proc/slabinfo看一下,不过这个命令显示出来的信息太多了点,不方便我贴出来,所以我改用另外一招,在kdb里使用slab命令.当然,目的是一样的,就是为了展示出slab内存的分配.凡是用kmem_cache_create申请过内存的都在这里留下了案底.比如咱们这里的blkdev_ioc,blkdev_requests,blkdev_queue.

[0]kdb> slab

name              actobj   nobj   size ob/sl pg/sl actsl   nsl

isofs_inode_cache      0      0    608    6    1      0      0

ext2_inode_cache       0      0    720    5    1      0      0

ext2_xattr             0      0     88   44    1      0      0

dnotify_cache          2     92     40   92    1      1      1

dquot                  0      0    256   15    1      0      0

eventpoll_pwq          1     53     72   53    1      1      1

eventpoll_epi          1     20    192   20    1      1      1

inotify_event_cache      0      0     40   92    1      0      0

inotify_watch_cache      1     53     72   53    1      1      1

kioctx                 0      0    320   12    1      0      0

kiocb                  0      0    256   15    1      0      0

fasync_cache           0      0     24  144    1      0      0

shmem_inode_cache    446    500    752    5    1    100    100

posix_timers_cache      0      0    128   30    1      0      0

uid_cache              8     30    128   30    1      1      1

ip_mrt_cache           0      0    128   30    1      0      0

tcp_bind_bucket       11    112     32  112    1      1      1

inet_peer_cache        0      0    128   30    1      0      0

secpath_cache          0      0     64   59    1      0      0

xfrm_dst_cache         0      0    384   10    1      0      0

ip_dst_cache          89    160    384   10    1     16     16

arp_cache              3     15    256   15    1      1      1

RAW                    9     10    768    5    1      2      2

UDP                   10     20    768    5    1      4      4

tw_sock_TCP            0      0    192   20    1      0      0

request_sock_TCP       0      0    128   30    1      0      0

TCP                   11     15   1536    5    2      3      3

blkdev_ioc            36    335     56   67    1      5      5

blkdev_queue          26     35   1576    5    2      7      7

blkdev_requests       77    168    272   14    1     12     12

当然,我在这里做了很多删减,否则肯定得列出好几页来.虽说哥们儿总被人称作垃圾中的战斗机,人渣中的VIP,可是毕竟脸皮没有赵丽华老师那么厚,就不贴那么多行了.

3717行,for_each_possible_cpu,针对每个cpu的循环,很显然,我们走到今天,smp的代码也不得不去接触一点了.虽然我们都不懂smp,可是人生不能象做菜,把所有的料都准备好了才下锅,此时此刻,我们不得不去面对smp.

再下来,open_softirq.这也是一个骨灰级的函数了.它的作用是开启使用软中断向量,咱们这里开启的是BLOCK_SOFTIRQ.准确一点说open_softirq的作用是初始化softirq.而真正激活softirq的函数是日后我们会见到的raise_softirq()或者raise_softirq_irqoff(),在真正处理softirq的时候,咱们这里传递进去的blk_done_softirq()函数就会被执行.此乃后话,不表.

然后是,register_hotcpu_notifier().老实说,真的没有一个函数是省油的灯,我这个汗哪!不过,看了这么多代码之后你会发现,眼前这个函数是最性感的一个函数,因为它实在太前卫了,它的存在为了支持CPU的热插拔.要让它的存在有意义,你必须在编译内核的时候打开编译开关CONFIG_HOTPLUG_CPU,否则它只是一个空函数.不过我谨慎估计你不会做这么性感的选择吧,因为你既不是梁朝伟,也不是佟大为.

剩下两行,max_low_pfn表示Low Memory中最大的物理页帧号,(Page frame number of the last page frame directly mapped by the kernel(low memory))确切的说是Low memory中最大的物理页帧号加上1.max_pfn表示整个物理内存的最后一个可用的页帧号(Page frame number of the last usable page frame),确切的说也应该是最后一个可用的页帧号加上1.所以这个取名是不合理的,也正是因为如此,咱们这里当block层也要用这些概念的时候就事先减掉了1.所谓的Low Memory,对那些32位的机器中,在我的记忆中,大约也就是指的896M以下的部分.所以我们下面可以利用kdb来检查一下max_low_pfn这个变量的值.

[5]kdb> md max_low_pfn

c0809900 00038000 00000847 00100000 00000000   ....G...........

c0809910 00000000 00000000 00000000 00000000   ................

c0809920 00000000 00000000 00000000 00000000   ................

c0809930 00000000 00000000 00000000 00000000   ................

c0809940 0040029b 00000000 00000000 00000000   ..@.............

c0809950 00000000 00000000 00000000 00000000   ................

c0809960 00000000 00038000 00000000 00000180   ................

c0809970 00003135 030f6000 c06a2700 c06a2700   51...`...'j..'j.

首先,可以看到max_low_pfn的值是0x38000,换成十进制就是229376,乘以Page Size,4k,得到917504,除以1024从而把单位换成M,得到896,所以很显然,max_low_pfn标志的是896M以上的那一个page.而blk_max_low_pfn比它少一,正好可以名副其实.

结束了blk_dev_init()我们再回到genhd_device_init()中来,很显然,这里还有两个函数我们并没有讲,一个是kobj_map_init(),一个是subsystem_register().相比之下,其实后者更容易理解,注册一个子系统,即Block子系统.反观前者,其实是农夫山泉,有点难.

搜索整个内核代码你会惊讶的发现,整个内核代码中这个kobj_map_init()函数竟然只被调用了两次.

localhost:/usr/src/linux-2.6.22.1 # grep -r kobj_map_init *

block/genhd.c:  bdev_map = kobj_map_init(base_probe, &block_subsys_lock);

drivers/base/map.c:struct kobj_map *kobj_map_init(kobj_probe_t *base_probe, struct mutex *lock)

fs/char_dev.c:  cdev_map = kobj_map_init(base_probe, &chrdevs_lock);

include/linux/kobj_map.h:struct kobj_map *kobj_map_init(kobj_probe_t *, struct mutex *);

可以看到,它被定义于drivers/base/map.c,在include/linux/kobj_map.h中做了声明,而调用它的地方就是block/genhd.c和fs/char_dev.c,前者正是我们这里遇到的这个.为了了解这个函数做了什么,我们需要先认识一些结构体,第一个要认识的就是struct 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 };

咱们这里用到的bdev_map正是struct kobj_map结构体指针,就定义于block/genhd.c:

    137 static struct kobj_map *bdev_map;

而kobj_map_init()的定义是这样子的:

    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 }

看得出,申请了一个struct kobj_map的指针p,然后最后返回的也是p,即最后把一切都献给了bdev_kmap.而这里真正干的事情无非就是让bdev_kmap->probes[]数组全都等于base.换言之,它们的get指针全都等于了咱们这里传递进来的base_probe函数,这个函数也不很短,来自block/genhd.c:

    342 static struct kobject *base_probe(dev_t dev, int *part, void *data)

    343 {

    344         if (request_module("block-major-%d-%d", MAJOR(dev), MINOR(dev)) > 0)

    345                 /* Make old-style 2.4 aliases work */

    346                 request_module("block-major-%d", MAJOR(dev));

    347         return NULL;

    348 }

这个函数看起来怪怪的,定义了三个参数却只使用其中的一个,定义的返回值类型是struct kobject*实际上却偏偏只返回NULL.看这个函数不由得让我想起了在复旦的那段日子,当复旦大学将同性恋课程搬进课堂后,我看周围的人们都有一种深邃而扑朔迷离的眼神,每个人都怪怪的,校园里充满了同性恋的味道.但这个函数怪是怪,总还是有点逻辑,每一个学过钱能老师那本<<C++程序设计教程>>的男人都会很快醒悟,这里八成是利用了C++的基类派生类那种函数重载的理念.没错,你的直觉是正确的,日后我们会彻底明白的.

你可能感兴趣的:(Linux那些事儿之我是Block层(1)Block子系统的初始化)