第四章 文件管理
9.文件基本操作
"打开文件和关闭文件"与平常鼠标双击打开文件和点击"X"关闭文件是有所不同的。
操作系统在处理open系统调用时主要做了以下两件事情,①根据我们提供的文件存放路径在外存当中找到这个目录对应的目录表,另外不同的用户对文件的操作权限是不一样的,有的用户可能只可以读这个文件,而有的用户既可以读文件也可以写文件,而这些用户对文件的访问权限信息(访问控制列表ACL)其实也是记录在目录项当中的,所以可以根据目录项来检查此时用户请求的这个操作到底是否合法,如果用户没有这种操作权限的话就可以拒绝用户打开文件;而如果用户有这种操作权限的话,那么接下来操作系统会把这个文件对应的目录项复制到内存中的"打开文件表"中,也就是说在用户打开了一个文件之后,这个文件相关的信息就已经放到内存当中了,之后用户想要再操作这个文件只需要根据这个"打开文件表"的编号就可以找到自己想要操作的这个文件的一切信息,这样的话就不需要每次查文件的时候都重新访问目录了,因此把目录项复制到打开文件表当中是可以大幅度的提升文件访问的速度的。
需要注意的是有两种打开文件表,一种是系统的打开文件表(整个系统只有一张),这个打开文件表中会记录所有的正在被其他进程使用的文件的一些信息。另外每个进程也会有自己的打开文件表,这张表中记录了自己的这个进程此时已经打开的文件是哪些,在进程的打开文件表中会有一个系统表的索引号,比如"test.txt"这个文件在系统打开文件表中是编号k这个表项,那么进程打开文件表会记录下这个编号k;同样的如果另一个进程B也打开了"test.txt"这个文件那它同样也会指向系统的打开文件表。在系统的打开文件表中有一个字段"打开计数器",用来记录这个文件此时已经被几个进程打开了,此时如果有两个进程打开了这个文件的话,那这个打开计数器就应该修改为2。"打开计数器"这个字段是系统打开文件表中所特有的一个字段。
在整个系统当中设置一个打开文件表的总表(即系统打开文件表)是比较方便实现某一些文件管理功能的。比如说在使用Windows操作系统的时候如果我们要尝试删除某一个txt文件,那此时如果这个txt文件已经被某个记事本进程打开了,那么系统是会提示我们暂时无法删除该文件,其实系统在背后做的事情就是当我们选择删除文件的时候它首先来检查这个文件是否已经被某个进程打开了,也就是查询了系统当中的打开文件表,如果此时这个文件正在被某个进程使用的话,那么这个文件的数据显然是暂时不能删除的,所以如果我们在系统当中设置了一个系统打开文件表这样的总表,那么对于一些文件管理的功能是很方便实现的。另外,在进程的打开文件表当中会有一个字段"读写指针",记录了这个进程对文件进行读写操作此时进行到了什么位置。在进程的打开文件表中还需要标明这个进程对文件的访问权限。比如说进程A在打开test.txt这个文件的时候只是声明了自己只会对test.txt这个文件进行"只读"操作,那么如果这个进程在之后尝试对这个文件进行"写操作",那操作系统会检查它之前申请的访问的类型,由于之前它只是声明了"只读",所以这个写操作应该拒绝。在进程打开文件表中比较特殊的是"读写指针",和"访问权限"这两个字段,不同的进程对一个文件进行读写操作进行到的位置是不一样的,所以不同进程的读写指针也是应该不一样;另外不同的进程在打开一个文件的时候所申请的这种访问类型也是不一样的,因此访问权限这个字段也应该放在进程的打开文件表中。当然除了这里列出的字段之外,在进程的打开文件表中还会有其他的一些文件的信息,这里没有全部列举。
用户使用完一个文件,要"关闭文件":
当这个进程选择关闭一个文件的时候那么可以把这个进程的打开文件表中这个文件对应的表项删除,相应的需要回收分配给这个文件内存缓冲区等等一系列的资源。另外需要对系统打开文件表中对应表项的打开计数器进行-1操作,由于此时这个打开计数器依然是>0的,所以说明此时这个文件还在被其中的某一些进程所使用,因此系统打开文件表中对应的表项暂时还不能删除,只有打开计数器为0的时候才需要删除系统打开文件表中的表项。
读文件、read系统调用:
在我们双击打开test.txt这个文本文档的时候在背后其实是调用了操作系统提供的read系统调用,也就是读文件的功能。通过之前的讲解可以知道,在对文件进行读写操作之前一定要先打开文件,所以其实在正式开始读文件的时候"记事本"这个进程的打开文件表当中已经有了这个文件对应的表项了,因此记事本这个进程在读文件的时候只需要指明自己要读的这个文件它对应的打开文件表中的编号到底是多少就可以了。这就是读文件的时候需要提供的第一个参数,就是要指明到底要读的是哪一个文件。第二个在读文件的时候还需要指明此时需要读入多少数据,另外还要指明这个读入的数据是存放在内存中的什么位置。这些参数的填充都是"记事本"这个进程在背后为我们完成的事情。
操作系统在处理read系统调用的时候会根据打开文件表中读写指针这个读指针所指向的外存地址那个地方读入用户指定的大小的这么多数据然后放入到用户指定的内存区域当中。
写文件和读文件是很类似的,在编辑完一个文本文档之后,我们可以点击文件保存,点击保存之后其实记事本这个应用程序在背后是帮我们调用了操作系统提供的写文件功能即write系统调用,这个系统调用的作用就是把这个文件在内存当中的数据再写回到外存,保存到外存当中。所以在进行write系统调用的时候我们也需要提供这样的几个参数,第一需要指明要写的是哪个文件,同样的这个进程只需要指明这个文件在打开文件表中的编号是多少,操作系统就知道要写的是哪个文件了。另外还需要指明这个写操作需要写回的数据大小到底是多少,另外还需要指明要写回外存的这些数据是放在内存当中的什么位置的。 操作系统根据write系统调用的参数,会从用户指定的内存区域中读出指定大小的数据然后写回"写指针"所指向的外存区域当中。
总结 :
最重要的是打开文件,该操作会把目录项的信息复制到内存当中的打开文件表中,需要知道内存当中有两种打开文件表,一种是系统的打开文件表(整个系统只有一张),另外一种是进程打开文件表。系统的打开文件表中包含了所有的正在被使用的文件信息,而进程的打开文件表中只包含了这个进程本身打开了的那些文件信息。
需要注意的是在打开文件的时候并不会把文件的数据直接读入内存 ,只是把文件的目录项给复制到了内存的打开文件表当中。另外系统会把打开文件表当中的索引号返回给用户,之后用户就可以根据这个索引号来查询打开文件表然后直接操作自己的文件,而不用再每一次都查询目录,这个地方的索引号在有的教材上也称为"文件描述符",这个术语在真题中出现过。所以"文件描述符"这个术语也需要注意一下,它指的其实就是进程的打开文件表中的编号。
另外需要注意在++进程的打开文件表和系统的打开文件表中都会有一些各自特有的属性,比如每个进程都不一样的读写指针和访问权限,这些肯定需要放在进程的打开文件表中的,而一个文件总共被多少个进程打开了这个数据肯定是需要放在系统的打开文件表中++ 。
比较容易和打开文件混淆的是读文件这个操作,只有读文件的时候才会把文件的数据真正的从外存读入内存。 而对文件进行读写操作的时候用户不需要再提供文件名,文件路径这些信息,只需要提供"文件描述符"也就是这个文件在打开文件表当中的索引号,操作系统就可以知道要读写的是哪个文件了。