在 Linux 中,最直观、最可见的部分就是 文件系统(file system)
。下面咱们就来一块儿探讨一下关于 Linux 中国的文件系统,系统调用以及文件系统实现背后的原理和思想。这些思想中有一些来源于 MULTICS,如今已经被 Windows 等其余操做系统使用。Linux 的设计理念就是 小的就是好的(Small is Beautiful)
。虽然 Linux 只是使用了最简单的机制和少许的系统调用,可是 Linux 却提供了强大而优雅的文件系统。node
Linux 在最初的设计是 MINIX1 文件系统,它只支持 14 字节的文件名,它的最大文件只支持到 64 MB。在 MINIX 1 以后的文件系统是 ext 文件系统。ext 系统相较于 MINIX 1 来讲,在支持字节大小和文件大小上均有很大提高,可是 ext 的速度仍没有 MINIX 1 快,因而,ext 2 被开发出来,它可以支持长文件名和大文件,并且具备比 MINIX 1 更好的性能。这使他成为 Linux 的主要文件系统。只不过 Linux 会使用 VFS
曾支持多种文件系统。在 Linux 连接时,用户能够动态的将不一样的文件系统挂载倒 VFS 上。shell
Linux 中的文件是一个任意长度的字节序列,Linux 中的文件能够包含任意信息,好比 ASCII 码、二进制文件和其余类型的文件是不加区分的。数据库
为了方便起见,文件能够被组织在一个目录中,目录存储成文件的形式在很大程度上能够做为文件处理。目录能够有子目录,这样造成有层次的文件系统,Linux 系统下面的根目录是 /
,它一般包含了多个子目录。字符 /
还用于对目录名进行区分,例如 /usr/cxuan 表示的就是根目录下面的 usr 目录,其中有一个叫作 cxuan 的子目录。数组
下面咱们介绍一下 Linux 系统根目录下面的目录名缓存
/bin
,它是重要的二进制应用程序,包含二进制文件,系统的全部用户使用的命令都在这里/boot
,启动包含引导加载程序的相关文件/dev
,包含设备文件,终端文件,USB 或者链接到系统的任何设备/etc
,配置文件,启动脚本等,包含全部程序所须要的配置文件,也包含了启动/中止单个应用程序的启动和关闭 shell 脚本/home
,本地主要路径,全部用户用 home 目录存储我的信息/lib
,系统库文件,包含支持位于 /bin 和 /sbin 下的二进制库文件/lost+found
,在根目录下提供一个遗失+查找系统,必须在 root 用户下才能查看当前目录下的内容/media
,挂载可移动介质/mnt
,挂载文件系统/opt
,提供一个可选的应用程序安装目录/proc
,特殊的动态目录,用于维护系统信息和状态,包括当前运行中进程信息/root
,root 用户的主要目录文件夹/sbin
,重要的二进制系统文件/tmp
, 系统和用户建立的临时文件,系统重启时,这个目录下的文件都会被删除/usr
,包含绝大多数用户都能访问的应用程序和文件/var
,常常变化的文件,诸如日志文件或数据库等在 Linux 中,有两种路径,一种是 绝对路径(absolute path)
,绝对路径告诉你从根目录下查找文件,绝对路径的缺点是太长并且不太方便。还有一种是 相对路径(relative path)
,相对路径所在的目录也叫作工做目录(working directory)
。ide
若是 /usr/local/books
是工做目录,那么 shell 命令性能
cp books books-replica
就表示的是相对路径,而操作系统
cp /usr/local/books/books /usr/local/books/books-replica
则表示的是绝对路径。设计
在 Linux 中常常出现一个用户使用另外一个用户的文件或者使用文件树结构中的文件。两个用户共享同一个文件,这个文件位于某个用户的目录结构中,另外一个用户须要使用这个文件时,必须经过绝对路径才能引用到他。若是绝对路径很长,那么每次输入起来会变的很是麻烦,因此 Linux 提供了一种 连接(link)
机制。3d
举个例子,下面是一个使用连接以前的图
以上所示,好比有两个工做帐户 jianshe 和 cxuan,jianshe 想要使用 cxuan 帐户下的 A 目录,那么它可能会输入 /usr/cxuan/A
,这是一种未使用连接以后的图。
使用连接后的示意以下
如今,jianshe 能够建立一个连接来使用 cxuan 下面的目录了。‘
当一个目录被建立出来后,有两个目录项也同时被建立出来,它们就是 .
和 ..
,前者表明工做目录自身,后者表明该目录的父目录,也就是该目录所在的目录。这样一来,在 /usr/jianshe 中访问 cxuan 中的目录就是 ../cxuan/xxx
Linux 文件系统不区分磁盘的,这是什么意思呢?通常来讲,一个磁盘中的文件系统相互之间保持独立,若是一个文件系统目录想要访问另外一个磁盘中的文件系统,在 Windows 中你能够像下面这样。
两个文件系统分别在不一样的磁盘中,彼此保持独立。
而在 Linux 中,是支持挂载
的,它容许一个磁盘挂在到另一个磁盘上,那么上面的关系会变成下面这样
挂在以后,两个文件系统就再也不须要关心文件系统在哪一个磁盘上了,两个文件系统彼此可见。
Linux 文件系统的另一个特性是支持 加锁(locking)
。在一些应用中会出现两个或者更多的进程同时使用同一个文件的状况,这样极可能会致使竞争条件(race condition)
。一种解决方法是对其进行加不一样粒度的锁,就是为了防止某一个进程只修改某一行记录从而致使整个文件都不能使用的状况。
POSIX 提供了一种灵活的、不一样粒度级别的锁机制,容许一个进程使用一个不可分割的操做对一个字节或者整个文件进行加锁。加锁机制要求尝试加锁的进程指定其 要加锁的文件,开始位置以及要加锁的字节
Linux 系统提供了两种锁:共享锁和互斥锁。若是文件的一部分已经加上了共享锁,那么再加排他锁是不会成功的;若是文件系统的一部分已经被加了互斥锁,那么在互斥锁解除以前的任何加锁都不会成功。为了成功加锁、请求加锁的部分的全部字节都必须是可用的。
在加锁阶段,进程须要设计好加锁失败后的状况,也就是判断加锁失败后是否选择阻塞,若是选择阻塞式,那么当已经加锁的进程中的锁被删除时,这个进程会解除阻塞并替换锁。若是进程选择非阻塞式的,那么就不会替换这个锁,会马上从系统调用中返回,标记状态码表示是否加锁成功,而后进程会选择下一个时间再次尝试。
加锁区域是能够重叠的。下面咱们演示了三种不一样条件的加锁区域。
如上图所示,A 的共享锁在第四字节到第八字节进行加锁
如上图所示,进程在 A 和 B 上同时加了共享锁,其中 6 - 8 字节是重叠锁
如上图所示,进程 A 和 B 和 C 同时加了共享锁,那么第六字节和第七字节是共享锁。
若是此时一个进程尝试在第 6 个字节处加锁,此时会设置失败并阻塞,因为该区域被 A B C 同时加锁,那么只有等到 A B C 都释放锁后,进程才能加锁成功。
许多系统调用都会和文件与文件系统有关。咱们首先先看一下对单个文件的系统调用,而后再来看一下对整个目录和文件的系统调用。
为了建立一个新的文件,会使用到 creat
方法,注意没有 e
。
这里说一个小插曲,曾经有人问 UNIX 创始人 Ken Thompson,若是有机会从新写 UNIX ,你会怎么办,他回答本身要把 creat 改为 create ,哈哈哈哈。
这个系统调用的两个参数是文件名和保护模式
fd = creat("aaa",mode);
这段命令会建立一个名为 aaa 的文件,并根据 mode 设置文件的保护位。这些位决定了哪一个用户可能访问文件、如何访问。
creat 系统调用不只仅建立了一个名为 aaa 的文件,还会打开这个文件。为了容许后续的系统调用访问这个文件,这个 creat 系统调用会返回一个 非负整数
, 这个就叫作 文件描述符(file descriptor)
,也就是上面的 fd。
若是在已经存在的文件上调用了 creat 系统调用,那么该文件中的内容会被清除,从 0 开始。经过设置合适的参数,open
系统调用也可以建立文件。
下面让咱们看一看主要的系统调用,以下表所示
系统调用 | 描述 |
---|---|
fd = creat(name,mode) | 一种建立一个新文件的方式 |
fd = open(file, ...) | 打开文件读、写或者读写 |
s = close(fd) | 关闭一个打开的文件 |
n = read(fd, buffer, nbytes) | 从文件中向缓存中读入数据 |
n = write(fd, buffer, nbytes) | 从缓存中向文件中写入数据 |
position = 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 返回的 fd 老是未被使用的最小数量。
什么是文件描述符?文件描述符就是一个数字,这个数字标示了计算机操做系统中打开的文件。它描述了数据资源,以及访问资源的方式。
当程序要求打开一个文件时,内核会进行以下操做
全局文件表(global file table)
中建立一个条目(entry)
文件描述符由惟一的非负整数组成,系统上每一个打开的文件至少存在一个文件描述符。文件描述符最初在 Unix 中使用,而且被包括 Linux,macOS 和 BSD 在内的现代操做系统所使用。
当一个进程成功访问一个打开的文件时,内核会返回一个文件描述符,这个文件描述符指向全局文件表的 entry 项。这个文件表项包含文件的 inode 信息,字节位移,访问限制等。例以下图所示
默认状况下,前三个文件描述符为 STDIN(标准输入)
、STDOUT(标准输出)
、STDERR(标准错误)
。
标准输入的文件描述符是 0 ,在终端中,默认为用户的键盘输入
标准输出的文件描述符是 1 ,在终端中,默认为用户的屏幕
与错误有关的默认数据流是 2,在终端中,默认为用户的屏幕。
在简单聊了一下文件描述符后,咱们继续回到文件系统调用的探讨。
在文件系统调用中,开销最大的就是 read 和 write 了。read 和 write 都有三个参数
文件描述符
:告诉须要对哪个打开文件进行读取和写入缓冲区地址
:告诉数据须要从哪里读取和写入哪里统计
:告诉须要传输多少字节这就是全部的参数了,这个设计很是简单轻巧。
虽然几乎全部程序都按顺序读取和写入文件,可是某些程序须要可以随机访问文件的任何部分。与每一个文件相关联的是一个指针,该指针指示文件中的当前位置。顺序读取(或写入)时,它一般指向要读取(写入)的下一个字节。若是指针在读取 1024 个字节以前位于 4096 的位置,则它将在成功读取系统调用后自动移至 5120 的位置。
Lseek
系统调用会更改指针位置的值,以便后续对 read 或 write 的调用能够在文件中的任何位置开始,甚至能够超出文件末尾。
lseek = Lseek ,段首大写。
lseek 避免叫作 seek 的缘由就是 seek 已经在以前 16 位的计算机上用于搜素功能了。
Lseek
有三个参数:第一个是文件的文件描述符,第二个是文件的位置;第三个告诉文件位置是相对于文件的开头,当前位置仍是文件的结尾
lseek(int fildes, off_t offset, int whence);
lseek 的返回值是更改文件指针后文件中的绝对位置。lseek 是惟一历来不会形成真正磁盘查找的系统调用,它只是更新当前的文件位置,这个文件位置就是内存中的数字。
对于每一个文件,Linux 都会跟踪文件模式(常规,目录,特殊文件),大小,最后修改时间以及其余信息。程序可以经过 stat
系统调用看到这些信息。第一个参数就是文件名,第二个是指向要放置请求信息结构的指针。这些结构的属性以下图所示。
存储文件的设备 |
---|
存储文件的设备 |
i-node 编号 |
文件模式(包括保护位信息) |
文件连接的数量 |
文件全部者标识 |
文件所属的组 |
文件大小(字节) |
建立时间 |
最后一个修改/访问时间 |
fstat
调用和 stat
相同,只有一点区别,fstat 能够对打开文件进行操做,而 stat 只能对路径进行操做。
pipe
文件系统调用被用来建立 shell 管道。它会建立一系列的伪文件
,来缓冲和管道组件之间的数据,而且返回读取或者写入缓冲区的文件描述符。在管道中,像是以下操做
sort <in | head –40
sort 进程将会输出到文件描述符1,也就是标准输出,写入管道中,而 head 进程将从管道中读入。在这种方式中,sort 只是从文件描述符 0 中读取并写入到文件描述符 1 (管道)中,甚至不知道它们已经被重定向了。若是没有重定向的话,sort 会自动的从键盘读入并输出到屏幕中。
最后一个系统调用是 fcntl
,它用来锁定和解锁文件,应用共享锁和互斥锁,或者是执行一些文件相关的其余操做。
如今咱们来关心一下和总体目录和文件系统相关的系统调用,而不是把精力放在单个的文件上,下面列出了这些系统调用,咱们一块儿来看一下。
系统调用 | 描述 |
---|---|
s = mkdir(path,mode) | 建立一个新的目录 |
s = rmdir(path) | 移除一个目录 |
s = link(oldpath,newpath) | 建立指向已有文件的连接 |
s = unlink(path) | 取消文件的连接 |
s = chdir(path) | 改变工做目录 |
dir = opendir(path) | 打开一个目录读取 |
s = closedir(dir) | 关闭一个目录 |
dirent = readdir(dir) | 读取一个目录项 |
rewinddir(dir) | 回转目录使其在此使用 |
可使用 mkdir 和 rmdir 建立和删除目录。可是须要注意,只有目录为空时才能够删除。
建立一个指向已有文件的连接时会建立一个目录项(directory entry)
。系统调用 link 来建立连接,oldpath 表明已有的路径,newpath 表明须要连接的路径,使用 unlink
能够删除目录项。当文件的最后一个连接被删除时,这个文件会被自动删除。
使用 chdir
系统调用能够改变工做目录。
最后四个系统调用是用于读取目录的。和普通文件相似,他们能够被打开、关闭和读取。每次调用 readdir
都会以固定的格式返回一个目录项。用户不能对目录执行写操做,可是可使用 creat 或者 link 在文件夹中建立一个目录,或使用 unlink 删除一个目录。用户不能在目录中查找某个特定文件,可是可使用 rewindir
做用于一个打开的目录,使他能在此从头开始读取。
做者:cxuan本文版权归做者和博客园共有,未经做者容许不能转载,不然追究法律责任的权利。 若是文中有什么错误,欢迎指出。以避免更多的人被误导。 |