许多系统调用都会和文件与文件系统有关。我们首先先看一下对单个文件的系统调用,然后再来看一下对整个目录和文件的系统调用。
为了创建一个新的文件,会使用到 creat
方法,注意没有 e
。
这里说一个小插曲,曾经有人问 UNIX 创始人 Ken Thompson,如果有机会重新写 UNIX ,你会怎么办,他回答自己要把 creat 改成 create ,哈哈哈哈。
这个系统调用的两个参数是文件名和保护模式
fd = creat(“aaa”,mode);
这段命令会创建一个名为 aaa 的文件,并根据 mode 设置文件的保护位。这些位决定了哪个用户可能访问文件、如何访问。
creat 系统调用不仅仅创建了一个名为 aaa 的文件,还会打开这个文件。为了允许后续的系统调用访问这个文件,这个 creat 系统调用会返回一个 非负整数
, 这个就叫做 文件描述符(file descriptor)
,也就是上面的 fd。
如果在已经存在的文件上调用了 creat 系统调用,那么该文件中的内容会被清除,从 0 开始。通过设置合适的参数,open
系统调用也能够创建文件。
下面让我们看一看主要的系统调用,如下表所示
为了对一个文件进行读写的前提是先需要打开文件,必须使用 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
系统调用看到这些信息。第一个参数就是文件名,第二个是指向要放置请求信息结构的指针。这些结构的属性如下图所示。
fstat
调用和 stat
相同,只有一点区别,fstat 可以对打开文件进行操作,而 stat 只能对路径进行操作。
pipe
文件系统调用被用来创建 shell 管道。它会创建一系列的伪文件
,来缓冲和管道组件之间的数据,并且返回读取或者写入缓冲区的文件描述符。在管道中,像是如下操作
sort <in | head –40
sort 进程将会输出到文件描述符1,也就是标准输出,写入管道中,而 head 进程将从管道中读入。在这种方式中,sort 只是从文件描述符 0 中读取并写入到文件描述符 1 (管道)中,甚至不知道它们已经被重定向了。如果没有重定向的话,sort 会自动的从键盘读入并输出到屏幕中。
最后一个系统调用是 fcntl
,它用来锁定和解锁文件,应用共享锁和互斥锁,或者是执行一些文件相关的其他操作。
现在我们来关心一下和整体目录和文件系统相关的系统调用,而不是把精力放在单个的文件上,下面列出了这些系统调用,我们一起来看一下。
可以使用 mkdir 和 rmdir 创建和删除目录。但是需要注意,只有目录为空时才可以删除。
创建一个指向已有文件的链接时会创建一个目录项(directory entry)
。系统调用 link 来创建链接,oldpath 代表已有的路径,newpath 代表需要链接的路径,使用 unlink
可以删除目录项。当文件的最后一个链接被删除时,这个文件会被自动删除。
使用 chdir
系统调用可以改变工作目录。
最后四个系统调用是用于读取目录的。和普通文件类似,他们可以被打开、关闭和读取。每次调用 readdir
都会以固定的格式返回一个目录项。用户不能对目录执行写操作,但是可以使用 creat 或者 link 在文件夹中创建一个目录,或使用 unlink 删除一个目录。用户不能在目录中查找某个特定文件,但是可以使用 rewindir
作用于一个打开的目录,使他能在此从头开始读取。
Linux 文件系统的实现
下面我们主要讨论一下 虚拟文件系统(Virtual File System)
。 VFS 对高层进程和应用程序隐藏了 Linux 支持的所有文件系统的区别,以及文件系统是存储在本地设备,还是需要通过网络访问远程设备。设备和其他特殊文件和 VFS 层相关联。接下来,我们就会探讨一下第一个 Linux 广泛传播的文件系统: ext2
。随后,我们就会探讨 ext4
文件系统所做的改进。各种各样的其他文件系统也正在使用中。 所有 Linux 系统都可以处理多个磁盘分区,每个磁盘分区上都有不同的文件系统。
Linux 虚拟文件系统
为了能够使应用程序能够在不同类型的本地或者远程设备上的文件系统进行交互,因为在 Linux 当中文件系统千奇百种,比较常见的有 EXT3、EXT4,还有基于内存的 ramfs、tmpfs 和基于网络的 nfs,和基于用户态的 fuse,当然 fuse 应该不能完全的文件系统,只能算是一个能把文件系统实现放到用户态的模块,满足了内核文件系统的接口,他们都是文件系统的一种实现。对于这些文件系统,Linux 做了一层抽象就是 VFS
虚拟文件系统,
下表总结了 VFS 支持的四个主要的文件系统结构。
超级块(superblock)
包含了有关文件系统布局的重要信息,超级块如果遭到破坏那么就会导致整个文件系统不可读。
i-node
索引节点,包含了每一个文件的描述符。
在 Linux 中,目录和设备也表示为文件,因为它们具有对应的 i-node
超级块和索引块所在的文件系统都在磁盘上有对应的结构。
为了便于某些目录操作和路径遍历,比如 /usr/local/cxuan,VFS 支持一个 dentry
数据结构,该数据结构代表着目录项。这个 dentry 数据结构有很多东西
目录项被缓存在 dentry_cache
缓存中。例如,缓存条目会缓存 /usr 、 /usr/local 等条目。如果多个进程通过硬连接访问相同的文件,他们的文件对象将指向此缓存中的相同条目。
最后,文件数据结构是代表着打开的文件,也代表着内存表示,它根据 open 系统调用创建。它支持 read、write、sendfile、lock 和其他在我们之前描述的系统调用中。
在 VFS 下实现的实际文件系统不需要在内部使用完全相同的抽象和操作。 但是,它们必须在语义上实现与 VFS 对象指定的文件系统操作相同的文件系统操作。 四个 VFS 对象中每个对象的操作数据结构的元素都是指向基础文件系统中功能的指针。
Linux Ext2 文件系统
现在我们一起看一下 Linux 中最流行的一个磁盘文件系统,那就是 ext2
。Linux 的第一个版本用于 MINIX1
文件系统,它的文件名大小被限制为最大 64 MB。MINIX 1 文件系统被永远的被它的扩展系统 ext 取代,因为 ext 允许更长的文件名和文件大小。由于 ext 的性能低下,ext 被其替代者 ext2 取代,ext2 目前仍在广泛使用。
一个 ext2 Linux 磁盘分区包含了一个文件系统,这个文件系统的布局如下所示
Boot 块也就是第 0 块不是让 Linux 使用的,而是用来加载和引导计算机启动代码的。在块 0 之后,磁盘分区被分成多个组,这些组与磁盘柱面边界所处的位置无关。
第一个块是 超级块(superblock)
。它包含有关文件系统布局的信息,包括 i-node、磁盘块数量和以及空闲磁盘块列表的开始。下一个是 组描述符(group descriptor)
,其中包含有关位图的位置,组中空闲块和 i-node 的数量以及组中的目录数量的信息。这些信息很重要,因为 ext2 会在磁盘上均匀分布目录。
图中的两个位图用来记录空闲块和空闲 i-node,这是从 MINIX 1文件系统继承的选择,大多数 UNIX 文件系统使用位图而不是空闲列表。每个位图的大小是一个块。如果一个块的大小是 1 KB,那么就限制了块组的数量是 8192 个块和 8192 个 i-node。块的大小是一个严格的限制,块组的数量不固定,在 4KB 的块中,块组的数量增大四倍。
在超级块之后分布的是 i-node
它们自己,i-node 取值范围是 1 - 某些最大值。每个 i-node 是 128 字节的 long
,这些字节恰好能够描述一个文件。i-node 包含了统计信息(包含了 stat
系统调用能获得的所有者信息,实际上 stat 就是从 i-node 中读取信息的),以及足够的信息来查找保存文件数据的所有磁盘块。
在 i-node 之后的是 数据块(data blocks)
。所有的文件和目录都保存在这。如果一个文件或者目录包含多个块,那么这些块在磁盘中的分布不一定是连续的,也有可能不连续。事实上,大文件块可能会被拆分成很多小块散布在整个磁盘上。
对应于目录的 i-node 分散在整个磁盘组上。如果有足够的空间,ext2 会把普通文件组织到与父目录相同的块组中,而把同一块上的数据文件组织成初始 i-node
节点。位图用来快速确定新文件系统数据的分配位置。在分配新的文件块时,ext2 也会给该文件预分配许多额外的数据块,这样可以减少将来向文件写入数据时产生的文件碎片。这种策略在整个磁盘上实现了文件系统的 负载
,后续还有对文件碎片的排列和整理,而且性能也比较好。
为了达到访问的目的,需要首先使用 Linux 系统调用,例如 open
,这个系统调用会确定打开文件的路径。路径分为两种,相对路径
和 绝对路径
。如果使用相对路径,那么就会从当前目录开始查找,否则就会从根目录进行查找。
目录文件的文件名最高不能超过 255 个字符,它的分配如下图所示
每一个目录都由整数个磁盘块组成,这样目录就可以整体的写入磁盘。在一个目录中,文件和子目录的目录项都是未经排序的,并且一个挨着一个。目录项不能跨越磁盘块,所以通常在每个磁盘块的尾部会有部分未使用的字节。
上图中每个目录项都由四个固定长度的属性和一个长度可变的属性组成。第一个属性是 i-node
节点数量,文件 first 的 i-node 编号是 19 ,文件 second 的编号是 42,目录 third 的 i-node 编号是 88。紧随其后的是 rec_len
域,表明目录项大小是多少字节,名称后面会有一些扩展,当名字以未知长度填充时,这个域被用来寻找下一个目录项,直至最后的未使用。这也是图中箭头的含义。紧随其后的是 类型域
:F 表示的是文件,D 表示的是目录,最后是固定长度的文件名,上面的文件名的长度依次是 5、6、5,最后以文件名结束。
rec_len 域是如何扩展的呢?如下图所示
我们可以看到,中间的 second
被移除了,所以将其所在的域变为第一个目录项的填充。当然,这个填充可以作为后续的目录项。
由于目录是按照线性的顺序进行查找的,因此可能需要很长时间才能在大文件末尾找到目录项。因此,系统会为近期的访问目录维护一个缓存。这个缓存用文件名来查找,如果缓存命中,那么就会避免线程搜索这样昂贵的开销。组成路径的每个部分都在目录缓存中保存一个 dentry
对象,并且通过 i-node 找到后续的路径元素的目录项,直到找到真正的文件 i - node。
比如说要使用绝对路径来寻找一个文件,我们暂定这个路径是 /usr/local/file
,那么需要经过如下几个步骤:
- 首先,系统会确定根目录,它通常使用 2 号 i -node ,也就是索引 2 节点,因为索引节点 1 是 ext2 /3/4 文件系统上的
坏块
索引节点。系统会将一项放在 dentry 缓存中,以应对将来对根目录的查找。 - 然后,在根目录中查找字符串
usr
,得到 /usr 目录的 i - node 节点号。/usr 的 i - node 同样也进入 dentry 缓存。然后节点被取出,并从中解析出磁盘块,这样就可以读取 /usr 目录并查找字符串local
了。一旦找到这个目录项,目录/usr/local
的 i - node 节点就可以从中获得。有了 /usr/local 的 i - node 节点号,就可以读取 i - node 并确定目录所在的磁盘块。最后,从 /usr/local 目录查找 file 并确定其 i - node 节点呢号。
如果文件存在,那么系统会提取 i - node 节点号并把它作为索引在 i - node 节点表中定位相应的 i - node 节点并装入内存。i - node 被存放在 i - node 节点表(i-node table)
中,节点表是一个内核数据结构,它会持有当前打开文件和目录的 i - node 节点号。下面是一些 Linux 文件系统支持的 i - node 数据结构。
现在我们来一起探讨一下文件读取过程,还记得 read
函数是如何调用的吗?
n = read(fd,buffer,nbytes);
当内核接管后,它会从这三个参数以及内部表与用户有关的信息开始。内部表的其中一项是文件描述符数组。文件描述符数组用文件描述符
作为索引并为每一个打开文件保存一个表项。
文件是和 i - node 节点号相关的。那么如何通过一个文件描述符找到文件对应的 i - node 节点呢?
这里使用的一种设计思想是在文件描述符表和 i - node 节点表之间插入一个新的表,叫做 打开文件描述符(open-file-description table)
。文件的读写位置会在打开文件描述符表中存在,如下图所示
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Linux运维工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Linux运维全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Linux运维知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加VX:vip1024b (备注Linux运维获取)
最全的Linux教程,Linux从入门到精通
======================
-
linux从入门到精通(第2版)
-
Linux系统移植
-
Linux驱动开发入门与实战
-
LINUX 系统移植 第2版
-
Linux开源网络全栈详解 从DPDK到OpenFlow
第一份《Linux从入门到精通》466页
====================
内容简介
====
本书是获得了很多读者好评的Linux经典畅销书**《Linux从入门到精通》的第2版**。本书第1版出版后曾经多次印刷,并被51CTO读书频道评为“最受读者喜爱的原创IT技术图书奖”。本书第﹖版以最新的Ubuntu 12.04为版本,循序渐进地向读者介绍了Linux 的基础应用、系统管理、网络应用、娱乐和办公、程序开发、服务器配置、系统安全等。本书附带1张光盘,内容为本书配套多媒体教学视频。另外,本书还为读者提供了大量的Linux学习资料和Ubuntu安装镜像文件,供读者免费下载。
本书适合广大Linux初中级用户、开源软件爱好者和大专院校的学生阅读,同时也非常适合准备从事Linux平台开发的各类人员。
需要《Linux入门到精通》、《linux系统移植》、《Linux驱动开发入门实战》、《Linux开源网络全栈》电子书籍及教程的工程师朋友们劳烦您转发+评论
一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
读,同时也非常适合准备从事Linux平台开发的各类人员。**
需要《Linux入门到精通》、《linux系统移植》、《Linux驱动开发入门实战》、《Linux开源网络全栈》电子书籍及教程的工程师朋友们劳烦您转发+评论
一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
[外链图片转存中…(img-b9uYYQS3-1712774452412)]