0906第六章Linux的文件系统设计与编程实现.docx

上传人:b****9 文档编号:25531899 上传时间:2023-06-09 格式:DOCX 页数:80 大小:193.89KB
下载 相关 举报
0906第六章Linux的文件系统设计与编程实现.docx_第1页
第1页 / 共80页
0906第六章Linux的文件系统设计与编程实现.docx_第2页
第2页 / 共80页
0906第六章Linux的文件系统设计与编程实现.docx_第3页
第3页 / 共80页
0906第六章Linux的文件系统设计与编程实现.docx_第4页
第4页 / 共80页
0906第六章Linux的文件系统设计与编程实现.docx_第5页
第5页 / 共80页
点击查看更多>>
下载资源
资源描述

0906第六章Linux的文件系统设计与编程实现.docx

《0906第六章Linux的文件系统设计与编程实现.docx》由会员分享,可在线阅读,更多相关《0906第六章Linux的文件系统设计与编程实现.docx(80页珍藏版)》请在冰豆网上搜索。

0906第六章Linux的文件系统设计与编程实现.docx

0906第六章Linux的文件系统设计与编程实现

第六章Linux文件系统与编程

技巧与提示

思考与练习

6.1文件系统概述

Linux文件系统对用户而言只有一个文件树,它不是由设备标识符如设备号或设备名称来表示的。

该树的根在顶部,称为根目录(root),以“/”表示,所有的文件和外部设备都以文件的形式挂接在这棵树上,包括硬盘、软盘、光驱、调制解调器等,这和以驱动器盘符为基础的Windows系统完全不同。

当磁盘分区之一被“mount”到文件树中称为"安装点(mountpoint)的目录上时,就成为了该目录树的一个组成部分。

当windows的C:

盘未被安装时,/mnt/winc是根文件系统中的一个普通子目录(格式可能是LinuxEXT2/3等),里面可以存放任何数据。

而FAT32、NTFS等格式的C盘上的数据独立于Linux系统,不能被Linux系统读取。

6.1.1文件系统的目录树

通常的Linux发行版的根目录下大都包含以下目录:

/bin/etc/lost+found/sbin/var/boot/root

/home/mnt/tmp/dev/lib/proc/usr

这些目录的基本性质在下面进行了简要的介绍,以便读者能够快速掌握其用法:

1./bin和/sbin

这两个目录中都包含串bin,是因为其中的程序都是二进制(binaryfiles)的可执行程序文件,用于维护Linux系统的大部分基本程序。

/bin目录下通常存放如login、shell等系统处理或文件操作实用程序、系统实用程序、压缩工具。

/sbin目录下通常存放基本的系统和系统维护程序,如:

fsck、fdisk、mkfs、shutdown、lilo、init等。

存放在/sbin中的程序只能由管理员root来执行。

2./etc

该目录一般用来存放程序所需的系统配置文件,如passwd、shadow、fstab、host、motd、profile、shells、services、lilo.conf。

3./lost+found

该目录用来存放在系统非正常关机后重启系统时,无处存放的中间文件。

4./boot

该目录中存放着和系统启动有关系的各种文件,包括系统的引导程序和系统核心部分。

5./root

该目录是系统管理员root的主目录。

6./home

系统中所有用户的主目录都存放在/home中,它包含实际用户的主目录和其他用户的主目录。

Linux同UNIX的不同之处是,Linux中root用户的主目录通常是在/root或/home/root下,而UNIX通常是在根目录下。

7./mnt

这是CD-ROM、软盘、Zip盘或Jaz等可移动介质的安装目录,其中的每个子目录就是某种设备类型的一个安装点,如/cdrom、/floppy、/zip。

如果要使用这些设备,还需要用mount命令从/dev目录中将外部设备挂接上。

8./tmp和/var

这两个目录用来存放临时文件和经常变动的文件。

9./dev

该目录存放各种外部设备的镜像文件。

如第一个软盘驱动器的名字是fd0,第一个硬盘的名字是hda,硬盘中的第一个分区是hda1,第二个分区是hda2;第一个光驱的名字是hdc等,以此类推。

10./usr

该目录用来存放与系统的用户直接相关的程序或文件,这里面有每一个系统用户的主目录,相对于用户的根目录“/”。

11./proc

该目录是当前系统中运行的进程的虚拟镜像,在这里可以看到由当前运行的进程号组成的一些目录,还有一个记录当前内存内容的kernel文件。

6.1.2文件系统的结构

文件系统上的所有文件在计算机里都是数据的集合。

一个文件系统不仅包括所有文件的内容,还包括文件系统的结构、用户与程序开发人员可能用到的关于文件、目录连接和文件权限等信息。

一个成功的文件系统必须安全可靠地保存这些信息,一个操作系统最基本的一致性和完整性首先依赖于文件系统的可靠性。

图6.1描述了一个文件系统的视图。

 

图6.1文件系统视图

其中,引导块的作用是在文件系统启动时,将操作系统核心程序从磁盘装入内存,然后由该核心程序来指挥文件系统的初始化工作。

超级块包含了整个文件系统所需的管理信息,包括系统的基本尺寸和形式。

系统管理程序利用这些信息对系统进行管理和维护。

inode链(索引节点链)中包含了很多inode(索引节点),一个文件(或目录)占据一个索引节点。

第一个索引节点是该文件系统的根节点。

数据块用来存放文件数据或者管理数据(如一级间址块、二级间址块等)。

在使用了Ext2文件系统后,Linux系统引入了虚拟文件系统(VirtualFileSystem),简称VFS。

通过它在操作系统和真正的文件系统之间提供一个接口。

对于mount到Linux上的不同文件系统,所有与文件系统相关的细节问题都被翻译成统一的格式,这样就实现了所有Linux支持的文件系统对Linux内核、应用程序和用户的透明化。

6.1.3Ext2文件系统

第二代扩展文件系统ext2的设计目标是为Linux提供专用的可扩展和高性能的文件系统,是Linux世界里最成功的文件系统,不同的Linux发行版本都使用ext2文件系统。

ext2的基本前提是文件中的数据是在数据块中保存的,这些数据块长度相等且长度可变,某个ext2文件系统的块大小在创建(使用mke2fs)时设置。

每个文件的大小最好是和刚好大于它的块大小的整数倍相等。

如果块大小为1024字节,而一个1025字节长的文件将占据两个1024字节大小的块。

这样你不得不浪费差不多一半的空间。

我们通常需要在CPU的内存利用率和磁盘空间使用上进行折中。

大多数操作系统,包括Linux在内,为了减少CPU的工作负载而被迫选择相对较低的磁盘空间利用率。

并不是文件中每个块都包含数据,其中有些块被用来包含描述此文件系统结构的信息。

ext2通过一个inode结构来描述文件系统中的文件并确定此文件系统的拓扑结构。

inode结构用来描述文件中的数据占据哪个块以及文件的存取权限、文件修改时间及文件类型。

EXT2文件系统中的每个文件用一个inode来表示,且每个inode都有唯一的编号。

文件系统中所有的inode都被保存在inode表中。

ext2目录仅是一个包含指向其目录入口指针的特殊文件(也用inode表示)。

Block

Group0

……

Block

Groupn-1

Block

Groupn

 

SuperBlock

GroupDescriptors

BlockBitmap

InodeBitmap

InodeTable

DataBlocks

图6.2ext2文件系统的物理分布

图6.2给出了占用一系列数据块的ext2文件系统的布局。

对文件系统而言,文件仅是一系列可读写的数据块。

文件系统并不需要了解数据块应该放置到物理介质上的什么位置,这些都是设备驱动程序的任务。

当文件系统需要从包含它的块设备中读取信息或数据时,它将请求底层的设备驱动程序读取一个基本块大小的整数倍的数据块。

ext2文件系统将它所使用的逻辑分区划分成数据块组,每个数据块组都包括保持文件系统完整性的信息和在数据块组中保存的文件和目录的信息,其中保持文件系统完整性的信息对于出现故障时恢复文件系统是必需的。

1.inode

在ext2文件系统中,inode是建立数据块的基础。

文件系统中的每个文件与目录由唯一的inode来描述。

每个数据块组的ext2_inode被保存在inode表中,同时系统还用一个位图来跟踪已分配和未分配的inode。

图6.3给出了ext2_inode表的格式,包含以下信息:

图6.2ext2_inode表

(1)状态(mode):

它包含两类信息。

一是inode描述的内容,二是用户的使用权限。

ext2中的inode可以表示一个文件、目录、符号连接、块设备、字符设备或FIFO等。

(2)所有者信息(OwnerInformation):

表示此文件或目录所有者的用户和组标识符。

文件系统根据它可以正确设置文件的访问权限。

(3)大小(Size):

以字节计算的文件大小。

(4)时间戳(Timestamps):

inode的创建时间和最后一次被修改的时间。

(5)数据块(Datablocks):

指向此inode描述的包含数据的块指针。

前12个指针是直接指向物理数据块的指针,后3个指针指向下一级多级间接指针。

例如,二级间接指针指向一级间接指针,而这些指针又指向一些数据块。

这意味着访问文件尺寸小于或等于12个数据块的文件将比访问大文件快得多。

在图6.2中,inode表里用于记录数据块地址的就是inode结构里的i_data[15]成员,它的类型为整数,在Linux系统中,整数占有四个字节。

ext2文件系统inode大小为128字节,inode在inode表中依次存放,其数据结构定义为:

structext2_inode{

__u16i_mode;

__u16i_uid;/*拥有者的用户ID*/

__u32i_size;/*文件大小*/

__u32i_atime;/*最近一次访问时间*/

__u32i_ctime;/*创建时间*/

__u32i_mtime;/*最近一次修改时间*/

__u16i_gid;/*文件的组ID*/

__u32i_blocks;/*分配给该文件的磁盘块的数目*/

__u32i_block[EXT2_N_BLOCKS];/*指向磁盘块的指针*/

......};

在Linux中用于描述文件系统inode信息的数据定义(见include/Linux/ext2_fs_i.h):

structext2_inode_info{

_u32i_data[15];/*用来记录数据块地址*/

_u32i_flags;

_u32i_faddr;

_u8i_frag_no;

_u8i_frag_size;

_u16i_osync;

_u32i_file_acl;

_u32i_dir_acl;

_u32i_dtime;

_u32i_version;

_u32i_block_group;/*inode所在的块组*/

_u32i_next_alloc_block;/*下一次分配的块*/

_u32i_next_alloc_goal;/*下一次分配的对象*/

_u32i_prealloc_block;/*文件系统中为该文件预分配的第一个数据块*/

_u32i_prealloc_count;/*文件系统中为该文件预分配的数据块总数*/

_u32i_high_size;

inti_new_inode:

1;/*标志位,确定是否为新分配的inode*/

};

在inode中,前12个数据块指针直接指向包含文件数据的数据块,后三个指针则是间接数据块指针,这样设计的目的是为了适应文件大小的变化和提高系统效率。

假定文件的最大尺寸为4MB,每个数据块的大小为1024字节。

如果inode全部使用直接数据块指针,则需要4096个数据块指针,而实际上文件系统中的文件大部分都不可能有如此之大。

对于一个长度为1MB的文件,实际只需要1024个数据块指针,因此大量的数据块指针被浪费了。

ext2文件系统采用多级数据块指针解决上述问题。

假设数据块的大小为1KB(1024字节),利用前12个直接指针,可以保存最大为12KB的文件,对这些尺寸的文件的访问将是非常快速的。

如果文件大小超过了12KB,则利用单级间接块指针。

这一指针指向保存数据块指针的数据块,该数据块中的每一个指针都指向包含实际数据的数据块。

如果每个数据块指针用4个字节表示,则一级间接块指针所指的数据块可以存储256(1024/4)个数据块的指针。

这样一来,一个一级间接块总共能存储256KB的数据。

当文件超过268KB(12+256)时,继续利用二级、三级间接块指针。

不难算出,利用二级间接块指针能存储的数据量为64MB(256*256K),利用三级间接块指针能存储的数据量为16GB(256*64M)。

但每个地址由32位编码而成,因此所能表示的最大地址值为4GB,这就是Linux文件大小的极限,也就是说整个文件系统都被一个文件所占满。

这种文件结构最大的好处是用固定尺寸和索引来存储大型文件,既可处理含有大量数据的文件,也不会使索引过分膨胀,形成系统额外的负担。

下面分析几个存取数据的应用实例。

【例6.1】假设某进程要存取一个文件的字节偏移量为2500的数据。

系统核心程序计算出该字节在第3个直接块(2500/1024=2…452,所以,2500在第三个直接块中),于是读取2号数据块567,在该块的第452号字节处找到所需的信息。

如图6.3所示。

【例6.2】有个进程要存取文件A内字节偏移量为208,450的数据,由于208,450大于12K,但小于268K,说明该地址存在于一级间接块所指的块中。

通过计算:

(208,450-12*1,024)/1,024=191…578。

得知,数据的地址存在于单级间接块所指向的块的第191号空间中,该位置指向的是数据块3650,可在该块的第578号字节处找到所需的信息。

如图6.3所示。

在图6.3中,有的块的进入点的值为零,这表示此逻辑块没有存放数据。

 

图6.3文件存取数据块的实例

2.目录:

在ext2文件系统中,所有目录都是特殊文件,也是文件路径的组成部分。

一个目录由多个目录项组成,图6.4描述出了目录项的结构。

图6.4目录项结构图

如:

file1文件的目录

long_file_name文件的目录

目录项数据结构的定义如下(在include/Linux/ext2_fs.h文件中):

structext2_dir_entry{

_u32inode;/*该目录项的inode号,用它可以查找inode表中对应的inode*/

_u16rec_len;/*用字节表示的目录项的长度*/

_u16name_len;/*用字节表示的文件名的长度*/

charname[EXT2_NAME_LEN];/*文件名*/

};

每个目录的前两个目录项始终是标准的“.”和“..”,分别指向当前目录、父目录的inode。

3.文件检索:

当用户打开一个文件时,首先要指定文件名。

根据文件名,文件系统首先搜索文件的inode,然后才能存取文件的数据。

以检索/usr/include/stdio.h文件为例。

文件检索顺序为:

根目录/的inode→usr目录项的inode→include目录项的inode→stdio.h文件的inode。

4.数据块的管理:

文件系统普遍存在碎片问题,ext2文件系统解决这个问题的办法是:

先为文件分配物理上相邻的数据块,至少让数据块保持在同一块组中。

找不到时才在另外的块组中进行分配。

当一个进程试图向文件写数据时,ext2文件系统首先检查文件的已分配数据块中是否有足够的空间容纳这些数据。

若没有,就要分配新数据块,分配过程中进程必须等待。

ext2的数据块分配程序首先锁定文件系统的超级块,Linux不允许两个以上进程同时更改超级块,这样,其它访问超级块的进程被挂起,直至前一个进程释放超级块。

对超级块的分配用先进先出策略即FCFS(FirstComeFirstServed)。

进程锁定超级块后,开始检查当前文件系统是否有足够的空闲块,若没有,分配失败,进程解除对超级块的锁定并返回。

6.1.4VFS

严格地说,VFS并不是一种实际的文件系统。

它只在内存中存在,在系统启动时建立,在系统关闭时消亡。

VFS的功能主要包括:

记录可用的文件系统的类型、将设备同对应的文件系统联系起来、处理一些面向文件的通用操作、涉及到针对文件系统的操作时,VFS把它们映射到与控制文件、目录、以及inode相关的物理文件系统。

图6.5给出了Linux内核中虚拟文件系统和实际文件系统之间的关系。

此虚拟文件系统必须能够管理在任何时刻mount到系统的不同文件系统。

它通过维护一些描述整个虚拟文件系统和实际已安装文件系统的数据结构来完成这个工作。

容易混淆的是:

VFS使用了和ext2文件系统类似的方式:

超级块和inode来描述文件系统。

象ext2_inode一样,VFS_inode描述了系统中的文件和目录以及VFS中的内容和拓扑结构,使用VFS_inode和VFS超级块将它们和ext2_inode和超级块进行区分。

在系统启动和操作系统初始化时,文件系统初始化将其自身注册到VFS中。

这些实际文件系统可以内置于内核中,也可以设计成可加载模块在系统需要时进行加载。

例如VFAT被实现成一个内核模块,当mountVFAT文件系统时它才被加载。

当一个基于块设备的文件系统(包括根文件系统)mount到系统上时,VFS首先读取其超级块。

每一类型的文件系统的超级块读取程序必须将文件系统的拓扑结构信息映射到VFS超级块的数据结构中。

VFS保存一个系统中已安装文件系统及其VFS超级块的列表。

每个VFS超级块包含一些信息以及执行特定功能的函数指针。

例如,一个已mount的ext2文件系统的超级块中包含一个指向读取ext2_inode例程的指针。

每个VFS超级块包含此文件系统中第一个VFS_inode的指针。

这种信息映射方式对于ext2文件系统来说非常有效,但对于其它文件系统较差。

 

 

图6.5虚拟文件系统的逻辑结构

当系统中的进程访问目录和文件时,将用系统调用来访问系统的VFS_inode。

例如,用ls查看目录下的内容或用cat来查看文件的内容时,虚拟文件系统就查找代表文件系统的VFS的inode。

由于系统中的每个文件与目录都使用一个VFS_inode来表示,所以许多inode会被重复访问。

这些inode被保存在inode缓冲区中以加快访问速度。

如果某个inode不在缓冲区,则必须调用一个文件系统相关例程来读取此inode。

读取inode的例程将把它放到inode缓冲区中以备下一次访问。

不经常使用的VFS_inode将会从inode缓冲区中移出。

所有的Linux文件系统使用一个共同的buffer缓冲区来缓存数据。

这个buffer缓冲区与具体的文件系统无关,而是与Linux内核中负责分配、读写数据缓冲区的机制紧密联系。

这样有一个明显的好处是可以使文件系统与具体的设备驱动程序无关。

所有的块设备都向Linux内核注册,这样形成一个统一的、基于块的异步接口。

当实际文件系统从物理磁盘中读取数据时,它向管理物理磁盘的设备驱动程序发出请求。

如果系统中经常需要相同的数据,那么这些数据可以在buffer缓冲区中读取,而不用从磁盘中读取。

VFS还支持目录缓冲对常用的目录的inode进行快速查找。

当在实际文件系统中查找目录时,目录的详细信息将被加载到目录缓冲区中。

当下一次访问这个目录时,就可以在目录缓冲区中找到这个目录。

6.2设备文件

6.2.1设备文件概述

Linux的设备管理(即输入输出子系统)是操作系统的重要组成部分。

如同所有的Unix系统一样,Linux将硬件设备当作一种特殊文件。

如/dev/null表示一个空设备。

设备文件并不占用文件系统空间,它只是作为设备的一个访问点。

Linux中的ext2文件系统和VFS都将设备文件作为特殊的inode来实现,分为两种类型:

字符型文件和块型文件。

在Linux内核中,由设备驱动程序实现文件的打开、关闭等操作。

字符设备允许以字符模式进行I/O操作,块设备的I/O操作则要通过buffer缓冲区。

设备文件通过一个主设备号(majornumber)和次设备号(minornumber)来引用。

主设备号标识设备的类型,次设备号标识主设备的一个实例。

例如,系统中的第一个IDE控制器上的IDE硬盘的主设备号为3,而其第一个分区的次设备号为1,那么我们用ls命令查看/dev/hda1时,将显示如下内容:

brw-rw----1rootdisk3,1Nov2415:

09/dev/hda1

一个设备可简单的分为两部分:

电子部分和机械部分。

其中,电子部分包括设备控制器或适配器,机械部分是指设备本身。

6.2.2设备驱动程序

设备驱动程序的目标是隐藏系统硬件设备的操作细节,实现设备的无关性。

Linux设备驱动程序实际上是一些常驻内存的底层硬件处理子程序,系统中的硬件设备基本上都是由这些驱动程序控制和管理的。

主设备号相同的设备使用相同的驱动程序,而次设备号用来区分具体设备的实例。

设备驱动程序提供了标准的核心接口。

终端设备驱动程序提供文件I/O接口,SCSI设备驱动程序提供文件I/O及缓冲区缓存接口等。

6.2.3中断处理

当一个进程通过设备驱动程序向设备发出读写请求后,CPU并不是等待设备响应,而是先转去执行别的代码,当设备执行完命令后,发出中断请求通知CPU,CPU响应中断请求,进行设备操作的后续处理。

6.2.4设备驱动程序的框架

DDI/DKI(device-driverinterface/driver-kernelinterface)规范,即设备驱动程序接口/驱动程序内核接口,通过它可以规范化设备驱动程序与内核之间的接口。

Linux的设备驱动程序与外界的接口和DDI/DKI规范相似,可分为三部分:

1.驱动程序与操作系统内核的接口是通过数据结构file_operation(include/Linux/fs.h)来完成的。

2.驱动程序与系统引导的接口是利用驱动程序对设备进行初始化的。

3.驱动程序与设备的接口描述了驱动程序如何与设备进行交互。

根据功能,设备驱动程序的代码可分为如下几个部分:

1.驱动程序的注册与注销。

系统引导时,通过sys_setup()进行系统初始化。

而该函数又调用device_setup()进行设备初始化。

初始化时要向内核注册,关闭时从内核注销设备。

2.设备的打开与释放。

打开设备由open()完成。

如打印机用lp_open()打开,硬盘用hd_open()打开。

3.设备的读写操作。

使用各自的read()和write()对设备进行数据读写。

4.设备的控制操作。

通过设备驱动程序中的ioctl()完成。

5.设备的中断和轮询处理。

对不支持中断的设备,读写时要轮流查询设备状态,如打印机驱动程序。

若设备支持中断,则按中断方式进行。

6.3文件的系统调用

本节主要介绍与文件基本操作相关的各类系统调用,并以示范程序说明这些系统调用的使用方法。

涉及的系统调用函数包括:

open()、close()、read()、write()、access()、stat()、fstat()、getcwd()、mkdir()、opendir()、closedir()等。

6.3.1文件的创建和读写

1.open与close系统调用

使用系统调用函数open()可以打开一个文件,调用close()函数将关闭不再使用的文件。

除了stdin,stdout及stderr,其它的文件在使用前都要先打开。

open()执行成功会返回一个介于0到255之间的文件描述字(Linux允许一个进程同时打开256个文件),如果执行失败,则返回值为-1。

close()系统调用若是执行成功,其返回值为0,否则返回值为-1。

下面是open()和close()的原型:

#include

#includ

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 高中教育 > 数学

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1