UNIX操作系统的文件管理一,UNIX文件系统的基本工作原理
1,UNIX文件的逻辑结构及分类
UNIX系统中文件的逻辑结构采用所谓无结构的流式文件,即是把文件视为一个无内部结构的字符流 。,并把它们分为:
(1) 一般文件
(2) 目录文件由文件的目录组成的文件称为目录文件。
在 UNIX里,为了加快文件目录的搜索速度,
便于实施文件共享,而把这些内容划分为两部分:
一部分称为该文件的文件控制块 ( 或索引节点 )
inode,它包含了文件的长度,物理位置,文件组,文件类型,存取权限,共享信息,管理住处等内容;
另一部分仍称为该文件的目录,它只含文件名以及相应 inode节点的编号 ( 见图 ) 。 因此,
UNIX的目录文件虽也是由文件的目录组成,
但相比之下要比通常所说的目录文件简单许多 。
(3) 特殊文件
2、基本文件系统及可装卸的子文件系统
UNIX的文件系统可分成基本文件系统和可装卸的子文件系统(又称文件卷)两部分
(1) 基本文件系统
基本文件系统固定在根存储设备上,是整个文件系统的基础 。 通常把硬盘做为根存储设备,
系统一旦启动运行,基本文件系统就不能脱卸 。
(2) 可装卸的子文件系统
存储在可装卸存储介质 ( 如软盘 ) 上的文件系统为可装卸的子文件系统,它可以随时更换 。
每个用户都可以把自己的文件存放在软盘上,
使用时插入软件驱动器,然后通过系统调用命令将其与基本文件系统勾连在一起,也可以用系统调用命令使子文件系统与基本文件系统脱勾 。
(3) 文件系统的目录结构
在 UNIX文件系统里,基本文件系统和子文件系统都独立采用树型带勾连的目录结构 。 所谓树型,即它们各自都有一个根目录文件,在根目录文件中所列的文件,可以是一个目录文件,也可以是一个一般文件或特殊文件 。
这样一层层地发展下去,就形成了一个通常意义下的树型文件目录结构 。 在这种结构下,叶节点为一般文件或特殊文件,中间节点为目录文件 。 图 18是 UNIX文件系统的目录结构图,我们以方框代表目录文件,圆圈代表一般文件或特殊文件 。
UNIX在树型结构的基础上增加交叉连接部分,
以达到文件共享的目的 。 在 UNIX系统中,是通过文件的 inode节点来实现文件共享勾连的,并且只允许勾连到代表一般文件的叶节点上去 。 由图 18可知,wang和 lee共享文件 f2。
二,UNIX文件系统的数据结构综述
在 UNIX文件系统的实施过程中,涉及到多种数据结构。有一类数据结构用于对文件的静态管理,
因此都分布在文件所在的存储设备上,它们包括外存文件控制块 inode、目录,以及存储资源管理信息块 filsys三种;
另一类数据结构用于文件打开时的管理,因此都出现在内存,它们包括内存文件控制块 inode、
打开文件控制块 file、以及进程打开文件表三种。
1、外存文件控制块 inode
由前知,文件存储设备上的每一个文件,都有一个文件控制块 inode与之对应,这些 inode被集中放在文件存储设备上的 inode区 。 文件控制块 inode对于文件的作用,
犹如进程控制块 proc,user对于每个进程的作用,这集中了这个文件的属性及有关信息,找到了 inode,就获得了它所对应的文件的一切必要信息 。
每一个 inode结构理用 32个字节,共九项内容,反映出一个文件的如下信息:文件长度及在存储设备上的物理位置、文件主的各种标识、文件类型、存取权限、文件勾连数、文件访问和修改时间、以及 inode节点是否空闲。
2、目录和目录文件
(1) 目录
UNIX中的每个文件都有一个目录项,目录项中记录了文件的名字以及该文件对应的外存 inode的编号 。 文件名是一个文件的外部标识,而这个文件的外存 inode编号,则是它的内部标识 。 可以看出,文件的目录项建立起了文件内,外部标识之间的对应关系:根据文件名找到它的目录项,由目录项的外存 inode编号找到文件控制块 inode,从而获得该文件的信息 。
(2) 目录文件
UNIX视每张目录表为一目录文件 。 作为一个文件,它有自己的名字以及对应的外存 inode。 要注意的是,每个文件系统 ( 基本的或子文件系统 )
都有一个根目录文件,它的外存 inode总是放于文件存储设备上 inode区中的第一个,于是保证很容易从它出发,到达树型目录结构上的任一节点 。
另外还要注意的是,由于每一个目录项需要 16个字节的存储空间,每个盘块的容量为 512字节,因此存放目录文件的盘块中,每一盘块可以存放
32个文件的目录 。 有了这些,UNIX文件目录的树型结构可以细化成如图
19所示 。
(3) 文件目录中的勾连
为了实现文件共享,UNIX允许对一般文件节点实行交叉连接,这称为勾连 。 它是通过在同一文件系统中的两个不同目录项里填入同一个外存 inode节点编号来实现的,图 9-25中的虚线处反映的正是这种勾连 。 譬如说,一般文件的原有路径名为 /a31,则在目录文件中就应有一目录项,其文件名为 a31,对应的外存 inode编号为 n。 如果再给它另起一个路径名 /a0/bx31,则在目录文件 a0中应该新设置一个目录项,它的文件名部分填入 bx31,它的外存 inode编号仍填为 n。 另外,在编号为 n的外存 inode里,将 i-
nlink的值加 1。 于是文件系统中就有两个目录项同时指向这一个 inode,实现了对文件 a31的不同名共享 。
3,存储资源管理信息块
文件系统所在存储设备上的存储资源,主要有两个用途,一是用来存放外存文件控制块 inode,
一是用来存放文件信息或扩展的地址索引表
( 当文件是大型的巨型的时,就需要这样做 ) 。
为了能对盘块的使用情况加以管理,UNIX将这些管理信息集中放在一个数据结构 ——存储资源管理信息块 filsys中 。 filsys总是固定在 1#盘块上,这一盘块通常称作该文件系统的管理块 。
这样,整个磁盘空间的安排情况就如图 20所示 。
文件系统磁盘存储区的分布图
每一个文件的 inode节点占用 32个字节,因此每一个 inode
块包含 16个文件控制块 。 这些 inode顺序编号,一个文件占用了某 inode,则其编号就成为这个文件的内部标识,
第 1号 inode是专门用于根目录文件的 。
数据结构 filsys共有 12项内容,下面给出与我们讲述有关的六项 。
(1) s-isize inode区占用的盘块数;
(2) s-fsize 盘块总数;
(3) s-nfree 直接管理 ( 也就是 s-free[100]指向 ) 的空闲块数;
(4) s-free[100] 空闲块索引表
(5) s-ninode 直接管理的空闲 inode节点数;
(6) s-sinode[100] 空闲 inode节点索引表 。
至于如何通过 filsys来对空闲 inode和空闲盘进行具体管理,
详情见后面关于 UNIX文件系统资源管理综述部分 。
4、内存文件控制块 inode和内存 inode表? 外存 inode记录了一个文件的属性和有关信息 。 可以想象,在对某一文件的访问过程中,会频繁地涉及到它,于是它就要不断来回于内,
外存之间,这当然是极不经济的 。 为此,UNIX在系统占用的内存区里开辟了一张表 ——内存 inode表 ( 或活动文件控制块表,活动索引节点表 ),该表共有 100个表目,每个表目称为一个内存文件控制块
inode,当需要使用某文件的信息,而在内存 inode表中找不到其相应的 inode时,就申请一个内存 inode,把外存 inode的大部分内存拷贝到这个内存 inode中,随之就使用这个内存 inode来控制磁盘上的文件 。 在最后一个用户关闭此文件后,内存 inode的内容被写到外存
inode,然后释放以供它用 。
内存 inode的结构基本上与外存 inode相同 。 增加的有关项目有:
(1) i-count
内存 inode访问计数 。 若为 0,表示此节点为空闲,
某文件被打开时,其内存 inode里的此项就加 1。
只有所有用户都关闭了此文件,以使 i-count为 0
后,这个文件才被真正关闭 。
(2) i-number
与此内存 inode相对应的外存 inode编号 。
5、打开文件控制块 file和 file表
一个文件可以被同一进程或不同进程,用同一路径名不同路径名,
具有同一特性或不同特性 ( 读,写,执行 ) 同时打开 。 做为内存
inode,基本上只包含文件的静态信息 ( 如文件的物理结构,勾连情况,存取权限等 ),没有记录下一个文件被打开时的动态信息 。
为此,UNIX又在系统占用的内存区里开辟一张表 ) ——打开文件控制块表,简称 file表,共有 100个表目,每一个表目称做一个打开文件控制块 file。 主要内容有
(1) f-flag打开文件的特性,指明对文件的读,写要求
(2) f-count共享该 file的进程数
(3) f-inode指向对应于此打开文件的内存 inode
每当打开一个文件时,都要在该表里申请分配一个 file,形成这个打开文件的控制块 。
6、进程打开文件表
在进程的 user结构里,设有一个整型数组 u-
ofile[15],它被称为该进程的打开文件表 。 在进程打开一个文件时,分配到一个打开文件控制块
file,就把这个控制块的地址填入 u-ofile[ ]的一个元素里 。 于是,这个打开文件就和它在 u-ofile[ ]
中占据的位置形成一个对应关系,这个位置就是打开文件号 。 图 21给出了 UNIX文件系统中各数据结构之间的关系 。
当某进程欲打开一个文件时,首先在自己 user结构的打开文件表里申请一个打开文件号 fd( 即找到一个 u-
ofile[ ]中的空表目 ),随之在系统打开文件控制块表里申请一个 file结构,且将其位置 fp填入 u-ofile表目中 。
然后在内存 inode表里找到该文件的内存 inode,或申请一个内存 inode,并将位置信息填入相应 file中的 f-
inode,i-count加 1。 这样,如果该文件的内存 inode原先就在内存 inode表中,那么现在这个进程则又一次通过同一路径名不同路径名将它打开 。 由于它们都有自己的 file结构,所以它们对这个文件可以持有不同的操作要求,拥有各自的读,写指针,从而形成了通过不同的 file结构,使用一个内存 inode的共享形式 。 这种共享在 file结构里的 f-count都为 1,但因大家都指向同一个内存 inode,故内存 inode里面的 i-count则大于 1( 有几个 file结构指向它,它的 i-count就为几 ),图 22描述了这一情形 。
共享打开文件的另一种情形是由父进程创建子进程引起的 。 在 UNIX中,当父进程创建一个子进程时,先要继承父进程的 u-ofile表的全部内容 。 这样它和父进程使用同一个 file结构,对这个文件有相同的操作要求和读写指针 。 所以,
这种共享打开文件表现为通过共享同一个 file结构来体现,图 23描述了这一情形 。 由此可知,
在 UNIX里提供了两种文件共享的方式,第一种是在目录结构里通过勾连,对同一文件提供不同路径名,以达到能够异名共享的目的;第二种是在打开文件结构里,通过共享同一个 file
结 构 或 共 享 同 一 个内存 inode而实现对打开文件的共享 。
三,UNIX文件的物理结构
1,小型文件的索引结构
文件存储设备以盘块为单位进行存取,每块 512个字节 。 当文件长度在 1~8个盘块之间时,称为小型文件,数组 i-addr[ ]就是通常意义下的地址索引表,它里面的内容就是文件在盘中的物理块号 。 因此,
小型文件是通过 i-addr[]的一级索引而找一盘文件的 。
2,大型文件的索引结构
当文件长度在 9~7?256个盘块之间时,称为大型文件,此时的数组
i-addr只使用七个元素 i-addr[0]~ i-addr[6],形成一个间接索引表,
每一个指向一个盘块,它们才是真正的地址索引表 。 由于一个盘块包含 256个字,所以通过这七盘块的索引,可最多得到 7?256个盘块 。 由此可见,大型文件是通过二级索引而找到盘文件的 。
3,巨型文件的索引结构
当文件长度在 ( 7?256+1) ~( 7?256+256?256) 个盘块之间时,
称为巨型文件,这时 i-addr前七个元素的作用不变,而把 i-addr[7]用来进行扩充 。 即把它指向的盘块作为间接索引表,再指向的 256个盘块才形成真正的地址索引表,所以巨型文件有一部分是通过三级索引而找到盘文件的 。
四,UNIX文件系统的资源管理综述
为了实施文件系统,需要涉及众多的资源 。 综前述,这些资源有如下几种:系统打开文件控制块表 ( file),系统内存 inode表,用户打开文件表 u-ofile,外存 inode区,以及外存一般存储块区 。 对于前三种资源的管理比较简单,都是采用线性搜索分配法,这里着重介绍后两种资源的管理方法 。 有关这两种资源的管理信息,
都集中放在文件存储设备的存储资源管理信息块 filsys中 ( 它总是固定存放在 1#盘块内,并且在内存中都有各自的副本 ) 。
1、外存 inode区的管理
在 filsys里,s-isize记录了 inode区所占用的盘块数 。 由于每个
inode占用 32个字节,因此可以得知在该 inode区里共有多少个
inode节点 。 由于存储设备上创建一个文件就需要有一个 inode节点与之对应,删除一个文件时,它占用的 inode节点就被系统收回,
所以 inode区中空闲 inode的数量是动态变化的 。
系统按照如下规定来实现对空闲 inode的管理:
(1) 在 filsys里,开辟一个空闲 inode索引表,s-inode[100]。 它是一个具有 100个元素的数里,每个元素可指向一个空闲 inode,这里系统直接管理的空闲 inode。 至于当前该数组里究竟含有多少个空闲 inode,则由 filsys里的 s-ninode加以记录 。
(2) 把 s-inode[ ]视为一个栈来使用 。 按照 C语言的约定,数组下标总是从 0开始,所以 s-nnode的值恰好是一个可以使用的索引表目的下标 。 当需要分配 inode 时,如果 s-ninode不为 0,则将 s-
inode[--s-ninode]里指示的 inode 节点分配出去;如果释放回一个
inode 节点,则把该节点指针送入 s-inode[s-ninode++]中 。
(3) 如果 s-inode[ ]已无直接管理的空闲区了 ( s-ninode=0),则搜索 inode 区,将找到的空闲 inode 依次登入,直至表满或搜索完整个 inode 区 。 如果 s-inode[ ]已经直接管理了 100个空闲 inode,
则对再释放的 inode不作任何处理,让这个空闲的 inode散布在
inode区里 。
由上述可知,系统对文件存储设备上 inode区里的空闲 inode,通过 s-ninode和 s-inode[100]只直接管理最多 100个空闲 inode,置其它空闲
inode而暂时不顾 。
在 filsys里,对一般存储块区也开辟了两个项目:
·s-free[100] 空闲块索引表
·s-nfree 直接管理的空闲块数目
形式上,它们与 s-inode[100],s-ninode相似,
但实际上却采用了不同的管理方法 ——分组链接法 。
(1),分组链接,法的基本思想
2、外存一般存储块区的管理
(2) 空闲块的分配
(3) 空闲块的释放七,UNIX文件操作的系统调用
(1) 文件的创建
文件创建首先是要求文件系统为新的文件建立一个新目录项和相应的索引节点,以便随后的写操作为这个新文件输入信息 。 该系统调用的 C语言格式为:
int fd,mode;
char * filenamep;
fd = creat (filenamep,mode);
例如,用户文件的路径名是 /usr/lib/d2,则用户可用如下的 C语言程序调用 creat:
char * dp;
int fdlib,fmode;
de =,/usr/lib/d2”;
fmode = 0775;
fdlib = creat (dp,fmode);
或用更简单的方式
int fdlib;
fdlib = creat (“/usr/lib/d2”,0775);
下面简述这一系统调用的执行过程,这里假定文件是首次创建,
即在执行之前,文件还未存在:
① 首先为新文件 d2分配索引节点和活动索引节点,并把索引节点编号与文件分量名 d2组成一个新的目录项,记到目录 /usr/lib中 。
在这一过程中,需要执行以前介绍过的目录检索程序 。
② 在文件 d2所对应的活动索引节点中置初值,包括把存取权限
i_mode置为 0775,连接计数 i_nlink置为,1”等等 。
③ 为文件分配用户打开文件表项和系统打开文件表项,置系统打开文件表项的初值 。 包括在 f_flag中置,写,标志,读写位移
f_offset清,0”等等 。 然后,把用户打开文件表项,系统打开文件表项及 d2所对应的活动索引节点用指针连接起来,最后把用户打开文件表项的序号,即文件描述字返回给调用者 。
由于在上述步骤中,也执行了文件,打开,功能,因此在以后操作中,不用再执行,打开,操作 。
(2) 文件的删除
删除的主要任务是把指定文件从所在的目录文件中除去 。 如果没有连接的用户,即如果在执行删除之前 i_link 为,1”,还要把这个文件占用的存储空间释放 。 文件删除系统调用的形式为:
unlink (filenamep);
2、文件的连接和解除连接
(1) 文件的连接
在文件共享一节中,已介绍文件连接的意义,它的调用方式为:
chat* oldnamep,* newnamep;
link(oldnamep,newnamep);
其中 oldnamep和 newnamep分别为指向已存在文件名字符串和文件别 名字符串的指针 。 这一系统调用的执行步骤如下:
① 检索目录 找到 oldnamep所指向文件的索引节点编号 。
② 再次检索目录 找到 newnamep所指文件的父目录文件,并把已存文件的索引节点编号与别名构成一个目录项,记入到该目录中去 。
③ 把已存文件索引节点的连接计数 i_nlink加,1”。
从上述过程可知,所谓连接,实际上是共享已存文件的索引节点 。
(2) 文件的解除连接
其调用形式与文件删除相同,unlink(namep);
3、文件的打开和关闭
(1) 文件的打开
其调用方式为,int fd,mode;
char * filenamep;
fd = open (filenamep,mode);
其中 mode是打开的方式,它表时打开后的操作要求,如读 ( 0),写 ( 1) 或又读又写 ( 2) 。
其余参数的意义与 creat中的相同 。 open的执行过程如下:
① 检索目录:一般来说,要求打开的文件应该是已经创建的文件,因此它应该在文件目录中登记,否则就算错 。 在检索到指定文件之后,就把它的索引节点复制到活动索引节点表中 。
② 把参数 mode提出的打开方式与活动索引节点中在创建文件时记录的文件访问权限相比较,如果非法,则这次打开失败 。
③ 当,打开,合法时,为文件分配用户打开文件表项和系统打开文件表项,并为系统打开文件表项设置初值 。 然后通过指针建立这些表项与活动索引节点之间的联系 。 在完成上述工作之后,把文件描述字,即用户打开文件表中相应文件表项的序号返回给调用者 。
文件使用完毕,就应该执行 close6系统调用把它关闭,从而切断用户进程与文件之间的联系 。 其调用方式为:
int fd;
close (fd);
显然,要关闭的文件应该是已经打开的,所以文件描述字 fd一定存在 。
close的执行过程如下:
① 根据 fd找到用户打开文件表项,继而找到系统打开文件表项 。 把用户打开文件表项释放 。
② 把对应的系统打开文件表项中的 f_count减,1”,如果不为,0”,说明进程族中还有子程序正在共享这一系统打开文件表项,所以不用释放系统打开文件表项,而直接返回;否则释放这个系统打开文件表项,并找到与之连接的活动索引节点 。
③ 把上述活动索引节点中的 i_count减,1”,若不为,0”,表明还有其它用户进程正在使用该文件,所以不用释放该活动索引节点而直接返回,
否则在把该活动索引节点中的内容复制回文件卷的相应索引节点之后,
释放该活动索引节点 。
\(2) 文件的关闭
4、文件的读和写
文件的读和写是文件夹的最基本操作 。,读,
是指文件的内容读入到用户进程的变量区中,
,写,是指把用户进程变量区中的信息写入到文件存储区中 。 从文件的什么逻辑位置读入数据,或把数据写入文件的什么逻辑位置均由系统打开文件表中的 f_offset决定 。
(1) 读文件
该系统调用的形式为:
int nr,fd,count;
char buf [ ]
nr = read (fd,buf,count);
假 定 我 们 通 过 打 开 系 统 调 用 打 开 了 文 件
/usr/lib/d2,与它有关的用户打开文件表项,系统打开文件表项和活动索引节点见图 29所示的关系 。
现要求读文件 d2的 1500个字符到指针 bufp指向的用户内存区中,number用来存放实际传送的字节数,则可按如下方式调用 read:
number = read (fdlib,bufp,1500);
在执行 read系统调用的过程中,系统首先根据
f_flag中记录的信息,检查读操作的合法性,如果合法,则根据当前位移量 f_offset的值,要读出的字节数,以及活动索引节点中 i_addr指出的文件物理块存放地址,把相应的物理块读到块设备缓冲区中,然后再送到 bufp指向的用户内存区中 。 由此可见,在执行 read的过程中,一定要用到块设备管理中的读程序 。
(2) 写文件
该系统调用的形式为:
nw = write (fd,buf,count);
其中,fd,count和 nw的意义类似于 read,只是
buf是信息传送的源地址,即把 buf所指向的用户内存区中的信息,写入到文件存储区中 。 只要情况正常 ( 中间无差错 ),nw一定与 count相等 。
5、文件的随机存取
在文件初次,打开,时,文件的位移量 f_offset总是清为零 。 如果不特别指明,以后的文件读写操作总是根据
offset的当前值,顺序地读写文件 。 为了支持文件的随机访问,文件系统提供了系统调用 lseek,它允许用户在读,
写文件之前,事先改变 f_offset的指向 。 这一系统调用的形式为:
long lseek;
long offset;
int whence,fd;
lseek (fd,offset,whence);
七、块设备 I/O操作与文件读写关系
下面进一步说明块设备读操作 bread (dev,blkno)与文件系统中的读文件系统调用 read(fd,base,count)的关系 。
读文件 read(fd,base,count)的过程如下:
1,用户程序请求操作系统为其服务,读取一文件 。 通过 trap处理进入读文件系统调用入口 read( )。 这时进程由用户态进入核心态 。
2,read( )调用 rdwr (FREAD),而由 rdwr(FREAD)执行;
根据文件描述符 fd,通过用户打开文件表项确定系统打开文件表项及内存活动 i节点,并确认读或写操作的合法性,置有关工作单元初值,调用 readi( )。
3,readi( )执行:
(1) 确定是块设备文件,还是字符设备文件,是后者则通过字符设备开关表转到特别文件处理 。 是块设备文件则转下述的读处理 。
(2) 由读写位移 u,u_off_set得到文件逻辑块号,本次实际传送字节数 。
并调用映象处理程序 bmap( )把逻辑块号转换成物理块号 。
(3) 确定一般方式读还是提前读,调用 bread ( )或 bread ( ),执行读取一块到缓冲区 。
(4) 调用 imove ( )程序,把已读入缓冲区的信息移至内存,并准备读取下一块 。
(5) 调用 brelse( ),释放缓冲区 。
(6) 全部文件块读完或出现错误时,则返回;否则继续读下一块文件信息 。
由上可知,块设备的读(写也一样)操作(即 I/O操作),就是文件读或写系统调用的一个内部过程。面向用户的只是文件读写的系统调用,设备的 I/O操作对用户则是完全透明
1,UNIX文件的逻辑结构及分类
UNIX系统中文件的逻辑结构采用所谓无结构的流式文件,即是把文件视为一个无内部结构的字符流 。,并把它们分为:
(1) 一般文件
(2) 目录文件由文件的目录组成的文件称为目录文件。
在 UNIX里,为了加快文件目录的搜索速度,
便于实施文件共享,而把这些内容划分为两部分:
一部分称为该文件的文件控制块 ( 或索引节点 )
inode,它包含了文件的长度,物理位置,文件组,文件类型,存取权限,共享信息,管理住处等内容;
另一部分仍称为该文件的目录,它只含文件名以及相应 inode节点的编号 ( 见图 ) 。 因此,
UNIX的目录文件虽也是由文件的目录组成,
但相比之下要比通常所说的目录文件简单许多 。
(3) 特殊文件
2、基本文件系统及可装卸的子文件系统
UNIX的文件系统可分成基本文件系统和可装卸的子文件系统(又称文件卷)两部分
(1) 基本文件系统
基本文件系统固定在根存储设备上,是整个文件系统的基础 。 通常把硬盘做为根存储设备,
系统一旦启动运行,基本文件系统就不能脱卸 。
(2) 可装卸的子文件系统
存储在可装卸存储介质 ( 如软盘 ) 上的文件系统为可装卸的子文件系统,它可以随时更换 。
每个用户都可以把自己的文件存放在软盘上,
使用时插入软件驱动器,然后通过系统调用命令将其与基本文件系统勾连在一起,也可以用系统调用命令使子文件系统与基本文件系统脱勾 。
(3) 文件系统的目录结构
在 UNIX文件系统里,基本文件系统和子文件系统都独立采用树型带勾连的目录结构 。 所谓树型,即它们各自都有一个根目录文件,在根目录文件中所列的文件,可以是一个目录文件,也可以是一个一般文件或特殊文件 。
这样一层层地发展下去,就形成了一个通常意义下的树型文件目录结构 。 在这种结构下,叶节点为一般文件或特殊文件,中间节点为目录文件 。 图 18是 UNIX文件系统的目录结构图,我们以方框代表目录文件,圆圈代表一般文件或特殊文件 。
UNIX在树型结构的基础上增加交叉连接部分,
以达到文件共享的目的 。 在 UNIX系统中,是通过文件的 inode节点来实现文件共享勾连的,并且只允许勾连到代表一般文件的叶节点上去 。 由图 18可知,wang和 lee共享文件 f2。
二,UNIX文件系统的数据结构综述
在 UNIX文件系统的实施过程中,涉及到多种数据结构。有一类数据结构用于对文件的静态管理,
因此都分布在文件所在的存储设备上,它们包括外存文件控制块 inode、目录,以及存储资源管理信息块 filsys三种;
另一类数据结构用于文件打开时的管理,因此都出现在内存,它们包括内存文件控制块 inode、
打开文件控制块 file、以及进程打开文件表三种。
1、外存文件控制块 inode
由前知,文件存储设备上的每一个文件,都有一个文件控制块 inode与之对应,这些 inode被集中放在文件存储设备上的 inode区 。 文件控制块 inode对于文件的作用,
犹如进程控制块 proc,user对于每个进程的作用,这集中了这个文件的属性及有关信息,找到了 inode,就获得了它所对应的文件的一切必要信息 。
每一个 inode结构理用 32个字节,共九项内容,反映出一个文件的如下信息:文件长度及在存储设备上的物理位置、文件主的各种标识、文件类型、存取权限、文件勾连数、文件访问和修改时间、以及 inode节点是否空闲。
2、目录和目录文件
(1) 目录
UNIX中的每个文件都有一个目录项,目录项中记录了文件的名字以及该文件对应的外存 inode的编号 。 文件名是一个文件的外部标识,而这个文件的外存 inode编号,则是它的内部标识 。 可以看出,文件的目录项建立起了文件内,外部标识之间的对应关系:根据文件名找到它的目录项,由目录项的外存 inode编号找到文件控制块 inode,从而获得该文件的信息 。
(2) 目录文件
UNIX视每张目录表为一目录文件 。 作为一个文件,它有自己的名字以及对应的外存 inode。 要注意的是,每个文件系统 ( 基本的或子文件系统 )
都有一个根目录文件,它的外存 inode总是放于文件存储设备上 inode区中的第一个,于是保证很容易从它出发,到达树型目录结构上的任一节点 。
另外还要注意的是,由于每一个目录项需要 16个字节的存储空间,每个盘块的容量为 512字节,因此存放目录文件的盘块中,每一盘块可以存放
32个文件的目录 。 有了这些,UNIX文件目录的树型结构可以细化成如图
19所示 。
(3) 文件目录中的勾连
为了实现文件共享,UNIX允许对一般文件节点实行交叉连接,这称为勾连 。 它是通过在同一文件系统中的两个不同目录项里填入同一个外存 inode节点编号来实现的,图 9-25中的虚线处反映的正是这种勾连 。 譬如说,一般文件的原有路径名为 /a31,则在目录文件中就应有一目录项,其文件名为 a31,对应的外存 inode编号为 n。 如果再给它另起一个路径名 /a0/bx31,则在目录文件 a0中应该新设置一个目录项,它的文件名部分填入 bx31,它的外存 inode编号仍填为 n。 另外,在编号为 n的外存 inode里,将 i-
nlink的值加 1。 于是文件系统中就有两个目录项同时指向这一个 inode,实现了对文件 a31的不同名共享 。
3,存储资源管理信息块
文件系统所在存储设备上的存储资源,主要有两个用途,一是用来存放外存文件控制块 inode,
一是用来存放文件信息或扩展的地址索引表
( 当文件是大型的巨型的时,就需要这样做 ) 。
为了能对盘块的使用情况加以管理,UNIX将这些管理信息集中放在一个数据结构 ——存储资源管理信息块 filsys中 。 filsys总是固定在 1#盘块上,这一盘块通常称作该文件系统的管理块 。
这样,整个磁盘空间的安排情况就如图 20所示 。
文件系统磁盘存储区的分布图
每一个文件的 inode节点占用 32个字节,因此每一个 inode
块包含 16个文件控制块 。 这些 inode顺序编号,一个文件占用了某 inode,则其编号就成为这个文件的内部标识,
第 1号 inode是专门用于根目录文件的 。
数据结构 filsys共有 12项内容,下面给出与我们讲述有关的六项 。
(1) s-isize inode区占用的盘块数;
(2) s-fsize 盘块总数;
(3) s-nfree 直接管理 ( 也就是 s-free[100]指向 ) 的空闲块数;
(4) s-free[100] 空闲块索引表
(5) s-ninode 直接管理的空闲 inode节点数;
(6) s-sinode[100] 空闲 inode节点索引表 。
至于如何通过 filsys来对空闲 inode和空闲盘进行具体管理,
详情见后面关于 UNIX文件系统资源管理综述部分 。
4、内存文件控制块 inode和内存 inode表? 外存 inode记录了一个文件的属性和有关信息 。 可以想象,在对某一文件的访问过程中,会频繁地涉及到它,于是它就要不断来回于内,
外存之间,这当然是极不经济的 。 为此,UNIX在系统占用的内存区里开辟了一张表 ——内存 inode表 ( 或活动文件控制块表,活动索引节点表 ),该表共有 100个表目,每个表目称为一个内存文件控制块
inode,当需要使用某文件的信息,而在内存 inode表中找不到其相应的 inode时,就申请一个内存 inode,把外存 inode的大部分内存拷贝到这个内存 inode中,随之就使用这个内存 inode来控制磁盘上的文件 。 在最后一个用户关闭此文件后,内存 inode的内容被写到外存
inode,然后释放以供它用 。
内存 inode的结构基本上与外存 inode相同 。 增加的有关项目有:
(1) i-count
内存 inode访问计数 。 若为 0,表示此节点为空闲,
某文件被打开时,其内存 inode里的此项就加 1。
只有所有用户都关闭了此文件,以使 i-count为 0
后,这个文件才被真正关闭 。
(2) i-number
与此内存 inode相对应的外存 inode编号 。
5、打开文件控制块 file和 file表
一个文件可以被同一进程或不同进程,用同一路径名不同路径名,
具有同一特性或不同特性 ( 读,写,执行 ) 同时打开 。 做为内存
inode,基本上只包含文件的静态信息 ( 如文件的物理结构,勾连情况,存取权限等 ),没有记录下一个文件被打开时的动态信息 。
为此,UNIX又在系统占用的内存区里开辟一张表 ) ——打开文件控制块表,简称 file表,共有 100个表目,每一个表目称做一个打开文件控制块 file。 主要内容有
(1) f-flag打开文件的特性,指明对文件的读,写要求
(2) f-count共享该 file的进程数
(3) f-inode指向对应于此打开文件的内存 inode
每当打开一个文件时,都要在该表里申请分配一个 file,形成这个打开文件的控制块 。
6、进程打开文件表
在进程的 user结构里,设有一个整型数组 u-
ofile[15],它被称为该进程的打开文件表 。 在进程打开一个文件时,分配到一个打开文件控制块
file,就把这个控制块的地址填入 u-ofile[ ]的一个元素里 。 于是,这个打开文件就和它在 u-ofile[ ]
中占据的位置形成一个对应关系,这个位置就是打开文件号 。 图 21给出了 UNIX文件系统中各数据结构之间的关系 。
当某进程欲打开一个文件时,首先在自己 user结构的打开文件表里申请一个打开文件号 fd( 即找到一个 u-
ofile[ ]中的空表目 ),随之在系统打开文件控制块表里申请一个 file结构,且将其位置 fp填入 u-ofile表目中 。
然后在内存 inode表里找到该文件的内存 inode,或申请一个内存 inode,并将位置信息填入相应 file中的 f-
inode,i-count加 1。 这样,如果该文件的内存 inode原先就在内存 inode表中,那么现在这个进程则又一次通过同一路径名不同路径名将它打开 。 由于它们都有自己的 file结构,所以它们对这个文件可以持有不同的操作要求,拥有各自的读,写指针,从而形成了通过不同的 file结构,使用一个内存 inode的共享形式 。 这种共享在 file结构里的 f-count都为 1,但因大家都指向同一个内存 inode,故内存 inode里面的 i-count则大于 1( 有几个 file结构指向它,它的 i-count就为几 ),图 22描述了这一情形 。
共享打开文件的另一种情形是由父进程创建子进程引起的 。 在 UNIX中,当父进程创建一个子进程时,先要继承父进程的 u-ofile表的全部内容 。 这样它和父进程使用同一个 file结构,对这个文件有相同的操作要求和读写指针 。 所以,
这种共享打开文件表现为通过共享同一个 file结构来体现,图 23描述了这一情形 。 由此可知,
在 UNIX里提供了两种文件共享的方式,第一种是在目录结构里通过勾连,对同一文件提供不同路径名,以达到能够异名共享的目的;第二种是在打开文件结构里,通过共享同一个 file
结 构 或 共 享 同 一 个内存 inode而实现对打开文件的共享 。
三,UNIX文件的物理结构
1,小型文件的索引结构
文件存储设备以盘块为单位进行存取,每块 512个字节 。 当文件长度在 1~8个盘块之间时,称为小型文件,数组 i-addr[ ]就是通常意义下的地址索引表,它里面的内容就是文件在盘中的物理块号 。 因此,
小型文件是通过 i-addr[]的一级索引而找一盘文件的 。
2,大型文件的索引结构
当文件长度在 9~7?256个盘块之间时,称为大型文件,此时的数组
i-addr只使用七个元素 i-addr[0]~ i-addr[6],形成一个间接索引表,
每一个指向一个盘块,它们才是真正的地址索引表 。 由于一个盘块包含 256个字,所以通过这七盘块的索引,可最多得到 7?256个盘块 。 由此可见,大型文件是通过二级索引而找到盘文件的 。
3,巨型文件的索引结构
当文件长度在 ( 7?256+1) ~( 7?256+256?256) 个盘块之间时,
称为巨型文件,这时 i-addr前七个元素的作用不变,而把 i-addr[7]用来进行扩充 。 即把它指向的盘块作为间接索引表,再指向的 256个盘块才形成真正的地址索引表,所以巨型文件有一部分是通过三级索引而找到盘文件的 。
四,UNIX文件系统的资源管理综述
为了实施文件系统,需要涉及众多的资源 。 综前述,这些资源有如下几种:系统打开文件控制块表 ( file),系统内存 inode表,用户打开文件表 u-ofile,外存 inode区,以及外存一般存储块区 。 对于前三种资源的管理比较简单,都是采用线性搜索分配法,这里着重介绍后两种资源的管理方法 。 有关这两种资源的管理信息,
都集中放在文件存储设备的存储资源管理信息块 filsys中 ( 它总是固定存放在 1#盘块内,并且在内存中都有各自的副本 ) 。
1、外存 inode区的管理
在 filsys里,s-isize记录了 inode区所占用的盘块数 。 由于每个
inode占用 32个字节,因此可以得知在该 inode区里共有多少个
inode节点 。 由于存储设备上创建一个文件就需要有一个 inode节点与之对应,删除一个文件时,它占用的 inode节点就被系统收回,
所以 inode区中空闲 inode的数量是动态变化的 。
系统按照如下规定来实现对空闲 inode的管理:
(1) 在 filsys里,开辟一个空闲 inode索引表,s-inode[100]。 它是一个具有 100个元素的数里,每个元素可指向一个空闲 inode,这里系统直接管理的空闲 inode。 至于当前该数组里究竟含有多少个空闲 inode,则由 filsys里的 s-ninode加以记录 。
(2) 把 s-inode[ ]视为一个栈来使用 。 按照 C语言的约定,数组下标总是从 0开始,所以 s-nnode的值恰好是一个可以使用的索引表目的下标 。 当需要分配 inode 时,如果 s-ninode不为 0,则将 s-
inode[--s-ninode]里指示的 inode 节点分配出去;如果释放回一个
inode 节点,则把该节点指针送入 s-inode[s-ninode++]中 。
(3) 如果 s-inode[ ]已无直接管理的空闲区了 ( s-ninode=0),则搜索 inode 区,将找到的空闲 inode 依次登入,直至表满或搜索完整个 inode 区 。 如果 s-inode[ ]已经直接管理了 100个空闲 inode,
则对再释放的 inode不作任何处理,让这个空闲的 inode散布在
inode区里 。
由上述可知,系统对文件存储设备上 inode区里的空闲 inode,通过 s-ninode和 s-inode[100]只直接管理最多 100个空闲 inode,置其它空闲
inode而暂时不顾 。
在 filsys里,对一般存储块区也开辟了两个项目:
·s-free[100] 空闲块索引表
·s-nfree 直接管理的空闲块数目
形式上,它们与 s-inode[100],s-ninode相似,
但实际上却采用了不同的管理方法 ——分组链接法 。
(1),分组链接,法的基本思想
2、外存一般存储块区的管理
(2) 空闲块的分配
(3) 空闲块的释放七,UNIX文件操作的系统调用
(1) 文件的创建
文件创建首先是要求文件系统为新的文件建立一个新目录项和相应的索引节点,以便随后的写操作为这个新文件输入信息 。 该系统调用的 C语言格式为:
int fd,mode;
char * filenamep;
fd = creat (filenamep,mode);
例如,用户文件的路径名是 /usr/lib/d2,则用户可用如下的 C语言程序调用 creat:
char * dp;
int fdlib,fmode;
de =,/usr/lib/d2”;
fmode = 0775;
fdlib = creat (dp,fmode);
或用更简单的方式
int fdlib;
fdlib = creat (“/usr/lib/d2”,0775);
下面简述这一系统调用的执行过程,这里假定文件是首次创建,
即在执行之前,文件还未存在:
① 首先为新文件 d2分配索引节点和活动索引节点,并把索引节点编号与文件分量名 d2组成一个新的目录项,记到目录 /usr/lib中 。
在这一过程中,需要执行以前介绍过的目录检索程序 。
② 在文件 d2所对应的活动索引节点中置初值,包括把存取权限
i_mode置为 0775,连接计数 i_nlink置为,1”等等 。
③ 为文件分配用户打开文件表项和系统打开文件表项,置系统打开文件表项的初值 。 包括在 f_flag中置,写,标志,读写位移
f_offset清,0”等等 。 然后,把用户打开文件表项,系统打开文件表项及 d2所对应的活动索引节点用指针连接起来,最后把用户打开文件表项的序号,即文件描述字返回给调用者 。
由于在上述步骤中,也执行了文件,打开,功能,因此在以后操作中,不用再执行,打开,操作 。
(2) 文件的删除
删除的主要任务是把指定文件从所在的目录文件中除去 。 如果没有连接的用户,即如果在执行删除之前 i_link 为,1”,还要把这个文件占用的存储空间释放 。 文件删除系统调用的形式为:
unlink (filenamep);
2、文件的连接和解除连接
(1) 文件的连接
在文件共享一节中,已介绍文件连接的意义,它的调用方式为:
chat* oldnamep,* newnamep;
link(oldnamep,newnamep);
其中 oldnamep和 newnamep分别为指向已存在文件名字符串和文件别 名字符串的指针 。 这一系统调用的执行步骤如下:
① 检索目录 找到 oldnamep所指向文件的索引节点编号 。
② 再次检索目录 找到 newnamep所指文件的父目录文件,并把已存文件的索引节点编号与别名构成一个目录项,记入到该目录中去 。
③ 把已存文件索引节点的连接计数 i_nlink加,1”。
从上述过程可知,所谓连接,实际上是共享已存文件的索引节点 。
(2) 文件的解除连接
其调用形式与文件删除相同,unlink(namep);
3、文件的打开和关闭
(1) 文件的打开
其调用方式为,int fd,mode;
char * filenamep;
fd = open (filenamep,mode);
其中 mode是打开的方式,它表时打开后的操作要求,如读 ( 0),写 ( 1) 或又读又写 ( 2) 。
其余参数的意义与 creat中的相同 。 open的执行过程如下:
① 检索目录:一般来说,要求打开的文件应该是已经创建的文件,因此它应该在文件目录中登记,否则就算错 。 在检索到指定文件之后,就把它的索引节点复制到活动索引节点表中 。
② 把参数 mode提出的打开方式与活动索引节点中在创建文件时记录的文件访问权限相比较,如果非法,则这次打开失败 。
③ 当,打开,合法时,为文件分配用户打开文件表项和系统打开文件表项,并为系统打开文件表项设置初值 。 然后通过指针建立这些表项与活动索引节点之间的联系 。 在完成上述工作之后,把文件描述字,即用户打开文件表中相应文件表项的序号返回给调用者 。
文件使用完毕,就应该执行 close6系统调用把它关闭,从而切断用户进程与文件之间的联系 。 其调用方式为:
int fd;
close (fd);
显然,要关闭的文件应该是已经打开的,所以文件描述字 fd一定存在 。
close的执行过程如下:
① 根据 fd找到用户打开文件表项,继而找到系统打开文件表项 。 把用户打开文件表项释放 。
② 把对应的系统打开文件表项中的 f_count减,1”,如果不为,0”,说明进程族中还有子程序正在共享这一系统打开文件表项,所以不用释放系统打开文件表项,而直接返回;否则释放这个系统打开文件表项,并找到与之连接的活动索引节点 。
③ 把上述活动索引节点中的 i_count减,1”,若不为,0”,表明还有其它用户进程正在使用该文件,所以不用释放该活动索引节点而直接返回,
否则在把该活动索引节点中的内容复制回文件卷的相应索引节点之后,
释放该活动索引节点 。
\(2) 文件的关闭
4、文件的读和写
文件的读和写是文件夹的最基本操作 。,读,
是指文件的内容读入到用户进程的变量区中,
,写,是指把用户进程变量区中的信息写入到文件存储区中 。 从文件的什么逻辑位置读入数据,或把数据写入文件的什么逻辑位置均由系统打开文件表中的 f_offset决定 。
(1) 读文件
该系统调用的形式为:
int nr,fd,count;
char buf [ ]
nr = read (fd,buf,count);
假 定 我 们 通 过 打 开 系 统 调 用 打 开 了 文 件
/usr/lib/d2,与它有关的用户打开文件表项,系统打开文件表项和活动索引节点见图 29所示的关系 。
现要求读文件 d2的 1500个字符到指针 bufp指向的用户内存区中,number用来存放实际传送的字节数,则可按如下方式调用 read:
number = read (fdlib,bufp,1500);
在执行 read系统调用的过程中,系统首先根据
f_flag中记录的信息,检查读操作的合法性,如果合法,则根据当前位移量 f_offset的值,要读出的字节数,以及活动索引节点中 i_addr指出的文件物理块存放地址,把相应的物理块读到块设备缓冲区中,然后再送到 bufp指向的用户内存区中 。 由此可见,在执行 read的过程中,一定要用到块设备管理中的读程序 。
(2) 写文件
该系统调用的形式为:
nw = write (fd,buf,count);
其中,fd,count和 nw的意义类似于 read,只是
buf是信息传送的源地址,即把 buf所指向的用户内存区中的信息,写入到文件存储区中 。 只要情况正常 ( 中间无差错 ),nw一定与 count相等 。
5、文件的随机存取
在文件初次,打开,时,文件的位移量 f_offset总是清为零 。 如果不特别指明,以后的文件读写操作总是根据
offset的当前值,顺序地读写文件 。 为了支持文件的随机访问,文件系统提供了系统调用 lseek,它允许用户在读,
写文件之前,事先改变 f_offset的指向 。 这一系统调用的形式为:
long lseek;
long offset;
int whence,fd;
lseek (fd,offset,whence);
七、块设备 I/O操作与文件读写关系
下面进一步说明块设备读操作 bread (dev,blkno)与文件系统中的读文件系统调用 read(fd,base,count)的关系 。
读文件 read(fd,base,count)的过程如下:
1,用户程序请求操作系统为其服务,读取一文件 。 通过 trap处理进入读文件系统调用入口 read( )。 这时进程由用户态进入核心态 。
2,read( )调用 rdwr (FREAD),而由 rdwr(FREAD)执行;
根据文件描述符 fd,通过用户打开文件表项确定系统打开文件表项及内存活动 i节点,并确认读或写操作的合法性,置有关工作单元初值,调用 readi( )。
3,readi( )执行:
(1) 确定是块设备文件,还是字符设备文件,是后者则通过字符设备开关表转到特别文件处理 。 是块设备文件则转下述的读处理 。
(2) 由读写位移 u,u_off_set得到文件逻辑块号,本次实际传送字节数 。
并调用映象处理程序 bmap( )把逻辑块号转换成物理块号 。
(3) 确定一般方式读还是提前读,调用 bread ( )或 bread ( ),执行读取一块到缓冲区 。
(4) 调用 imove ( )程序,把已读入缓冲区的信息移至内存,并准备读取下一块 。
(5) 调用 brelse( ),释放缓冲区 。
(6) 全部文件块读完或出现错误时,则返回;否则继续读下一块文件信息 。
由上可知,块设备的读(写也一样)操作(即 I/O操作),就是文件读或写系统调用的一个内部过程。面向用户的只是文件读写的系统调用,设备的 I/O操作对用户则是完全透明