当前位置: 首页 > 科技观察

文件系统:隐藏在Linux背后的机制

时间:2023-03-16 18:36:35 科技观察

本文转载自微信公众号“JavaBuilder”,可通过以下二维码关注。转载本文请联系JavaBuilder公众号。在Linux中,最直观可见的部分就是文件系统(filesystem)。下面我们来探讨一下Linux中国的文件系统背后的原理和思路,系统调用,文件系统实现。其中一些想法起源于MULTICS,现在被其他操作系统(如Windows)使用。Linux的设计理念是小即是美(SmallisBeautiful)。虽然Linux只使用了最简单的机制和少量的系统调用,但Linux提供了一个强大而优雅的文件系统。Linux文件系统的基本概念Linux最初的设计是MINIX1文件系统,只支持14字节的文件名,最大的文件也只支持64MB。MINIX1之后的文件系统是ext文件系统。与MINIX1相比,ext系统对支持的字节大小和文件大小都有了很大的提升,但是ext的速度还是没有MINIX1快,所以开发了ext2,可以支持长文件名和大文件。文件,并且比MINIX1具有更好的性能。这使其成为Linux的主要文件系统。只不过Linux会使用VFS来支持多文件系统。连接Linux时,用户可以在VFS上动态挂载不同的文件系统。Linux中的文件是一个任意长度的字节序列,Linux中的一个文件可以包含任意的信息,比如ASCII码、二进制文件,其他类型的文件是不分青红皂白的。为了方便起见,可以将文件组织在一个目录中,目录作为文件存储的形式在很大程度上可以看作是一个文件。目录可以有子目录,子目录构成分层文件系统。Linux系统下的根目录是/,通常包含多个子目录。字符/也用于区分目录名称。比如/usr/cxuan代表根目录下的usr目录,还有一个子目录叫cxuan。下面我们介绍Linux系统根目录下的目录名/bin。它是一个重要的二进制应用程序,包括二进制文件。系统所有用户使用的命令都在这里/boot,它启动包含bootloader的相关文件/dev。,包含设备文件、终端文件、USB或连接到系统的任何设备/等、配置文件、启动脚本等,包含所有程序所需的配置文件,还包括启动/停止单个应用程序的启动和关闭shell脚本/home,本地主路径,所有用户使用home目录存放个人信息/lib,系统库文件,包括支持/bin和/sbin下的二进制库文件/lost+found,提供根目录下的lost+found系统目录,必须在root用户下才能查看当前目录/media的内容,挂载可移动媒体/mnt,挂载文件系统/opt,提供一个可选的应用程序安装目录/proc,一个专门用于维护系统信息的动态目录和状态,包括当前运行的进程信息/root,root用户的主目录文件夹/sbin,重要的二进制系统文件/tmp,系统和用户创建的临时文件,以及该目录下的文件ctory系统重启时会删除/usr,包括大部分用户可以访问的应用程序和文件/var,经常变化的文件,比如日志文件或者数据库等。在linux中,有两种路径,一种是绝对路径(absolutepath),绝对路径告诉你从根目录找到文件。绝对路径的缺点是太长,不方便。还有一种相对路径(relativepath),相对路径所在的目录也称为工作目录(workingdirectory)。如果/usr/local/books是工作目录,那么shell命令cpbooksbooks-replica表示相对路径,而cp/usr/local/books/books/usr/local/books/books-replica表示绝对路径。在Linux中经常会发生一个用户使用另一个用户的文件或者使用文件树结构中的文件。两个用户共享同一个文件。该文件位于某个用户的目录结构中。当另一个用户需要使用这个文件时,必须通过绝对路径来引用他。如果绝对路径很长,每次输入都会变得很麻烦,所以Linux提供了链接(link)机制。比如下面是使用链接前的一张图,比如有两个工作账号jianshe和cxuan,而jianshe要使用cxuan账号下的A目录,那么可能会进入/usr/cxuan/A,这是一个未使用链接之后的图表。使用链接后的示意图如下现在,建设可以创建链接使用cxuan下的目录。'创建目录时,会同时创建两个目录项,它们是.而..前者代表工作目录本身,后者代表目录的父目录,也就是目录所在的目录。这样访问/usr/jianshe中cxuan中的目录就是../cxuan/xxxLinux文件系统不区分磁盘。这是什么意思?一般来说,一个磁盘中的文件系统是相互独立的,如果一个文件系统目录要访问另一个磁盘中的文件系统,在Windows中可以这样做。两个文件系统在不同的磁盘上,相互独立。在linux中支持挂载,可以让一个磁盘挂载到另一个磁盘上,那么上面的关系就会变成如下挂载后,两个文件系统不再需要关心文件系统在哪个磁盘上了,两个文件系统彼此可见。Linux文件系统的另一个特性是支持锁定。在某些应用程序中,两个或多个进程可能同时使用同一个文件,这可能会导致竞争条件。一种解决方案是加不同粒度的锁,防止某个进程只修改某行记录,导致整个文件不可用。POSIX提供了灵活的不同粒度的锁定机制,允许进程以不可分割的操作锁定一个字节或整个文件。加锁机制要求尝试加锁的进程指定要加锁的文件、起始位置和加锁的字节。Linux系统提供了两种锁:共享锁和互斥锁。如果文件的一部分已经加了共享锁,那么加排他锁是不会成功的;如果文件系统的一部分已经添加了互斥量,那么在释放互斥量之前的任何锁定都不会成功。为了使锁定成功,请求锁定的部分的所有字节都必须可用。在加锁阶段,进程需要设计加锁失败后的情况,即判断加锁失败后是否选择阻塞。如果选择了block,那么当被锁定的进程中的锁被删除时,进程就会被释放。阻止并更换锁。如果进程选择非阻塞,则不会更换锁,立即从系统调用返回,标记状态码表示是否加锁成功,然后进程选择下一次重试.锁定区域可以重叠。下面我们演示三个不同的锁定区域。如上图所示,A的共享锁是从第四字节到第八字节加锁的。如上图所示,进程同时在A和B上加了一把共享锁,其中6-8个字节是重叠锁。如上图所示,进程A、B、C同时加了共享锁,则第六字节和第七字节为共享锁。如果此时有进程试图锁定第6个字节,则此时设置会失败并阻塞。由于这个区域同时被ABC加锁,所以只有ABC释放锁后进程才能加锁成功。Linux文件系统调用许多系统调用都与文件和文件系统有关。我们将首先查看对单个文件的系统调用,然后查看对整个目录和文件的系统调用。为了创建一个新文件,使用了creat方法,注意没有e。这是一集。曾经有人问UNIX的创始人KenThompson,如果有机会重写UNIX,你会做什么。他回答说他会把creat改成create,哈哈哈哈。这个系统调用的两个参数是文件名和保护模式fd=creat("aaa",mode);此命令将创建一个名为aaa的文件,并根据模式设置文件的保护位。这些位确定哪个用户可以访问该文件以及如何访问。creat系统调用不仅创建了一个名为aaa的文件,而且还打开了它。为了让后续的系统调用能够访问这个文件,creat系统调用会返回一个非负整数,称为文件描述符(filedescriptor),也就是上面的fd。如果对一个已经存在的文件调用creat系统调用,文件的内容会被清空,从0开始。通过设置适当的参数,open系统调用也可以创建文件。我们来看看主要的系统调用,如下表所示系统调用说明fd=creat(name,mode)创建新文件的方法fd=open(file,...)打开一个文件进行读取,writingorReadandwrites=close(fd)关闭一个打开的文件n=read(fd,buffer,nbytes)从文件读取数据到缓冲区n=write(fd,buffer,nbytes)从缓冲区写入文件dataposition=lseek(fd,offset,whence)移动文件指针s=stat(name,&buf)获取文件信息s=fstat(fd,&buf)获取文件信息s=pipe(&fd[0])创建管道s=fcntl(fd,...)对于文件加锁等其他操作,要想读写一个文件,前提是要先打开文件,而且必须用creat或open打开。参数是打开文件的方式,是只读和读写或只写。open系统调用还返回一个文件描述符。打开文件后,需要使用close系统调用将其关闭。close和open返回的fds总是未使用的最小数量。什么是文件描述符?文件描述符是一个数字,用于标识计算机操作系统中打开的文件。它描述了数据资源以及如何访问它们。当一个程序要求打开一个文件时,内核会执行以下操作来授予访问权限,在全局文件表(globalfiletable)中创建一个条目,以提供该条目的位置给软件。文件描述符由一个唯一的非负整数组成,系统上每个打开的文件至少存在一个文件描述符。文件描述符最初用于Unix,并被包括Linux、macOS和BSD在内的现代操作系统使用。当一个进程成功访问一个打开的文件时,内核返回一个文件描述符指向全局文件表中的一个条目。这个文件表项包含文件的inode信息、字节偏移量、访问限制等。例如,如下图所示,默认情况下,前三个文件描述符是STDIN(标准输入)、STDOUT(标准输出)和STDERR(标准错误)。标准输入的文件描述符是0。在终端中,默认是用户的键盘输入。标准输出的文件描述符是1。在终端中,默认是用户屏幕。错误的默认流为2。在终端中,默认为用户屏幕。在简要讨论文件描述符之后,让我们回到文件系统调用的讨论。在文件系统调用中,最昂贵的是读取和写入。read和write都有三个参数文件描述符:告诉需要读取和写入哪个打开的文件缓冲区地址:告诉需要从哪里读取数据,向哪里写入数据统计信息:告诉需要传输多少字节就是这些参数,设计非常简单轻便。虽然几乎所有程序都按顺序读取和写入文件,但某些程序需要能够随机访问文件的任何部分。与每个文件相关联的是一个指示文件中当前位置的指针。顺序读取(或写入)时,通常指向下一个要读取(或写入)的字节。如果指针在读取1024字节之前位于位置4096,则在成功读取系统调用后它将自动移动到位置5120。Lseek系统调用更改指针位置的值,以便后续的读取或写入调用可以从文件中的任何位置开始,甚至可以超出文件末尾。lseek=Lseek,段落开头大写。lseek避免被称为seek的原因是seek已经用于以前的16位计算机上的查找功能。lseek有三个参数:第一个是文件的文件描述符,第二个是文件的位置;第三个告诉文件位置是相对于文件的开头、当前位置还是文件的结尾lseek(intfildes,off_toffset,intwhence);lseek的返回值是更改文件指针后在文件中的绝对位置。lseek是唯一一个从不进行真正磁盘搜索的系统调用,它只是更新当前文件位置,这是内存中的一个数字。对于每个文件,Linux都会跟踪文件模式(一般、目录、特殊)、大小、最后修改时间和其他信息。程序可以通过stat系统调用看到这些信息。第一个参数是文件名,第二个是指向放置请求信息的结构的指针。这些结构的属性如下图所示。fstat调用与stat相同,只是fstat可以对打开的文件进行操作,而stat只能对路径进行操作。管道文件系统调用用于创建外壳管道。它创建一系列伪文件来缓冲管道组件的数据,并返回用于读取或写入缓冲区的文件描述符。在管道中,比如下面的操作sort