您现在的位置是:首页 > 电气技术 > 电气技术

探秘Linux:从分区表到块设备——我不是表, 我是块设备

来源:艾特贸易2017-09-13

简介作者简介: 黄伟亮(Huang weller),毕业于苏州大学,就职于苏州博世汽车部件汽车多媒体事业部,从事汽车多媒体娱乐系统的平台开发工作六年有余, 接触Linux 系统近10年。感兴趣的方向有Linu

作者简介: 黄伟亮(Huang weller),毕业于苏州大学,就职于苏州博世汽车部件汽车多媒体事业部,从事汽车多媒体娱乐系统的平台开发工作六年有余, 接触Linux 系统近10年。感兴趣的方向有Linux系统性能优化,多媒体框架, 文件系统和存储器件, USB以及虚拟化等。


前言


 

我们的学习习惯基本都是由浅入深的, 比如我们先学习如何使用fdisk工具来给磁盘分区, 之后才想到去看看fdisk到底对磁盘做了什么, 许久以后看到除了fdisk还有别的分区工具可以给磁盘分区. 通常我们只需要知道怎么用就可以了, 也有很多原因促使我们去思考它的背后到底发生了什么,这些原因可能是你碰到了具体问题, 不得不让你往下去看, 去看你认为肯定不会出问题的那一部分, 也有可能是你觉得现在的自己技术太浮于表面, 想深入一些, 也可能是受周围人的影响, 一起学习.

佛家讲究因果, 使用者往往接触的是果, 因为使用者在乎的是可用性, 开发者或者说设计者则要考虑因, 因为什么样的因就会导致什么样的果, 因为这样的设计, 所以就有那样的bug, 就像黑客帝国中的Neo 要找到architecture!

本文由来于心中的两个疑问,即平凡的存储器件是怎么从分区变成一个个块设备的, 根是怎么被mount.

Romcode


 

通常,processor上电后, 最早执行的代码是固化在CPU ROM中的程序, 它其实就是最早被执行的bootloader, 它的终极目的是从存储介质(包含uart, USB)加载另外一个bootloader, 比如u-boot. 拿启动介质是eMMC或者SD的例子来说, romcode一般会从offset = sector_size * 1的位置开始读取程序, 这么做的原因其实是为了跳开分区表

当然笔者也见过一个奇葩的例子, 海思的一款ARM9的芯片, ROMCODE直接从eMMC data partition offset 0的位置开始读取程序, 这就导致当你从emmc 上的u-boot启动, 兴冲冲的进到ramdiskeMMC进行分区, 格式化, 烧写系统之后, 发现系统再也起不来了, 那是因为分区表已经覆盖了第一个分区,破坏了bootloader.  整个系统没有使用分区工具来划分分区, 而是通过u-bootboot arguments描述分区信息. 不管怎样, 本文描述的是前者,而非这个特例.

从分区表到块设备: 我不是表, 我是块设备


 

Linux Storage device 通常是作为块设备被访问的,例如mtdblock, mmcblock 和宇宙第一强的nandblk 块设备(实在太崇拜该设备了,NAND发挥到了极致).  Storage device的设备驱动作为底层支撑, 负责注册块设备并且直接和存储器件打交道, 接受, 执行和响应块设备层的过来的访问请求.  比如说,  一次文件读取的操作会变成文件系统提交到块设备的块读取请求, 该块设备的读访问请求, 在块设备驱动和Host controller driver会把它被转化为MMC协议的CMD17或者CMD18指令给到EMMC物理设备.

Storage device的设备驱动注册到块设备层,并且扫描分区表, 识别分区表, 然后解析分区表, 把磁盘上的分区注册为块设备. 所以, 当你奇怪为什么没有mmcblkp0这个设备时其实不是没有mmcblk0p0,而是p0被定义为就是mmcblk0. 代表了整个磁盘. 下面从代码上来看一下一个块设备驱动的初始化过程:

: 木叶的根


 

我们知道u-bootboot argument 会把root device的设备名称带给内核, 例如通过参数root=/dev/mmcblk0p2来告知内核, 根目录所在的分区.  事情真的那么简单吗?

原来这里面有一点点小弯弯:

原来在天地混沌的时候, 内核已经为了自己的未来初始化了一个ramfs, 并在ramfs中创建了一些必要的目录和设备节点, 例如/dev , /dev/console, /root.

然后, 为了mount mmcblk0p2, 它又根据设备文件名/dev/mmcblk0p2查找设备变量, ramfs下创建设备节点/dev/root, 这个设备文件指向的就是设备mmcblk0p2. 然后把ramfs下的设备/dev/mmcblk0p2挂载到ramfs/root. 切换当前路径到ramfs/root. 至此已经完成了设备mmcblk0p2的挂载工作, 但是此时它还不是根.

接着, 内核挂载devtmpfsdev目录, 然后调用sys_chroot(“.”)change root 到当前路径, 也就是ramfs/root.

反映到代码上:

因此在系统起来后, cat /proc/mounts, 我们可以看到以下信息:

可以看到有一个文件系统的类型是rootfs, 它被mount到了“/”.

这个文件系统在init/do_mount.c中被定义, 它就是ramfs的一个实例,

该类型的文件系统在init_rootfs()中被注册.

mount命令的输出上, 我们可以看到/dev/mmcblk0p2被挂载在了”/”, 殊不知这里的根已经不是原来的根.

结束语


本文没有对代码的细节作过多的分析, 一方面本文不是为了做代码分析的, 另一方面网络上有很多朋友也做过块设备的代码分析, 本文罗列了代码的脉络是为了来更好的表达分析的结果. Block layer是个很复杂的子系统, 有很多关于它的内容,比如IO-schecduler,  buffer cache,  相信自己可以越来越深入的研究这个子系统.

Linux