Linux的一切皆文件

Linux 中的各种事物比如像文档、目录(Mac OS X 和 Windows 系统下称之为文件夹)、键盘、监视器、硬盘、可移动媒体设备、打印机、调制解调器、虚拟终端,还有进程间通信(IPC)和网络通信等输入/输出资源都是定义在文件系统空间下的字节流

一切都可看作是文件,其最显著的好处是对于上面所列出的输入/输出资源,只需要相同的一套 Linux 工具、实用程序和 API。你可以使用同一套api(read, write)和工具(cat , 重定向, 管道)来处理unix中大多数的资源。

“文件”作为一个抽象概念,其原子操作非常简单,只有读和写,这无疑是一个非常好的模型。通过这个模型,API的设计可以化繁为简,用户可以使用通用的方式去访问任何资源,自有相应的中间件做好对底层的适配。

目录使文件可被分类管理,且目录的引入使 Linux 的文件系统形成一个层级结构的目录树

在Linux系统中,一切都是文件。

Linux上的文件系统一般来说就是EXT2EXT3

文件系统的作用

  • 存储管理:文件系统管理存储设备上的文件,负责存储空间的分配和回收,通过数据块的分配和索引机制确保文件能够被快速定位和访问。
  • 文件组织:定义文件和目录的结构与组织方式,提供创建、删除、移动和重命名文件和目录的操作,帮助用户组织和管理文件。
  • 文件访问控制:提供对文件的访问权限控制,保护文件的机密性和完整性,限制未授权的访问和修改。
  • 文件操作和管理:提供一系列接口和工具,允许用户进行文件的各种操作和管理,如复制、移动、重命名、压缩和解压缩等。
  • 文件共享和备份:支持文件共享,使多个用户能同时访问同一文件,并提供备份和恢复功能,确保数据的安全性和可靠性。

操作系统的文件访问方式

  • 顺序访问:按照文件中的顺序读取或写入数据。
  • 随机访问:根据位置直接读取或写入数据。
  • 直接访问:通过记录标识符直接访问数据。

机械硬盘的物理存储机制

一个硬盘中的磁性存储单位数以亿计(1T硬盘就有约80亿个),所以需要一套规则来规划信息如何存取(比如一本存储信息的书我们还会分为页,每一页从上到下从左到右读取,同时还有章节目录)

  • 扇区: 每512*8512字节,0.5KB)个存储颗粒作为一个扇区,扇区是硬盘上存储的最小物理单位
  • N个扇区可以组成簇,N取决于不同的文件系统或是文件系统的配置,簇是此文件系统中的最小存储单位
  • 柱面:所有盘面上的同一磁道构成一个圆柱,称为柱面,柱面是系统分区的最小单位。 image

Linux文件体系

Linux中的文件类型

文件类型描述
普通文件包括文本文件、bin文件等。
可执行文件包括脚本和应用程序,这些文件可被系统加载运行,例如Windows下的.bat脚本、.exe程序文件等。
链接文件(硬链接)指同一个文件的不同别名。
链接文件(软链接)类似于Windows的快捷方式,是一个特殊的文件,包含另一文件的位置信息。
目录文件在Linux中,目录也是文件。
设备文件硬件设备也是文件,通过打开对应的设备文件可以初始化设备,部分设备还可以通过读写设备文件实现对硬件的控制。

普通文件(-)

类似mp4、pdf、html这样应用层面上的文件类型都属于普通文件。 Linux用户可以根据访问权限对普通文件进行查看、更改和删除。

目录文件(d,directory file)

目录也是文件的一种,目录文件包含了各自目录下的文件名和指向这些文件的指针,打开目录事实上就是打开目录文件。可以能够通过vim去查看目录文件的内容。

类似Windows中的快捷方式,是指向另一个文件的间接指针,也就是我们常说的软链接

块设备文件(b,block)和字符设备文件(c,char)

这些文件一般隐藏在/dev目录下,在进行设备读取和外设交互时会被使用到,比如磁盘光驱就是块设备文件,串口设备则属于字符设备。

文件系统中的所有设备要么是块设备文件,要么是字符设备文件,无一例外。

FIFO(p,pipe)

管道文件主要用于进程间通讯

比如使用mkfifo命令可以创建一个FIFO文件,启用一个进程AFIFO文件里读数据,启动进程BFIFO里写数据,先进先出,随写随读。

套接字(s,socket)

用于进程间的网络通信,也可以用于本机之间的非网络通信。

这些文件一般隐藏在/var/run目录下,证明着相关进程的存在。

FHS(Filesystem Hierarchy Standard)

缩写全称目录放置的内容
binbinaries存放二进制可执行文件
sbinsuper user binaries存放二进制可执行文件,只有root才能访问
etcetcetera存放系统配置文件
usrunix shared resources用于存放共享的系统资源
home-存放用户文件的根目录
root-超级用户目录
devdevices用于存放设备文件(如声卡、硬盘、光驱)
liblibrary存放跟文件系统中的程序运行所需要的共享库及内核模块
mntmount系统管理员安装临时文件系统的安装点
boot-存放用于系统引导时使用的各种文件
tmptemporary用于存放各种临时文件
varvariable用于存放运行时需要改变数据的文件

文件拓展名

  • 压缩文件(以压缩软件为后缀):.tar.tar.gz.tgz.zip.tar.bz
  • .sh表示shell脚本文件
  • .pl表示perl语言文件,通过perl语言开发的程序。
  • .conf表示系统服务的配置文件。
  • .rpm表示rpm安装包文件。

文件属性

按照读写类别:

  • 读权限
  • 写权限
  • 可执行权限—可加载到内存中执行

按照所属

  • 文件拥有着(owner
  • 分组成员(groups
  • 其他分组成员(other

Linux目录树

Linux系统和用户来说,所有可操作的计算机资源都存在于目录树这个逻辑结构中,对计算机资源的访问都可以认为是目录树的访问。

就硬盘来说,所有对硬盘的访问都变成了对目录树中某个节点也就是文件夹的访问,访问时不需要知道它是硬盘还是硬盘中的文件夹。目录树的逻辑结构也非常简单,就是从根目录(/)开始,不断向下展开各级子目录。

硬盘分区

每块硬盘上最重要的第一扇区,这个扇区中有硬盘主引导记录(Master boot record, MBR) 及分区表(partition table), 其中 MBR 占有 446 bytes,而分区表占有 64 bytes

  • Master boot record:最基本的引导加载程序,是系统开机启动的关键环节,在附录中有更详细的说明。
  • partition table:记录了硬盘分区的相关信息,但因分区表仅有 64bytes, 所以最多只能记彔四块分区(分区本身其实就是对分区表进行设置)。
  • 主分区:普通可以访问的分区。
  • 拓展分区:不同于主分区,本身并没有内容,为进一步逻辑分区提供空间。
  • swap分区:(内存置换空间),独为一类,将不常被使用的程序将会被丢到硬盘的 swap 置换空间当中, 而将速度较快的物理内存空间释放出来给真正需要的程序使用。

操作系统规定:

  • 四块分区每块都可以是主分区或扩展分区。
  • 扩展分区最多只能有一个(也没必要有多个)。
  • 扩展分区可以进一步分割为多个逻辑分区。
  • 扩展分区只是逻辑概念,本身不能被访问,也就是不能被格式化后作为数据访问的分区,能够作为数据访问的分区只有主分区和逻辑分区。
  • 逻辑分区的数量依操作系统而不同,在 Linux 系统中,IDE 硬盘最多有 59 个逻辑分区(5 号到 63 号), SATA 硬盘则有 11 个逻辑分区(5 号到 15 号)。

一般给硬盘进行分区时,一个主分区一个扩展分区,然后把扩展分区划分为N个逻辑分区。

格式化

Linux操作系统支持很多不同的文件系统,比如ext2、ext3、XFS、FAT等等,而Linux把对不同文件系统的访问交给了VFS(虚拟文件系统),VFS能访问和管理各种不同的文件系统。有了区之后就需要把它格式化成具体的文件系统以便VFS访问。

标准的Linux文件系统Ext2是使用「基于inode的文件系统」

  • 操作系统的文件数据包括文件实际内容和属性(文件权限(rwx)与文件属性(拥有者、群组、 时间参数等))等,文件与属性放置在不同分区中。
  • 在基于inode的文件系统中,权限与属性放置到 inode 中,实际数据放到 data block 区块中。

Ext2 文件系统

  1. 文件系统最前面有一个启动扇区(boot sector)

    • 安装开机管理程序, 将不同的引导装载程序安装到不同的文件系统前端,而不用覆盖整个硬盘唯一的MBR, 也就是这样才能实现多重引导的功能。
  2. 把每个区进一步分为多个块组 (block group),每个块组有独立的inode/block体系。

    • 如果文件系统高达数百 GB 时,把所有的 inodeblock 通通放在一起会因为 inodeblock的数量太庞大,不容易管理
  3. 每个块组实际还会分为分为6个部分,除了inode tabledata block外还有4个附属模块,起到优化和完善系统性能的作用

Ext2 分区划分

image

inode table

  • 主要记录文件的属性以及该文件实际数据是放置在哪些block中,它记录的信息至少有这些:
    • 大小、真正内容的block号码(一个或多个)
    • 访问模式(read/write/execute)
    • 拥有者与群组(owner/group)
    • 各种时间:建立或状态改变的时间、最近一次的读取时间、最近修改的时间
    • 没有文件名!文件名在目录block中!
  • 一个文件占用一个 inode,每个inode有编号
  • 文件系统能够建立的文件数量与inode 的数量有关,Linux 系统存在 inode 号被用完但磁盘空间还有剩余的情况
  • inode 的数量与大小在格式化时就已经固定了,每个inode 大小均固定为128 bytes (新的ext4 与xfs 可设定到256 bytes)。
  • 系统读取文件时需要先找到inode,并分析inode 所记录的权限与使用者是否符合,若符合才能够开始实际读取 block 的内容
  • inode 记录block 号码的区域定义12个直接,一个间接, 一个双间接与一个三间接记录区。

data block

  • 放置文件内容数据
  • 格式化时固定block大小,且每个block都有编号,以方便inode的记录。
  • Ext2文件系统中所支持的block大小有1K, 2K4K三种,由于block大小的区别,会导致该文件系统能够支持的最大磁盘容量与最大单一文件容量各不相同:
    • Block 大小 1KB 2KB 4KB
    • 最大单一档案限制 16GB 256GB 2TB
    • 最大档案系统总容量 2TB 8TB 16TB
  • 每个block 内最多只能够放置一个文件的资料,但一个文件可以放在多个block中(大的话)
  • 若文件小于 block ,则该block 的剩余容量就不能够再被使用了(磁盘空间会浪费)
  • 一般都会选择4K block 大小。

superblock

记录整个文件系统相关信息,一般大小为1024bytes,记录的信息主要有:

  • blockinode 总量
    • 未使用与已使用的inode / block 数量。
    • 一个valid bit 数值,若此文件系统已被挂载,则valid bit 为0 ,若未被挂载,则valid bit 为1。
    • block inode大小 (block 为1, 2, 4K,inode 为128bytes 或256bytes);
    • 其他各种文件系统相关信息:filesystem 的挂载时间、最近一次写入资料的时间、最近一次检验磁碟(fsck) 的时间。
  • 如果superblock死掉了,文件系统可能就需要花费很多时间去挽救。
  • 一个文件系统仅有一个superblock,一般选用第一个块,不过后续块中可能存在第一个块内superblock的备份,这样可以进行superblock的救援。

Filesystem Description

文件系统描述,这个区段可以描述每个block group的开始与结束的block号码,以及说明每个区段(superblock, bitmap, inodemap, data block)分别介于哪一个block号码之间。

block bitmap

块对照表

  • 记录空闲block,因此我们的系统就能够很快速的找到可使用的空间来记录。
  • 在你删除某些文件时,那些文件原本占用的block号码就得要释放出来, 此时在block bitmap 中对应该block号码的标志位就得要修改成为未使用

inode bitmap

记录空闲inode号码。 1、与block bitmap 是类似的功能,只是block bitmap 记录的是使用与未使用的block 号码, 至于inode bitmap 则是记录使用与未使用的inode 号码

mount

利用一个目录当成进入点(类似选一个现成的目录作为代理),将文件系统放置在该目录下。

  • 进入点的目录称挂载点
  • 根目录一定需要挂载到某个分区。 而其他的目录则可依用户自己的需求来给予挂载到不同的分去。
  • 硬盘经过分区和格式化,每个都成为了一个文件系统,挂载这个文件系统后就可以让Linux操作系统通过VFS访问硬盘时跟访问一个普通文件夹一样。

目录树的读取过程

  1. 每个文件(不管是一般文件还是目录文件)都会占用一个inode
  2. 依据文件内容的大小来分配一个或多个block给该文件使用
  3. 创建一个文件后,文件完整信息分布在3处地方,生成2个新文件:
    • inode:新生成block文件记录文件属性权限信息、记录具体内容的block编号
    • block:记录文件具体内容。
  4. 文件名记录在该文件所在目录的目录文件block中,在Linux/Unix中,文件名称只是文件的一个属性
    • 系统内部并不需要用文件名来确定文件位置,可以对正在使用的文件改名,换目录,甚至放到废纸篓,都不会影响当前文件的使用 。

创建文件过程

当在ext2下建立一个一般文件时,ext2 会分配一个inode相对于文件大小block 数量给该文件。

  • 假设我的一个block 为4 Kbytes ,而我要建立一个100 KBytes 的文件,那么linux 将分配一个inode 与25 个block 来储存该文件
  • 但同时请注意,由于inode 仅有12直接指向,因此还要多一个block 来作为区块号码的记录。

创建目录过程

当在ext2文件系统建立一个目录时(就是新建了一个目录文件),文件系统会分配一个inode至少一块block给该目录。

  • inode记录该目录的相关权限与属性,并记录分配到的那块block号码。
  • block记录在这个目录下的文件名与该文件对应的inode号。
  • block中还会自动生成两条记录,一条是.文件夹记录,inode指向自身,另一条是..文件夹记录,inode指向父文件夹

从目录树中读取某个文件过程

  • 因为文件名是记录在目录的block当中,因此当我们要读取某个文件时,就一定会经过目录的inodeblock ,然后才能够找到那个待读取文件的inode号码,最终才会读到正确的文件的block内的资料。
  • 由于目录树是由根目录开始,因此操作系统先通过挂载信息找到挂载点的inode号,由此得到根目录的inode内容,并依据该inode读取根目录的block信息,再一层一层的往下读到正确的文件。

以读取/etc/passwd文件为例:

/etc/passwd文件及相关路径文件夹信息。

1$ ll -di / /etc /etc/passwd
2     128 dr-xr-x r-x . 17 root root 4096 May 4 17:56 /
333595521 drwxr-x r-x . 131 root root 8192 Jun 17 00:20 /et
c436628004 -rw-r-- r-- . 1 root root 2092 Jun 17 00:20 /etc/passwd

文件的读取流程为:

  1. /inode: 通过挂载点的信息找到inode号码为128的根目录inode,且inode规定的权限让我们可以读取该block的内容(有rx)
  2. /block: 取得block的号码,并找到该内容有etc/目录的inode号码(33595521)
  3. etc/inode: 读取33595521inode得知具有rx的权限,因此可以读取etc/block内容
  4. etc/block: 经过上个步骤取得block号码,并找到该内容有passwd文件的inode号码(36628004)
  5. passwdinode: 读取36628004inode得知具有r的权限,因此可以读取passwdblock内容
  6. passwdblock: 最后将该block内容的资料读出来

VFS文件系统

VFS(Virtual File System,虚拟文件系统)是操作系统中的一个重要概念,主要目的是为不同类型的文件系统提供一个统一的接口,隐藏底层文件系统的实现细节,使得用户程序和内核能够以一致的方式访问不同的存储设备。VFS 是一种抽象层,允许操作系统通过同一套接口与多种文件系统进行交互。

VFS的主要功能

  • 抽象底层文件系统:VFS 为不同的文件系统(如 ext4、NTFS、FAT32、NFS 等)提供统一的接口,使得用户可以使用相同的系统调用来操作不同的文件系统。
  • 管理文件操作:VFS 处理所有的文件操作(如打开、读取、写入、删除、文件系统挂载等),并将这些操作委托给相应的具体文件系统实现。
  • 提高文件系统的扩展性:VFS 使得操作系统能够方便地支持新的文件系统类型。只要实现了 VFS 定义的接口,操作系统就可以支持新的文件系统而不需要修改用户应用程序代码。

VFS的工作原理

VFS的主要任务是将文件操作的请求映射到底层具体文件系统的实现。

VFS 通过定义一些抽象的数据结构和接口,使得操作系统能够在不关注具体文件系统实现的情况下处理文件系统的操作。每当用户程序执行文件操作时,VFS 将该操作转发到合适的文件系统驱动中去。

VFS的关键数据结构

struct super_block

  • 代表一个挂载在系统上的文件系统。每个文件系统在内核中都有一个 super_block 结构,包含了该文件系统的元数据(例如文件系统类型、挂载点、文件系统状态等)。
  • 当文件系统被挂载时,内核会创建一个 super_block,它包含了文件系统的总体信息。
struct super_block {
    struct file_system_type *s_type; // 文件系统类型
    struct dentry *s_root;          // 根目录的 dentry
    // 其他文件系统信息,如磁盘块大小等
};

struct inode

  • 代表一个文件或目录的元数据(也称为 inode),包括文件的权限、大小、时间戳、指向文件数据块的指针等信息。
  • 每个文件或目录在文件系统中都有一个对应的 inode。inode 存储了文件的元数据,但不包含文件名。文件名存储在目录条目(dentry)中。
struct inode {
    unsigned long i_ino;         // inode 编号
    umode_t i_mode;              // 文件类型和权限
    unsigned long i_size;        // 文件大小
    struct timespec i_atime;     // 最后访问时间
    struct timespec i_mtime;     // 最后修改时间
    struct timespec i_ctime;     // inode 状态改变时间
    struct block_device *i_sb;   // 所属文件系统
    // 其他元数据
};

struct dentry

  • 目录项(dentry)表示文件系统中的一个目录项,它通常包含文件名和指向 inode 的指针。dentry 用于实现文件名到 inode 的映射。
  • 目录项在文件系统中起到重要作用,特别是在文件路径查找和缓存中。
struct dentry {
    struct inode *d_inode;  // 指向文件的 inode
    struct dentry *d_parent; // 父目录
    const char *d_name;      // 目录项的名称
};

struct file_operations

  • 文件操作结构体,定义了一个文件的操作接口,包括读、写、打开、关闭等操作。
  • 每个文件系统会为其文件提供具体的实现。操作系统通过 file_operations 结构体中的函数指针来调用文件系统提供的具体实现。
struct file_operations {
    int (*open)(struct inode *inode, struct file *file);
    ssize_t (*read)(struct file *file, char __user *buf, size_t len, loff_t *offset);
    ssize_t (*write)(struct file *file, const char __user *buf, size_t len, loff_t *offset);
    int (*release)(struct inode *inode, struct file *file);
    // 更多操作
};

VFS的主要操作流程

  1. 打开文件

    • 用户通过 open() 系统调用请求打开文件。VFS 将文件路径传递给底层文件系统。
    • VFS 通过查找 dentry 和 inode 来定位文件。如果文件存在,VFS 会调用相应文件系统的 open() 函数,完成文件的打开操作。
  2. 读取文件

    • 用户通过 read() 系统调用请求读取文件。VFS 会调用相应文件系统的 read() 函数来读取数据。
    • 具体文件系统的 read() 实现会从文件的物理存储中读取数据并返回给用户。
  3. 写入文件

    • 用户通过 write() 系统调用请求写入文件。VFS 会调用相应文件系统的 write() 函数来处理写入操作。
    • 具体文件系统的 write() 实现会将数据写入文件的物理存储中。
  4. 查找路径

    • 用户通过文件路径访问文件。VFS 会根据路径查找相应的 dentry 和 inode,定位到文件。
    • 在查找过程中,VFS 需要访问目录项,并通过 dentry 来映射到文件的 inode。
  5. 挂载文件系统

    • 文件系统的挂载是通过 mount() 系统调用实现的,VFS 将文件系统挂载到指定目录(即挂载点)。挂载后,用户可以通过 VFS 提供的接口访问文件系统中的文件。

    挂载过程

    • 文件系统驱动通过 mount() 向 VFS 注册自己的 file_operationssuper_block
    • 挂载文件系统后,VFS 更新文件系统信息并将文件系统根目录的 dentry 添加到 VFS 的根目录树中。
  6. 卸载文件系统

    • 用户通过 umount() 系统调用卸载文件系统,VFS 会卸载该文件系统并清理与之相关的资源。

VFS的优势

  • 透明性:VFS 为用户提供了一种透明的文件访问方式,用户不需要关心底层文件系统的具体实现。无论使用的是 ext4、NTFS 还是 NFS,都可以通过统一的接口进行操作。
  • 扩展性:VFS 提供了抽象层,操作系统可以方便地支持多种文件系统,只需要实现 VFS 定义的接口即可。
  • 一致性:VFS 提供了统一的文件操作接口,简化了文件系统的管理和操作。

总结

VFS(虚拟文件系统)是操作系统中对不同文件系统进行抽象的机制,通过为文件系统提供统一的接口,使得应用程序和用户程序能够方便地访问不同类型的存储设备。VFS 的主要任务是通过 super_blockinodedentryfile_operations 等结构来管理文件系统和文件操作,实现对不同文件系统的统一支持。