《操作系统导论》第四部分 持久性 P4 文件和目录

C4 文件和目录

持久存储,永久存储设备永久地(或至少长时间地)存储信息,如传统硬盘驱动器或现代地固态存储设备,持久存储设备与内存不同,内存在断电时,其内容会丢失,而持久存储设备会保持这些数据不变,因此操作系统需要特别注意这些设备,用户用它们来保存真正关心地数据

本章主要学习UNIX文件系统中的API

4.1 文件和目录

存储虚拟化形成了两个关键的抽象:
1,文件文件就是一个线性字节数组,每个字节都可以读取或写入,每个文件往往都有各自的低级名称,即inode号

2,目录,每个目录也有一个低级名称inode号,但它的内容很具体,它包含一个(用户可读名,低级名称)的映射关系,如一个目录,它的用户可读名称为“abc”,它的inode号为10,那么该目录存在条目(“abc”, 10)的映射,通过将目录放在其它目录中,可以构建任意的目录树

在这里插入图片描述
文件包含两部分,一部分是文件名称,另一部分是文件的类型,如.c,.jpg,.mp3等

文件系统提供了一种方便的形式来命名我们感兴趣的所有文件,名称在文件系统中很重要,因为访问资源的第一步就是先能够命名它,在UNIX中,文件系统提供了一种统一的方式来访问磁盘,U盘,许多其它设备上的文件,事实上还有很多其它的东西,都位于单一目录树下

4.2 文件系统接口

(1) 创建文件

创建文件可以通过open系统调用来完成,通过调用open()并传入O_CREAT标志,程序可以创建一个新文件:

int fd = open("foo", O_CREAT | O_WRONLY | O_TRUNC);
fd:创建文件成功后,该文件的文件描述符
O_CREAT:创建文件
O_WRONLY:只能写入
O_TRUNC:如果该文件已经存在,则删除现有内容

open()最重要的是它的返回值,即该文件的文件描述符,文件描述符只是一个整数,在UNIX中用于访问文件,因此一旦文件被打开,就可以使用文件描述符来读取或写入文件,也可以将文件描述符看作指向文件类型对象的指针,可以调用其它方法来访问文件,如read(),write()等

(2) 读写文件

创建了文件后,接下来就要对文件进行读取或写入,先读取一个现有的文件,在命令行中就可以用cat程序,将文件的内容显示到屏幕上:
在这里插入图片描述
使用echo将"hello"重定向到文件foo,然后文件就包含了"hello"这个字符串,然后调用cat来查看文件的内容,但是cat如何访问文件foo

扫描二维码关注公众号,回复: 12642655 查看本文章

通过UNIX中的strace来跟踪程序在运行时所做的每个系统调用,然后将这些跟踪结果显示在屏幕上:
在这里插入图片描述
cat访问foo并打印的过程:
1,cat所做的第一件事是打开文件准备读取

open("foo", O_RONLY | O_LARGEFILE)
使用O_RONLY表明只读的打开该文件, O_LARGEFILE表明使用64位偏移量

open()调用成功后返回一个文件描述符,其值为3

每个正在运行的进程已经打开了3个文件:标准输入,标准输出,标准错误,这些分别由文件描述符0,1,2表示,第一次打开一个文件时,它的文件描述符为3

2,打开成功后,cat使用read()系统调用重复读取文件中的一些字节

read(3, "hello\n", 4096)
3就是open返回的文件描述符,用于指明要操作的文件,
"hello\n"指向一个用于放置read()结果的缓冲区
4096即缓冲区的大小即4KB

read()调用成功后,返回了6,即它所读取文件内的字节数(hello和一个字符串结束标记)

3,读取到文件中的内容后,调用write()打印到屏幕上

write(1, "hello\n", 6)
文件描述符1即标准输出用于将单词hello写到屏幕上
"hello\n"read()存放读取结果的缓冲区
6即需要打印长度是6字节

4,cat()继续调用read()查看文件中是否还有其余内容

read(3, "", 4096)

由于文件中没有剩余内容,read()返回0,程序知道它已经读取完整个文件,因此程序调用close

close(3)

close()传入相应文件描述符,表明它已经用完文件"foo",该文件已经被关闭,对它的读取完成了

写入文件也是通过类似的步骤完成的,首先,打开一个文件准备写入,然后调用write(),对于较大的文件可能重复调用write(),然后调用close()

(3) 不按顺序地读取写入文件

先前的对文件的读取和写入都是按顺序的,即从头到尾地读取或写入一个文件,但有时能够读取或写入文件地特定偏移是有用的,如在文本文件上构建了索引并用它来查找特定单词,最终可能会从文件中的某些随机偏移量中读取数据

为此可以使用lseek()

off_t lseek(int fields, off_t offset, int whence);
fields即文件描述符
offset即文件偏移量,它将文件偏移量定位到文件中的特定位置
whence指定了搜索的执行方式

对于每个进程打开的文件,操作系统都会跟踪一个“当前”偏移量,这将决定在文件中读取或写入时,下一次读取或写入开始的位置,lseek()可以改变偏移量,调用lseek()与寻道(seek)无关,对于lseek()的调用是改变内核中变量的值

(4) fsync()立即写入

当程序调用write()时,它只是告诉文件系统:在将来的某个时刻,将此数据写入持久存储,出于性能的原因,文件系统会将这些写入在内存缓冲一段时间(5-30s),在稍后的时间点,写入将实际发送到存储设备

但是有些应用程序需要的不只是这种保证,如在数据库管理系统中,开发正确的恢复协议要求能够经常强制写入磁盘,UNIX中提供了fsync(int fd)接口,当进程对特定文件描述符调用fsync()时,文件系统将所有的数据立刻写入磁盘来响应

(5) 文件重命名

有了文件,有时需要给文件一个不同的名字,通过mv命令来完成

mv foo bar
文件foo被重命名为bar

mv使用了系统调用rename(char *old, char *new),它只需要两个参数,文件的原来名称和新名称

rename()调用是一个原子操作,如果系统在文件重命名期间崩溃,文件被命名为旧名称或新名称,不会出现中间状态,对于支持需要对文件状态进行原子更新的应用程序,rename()非常重要

(6) 获取文件信息

除了文件访问外,还希望文件系统能够保存关于它正在存储的每个文件的大量信息,通常称这些数据为元数据 metedata,要查看特定文件的描述符,可以使用stat()或fstat(),这些调用将文件描述符添加到一个文件中,并填充一个stat结构
在这里插入图片描述
可以通过调用stat查看一个文件的详细信息:
在这里插入图片描述
每个文件系统通常将这种类型的信息保存在一个名为inode(索引节点)的结构中,可以将inode看作是文件系统保存的持久数据结构

(7) 删除文件

通过rm程序来删除文件,rm调用了unlike()

unlike("foo");

unlike()只需要待删除文件的名称,并在成功后返回0

(8) 创建目录

除了文件外,还可以使用一组与目录相关的系统来创建,读取和删除目录,不能直接写入目录,因为目录格式的文件被视为文件系统元数据,只能间接的更新目录,如通过在其中创建文件,目录或其它类型对象,通过这种方式,文件系统可以确保目录的内容始终符合预期

创建目录,可以使用mkdir():

mkdir("foo", 0777);

(9) 读取目录

虽然创建了目录,但希望也能读取目录,可以通过ls程序来完成

ls程序使用了opendir(),readdir(),closedir()这3个调用来完成工作

由于目录本身只有少量的信息(只是将名称映射到inode号,以及少量其它细节),程序可能需要在每个文件上调用stat()来获取每个文件的更多信息,如长度和其它详细信息,就可以通过ls -l来完成

(10) 删除目录

可以通过rmdir()来删除目录,与删除文件不同,删除目录更加危险,因此使用rmdir()之前要求被删除的目录必须为空,如果视图删除一个非空目录,那么rmdir()的调用就会失败

4.3 链接

(1) 硬链接

现在回到删除文件为什么使用unlike(),理解在文件系统中创建条目的新方法,即通过link()系统调用,link()有两个参数:一个旧路径名和一个新路径名,当你将一个新文件名“链接”到一个旧文件名时,实际上创建了另一种引用同一个文件的方法,通过ln来执行此操作

prompt> echo hello > file
prompt>cat file
hello
prompt> ln file file2
prompt>cat file2
hello

在这里先创建了一个文件file,包含了单词“hello”,然后用ln程序创建了该文件的一个硬链接,之后可以打开file或file2来检查文件

link在要创建链接的目录中创建了另一个名称,并将其指向原有文件的相同inode号,现在就有了两个人类可读的名称(file和file2),它们都指向同一个文件file和file2都只是指向文件底层元数据的链接

创建一个文件时,其实做了两件事:
1,创建一个inode结构,它将跟踪几乎所有关于该文件的信息,包括其大小,文件块在磁盘上的位置等等
2,将人类可读的名称链接到该文件,并将该链接放到目录中

因此为了从文件系统中删除一个文件,调用unlike(),可以删除文件名file,但还可以访问该文件中的内容:

prompt> rm file
removed 'file'
prompt> cat file2
hello

这是因为当文件系统取消链接文件时,它会检查inode号中的引用计数,该引用计数允许文件系统跟踪有多少不同的文件名已经链接到这个inode,调用unlike()时,会删除人类可读的名称(当前删除的文件)与给定inode号之间的链接,并减少引用次数,当引用次数到达0时,文件系统才会释放inode和相关数据块,从而真正删除文件

示例,为同一个文件创建3个链接,然后查看它们,观察引用计数:
在这里插入图片描述

(2) 符号链接(软链接)

还有一种非常有用的链接类型,称为符号链接或软链接,事实表明硬链接存在局限性:不能创建目录的硬链接,不能硬链接到其它磁盘分区中的文件,为此人们创建了一种称为符号链接的新型链接

创建这样的链接,可以使用程序ln -s:

prompt> echo hello > file
prompt> ln -s file file2
prompt> cat file2
hello

现在就可以通过文件名称file和链接名称file2来访问原始文件,除了表面相似外,符号链接实际上与硬链接完全不同,第一个区别是符号链接本身实际上是一个不同类型的文件,除了文件和目录,符号链接是文件系统所知道的第3种类型

对符号链接运行stat:
在这里插入图片描述
符号链接与硬链接完全不同,删除名为file的原始文件会导致符号链接指向不再存在的路径名,因此造成了 “悬空引用”

4.4 创建并挂载文件系统

现在已经有了访问文件,目录和特定类型链接的基本接口,接下来如何在许多底层文件系统组件完整的目录树,这项任务的实现是先制作文件系统,然后挂载它们,使其内容可以访问

为了创建一个文件系统,大多数文件系统提供了一个工具,通常命名为mkfs,作为输入,为该工具提供一个设备(如磁盘分区),一种文件系统(如ext3 标准的基于磁盘的文件系统),它就在磁盘分区上写入一个空文件系统,从根目录开始

但是一旦创建了这样的文件系统,就需要在统一的文件系统树种进行访问,这个任务是通过mount程序来实现的,mount的作用很简单,以现有目录作为目标挂载点,本质上是将新的文件系统粘贴当目录树的这个点上

现在有一个未挂载的ext3文件系统,存储在设备分区/dev/sda1中,它的内容包括:一个根目录,其中包含两个子目录a,b,每个子目录下有一个名为foo的文件,假设希望挂载点在/home/user上挂载此文件系统,可以输入:

prompt> mount -t ext3 /dev/sda1 /home/user

如果成功,mount就让这个新的文件系统可用了

prompt> ls /home/user
a b

路径名/home/user现在指的是新挂载目录的根,mount可以将所有文件系统统一到一颗树中,而不是拥有多个独立的文件系统,这让命名统一且方便

猜你喜欢

转载自blog.csdn.net/weixin_43541094/article/details/111769326