`
dato0123
  • 浏览: 913524 次
文章分类
社区版块
存档分类
最新评论

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.cfs/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驱动开发基础总结

    1, linux驱动一般分为3大类: * 字符设备* 块设备* 网络设备 2, 开发环境构建: ...如果你希望查看所有可能包含文件系统的设备是如何初始化的,你可以看 drivers/block/genhd.c中的device_se

    Android驱动开发权威指南

    13.3.1网络设备初始化 13.3.2网络数据包的收发 第三篇 实践出真知——Android驱动实践篇 第14章Android HAL层的设计 14.1 Android HAL概述 14.2为Android开发虚拟驱动virtualio 14.3 Android集成C程序访问virtualio ...

    linux内核 0.11版本源码 带中文注释

    // linux 初始化(仅在这个程序中被调用)。 static inline _syscall0 (int, sync) // int sync()系统调用:更新文件系统。 #include &lt;linux/tty.h&gt; // tty 头文件,定义了有关tty_io,串行通信方面的参数、常数...

    操作系统实验

    (4)关于随机数产生办法,Linux/UNIX系统提供函数srand()和rand(),分别进行初始化和产生随机数。例如:srand()语句可初始化一个随机数: a[0]=10*rand()/32767*319+1, a[1]=10*rand()/32767*a[0]; … … … ...

    自己动手写操作系统(含源代码).part1

    首先是操作系统的名字改变了,原因在于虽然我们的试验性 OS从前辈们那里借鉴了很多东西,但其各个部分的设计(比如文件系统和内存管理)往往有其独特之处,所以我将原先的 Tinix(本意为 TryMinix)改成了新名字...

    操作系统(内存管理)

    它要完成以下三件事:将分配程序标识为已经初始化,找到系统中最后一个有效内存地址,然后建立起指向我们管理的内存的指针。这三个变量都是全局变量: 清单 1. 我们的简单分配程序的全局变量 int has_...

    文件系统代码

    /*------------初始化-----------------------*/ int format() { current = 2; currentPath="C:\\\\"; //当前路径 osPoint-&gt;format();//打开文件列表初始化 delete openlist; openlist=new OPENLIST; /*------...

    自己动手写操作系统(含源代码).part2

    首先是操作系统的名字改变了,原因在于虽然我们的试验性 OS从前辈们那里借鉴了很多东西,但其各个部分的设计(比如文件系统和内存管理)往往有其独特之处,所以我将原先的 Tinix(本意为 TryMinix)改成了新名字...

    Simulink Manipulation Utilities:一组用于操作 Simulink 模型结构和连通性的函数-matlab开发

    通常,这些函数最常见的用途是在掩码子系统初始化回调中。 例如,如果您设计一个具有可调整输入数量的封装子系统,当用户更改所需的输入数量时,初始化回调将需要创建额外的或销毁额外的模块输入端口,并将它们连接...

    uboott移植实验手册及技术文档

    (1)加入Nand Flash的初始化函数 在文件的最后加入Nand Flash的初始化函数,该函数在后面Nand Flash的操作都要用到。 u-boot运行到第2阶段会进入start_armboot()函数。其中nand_init()函数是对nand flash的最 初...

    SPDK官方文档中文版(2019年8月版).pdf

    2.6.3 设备初始化 18 2.6.4 I / O路径 19 2.6.5 SPDK优化 20 2.7. SPDK目录结构概述 20 2.8. SPDK移植指南 22 第三章 用户指南 22 3.1. 系统配置用户指南 22 3.1.1 IOMMU配置 22 3.2. SPDK应用程序概述 23 3.2.1 ...

    浅谈关于能量管理系统

    可以区别EM S 各模块的关闭状态、初始化状态、起动预备状态、请求执行状态、正在执行状态、超时状态、闭锁状态、执行完成状态、出错状态和停止状态等。在 EM S 应用监视器的流程图上, 标明各模块的运行状态。并将各...

    c#学习笔记.txt

    在类中,必须初始化实例对象. 使用 new 运算符创建结构对象时,将创建该结构对象,并且调用适当的构造函数。与类不同的是,结构的实例化可以不使用 new 运算符。如果不使用 new,那么在初始化所有字段之前,字段将...

    SPDK开发手册中文版.docx

    2.6.3 设备初始化 18 2.6.4 I / O路径 19 2.6.5 SPDK优化 20 2.7. SPDK目录结构概述 20 2.8. SPDK移植指南 22 第三章 用户指南 22 3.1. 系统配置用户指南 22 3.1.1 IOMMU配置 22 3.2. SPDK应用程序概述 23 3.2.1 ...

    Petri Nets Building Blocks Library:Petri Nets Building Blocks 库,用于在 Simulink 模型中轻松构建网络模型-matlab开发

    一组 Matlab 脚本用于初始化表示构建块的封装子系统。 这些脚本也在 zip 文件中提供。 该库还包括文献中报道的 Petri Net 模型的一些说明性示例,例如 Dining Philosophers Problem、Communicating Processes、...

    高度自定义TabBarController组件ESTabBarController.zip

    功能1、支持默认样式如果直接使用ESTabBarController进行初始化,你会得到与UITabBarController完全相同的仿系统样式UITabBarController样式:ESTabBarController仿系统样式:2、支持带有"More"的默认样式使用...

    OCPOCA认证考试指南全册:Oracle Database 11g(1Z0-051,1Z0-052,1Z0-053)--详细书签版(第2/2部分)

    3.1.1 静态参数和动态参数以及初始化参数文件 82 3.1.2 基本参数 84 3.2 描述启动和关闭数据库时的多个阶段 88 3.2.1 Database Control的启动和连接 88 3.2.2 启动数据库侦听器 90 3.2.3 启动SQL*Plus 91 ...

    procfs:procfs提供了从伪文件系统proc检索系统,内核和进程指标的功能

    首先,初始化proc文件系统安装点,然后读取stat信息。 fs , err := procfs . NewFS ( "/proc" ) stats , err := fs . Stat () 某些子软件包(例如blockdevice )需要访问proc和sys文件系统。 fs , err := block...

    c语言编写单片机技巧

    我是一名武汉大学电子科技大3的学生,学了电子线路、数字逻辑、汇编和接口、C语言,但是总是感觉很迷茫,觉好象什么都不会。怎么办? 答:大学过程是一个理论过程,实践的机会比较少,往往会造成理论与实践相...

Global site tag (gtag.js) - Google Analytics