FreeBSD下的内存文件系统
大多数操作系统,包括FreeBSD,通常使用磁盘来保存需要存储的数据。而操作系统采用文件的形式来保存数据,因此操作系统在磁盘上存储数据需要按照一定的格式进行,以便系统能够正确存储和访问文件,数据在磁盘上的组织格式被称为文件系统,不同的操作系统通常使用不同的数据组织格式,就是说使用不同的文件系统,例如FreeBSD使用UFS,而Linux使用Ext2FS等等。UFS是最古老和标准的Unix文件系统,但FreeBSD下对其进行了改进,主要目的是改善性能,改进后的文件系统也被称为FFS。
由于文件系统已经成为了操作系统访问外部磁盘数据的标准形式,操作系统很少直接访问磁盘获取数据。由于数据存储在实际应用中的重要意义,文件系统已经成为操作系统最基本的元素之一。进一步,多个操作系统可以通过同样的文件系统访问外部磁盘,而同一个操作系统也可以通过不同的文件系统访问不同的外部磁盘设备。这样一来,文件系统就成为了界于操作系统和外部磁盘设备之间的一个独立层次。
正是由于文件系统已经成为了独立的一个抽象层,因此可以在这个层次进行更为复杂的处理,例如不让文件系统从磁盘设备设备上读取数据,而是从网络上读取数据,就形成了网络文件系统,从内存中读取数据就形成了内存文件系统,对读取的数据进行加解密处理,就形成了加密文件系统,等等。
虚拟磁盘和内存文件系统
虽然目前磁盘的容量、速度和可靠性基本上能满足绝大多数应用系统的要求,但是在某些情况下,使用磁盘存储数据仍然存在一些问题。一个例子是在嵌入式系统中,系统通常没有磁盘,数据通常存储在各种不可擦写或可擦写型半导体芯片中。另一个常遇到的例子是应用系统对于数据访问要求特别高的性能,由于磁盘是一种机械设备,读写的性能总是有限的,不能满足应用系统的需要。
在这些例子中,在面对需要解决的问题的时候,通常有一个隐含前提,就是不改动操作系统和应用程序本身。如果可以改动系统,那么,人们就可以针对具体的需要设计另外一套完整的系统,上述问题事实上也就并不存在了。但是这种做法并不现实,因为这将意味着将全部操作系统和应用系统都推倒重来。因此,为了解决这些极端的需求,最简单的方法是在内存中分配一个区域作为文件系统的数据存储区,而不是使用磁盘作为存储设备,这样既能够满足操作系统对文件系统的需求,也能够满足应用系统的特殊需要。
使用内存作为文件系统基本上有两种不同的选择方式,最简单的方式是将内存中的一个区域模拟一个磁盘分区,然后就可以在这个虚拟磁盘上按照现有的文件系统组织数据,因此就需要初始化文件系统、装载与卸载等标准文件系统操作。另一种方法为重新设计一套全新的文件系统,其中并不包含任何磁盘的概念,但在系统中表现为一个标准文件系统。明显的,重新设计一套文件系统需要更多的工作量,因此,绝大多数情况下,人们使用第一种虚拟磁盘的方法。但是,现有的文件系统,如UFS等,都是基于磁盘设备而设计的,因此很多概念,比如按磁盘块读取数据、缓冲、碎片等等,都是针对磁盘设备提出的,理论上基于内存的存储不需要这些概念,因而也不会出现这些概念需要解决的一些问题。
由于内存文件系统是使用内存来模拟磁盘操作,因此数据都是在内存之间传输,就可以得到比较高的读写性能。当然,使用内存文件系统,一旦系统重新启动,所有的内容也就不再存在了,因此只能用来保存临时性的数据。也是由于是使用内存来保存数据,缓冲实质上没有任何意义,传统文件系统中的异步”async”和同步”sync”这两种概念的意义也不大了。
虽然从本质上讲,内存文件系统根本不需要缓冲,直接访问就能达到最好的效率,但是很难做到这一点,尤其是由于目前都还是使用内存来模拟一个磁盘设备的情况,内存文件系统事实上还是经过了缓冲。从这个地方,也可以看出目前使用的内存文件系统的弊病,内存文件系统本身占据了一份内存,磁盘缓冲又会占据一份内存,这样就导致了内存的浪费。在系统有交换分区存在的条件下,一旦系统内存紧张,该文件系统中的有关数据也会被交换到具体的交换设备上,同样避免不了对磁盘的物理访问,达不到提高性能的目的。而且一旦包括交换空间在内的所有虚拟内存用光,系统就无法正常提供服务了,从而影响系统的稳定性。
试图使内存文件系统绕过磁盘缓冲其实并不容易,这是因为在目前的操作系统中,磁盘是非常重要的一部分。非常重要的虚拟内存概念就是使用磁盘设备作为交换设备模拟内存,而磁盘缓冲而是通过内存来缓冲磁盘数据,虚拟内存、磁盘缓冲就是操作系统内存管理中的最基本元素,如何处理这两个部分,也是影响系统性能的最重要的因素。文件系统实质上是建立在操作系统的内存管理部分之上的,因此绕过这一部分,需要更困难的内核工作。
因此,对于高负载的服务器,使用内存文件系统实际上得不偿失,在系统内存很快用光的情况下,操作系统将进行磁盘交换,导致系统性能严重下降,这比起直接使用磁盘文件系统更为糟糕。事实上,在高负载的服务器环境下,允许系统使用更多的内存来缓冲磁盘数据,更有效的发挥系统的磁盘缓冲能力,在实际使用中更为有效。
使用MFS
FreeBSD下最基本的内存文件系统为MFS(Memory File System)文件系统,它是直接从虚拟内存中为文件系统申请空间。虽然它命名为MFS,实质上它还是使用的标准UFS的数据组织格式,仍然有扇区、磁盘块等基本概念,但是为了使用方便,它没有创建可以被直接访问的虚拟磁盘设备。实际上MFS是一个不完整的虚拟磁盘系统,由于它没有虚拟磁盘设备,因此导致在一些情况下它不能很方便的应用。
使用MFS需要内核中的”options MFS”支持,这个选项实质上是一个标准选项,在缺省情况下的FreeBSD内核都支持,因此一般不需要重新定制内核。然后就可以使用mount_mfs来安装内存文件系统,或者在标准mount命令中指定mfs选项。
# mount_mfs -s 131072 /dev/da0s1b /tmp
执行这个命令之后,mount_mfs就从虚拟内存中申请131072个扇区大小的内存,用来作为MFS文件系统的存储区域,并将该文件系统安装到/tmp目录下。这里使用/dev/da0s1b作为设备文件参数,这个磁盘分区为一个交换分区,它并不是实际使用的磁盘设备或虚拟磁盘,它的基本目的是用来满足mount_mfs的参数需要。即使系统中有多个交换设备,这也并不意味着MFS就只会交换到这个指定设备上,虚拟内存按照自己的规则分配物理内存或交换空间。
当然,使用交换设备作为参数事实上也起到了一些额外的作用,因为对于标准文件系统来讲,必须通过初始化的过程确定文件系统的组织格式,而MFS不需要独立的初始化过程,在mount_mfs操作的时候就同时执行了初始化,因而mount_mfs可以从这个设备文件中读取一些初始化相关的参数信息,例如每个扇区大小等,来初始化MFS文件系统。通常扇区尺寸为512字节,因此该文件系统总大小为64M。
这里就可以看出,基本的MFS是不存在虚拟磁盘设备的,mount_mfs直接申请内存并用作文件系统,而在mount命令中使用的设备参数为交换设备而非虚拟磁盘设备。一些情况下希望操作虚拟磁盘设备,那么使用MFS就不方便了。
使用vn伪设备
MFS是通过一个独立的文件系统来达到内存文件系统的目的,伪设备VN就是通过另一种方式来达到这个目的,它直接模拟一个虚拟的磁盘设备,那么在这个虚拟磁盘设备中可以应用各种不同的文件系统来保存数据。VN设备需要内核支持"pseudo-device vn"配置和/dev目录下的设备文件vn0、vn0c等,这通常不是缺省配置,需要用户重新定制内核。
VN设备主要使用文件作为虚拟磁盘的存储空间,例如将光盘的镜像文件用作虚拟光盘设备,将软盘的镜像文件作为虚拟软盘设备等等。当然模拟是有一定限度的,主要用来模拟文件系统,例如虚拟光盘设备上就没有音轨数据,无法作为CD播放等等。
显然使用内存保存虚拟磁盘数据,与使用文件相比甚至更为简单。因此,VN设备也支持使用内存来模拟一个虚拟磁盘。VN设备需要使用vnconfig程序来控制虚拟磁盘设备,那么为指定虚拟磁盘磁盘申请内存,并配置该虚拟磁盘的操作为:
# vnconfig -s 131072 /dev/vn0c
这里使用-s参数指明申请内存空间的大小,而vn0c为空闲的虚拟磁盘设备。配置好了虚拟磁盘之后,就可以使用标准的磁盘操作命令对磁盘进行操作,包括文件系统的初始化。对于虚拟磁盘设备来讲,一般不需要分区操作,而是直接进行文件系统操作,事实上虚拟磁盘通常也没有分区的概念,fdisk命令也不识别虚拟磁盘,可以将虚拟磁盘设备当作一个完整的分区设备。这是因为在Unix下本来是没有磁盘分区的概念的,这个概念是DOS/PC概念,因此FreeBSD对磁盘分区的支持限于可能存在其他系统的物理磁盘,对于只用于Unix的虚拟磁盘,就不需要这个概念了。
# disklabel -r -w /dev/vn0c auto
# newfs /dev/vn0c
# mount /dev/vn0c /tmp
由于使用VN设备比起MFS来讲要多一个创建虚拟磁盘设备的过程,因此使用vnconfig就与使用mount_mfs不同,要略微麻烦一些,除了需要经历vnconfig配置虚拟磁盘之外,还需要初始化磁盘设备、创建文件系统等步骤。
显然,由于创建了虚拟磁盘设备,在这里就不再是裸的MFS系统,而是一个更为完善的虚拟磁盘系统。显然,这里就不需要MFS的帮助,而在newfs和mount时直接使用UFS文件系统。理论上可以使用各种不同的文件类型格式,并不限于是UFS。
内存磁盘设备md
使用MFS系统,就可以最方便快捷的建立内存文件系统,使用vn设备,就可以建立内存文件系统相关的虚拟磁盘设备,基本上,这两个内存文件系统将满足绝大多数应用系统的需要。然而,一个非常重要的问题仍然存在,就是这些内存文件系统都是在系统启动之后,通过相应的配置命令进行配置的,而在一些情况下,需要在系统启动或启动之前,就配置好内存文件系统。
这种在系统启动之前要求配置好内存文件系统的例子之一就是系统安装程序,因为安装系统通常都是使用光盘或软盘启动,光盘或软盘作为系统的根文件系统当然是可行的,但总存在种种限制,例如软盘的访问速度和可靠性限制,光盘的只读限制等等。因此,目前的FreeBSD的系统安装程序,是使用内存文件系统作为安装系统的根文件系统,而不是试图将软盘或光盘作为安装系统的根文件系统。在这种情况下,通常使用一个独立的系统镜像文件,在启动之前载入内存,作为内存文件系统的初始数据来配置内存文件系统。
为了达到这个目的,就需要使用系统中的伪设备md,这需要相应的内核支持"pseudo-device md",这个选项通常需要重新定制内核。此后,还需要在/dev目录下创建相应的设备文件md0和md0c。这样使用新内核重新启动之后,就可以用使用普通磁盘一样的方法来使用它了,而不需要任何配置过程。
# disklabel -r -w /dev/md0c auto
# newfs /dev/md0c
# mount /dev/md0c /tmp
这里不需要使用任何配置程序配置虚拟磁盘设备,因为在启动过程中它就被自动配置了,内核为设备自动申请必要的内存空间。需要注意的是,使用MD设备并没有指定磁盘的大小,因为预先保留的md磁盘的大小是在编译内核时就确定的,缺省大小为20000个扇区。如果要更改内核为MD设备分配的空间大小,就需要在定制内核时改变设置选项”MD_NSECT”的值,并重新编译内核。显然,这样做显然比较麻烦,也使得它的实际用处不大。
但当MD设备用在安装系统的时候,作为最初启动的虚拟磁盘需要载入一个预先配置好的磁盘镜像文件,这个时候虚拟磁盘的大小就是由这个磁盘镜像文件决定的,而不再是MD_NSECT设置的值。正是由于MD设备在启动过程中配置,因而灵活性不足,就使得MD设备主要用在安装系统中。
启动镜像设置
对于系统安装程序,或者一些嵌入式系统来讲,存储数据的物理设备无法很方便的作为文件系统存在,例如存储设备为不可擦写的ROM或者具备一定擦写寿命的Flash中,这些设备如果用作文件系统就有各种各样的限制,此时就需要使用内存文件系统作为辅助,例如使用内存文件系统作为根文件系统,或者作为临时文件系统等等。
但是由于内存文件系统是使用不可长期保存的RAM存储器保存数据,系统重新启动或断电后其中保存的数据就不再存在,因此每次启动之后虚拟磁盘中的数据都是随机的,需要重新进行初始化操作。也可以提供一种手段为最初的虚拟磁盘提供初始数据,通常这通过使用镜像文件的方法来完成。
使用伪设备MD,就可以将一个预先准备好的文件作为md设备的镜像数据,这样MD设备一旦创建就已经具备了必要的数据,而系统启动之后立即可以访问已经具备数据的虚拟磁盘了。通过这样的操作,就可以使用虚拟磁盘文件系统作为根文件系统,启动其中的系统安装程序。
因此,这个时候就需要为MD设备准备初始镜像文件,最直接的方法是首先用确定的大小创建MD设备,初始化文件系统,安装上文件系统并复制必要的数据,然后在卸载文件系统,使用dd命令直接操作虚拟磁盘设备,将数据复制到一个镜像文件中。但是由于MD设备的大小比较不容易改变,因此这种方法并不灵活。此外,由于MD设备被作为一个虚拟的物理磁盘被系统处理,系统对它的处理事实上是和真实物理磁盘设备的处理方法也是一样的,因此也可以使用物理磁盘,创建文件系统并复制其镜像的方法来获得镜像数据。
使用虚拟磁盘MD和物理磁盘都可以得到镜像文件,但是这两种方法都不是很灵活,最好的方法是可以直接修改镜像文件本身。事实上VN设备正是用作这个处理任务,它能使用镜像文件作为虚拟磁盘的存储区域,使用VN设备创建并修改镜像文件的数据是非常适合的。因此在大小一致的条件下,就可以直接将VN设备的镜像文件复制到MD设备上,然后将MD设备安装到系统中,就可以访问到具体数据。
因此,使用VN设备预先定制好MD设备使用的镜像文件是最常用、最方便的方法。系统载入镜像文件的过程应该在启动之前完成,以便系统在启动过程中能够配置好该MD设备。事实上,镜像文件是由Boot Loader载入的,作为MD设备的缺省数据。当然,另一种选择是直接将镜像文件写入内核文件中,使得镜像数据和内核一起载入。将镜像文件写入内核的方式需要改动内核,并且写入之后就不容易改变镜像文件中的数据,因此,目前这种方式很少被用到。
为了将镜像文件写入内核,需要设置内核选项MD_ROOT_SIZE,需要指定它的尺寸大于镜像文件的尺寸,以便系统在内核中保留出大于镜像文件的自由空间,以使得写入的数据不至于覆盖内核中有用的数据。这个参数和MD_NSECT是不同的,但它也为一个虚拟磁盘预保留了空间。如果是使用Boot Loader来载入镜像文件,就不能设置这个选项,因为这将导致系统在启动时初始化多个虚拟磁盘,而只有第一个虚拟磁盘md0才是可以启动的。
当然,一个能够将MD设备作为启动根文件系统的内核,除了标准的md和MFS选项之外,还需要"options MD_ROOT"的设置选项,以保证内核搜索MD设备的作为根文件系统。这个设置也可以写作"MFS_ROOT",以便和早期的FreeBSD系统兼容,早期的FreeBSD系统没有使用md伪设备,而是使用第二种直接写入内核的MFS文件系统作为根文件系统。
几种不同方式的比较
无论是MFS,还是VN设备和MD设备,它们对数据的处理方式其实是非常类似的,MFS就是直接在内存中开辟一个UFS格式的区域,用作文件系统,这个区域其实就是一个虚拟磁盘镜像。而VN设备和MD设备是先申请内存空间,然后采用标准的文件系统工具进行处理,因此更为灵活。
最为重要的一点区别实际上是它们获得自由内存空间的方式不同,MFS和VN实际上是使用动态的方式申请虚拟内存,而md设备实际上是在系统启动之前已经分配完毕,是一种静态的方式,其实是通过内核申请空间的方式MALLOC分配的。这样就导致MFS和VN设备申请的内存是按照虚拟内存的方式进行处理,就是说它们是基于交换空间的,在物理内存不够的情况下将被自然的交换到磁盘设备中。而MD设备使用的内存是内核申请的,因而位于内核空间中,主要占用物理内存。
这两种不同的使用内存的方式,造成了这几种不同的内存文件系统的不同用途和使用限制,MFS和VN可以使用更多的交换空间,因而容量更大,使用更灵活,但在高负载的情况下由于系统交换,仍然会造成磁盘访问。虽然对于md设备,这种现象理论上不会发生,但md设备的大小是受到物理内存的限制的,占用的是宝贵的内核空间,因此主要用来处理比如启动镜像这样的情况,而很少用来处理其他任务。
除了这些文件系统之外,FreeBSD上事实上还有其他的一些内存文件系统,例如V9FS,这种文件系统的特征是一个纯粹的文件系统,没有涉及磁盘的那些扇区、块等概念等等,这些文件系统目前还不是很完善,也不是标准系统的一部分。
对于内存文件来讲,一些特色其实是非常有意义的,例如文件系统的压缩技术,由于它能够减少内存空间的大小,因此对于内存文件系统是非常有意义的,可以用于嵌入式系统等领域。虽然有人在这个方面曾经做过努力,但目前这种特性在FreeBSD下还不能直接得到。
FreeBSD 5.0中内存文件系统
当对这几种不同的文件系统进行分析比较之后,可以发现它们存在很多种共性,例如MFS和vn设备对于内存的使用方式是相同的,而vn设备和md设备由于都是虚拟磁盘,因此其内容是相同的,这也导致了可以使用vn设备为md设备创建镜像文件。因此,完全可以将这三种不同的使用内存文件系统的方式合并起来,使设置和操作更为简单易用。
事实上,之所以存在这几种不同的内存文件系统,源于FreeBSD的历史开发过程。最早的内存文件系统显然是MFS,但由于不存在虚拟磁盘,存在种种不统一和协调的地方,因此后来就设计了MD设备。VN设备则与此无关,它最初就是为了文件作为存储设备而设计的,但使用内存作为存储显然也十分直接和简单。
因此,在最新的FreeBSD 5.0版本中,这些内存文件系统的设置都统一起来,特别是将vn设备和md设备的功能都统一到新版本的伪设备md中。并且在FreeBSD 5.0下,设备的创建和内存的分配更为方便,不象在4.x之前,系统中的配置选项只能指定一定数量的虚拟磁盘设备(缺省是一个设备)。而且,由于FreeBSD 5.0使用了DEVFS特色,设备文件的创建是自动进行的,不再需要手工使用MAKEDEV命令创建设备文件入口。
例如,需要从虚拟内存中申请内存创建内存文件系统的时候,就需要执行md设备的控制程序mdconfig:
# mdconfig -a -t swap -s 30M
这就会在虚拟内存中申请30M空间,并创建虚拟磁盘,使用的虚拟设备为第一个md设备md0,如果系统中的md0设备已经被占用,那么mdconfig就依序向后寻找下一个空余的md设备,并创建它。由于mdconfig能自动创建新的设备,这样就解决了在内核配置文件指定伪设备数量的问题。
也可以使用-u指定使用的md伪设备的序号,例如下列命令将创建md10,并使用它作为虚拟磁盘设备:
# mdconfig -a -t swap -s 30M -u 10
上面的命令都是使用虚拟内存空间作为数据存储空间,是由swap参数指定的。同样,使用mdconfig也能从内核空间中创建虚拟磁盘,此时-t指定的存储类型参数为malloc,这告诉内核使用内核的MALLOC方法申请内存。
# mdconfig -a -t malloc -s 30M
这种方法就相当于老的md设备的申请内存的方法,但显然更为灵活,因为可以在具体使用过程中申请内存和设备,这是因为FreeBSD 5.0的内核允许更灵活的使用MALLOC内存申请方式。当然,一般还是主要使用swap申请虚拟空间的内存。
由于mdconfig和伪设备md将完全代替vnconfig和伪设备vn,那么使用mdconfig也能创建使用文件作为虚拟磁盘的与vn兼容的方式,这需要指定存储类型参数为vnode,并使用 -f指定具体的存储数据的物理文件名字。
# mdconfig -a -t vnode -f imagefile -s 30M
在使用mdconfig配置好虚拟磁盘之后,就可以使用disklabel、newfs、mount等管理虚拟磁盘。而在不需要这些虚拟磁盘的时候,就可以卸载相应的文件系统,并使用mdconfig删除指定的磁盘等。
# mdconfig -l
使用参数”-l”,则mdconfig列出系统中所有的虚拟磁盘设备。
# mdconfig -d -u 0
为了删除指定了磁盘,需要使用”-d"参数,而使用"-u 0”则指定删除序号为0的虚拟磁盘,即md0。