下面我们来讨论
文件描述符这个基本的概念
文件描述符是指打开的文件
它在内存当中所维护的相关信息
首先我们来讨论一下打开文件的过程
我在读写数据之前
我必须打开文件
这是我们通常情况下
你写的程序里头的文件访问的模式
打开然后进行读 或者写操作
完成之后 一个关闭
我就不再对这个文件进行操作了
针对于这种模式呢
在操作系统内核当中
维护了打开文件的相关信息
也就是说它会跟踪进程打开的文件
所谓的跟踪就是在内核当中
维护了一个打开文件表
这个文件表里的每一项
对应一个打开的文件
这时候我给它一个标识
就是文件描述符
也就是说文件描述符
是打开文件的一个标识
为什么我不直接用文件的标识呢
原因在于打开文件的数目和
你的文件系统里的文件数目
是有数量级的差别的
这样的话我可以给它一个
更简单的标识来方便我们的访问
文件描述符是操作系统
在打开文件表中维护的
打开文件的状态和信息
这里有些什么样的信息呢
首先第一个是文件指针
我们在读写文件的时候
最后一次读写的位置
每个进程都维护自己的打开文件指针
也就相当于我有一个文件
有多个进程对它进行读写
在读的时候你的这个指针
每个进程只是跟自己的指针相关联
另外的一个进程读写相同的文件
这时候你的文件指针
并不随着它的读写而发生改变
这就是我们这里说的
每个进程分别维护自己的打开文件指针
除了文件指针之外
再有一个是文件打开计数
这里指的是当前这个
被打开的文件打开的次数
记录这个次数的意思是在于
最后一个进程关闭文件的时候
那么就可以将这个文件
从打开文件表当中去除了
这是打开文件计数
同时还有就是文件在磁盘上的位置
这时候我们会把一部分
磁盘上的文件的内容缓存到内存当中
所以这时候
我要记录文件在磁盘上的位置
你下次再读的时候
有缓存的这些内容
我就可以利用内存当中
缓存的数据让你进行访问
再有是访问权限
每个进程访问文件的时候
它是以什么样的方式来进行访问
你比如说只读 可读可写
为了能够通过文件系统
来实现对文件的访问
我们有必要来讨论一下
文件从用户和系统的角度来讲
它是个什么样的
这就是我们这里说到的
文件的用户视图和系统视图
用户视图是指
我们用户进程看到的文件是什么样
这里头呢 文件是持久的数据结构
这是你应用进程
我需要读写文件里的数据的时候
这些数据是有结构的
这是我们这里想看到的
但是从系统的角度来讲
它并不关心这个事
所以系统提供了一个文件的访问接口
在这里头呢
文件变成了一个字节序列了
当然对这一点的理解
不同的系统会有一些差别
在Unix 系统Linux系统 这一类的系统里
它认为文件内容是字节的序列
操作系统并不关心
存储在磁盘上的数据这些数据的格式
这是你应用进程需要关心的
这就是我们这里的系统视图
也就是说它把文件
视为是数据块的集合
说到数据块呢 这有两个
我们访问的数据块和磁盘的扇区
有关系但是也有区别
数据块是逻辑的存储单位
可能由多个扇区组成
而扇区是物理的存储单元
数据块的大小和扇区的大小
可能不一样
通常情况下是
几个扇区构成一个数据块
特别是对于容量比较大的
这些磁盘来说是这样的
有了用户的视角和系统的视角
那这时候它们之间的转换怎么来做呢
我们先来看一个文件读写的例子
进程读文件的时候
它是怎么在做呢
首先读取你对应的这个内容
所在的那个数据块
整块读出来 也就是说磁盘的访问
它具有这样一个特征
它的最小访问单位只能是块
我不能去对块当中的单独的字节
进行读或者是写
也就是说你想读出一个字节
也必须把整块读出来
你想写一个字节
也必须把整块重新写
那这样一来的话
我在这个地方就是
读出字节所在的数据块
然后把其中需要的内容返回给进程
这是你的读操作
而进程的写文件操作呢
它是先读取相应的数据块
也就是说你要写的那个位置所对应的
那一块的内容全部读出来
然后修改相应块当中的对应的内容
修改完毕之后 把整块写回去
这是你的写操作 对于这种情况呢
我们在这里头从用户视图到系统视图
就有一个转换
这个转换过程当中的
一个重要的特征是文件系统当中的操作
它的基本操作单位是数据块
有了这个之后
我们在这里就会有这样一个特征
你即使只读一个字节
也需要把整块读出来
你比如说我这里一个字节
而一块呢 是4K 那这时候呢
也就相当于我为了
读其中千分之零点几的数据
读写的量实际上是它的上千倍
所以在这里头呢
读写数据的时候我们一种优化
就是一块的内容
我是不是有可能
把它充分的利用起来
在具体讨论内容的时候
我们还有一个问题需要在这来讨论
就是进程对文件的访问模式
也就是说操作系统
在实现文件系统的时候
它需要了解用户进程
是如何访问文件的
那么在这里头呢
有这样三种方式
第一种是顺序访问
按文件的字节序列这个顺序
依次进行读写操作
我们现在用到的大多数文件访问
都是顺序访问
而第二种是随机访问
我访问的位置并不是顺序的
从中间读
这种做法呢
在实际的进程访问当中并不常用
但是仍然很重要
比如说像我们在这里的虚拟存储当中
把内存页存储到对换文件当中
这时候它的访问就是随机的
它对系统的性能的影响很大
第三个是索引访问
依据数据特征
来找文件当中的相应位置的数据
这种做法通常情况下
在我们操作系统里
并不完整的提供索引访问机制
一种做法是在上边建数据库
数据库提供完善的索引访问方式
实际上某种角度上来讲
我们可以认为操作系统里的文件系统
是一个小型的数据库
这是索引访问文件的一个示例
说我要想找文件里的某一个记录
这时我怎么找呢
我需要先找着
这个记录对应的位置在哪
这时候形成一个索引
一种我把它整个读出来
然后再挑我想要的这个
这时候它的效率会比较低
所以在这呢
基于索引的访问的话
就是我在这里加了一个索引
事先把相应的内容做了一种抽象
然后这时候我再来访问的时候
我先访问索引文件里的内容
找到它对应的位置
这时候再去读它相应的记录的内容
这样的话就可以提高它的读写效率
这是索引访问的做法
对于文件的内部呢
我们说从操作系统的角度来讲
它并不关心它的结构
在实际的系统当中
我们可以把它视为这样几种结构
一种是无结构的
它就是一个单词 或者说字节的序列
第二种是简单的记录结构
我把它分成若干列
每一列是其中的一个内容
这种列的大小或者记录的大小
可以有固定长度的
也可以是可变长度的
再有一种就是复杂结构的
我在这里定义复杂的结构
通常情况下这些复杂的结构
都是由你应用程序来识别
而从操作系统层面来讲
这些结构并不是很关心
但是为什么我们在这里会来讨论
它的内部结构呢
就是你到底是可执行文件
还是文本文件
在操作系统接口这一层面上
它提供某种程度的支持和识别
接下来是文件的共享和访问控制
多用户系统里头
文件的共享就变得很必要了
你比如说系统里很多文件
你不可以每个用户保存一份
通过共享可以节约磁盘的存储空间
在这里共享之后就带来一个问题
我的访问如何来控制
访问控制是每个用户
它到底可以访问哪些文件
可以以什么样的方式来访问
这是访问权限
在我们现在的系统里
通常情况下的访问权限有这样几种
读 写 执行 删除和看到它的列表
这些基本的权限呢
对应到每一个用户
这就是我们的文件访问控制列表 ACL
对于每一个文件 每一个用户
它有什么样的权限
在Unix系统里它的做法是这样的
用户分成当前的用户
用户所在的组和系统里的所有人
也就是分成三个范围
然后每一个文件的访问权限
分成读 写 执行这样三种权限
如果在这种情况下
你要想把所有的文件权限全部列清楚
这时候就构成一个访问矩阵了
三种类型的用户
然后它有三种权限组合到一起
每一个文件有一个自己的选择
那这样我给出整个系统当中的
每一个文件的访问权限
为了让这件事情做起来方便
我们需要识别用户
我必须识别用户是谁
我才能决定它有什么样的访问权限
然后为了让这件事情做起来效率更高
我们可以把用户分组 提供组标识
那这样的话
我可以给一组人设相同的权限
我只需要判断这个用户
是否在这个组就行了
关于访问权限
我们在这里就只做一个简单讨论
深入的讨论在不同的操作系统当中
都有很多的内容
接下来一个问题是说
共享所导致的语义一致性
当多个进程同时访问共享文件的时候
它们如何来协调
这里的协调
我们同前边的同步算法很相似
我需要协调我写到里头的内容
你读的内容大家是不是一致的
由于磁盘和网络延时的缘故
我们在文件系统里
对这个问题是弱化处理的
也就是说它的设计很简单
在Unix系统里简单到什么程度呢
对于文件的读写
一个用户写进去的内容
其他的用户立即可见
然后共享文件呢
允许多个用户同时读和写
基本上是说在这里
它没有协调的这些控制
这样一来的话
你的内容读写是不是完整
就需要靠应用程序自己来把握
那也就相当于我把这个一致性的问题
甩给应用进程自己去处理了
还有两种做法呢
一个是会话语义
写入的内容只有当文件被关闭的时候
其它用户才可见 这样一来的话
你相当于改的内容必须写完整了
其他的进程才能看得到
这样一来它们之间要协调的事情
就变得会简单一些
但是它的效率会低一些
还有一种是读写锁
操作系统在这里呢
提供几种基本的互斥访问锁
由应用进程自己来选择
我需要什么样的同步互斥
来保证我内容的一致性
这是一致性的问题