title: 根文件系统理解
tags: linux
date: 2018-09-19 19:56:05
---
根文件系统理解
文件系统概念
文件系统是一些代码,是一套软件,这套软件的功能就是对存储设备的扇区进行管理,将这些扇区的访问变成了对目录和文件名的访问。我们在上层按照特定的目录和文件名去访问一个文件时,文件系统会将这个目录+文件名转换成对扇区号的访问。
- 虚拟文件系统接口(VFS)是linux定义的一个文件系统的统一接口,这样各种类型的文件系统都采用一样的接口给用户
- 虚拟文件系统存储在RAM里的,没有实际的设备(ROM)与之对应
- 实际文件系统有实际的存储设备(ROM)与之对应,又可分为远程文件系统和本地文件系统
- 根文件系统处于文件系统的最上层,其很重要的作用是用来挂载其他文件系统。根文件系统可以是虚拟文件系统也可以是实际文件系统,只要条件支持.
- 文件系统格式 linux支持包括ext2,ext3,vfat,jffs,ramfs,nfs等文件系统
- jffs2:主要用于nor型flash,特点是可读写,支持数据压缩的日志型文件系统。
- yaffs/yaffs2:主要用于nand型flash,支持跨平台。
- cramfs:只读的压缩文件系统。可用于两种flash。
- ramdisk:基于ram的文件系统。是将一部分固定大小的内存当做块设备来用。它并非是一个实际的文件系统,而是一种将实际的文件系统装入内存的机制。将一些经常访问而又无需更改的文件通过ramdisk放在内存中,可以明显的提高系统的性能。
- initramfs:基于ram的文件系统。initramfs出现在2.6内核中,它类似于tmpfs,是一种基于内存的文件系统,它的使用不需要创建内存块设备。增加文件到ramfs会自动配置更多的内存,并删除或截去文件以释放内存。(若ramdisk没有满,已被占用的额外的内存也不能用来做其它事情,若ramdisk满了,但其它仍有闲置的内存,也必须重新格式化以后才能扩展使用)
- nfs:是由sun开发的一种在不同机器之间通过网络共享文件的技术。在嵌入式linux系统的开发调试阶段,可以利用该技术在主机上建立基于nfs的根文件系统,挂载到嵌入式设备,可以很方便的修改根文件系统的内容。
启动流程
内核通过一系列初始化之后,挂载根文件系统来执行应用程序. 所谓根文件系统,需要提前按照一定的文件系统的格式化,并放入相应的内容数据.Kernel 根据启动参数去寻找根文件系统的位置去mount
根文件系统
ramdisk启动
- 制作ramdisk文件系统压缩包
- 然后将这个文件压缩包通过bootloader下载到内存中
- 当系统启动的时候
- 通过uboot的bootargs环境变量来传递启动参数,修改为bootargs=initrd=0x31000000,0x200000 root=/dev/ram rw init=/linuxrc console=ttySAC0 mem=64,以此指定从ramdisk启动,ramdisk压缩文件起始地址在内存地址0x31000000处,文件大小为0x200000。
- 也可以通过修改内核配置Default kernel command string为”initrd=0x31000000,0x200000 root=/dev/ram rw init=/linuxrc console=ttySAC0 mem=64”
flash启动
文件系统存在FLASH
,内核根据命令行参数root=/dev/xxx
,直接mount
。
问题? 在root文件系统本身还不存在的情况下,内核如何根据/dev/xxx
来找到对应的设备呢?
解答: 根文件系统和其他文件系统的mount方式是不一样的,内核通过直接解析设备的名称来获得设备的主、从设备号,然后就可以访问对应的设备驱动了。所以在init/main.c
中有很长一串的root_dev_names
(如hda,hdab,sda,sdb,nfs,ram,mtdblock……),通过这个表就可以根据设备名称得到设备号。注意,bootloader
或内核中设定的启动参数root=/dev/xxx
只是一个代号,实际的根文件系统中不一定存在这个设备文件!
拿ARM来说,在文件arch/arm/plat-s3c24xx/common-smdk.c
中有分区表:
static struct mtd_partition smdk_default_nand_part[] = {
[0] = {
.name = "Boot",
.size = 0x00020000,
.offset = 0,
},
[1] = {
.name = "Kernel",
.size = 0x00240000,
.offset = 0x00040000,
},
[2] = {
.name = "RootFileSystem",
.size = 0x02000000,
.offset = 0x00280000,
},
[3] = {
.name = "ExtendFileSystem",
.size = 0x05d80000,
.offset = 0x02280000,
}
};
initramfs
initramfs将根目录直接编译到linux内核镜像中 ,这种方法不同于前面两种方法需要在uboot启动参数中指定ramdisk加载到内存的地址或者文件系统在flash中的分区,由于已经将根目录编译到linux的.init.ramfs段中了,所以启动的时候直接将linux内核镜像的.init.ramfs段的内容解压到系统的rootfs中。
initrd
启动时用到initrd来mount根文件系统。注意理解ramdisk和initrd这两个概念,其实ramdisk只是在ram上实现的块设备,类似与硬盘操作,但有更快的读写速度,它可以在系统运行的任何时候使用,而不仅仅是用于启动;
initrd(boot loader initialized RAM disk)可以说是启动过程中用到的一种机制,具体的实现过程也使用ramdisk技术。就是在装载linux之前,bootloader可以把一个比较小的根文件系统的映象装载在内存的某个指定位置,姑且把这段内存称为initrd(这里是initrd所占的内存,不是ramdisk,注意区别),然后bootloader通过传递参数的方式告诉内核initrd的起始地址和大小(也可以把这些参数编译在内核中),在启动阶段就可以暂时的用initrd来mount根文件系统。initrd的最初的目的是为了把kernel的启动分成两个阶段:在kernel中保留最少最基本的启动代码,然后把对各种各样硬件设备的支持以模块的方式放在initrd中,这样就在启动过程中可以从initrd所mount的根文件系统中装载需要的模块。这样的一个好处就是在保持kernel不变的情况下,通过修改initrd中的内容就可以灵活的支持不同的硬件。在启动完成的最后阶段,根文件系统可以重新mount到其他设备上,但是也可以不再 重新mount(很多嵌入式系统就是这样)。 initrd的具体实现过程是这样的:bootloader把根文件系统映象装载到内存指定位置,把相关参数传递给内核,内核启动时把initrd中的内容复制到ramdisk中(ram0),把initrd占用的内存释放掉,在ram0上mount根文件系统。从这个过程可以看出,内核需要对同时对ramdisk和initrd的支持(这种需要都编入内核,不能作为模块)。
鸡生蛋蛋生鸡
内核刚启动时,磁盘设备、网络设备都还没有被驱动起来,所以无法访问磁盘,没法给磁盘启用对应的文件系统。那赶紧安装磁盘驱动程序,网络驱动程序呀,怎么不加载呢?因为磁盘种类太多了,没法把所有的驱动都编译到内核里头,那样内核得变得多大呀,所以就只能把这些驱动程序编译成模块的方式,在内核加载的时候现场判断当前用的是什么磁盘再加相应的磁盘驱动模块。那就加载磁盘驱动模块呀,等什么呢?原因是编译成驱动模块后,在这个阶段压根就没法加载!还没文件系统呢,怎么加载驱动模块?结果启用文件系统的前提是磁盘的驱动程序已经加载,而驱动程序的加载的前提是已经有文件系统存在,这就成了鸡生蛋,蛋生鸡的问题,怎么破?想到内核加载的时候,RAM其实已经可用了,那就基于RAM建立一个临时文件系统吧,这个临时的文件系统自己挂载到自己身上,然后我们指定这个文件系统为根文件系统,这样就有了起步的文件系统啦,借助这个临时的文件系统把磁盘驱动模块、网络驱动模块加载上,这样就可以挂载实际的文件系统啦,有了实际的文件系统之后再把这个实际的文件系统指定为根文件系统,这就好啦,然后其他的各式各样的文件系统就可以陆陆续续的挂载在这个根文件系统下了。
回到之前的一个问题,怎么建立一个基于RAM的虚拟文件系统?首先,在编译内核得时候就编译一个很精简的虚拟文件系统进去,然后内核在启动的时候先注册一个rootfs这个虚拟文件系统,然后挂载这个虚拟文件系统,那rootfs这个虚拟文件还是个空的,得给里头放点东西呀,放什么呢?就放编译进内核里头的那个很精简的虚拟文件系统里的内容。怎么内容放进rootfs里去?方法简单粗暴,直接把编译进内核里头的那个很精简的虚拟文件系统里的内容解压到rootfs里,这个过程叫填充rootfs。