文件系统的安装卸载以及读写Word文档格式.docx
- 文档编号:21704625
- 上传时间:2023-01-31
- 格式:DOCX
- 页数:14
- 大小:92.46KB
文件系统的安装卸载以及读写Word文档格式.docx
《文件系统的安装卸载以及读写Word文档格式.docx》由会员分享,可在线阅读,更多相关《文件系统的安装卸载以及读写Word文档格式.docx(14页珍藏版)》请在冰豆网上搜索。
其实,在系统启动后你所看到的文件系统,都是在启动时安装的。
如果你需要自己(一般是超级用户)安装文件系统,则需要指定三种信息:
文件系统的名称、包含文件系统的物理块设备、文件系统在已有文件系统中的安装点。
例如:
$mount-tiso9660/dev/cdrom/mnt/cdrom
其中,iso9660是光驱文件系统的名称,/dev/cdrom是包含文件系统的物理块设备,/mnt/cdrom就是将要安装到的目录,即安装点。
从这个例子可以看出,安装一个文件系统实际上是安装一个物理设备。
把一个文件系统(或设备)安装到一个安装点时要用到的主要数据结构为vfsmount,定义如下:
structvfsmount
{
structlist_headmnt_hash;
/*哈希表*/
structvfsmount*mnt_parent;
/*指向上一层安装点的指针*/
structdentry*mnt_mountpoint;
/*安装点的目录项*/
structdentry*mnt_root;
/*安装树的根*/
structsuper_block*mnt_sb;
/*指向超极块的指针*/
structlist_headmnt_mounts;
/*子链表*/
structlist_headmnt_child;
/*通过mnt_child进行遍历*/
atomic_tmnt_count;
intmnt_flags;
char*mnt_devname;
/*设备名,如/dev/hda1*/
structlist_headmnt_list;
};
下面对结构中的主要域给予进一步说明:
(1)为了对系统中的所有安装点进行快速查找,内核把它们按哈希表来组织,mnt_hash就是形成哈希表的队列指针。
(2)mnt_mountpoint是指向安装点dentry结构的指针。
而dentry指针指向安装点所在目录树中根目录的dentry结构。
(3)mnt_parent是指向上一层安装点的指针。
如果当前的安装点没有上一层安装点(如根设备),则这个指针为NULL。
同时,vfsmount结构中还有mnt_mounts和mnt_child两个队列头,只要上一层vfsmount结构存在,就把当前vfsmount结构中mnt_child链入上一层vfsmount结构的mnt_mounts队列中。
这样就形成一颗设备安装的树结构,从一个vfsmount结构的mnt_mounts队列开始,可以找到所有直接或间接安装在这个安装点上的其他设备。
如图8.2。
(4)mnt_sb指向所安装设备的超级块结构super_block。
(5)mnt_list是指向vfsmount结构所形成链表的头指针。
每个文件系统都有它自己的根目录,如果某个文件系统(如)的根目录是系统目录树的根目录,那么该文件系统称为根文件系统。
而其他文件系统可以安装在系统的目录树上,把这些文件系统要插入的目录就称为安装点。
根文件系统的安装函数为mount_root()。
一旦在系统中安装了根文件系统,就可以安装其他的文件系统。
每个文件系统都可以安装在系统目录树中的一个目录上。
前面我们介绍了以命令方式来安装文件系统,在用户程序中要安装一个文件系统则可以调用mount()系统调用,其内核实现函数为sys_mount()。
安装过程主要工作是创建安装点对象,将其挂接到根文件系统的指定安装点下,然后初始化超级块对象,从而获得文件系统基本信息和相关的操作。
8.3.3文件系统的卸载
如果文件系统中的文件当前正在使用,该文件系统是不能被卸载的。
如果文件系统中的文件或目录正在使用,则VFS索引节点缓冲区中可能包含相应的VFS索引节点。
内核根据文件系统所在设备的标识符,检查在索引节点缓冲区中否有来自该文件系统的VFS索引节点,如果有且使用计数大于0,则说明该文件系统正在被使用,因此,该文件系统不能被卸载。
否则,查看对应的VFS超级块,如果该文件系统的VFS超级块标志为“脏”,则必须将超级块信息写回磁盘。
上述过程结束之后,对应的VFS超级块被释放,vfsmount数据结构将从vfsmntlist链表中断开并被释放。
具体的实现代码为fs/super.c中的sys_umount()函数,在此不再进行详细的讨论。
8.5文件的打开与读写
让我们重新考虑一下在本章开始所提到的例子,用户发出一条shell命令:
把/floppy/TEST中的MS-DOS文件拷贝到/tmp/test中的Ext2文件中。
命令shell调用外部程序(如cp),在实现cp的代码片段中,涉及文件系统常见的三种文件操作,也就是三个系统调用open()、read()和write()。
下面就对这三个系统调用的实现及涉及的相关知识给予介绍。
8.5.1文件打开
open()系统调用就是打开文件,它返回一个文件描述符。
所谓打开文件实质上是在进程与文件之间建立起一种连接,而“文件描述符”唯一地标识着这样一个连接。
在文件系统的处理中,每当一个进程打开一个文件时,就建立起一个独立的读/写文件“上下文”,这个“上下文”由file数据结构表示。
另外,打开文件,还意味着将目标文件的索引节点从磁盘载入内存,并对其进行初始化。
open操作在内核中是由sys_open()函数完成的,其代码如下:
asmlinkagelongsys_open(constchar*filename,intflags,intmode)
char*tmp;
intfd,error;
tmp=getname(filename);
fd=PTR_ERR(tmp);
if(!
IS_ERR(tmp)){
fd=get_unused_fd();
if(fd>
=0){
structfile*f=filp_open(tmp,flags,mode);
error=PTR_ERR(f);
if(IS_ERR(f))
gotoout_error;
fd_install(fd,f);
}
out:
putname(tmp);
returnfd;
out_error:
put_unused_fd(fd);
fd=error;
gotoout;
}
其中,调用参数filename是文件的路径名(绝对路径名或相对路径名);
mode表示打开的模式,如“只读”等;
而flag则包含许多标志位,用以表示打开模式以外的一些属性和要求。
函数通过getname()从用户空间把文件的路径名拷贝到内核空间,并通过get_unused_fd()从当前进程的“打开文件表”中找到一个空闲的表项,该表项的下标即为“文件描述符fd”。
然后,通过file_open()找到文件名对应索引节点的dentry结构以及ionde结构,并找到或创建一个由file数据结构代表的读/写文件的“上下文”。
通过fd_install()函数,将新建的file数据结构的指针“安装”到当前进程的file_struct结构中,也就是已打开文件指针数组中,其位置即已分配的下标fd。
在以上过程中,如果出错,则将分配的文件描述符、file结构收回,inode也被释放,函数返回一个负数以示出错,其中PTR_ERR()和IS_ERR()是出错处理函数。
由此可以看到,打开文件后,文件相关的“上下文”、索引节点、目录对象等都已经生成,下一步就是实际的文件读写操作了。
8.5.2文件读写
让我们再回到cp例子的代码。
open()系统调用返回两个文件描述符,分别存放在inf和outf变量中。
然后,程序开始循环。
在每次循环中,/floppy/TEST文件的一部分被拷贝到一个缓冲区中,然后,这个缓冲区中的数据又被拷贝到/tmp/test文件。
read()和write()系统调用非常相似。
它们都需要三个参数:
一个文件描述符fd、一个内存区的地址buf(该缓冲区包含要传送的数据),以及一个数count(指定应该传送多少字节)。
当然,read()把数据从文件传送到缓冲区,而write()执行相反的操作。
两个系统调用都返回所成功传送的字节数,或者发一个错误条件的信号并返回-1。
简而言之,read()和write()系统调用所对应的内核函数sys_read()和sys_write()执行几乎相同的步骤:
(1)file=fget(fd),也就是调用fget()从fd获取相应文件对象的地址file,并把引用计数器file->
f_count加1。
(2)检查file->
f_mode中的标志是否允许所请求的访问(读或写操作)。
(3)调用locks_verify_area()检查对要访问的文件部分是否有强制锁。
(4)调用file->
f_op->
read或file->
write来传送数据。
这两个函数都返回实际传送的字节数。
另一方面的作用是,文件指针被更新。
(5)调用fput()以减少引用计数器file->
f_count的值。
(6)返回实际传送的字节数。
以上概述了文件读写的基本步骤,但是f_op->
read或f_op->
write两个方法属于VFS提供的抽象方法,对于具体的文件系统,必须调用针对该具体文件系统的具体方法。
而对基于磁盘的文件系统,比如EXT2等,所调用的具体的读写方法都是Linux内核已提供的通用函数generic_file_read()或generic_file_write()。
简单地说,这些通用函数的作用是确定正被访问数据所在物理块的位置,并激活块设备驱动程序开始数据传送,所以基于磁盘的文件系统没必要再实现专用函数了。
下面对generic_file_read()函数所执行的主要步骤简述如下:
第一步:
利用给定的文件偏移量和读写字节数计算出数据所在页的逻辑号(index)。
第二步:
开始传送数据页。
第三步:
更新文件指针,记录时间戳等收尾工作。
其中最复杂的是第二步,首先内核会检查数据是否已经驻存在页缓冲区,如果在页缓冲区中发现所需数据而且数据是有效的,那么内核就可以从缓存中快速返回需要的页;
否则如果页中的数据是无效的,那么内核将分配一个新页面,然后将其加入到页缓冲区中,随即调用address_space对象的readpage方法,激活相应的函数进行磁盘到页的I/O数据传送。
在进行一定预读,并完成数据传送之后,还要调用file_read_actor()方法把页中的数据拷贝到用户态缓冲区,最后进行一些收尾等工作,如更新标志等。
总之,从用户发出读请求到最终的从磁盘读取数据,可以概括为以下几步:
(1)用户界面层——负责从用户函数经过系统调用进入内核;
(2)基本文件系统层——负责调用文件读方法,从缓冲区中搜索数据页,返回给用户。
(3)I/O调度层——负责对请求排队,从而提高吞吐量。
(4)I/O传输层——利用任务队列,异步操作设备控制器,完成数据传输。
图8.9给出读操作的逻辑流程。
图8.9读操作流程
8.6编写一个文件系统
文件系统比较庞杂,很难编写一个恰当的例子来演示文件系统的实现。
如果写一个纯虚的文件系统(没有存储设备),因为虚文件系统不涉及I/O操作,缺少实现文件系统中至关重要的部分,因此必要性不大;
如果写一个实际文件系统,但是涉及的东西太多,不容易让读者简明扼要的理解文件系统的实现。
幸好,内核中提供的romfs文件系统是个非常理想的实例,它既有实际应用结构,也清晰明了,因此我们以romfs为实例分析文件系统的实现。
8.6.1Linux文件系统的实现要素
编写新文件系统涉及一些基本对象,具体地说,需要建立“一个结构四个操作表”:
●文件系统类型结构(file_system_type)
●超级块操作表(super_operations)
●索引节点操作表(inode_operations)
●页缓冲区表(address_space_operations)
●文件操作表(file_operations)
对上述几种结构的操作贯穿了文件系统实现的主要过程,理清晰这几种结构之间的关系是编写文件系统的基础,如图8.10所示,下来我们具体分析这几个结构和文件系统实现的要点。
首先,必须建立一个文件系统类型(file_system_type)来描述文件系统,它含有文件系统的名称、类型标志以及get_sb()等操作。
当安装文件系统时,系统会对该文件系统进行注册,即填充file_system_type结构,然后调用get_sb()函数来建立该文件系统的超级块。
注意对于基于块的文件系统,如Ext3、romfs等,需要从文件系统的宿主设备读入超级块,然后在内存中建立对应的超级块,如果是虚文件系统(如proc文件系统),则不读取宿主设备的信息(因为它没有宿主设备),而是在现场创建一个超级块,这项任务也由get_sb()完成。
可以说,超级块是一切文件操作的鼻祖,因为超级块是我们寻找索引节点的唯一源头。
我们操作文件必然需要获得其对应的索引节点(或从宿主设备读取或现场建立),而获取索引节点是通过超级块操作表提供的read_inode()函数完成的。
同样操作索引节点的底层次任务,如创建一个索引节点、释放一个索引节点,也都是通过超级块操作表提供的有关函数完成的。
所以超级块操作表(super_operations)是第二个需要创建的数据结构。
除了派生或释放索引节点等操作是由超级块操作表中的函数完成外,索引节点还需要许多自操作函数,比如lookup()搜索索引节点,建立符号链接等,这些函数都包含在索引节点操作表中,因此索引节点操作表(inode_operations)是第三个需要创建的数据结构。
为了提高文件系统的读写效率,Linux内核设计了I/O缓存机制。
所有的数据无论出入都会经过系统管理的缓冲区,不过,对基于非块的文件系统则可跳过该机制。
页缓冲区同样提供了页缓冲区操作表(address_space_operations),其中包含有readpage()、writepage()等函数负责对页缓冲区中的页进行读写等操作。
文件系统最终要和用户交互,这是通过文件操作表(file_operations)完成的,该表中包含有关用户读写文件、打开、关闭、映射文件等用户接口。
一般来说,基于块的文件系统的实现都离不开以上5种数据结构。
但根据文件系统的特点(如有的文件系统只可读、有的没有目录),并非要实现操作表中的全部函数,因为有的函数系统已经实现,而有的函数不必实现。
8.6.2什么是Romfs文件系统
Romfs是一种相对简单、占用空间较少的文件系统。
空间的节约来自于两个方面:
首先内核支持Romfs文件系统比支持Ext3文件系统需要更少的代码;
其次Romfs文件系统相对简单,在建立文件系统超级块(Superblock)需要更少的存储空间。
Romfs是只读的文件系统,禁止写操作,因此系统同时需要虚拟盘(RAMDISK)支持临时文件和数据文件的存储。
Romfs是在嵌入式设备上常用的一种文件系统,具备体积小,可靠性好,读取速度快等优点。
同时支持目录,符号链接,硬链接,设备文件。
但也有其局限性。
Romfs是一种只读文件系统,同时由于Romfs本身设计上的原因,使得Romfs支持的最大文件不超过256M。
Linux,uclinux都支持Romfs文件系统。
除Romfs外,其它常用的嵌入式设备的文件系统还有CRAMFS,JFFS2等,它们各有特色。
下面我们来分析它的实现方法,为读者勾勒出编写新文件系统的思路和步骤。
8.6.3Romfs文件系统布局与文件结构
设计一个文件系统首先要确定它的数据存储方式。
不同的数据存储方式对文件系统占用空间,读写效率,查找速度等主要性能有极大影响。
Romfs是一种只读的文件系统,它使用顺序存储方式,所有数据都是顺序存放的。
因此Romfs中的数据一旦确定就无法修改,这是Romfs只能是一种只读文件系统的原因,它的数据存储方式决定了无法对Romfs进行写操作。
由于采用了顺序存放策略,Romfs中每个文件的数据都能连续存放,读取过程中只需要一次寻址操作,进而就可以读入整块数据,因此Romfs中读取数据效率很高。
在Linux内核源代码的Document/fs/romfs中介绍了romfs文件系统的布局和文件结构,如图8.11所示。
图8.11romfs文件系统布局
从图可以看出,文件系统中每部分信息都是16位对齐的,也就是说存储的偏移量必须最后4位为0,这样作是为了提高访问速度。
如果信息不足时,需要填充0以保证所有信息的开始位置都以16来对齐。
文件系统的开始8个字节存储文件系统的ASCII形式的名称,在这里是”-rom1fs-”;
接着4个字节记录文件大小;
然后的4个字节存储的是文件系统开始处512字节的检验和;
接下来是卷名;
最后是第一个文件的文件头,从这里开始依次存储的就是文件本身的信息了。
Romfs的文件结构如图8.12所示。
图8.12Romfs的文件结构
8.6.4具体实现的对象
针对文件系统布局和文件结构,Romfs文件系统定义了一个磁盘超级块结构和文件的inode结构:
structromfs_super_block{
__u32word0;
__u32word1;
__u32size;
_u32checksum;
charname[0];
该结构用于识别整个Romfs文件系统,大小为512字节。
word0初始值为'
-'
'
r'
o'
m'
,word1初始值为'
1'
f'
s'
,通过这两个字操作系统确定这是一个Romfs文件系统。
size字段用于记录整个文件系统的大小,理论上Romfs大小最多可以达到4G。
checksum是前512字节的校验和,用于确认整个文件系统结构数据的正确性。
前面4个字段占用了16字节,剩下的都可以用作文件系统的卷名,如果整个首部不足512字节便用0填充,以保证首部符合16字节对齐的规则。
structromfs_inode{
__u32next;
__u32spec;
__u32size;
__u32checksum;
charname[0];
next字段是下一个文件的偏移地址,该地址的后4位是保留的,用于记录文件模式信息,其中前两位为文件类型,后两位则标识该文件是否为可执行文件。
因此Romfs用于文件寻址的字段实际上只有28bit,所以Romfs中文件大小不能超过256M。
spec字段用于标识该文件类型。
目前Romfs支持的文件类型包括普通文件,目录文件,符号链接,块设备和字符设备文件。
size是文件大小,checksum是校验和,校验内容包括文件名,填充字段。
name是文件名首地址,文件名长度必须保证16字节对齐,不足的部分可以用0填充。
上述两种结构分别描述了文件系统结构与文件结构,它们将在内核装配超级块对象和索引节点对象时被使用。
Romfs文件系统首先要定义的对象是文件系统类型romfs_fs_type:
staticDECLARE_FSTYPE_DEV(romfs_fs_type,"
romfs"
romfs_read_super);
DECLARE_FSTYPE_DEV是内核定义的一个宏,用来建立file_system_type结构。
从上面的申明可以看出,file_system_type结构的类型变量为romfs_fs_type,文件系统名为”romfs”,读超级块的函数为romfs_read_super()。
Romfs_read_super()从磁盘读取磁盘超级块,主要步骤如下:
1.装配超级块,具体步骤为:
(1)初始化超级块对象某些域。
(2)从设备中读取磁盘第0块到内存,即调用函数bread(dev,0,ROMBSIZE),其中dev是文件系统安装时指定的设备,0为设备的首块,也就是磁盘超级块,ROMBSIZE是读取的大小。
(3)检验磁盘超级块中的校验和。
(4)继续初始化超级块对象某些域。
2.给超级块对象的操作表赋值(s->
s_op=&
romfs_ops)
3.给根目录分配目录项。
其中,romfs文件系统在超级块操作表中实现了两个函数:
staticstructsuper_operationsromfs_ops={
read_inode:
romfs_read_inode,
statfs:
romfs_statfs,
第一个函数romfs_read_inode(inode)是从磁盘读取数据填充参数指定的索引节点,主要步骤如下:
1.根据inode参数寻找对应的索引节点。
2.初始化索引节点某些域
3.根据文件类型设置索引节点的相应操作表
(1)如果是目录文件,则将索引节点表设
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 文件系统 安装 卸载 以及 读写