416_文件的基本操作
# 4.1_6_文件的基本操作
在这个小节中我们会学习文件的几种基本操作,包括创建文件、删除文件,读文件、写文件、打开文件和关闭文件,这些操作在背后到底做了一些什么事情。那么其他的一些复杂的操作,也可以由这些基本操作来组合的完成,但是在考试当中最经常考的还是这几种。
# 创建文件
首先我们来看一下创建文件的时候需要做一些什么事情,在我们平时自己使用电脑的时候,大家应该做过这样的操作,就是在某一个文件夹下,然后新建一个文本文档,也就是 txt 那种文档,那么在我们点了新建之后,其实他在背后做的事情是调用了操作系统的 create 系统调用,那么我们在进行 create 系统调用的时候需要提供的几个主要参数,
- 第一是要说明我们此时新建的文件到底需要占用多大的外存空间,比如说一个盘块是 1KB,然后这次我们申请的就刚好是一个盘块,1KB 这么大小的空间。
- 第二个我们需要说明的是我们新建的文件它存放的路径是什么地方?我们在这个例子当中,它存放路径就是 D 盘的 demo 目录下。
- 第三我们还需要说明这个文件的文件名到底是什么。在 windows 操作系统当中,我们点击右键新建一个文本文档的时候,它在背后会帮我们自动的填写这些参数,然后文件名是默认填写为新建文本文档.txt 然后大家也可以动手去自己的电脑上试一下。
那操作系统在接收到我们的系统调用请求的时候,它在背后需要主要做这样的两件事情,第一需要在外存当中找到这个文件所申请的这么一些磁盘块磁盘空间,这个地方可以结合上个小节,我们学习的空闲链表法、位图法,还有空闲表法、成组连接法等等这一系列的管理策略,来思考一下为文件分配存储空间到底是什么样一个流程。当然根据不同的这种存储空间管理策略的不同,在分配存储空间的时候所需要做的事情算法也是不一样的,这个是上个小节介绍的内容,这是系统需要做的第一个事情。
第二个事情,既然我们的这个文件它是放在某一个文件夹,一个目录下的,这样的话我们肯定是需要找到目录对应的目录表,然后在目录当中插入文件对应的目录项,那么目录项中需要包含文件名,还有文件的存放位置等等一系列的信息,这是咱们在文件目录小节当中介绍的内容,这是创建文件的时候需要做的两个事情。
# 删除文件
删除文件的话和创建文件刚好是相反的,当我们点右键删除这个文件的时候,其实它在背后是帮我们调用了操作系统提供的 delete 系统调用,在进行 delete 系统调用的时候需要提供的几个参数:
- 第一文件的存放路径,这个文件存放在 D 盘的 demo 这个目录下
- 第二需要提供文件名,test.txt
操作系统在接收到这个系统调用请求的时候,根据存放路径这个参数来找到 demo 这个目录对应的目录表,接下来可以根据这个文件名找到目录表当中对应的目录项,既然找到了目录项,我们就可以知道这个文件在外存当中是存放在什么位置,它占用了多少磁盘块这些信息,于是根据目录项当中记录的这些信息,操作系统就可以回收文件占用的这些磁盘块了。
在回收这些磁盘块的时候,又根据空闲表法空闲练表法等等这些存储空间管理的策略不同,也需要做不同的处理,这也是咱们上个小节介绍过的内容。
第三,当文件占用的磁盘块全部被回收之后,需要把文件在目录表当中对应的目录项也给删除,这是删除文件的时候需要做的一些事情。
# 打开文件
接下来我们来介绍一个大家可能不太熟悉的操作叫做打开文件,在很多操作系统当中对文件进行操作之前,都是要求先使用 open 系统调用来打开文件的,那么在打开文件的时候需要提供这样的几个参数,
- 第一,我们要指明我们要打开的文件存放路径是什么地方,
- 第二文件名是什么
- 第三,我们还需要指明我们打开这个文件之后会对文件进行哪些操作,比如说我们对这个文件只是想进行读操作,或者我们对文件既有可能读也有可能写,那么根据我们想要进行的操作类型的不同,也需要提供不同的参数
操作系统在处理 open 系统调用的时候,主要做了这样的两件事情。第一会根据我们提供的文件存放路径,在外存当中找到这个目录对应的目录表,在这个地方就是 demo 目录对应的目录表。另外不同的用户对文件的操作权限是不一样的,有的用户可能只可以读这个文件,而有的用户既可以读文件,也可以写文件,而这些用户对文件的这些访问权限的信息,其实也是记录在目录项当中的,所以可以根据目录项来检查此时用户请求的操作到底是否合法。如果说用户没有这种操作的权限的话,那就可以拒绝用户打开这个文件就终止处理。
如果用户是有这种操作权限的话,那么接下来操作系统会把这个文件对应的目录项复制到内存当中的打开文件表当中。也就是说在用户打开了一个文件之后,这个文件相关的那些信息就已经放到内存当中了,那之后用户想要在操作这个文件,只需要根据打开文件表的编号,就可以找到自己想要操作的文件的一切信息,这样的话就不需要每次查文件的时候都重新访问目录了,因此把目录项复制到打开文件表当中,是可以大幅度的提升文件访问的速度的,这是打开文件的时候需要做的一些事情。
另外大家需要注意的是有两种打开文件表,一种是系统的打开文件表,整个系统只有一张,然后打开文件表当中会记录所有的正在被其他进程使用的文件的一些信息。
另外每个进程也会有一个自己的打开文件表,这张表当中记录了自己的进程,此时已经打开的文件到底是哪一些。在进程打开文件表当中会有一个系统表的索引号,比如说 test.txt 这个文件在系统表当中是编号是 k 表项,那么这个地方 k 就是指向了表项。如果另一个进程 b也打开了 test.txt 这个文件,它同样也会指向这个系统的打开文件表。
这个地方大家需要注意的是,在系统的打开文件表当中,有一个字段叫做打开计数器,就是用来记录这个文件此时已经被几个进程打开了,此时如果有两个进程打开了这个文件的话,这个打开计数器就应该修改为 2 这样的数值。所以像打开计数器字段,是系统的打开文件表当中所特有的一个字段,其实在整个系统当中设置一个打开文件表的总表,这样的方式是比较方便实现某一些文件管理功能的。比如说我们在使用 windows 操作系统的时候,如果我们要尝试删除某一个 txt 文件,此时如果这个 txt 文件已经被某个记事本进程打开了,那么系统是会提示我们暂时无法删除该文件,大家可以自己动手试一下。
其实系统在背后做的事情,当我们选择删除文件的时候,他首先来检查了一下这个文件是否已经被某个进程打开了,也就是查询了系统当中的打开文件表。如果此时这个文件正在被某个进程使用的话,这个文件的数据显然是暂时不能删除的。所以如果我们在系统当中设置了一个这样的总表的话,那么对于一些文件管理的功能是很方便实现的。
另外在进程的打开文件表当中,大家会发现有两个比较特殊的字段,第一是叫读写指针,它其实就是记录了进程对文件进行读写操作,此时进行到了什么位置。第二,在进程的打开文件表当中,还需要标明进程对文件的访问权限,比如说进程 a再打开 test.txt 文件的时候,只是声明了自己是只会对 test.txt 文件进行只读的操作。那么如果进程在之后,尝试对这个文件进行写操作,系统会检查他之前申请的这种访问的类型,由于之前他只声明了自己是只读,所以这个写操作应该是被拒绝的,是认为是不合法的。
所以在进程打开文件表当中,比较特殊的是读写指针和访问权限这两个字段,不同的进程对一个文件进行读写操作进行到的位置是不一样的,所以不同的进程的读写指针也是应该不一样。另外不同的进程在打开一个文件的时候,所申请的这种访问类型也是不一样的,因此访问权限这个字段也应该放在进程的打开文件表当中。
当然除了这个地方列出的这些字段之外,在进程的打开文件表当中还会有其他的一些文件的信息,这就没有全部列举。
# 关闭文件
接下来我们来看一下,当一个用户使用完一个文件选择关闭文件的时候,它在背后需要做一些什么事情?
第一,当进程选择关闭一个文件的时候,那么我们可以把进程的打开文件表当中,这个文件对应的表项先给删除,相应的需要回收分配给文件的内存缓冲区等等一系列的资源。
另外我们需要对系统打开文件表当中的表项,打开计数器进行一个减 1 的操作,由于此时打开计数器依然是大于 0 的,所以说明此时这个文件还在被其中的某一些进程所使用,因此系统打开文件表当中表项暂时不能删除,只有打开计数器为 0 的时候,才需要删除系统打开文件表当中的表项,这是关闭文件的时候需要做的一些事情。
# 读文件
接下来我们来看一下读文件也叫 read 系统调用在背后做了一些什么事情。其实在我们双击打开 test.txt 文本文档的时候,在背后其实是调用了操作系统提供的 read 系统调用,也就是读文件的功能。
不过通过之前的讲解,我们知道在对文件进行读写操作之前,一定要先打开文件,所以其实在正式开始读文件的时候,记事本进程的打开文件表当中,已经有了这个文件对应的一个表项了。因此记事本进程在读文件的时候,只需要指明自己要读的文件,它对应的打开文件表的编号到底是多少就可以了,这是读文件的时候需要提供的第一个参数,就是要指明到底要读的是哪一个文件。
第二,在读文件的时候还需要指明此时需要读入的是多少个数据,比如说此时是要把这个文件全部 1KB 的内容都读入内存,那么就需要指明此时需要读入的是 1KB 这么多,另外还需要指明读入的数据到底是要放在内存当中的什么位置,这些参数的填充都是记事本进程在背后为我们完成的事情。
操作系统在处理 Read 系统调用的时候,会根据打开文件表当中,读指针所指向的外存地址那个地方,读入用户指定的大小的这么多的数据,然后放到用户指定的内存区域当中,这是读文件需要做的事情,
# 写文件
写文件的操作其实和读文件是很类似的,在我们编辑完一个文本文档之后,我们可以点击文件保存这样一个功能。点击保存之后,其实记事本这个应用程序在背后是帮我们调用了操作系统提供的写文件功能,也就是 write 系统调用,这个系统调用的作用就是把这个文件在内存当中的数据再写回到外存,保存在外存当中。
所以在进行 write 系统调用的时候,我们也需要提供这样的几个参数。
第一是需要指明我们要写的到底是哪一个文件,同样的进程只需要指明这个文件在打开文件表当中的序号到底是多少,操作系统就知道我们要写的是哪个文件了。
另外还需要指明这个写操作需要写回的数据到底是多少,比如说要把内存当中的 1KB 全部写回外存,那么我们指明要写回的是 1KB。
另外我们还需要指明我们要写回外存的这些数据是放在内存当中的什么位置的,那操作系统根据 write 系统调用的这些参数,会从用户指定的内存区域当中读出指定大小的那些数据,然后写指针所指向的外存区域当中,所以这是写文件要做的事情,它和读文件很类似。
# 小结
那么这个小节我们介绍了文件的几种基本操作,包括创建文件、删除文件、打开文件、关闭文件、读文件和写文件。我们最需要注意的是打开文件操作,这个操作会把目录项的信息复制到内存当中的打开文件表当中。
另外大家需要知道内存中有两种打开文件表,一种是系统的打开文件表,另外一种是进程的打开文件表。系统打开文件表当中包含了所有的正在被使用的文件信息,而进程的打开文件表当中,只包含了进程本身打开了的那些文件信息,
需要注意的是在打开文件的时候,并不会把文件的数据直接读入内存,只是把文件的目录项给复制到了内存的打开文件表当中,这一点也是很容易混淆的一个点。
另外系统会把打开文件表当中的索引号返回给用户,之后用户就可以根据索引号来查询打开文件表,然后直接操作自己的文件,而不再需要每一次都查询目录。这个地方的索引号在有的教材上也称为文件描述符,这个概念在咱们的考试当中也出现过,所以文件描述符这个术语大家也需要注意一下,它指的其实就是打开文件表的一个序号。
另外大家需要注意的是在进程的打开文件表和系统的打开文件表当中都会有一些各自特有的属性,比如说每个进程都不一样的读写指针,还有访问权限,这些肯定是需要放在进程的打开文件表当中,而一个文件总共被多少个进程打开了,这个数据肯定是需要放在系统的打开文件表当中
比较容易和打开文件混淆的是读文件操作,只有读文件的时候才会把文件的数据真正的从外存读入内存,而对文件进行读写操作的时候,用户不需要再提供文件名文件路径这些信息,只需要提供文件描述符,也就是这个文件在打开文件表当中的索引号,操作系统就可以知道我们要读要写的到底是哪一个文件了。打开文件和读文件,这两个操作是最容易在选择题当中进行考察的,大家需要重点关注。