>> >> >> Reference << << << <<<<<<Ref>>>>>>
linux file system
Update: 2026-01-14

File System

Linux文件系统就像是整个操作系统的地基和骨架——所有数据都靠它来组织和存取。要理解它,需要同时把它当作一棵“倒挂的树”和一套“抽象层”

整体视角:一棵单一的大树(统一的命名空间)

在Linux里,不管有多少硬盘、分区、U盘,统统被挂到一个统一的目录树下

Linux常见目录结构(FHS标准)

熟悉目录相当于熟悉操作系统的器官功能

/dev

/dev/null

dev/null是Unix/Linux系统中的一个特殊设备文件,通常被称为“空设备(null device)”
从工程角度讲,它的语义非常明确:写入它的数据会被立即丢弃;从它读取永远得到EOF
它是一个字符设备文件,由内核实现,位于虚拟设备文件系统(/dev),它不是普通文件,不占磁盘空间,不保存任何数据

行为模型

写入/dev/null

echo "hello" > /dev/null 

结果

内核行为等价于

write(fd, buf, len); // 返回 len,但什么都没保存

/dev/null读取

cat /dev/null 

结果

等价于

read(fd, buf, len); // 立即返回 0 
存在意义

Unix的一条核心哲学是:一切皆文件;既然标准输入/输出/错误都是“文件描述符”,那就需要一个合法的“什么都不做”的端点。/dev/null就是这个“黑洞”

常见用途
  1. 丢弃输出
make > /dev/null 

只关心返回码,不关心输出

  1. 丢弃错误输出
cmd 2> /dev/null 

忽略所有错误输出

  1. 同时丢弃stdout + stderr
cmd > /dev/null 2>&1 
  1. 禁用程序读取输入
cmd < /dev/null 

告诉程序:不要等用户输入

设计哲学

/dev/null的意义在于:提供一个“合法但无副作用”的IO终点
这让

都可以不加分支地工作

底层机制:inode与block

这是Linux文件系统的灵魂

inode结构

inode(索引节点)是Linux文件系统的灵魂。一个文件对应一个inode,里面存的是“元数据”,而不是文件名
典型的ext4 inode结构里包含:

指针机制是关键:

这就是为什么一个inode本身不存放数据,而是存放“数据的地图”

inode本质

inode是文件系统设计的分水岭。早期系统(如FAT)只有“文件名 + 数据块列表”,无法支持硬链接或权限模型
inode的发明,让“名字”和“实体”解耦:

这种分离让Linux能支持:

文件名与目录

文件名其实不在inode里

虚拟文件系统(VFS)

Linux内核引入了一个抽象层叫VFS(Virtual File System),程序调用open/read/write系统调用时,不用管底层是ext4还是XFS,VFS会帮忙翻译。它就像一个“通用接口”,底下可以换不同实现,这就是Linux能挂载不同文件系统的原因。VFS在内核中定义了一组所有文件都必须实现的通用接口(如inode_operations, file_operations)。当用户程序执行文件操作时,调用先到VFS,再由VFS根据文件所在的路径,路由到具体的文件系统实现去处理

Linux文件系统的三层抽象

真正理解文件系统,需要看到三层“视角”

  1. 用户空间(User Space) 程序看到的只是路径和文件描述符(fd),并不直接感知inode或block。所有访问都要经过系统调用接口,比如open(), read(), write()
  2. 内核抽象层(VFS层) VFS是统一接口层,它让ext4, xfs, btrfs, procfs这些完全不同的实现都能“看起来像文件系统”
    内核中,每种文件系统驱动都要注册一组操作函数(super_operations, inode_operations, file_operations
    这就像面向对象编程中的“虚函数表”:VFS只关心接口,不关心底层实现
  3. 存储层(Block Layer) VFS最终调用具体的文件系统驱动(如ext4),它通过块设备层(Block I/O Layer)与物理设备交互
    在这里出现的关键词包括:页缓存(Page Cache)、缓冲区(Buffer Head)、I/O调度器(Elevator Algorithm)、DMA(直接内存访问)

这三层结构保证了可替换性与高性能。Linux可以在不改动应用程序的前提下挂载任意文件系统,也能用同一套机制管理网络文件、虚拟内存文件、甚至设备节点

mount

Linux的目录树只是一个逻辑结构,它与物理存储设备(硬盘、分区、U盘、光盘)是解耦的

官方定义: 挂载是指将一个存储设备(如硬盘分区)上的文件系统,关联到Linux目录树中的一个特定目录(称为挂载点,Mount Point)的过程
一旦挂载成功,对该挂载点目录的任何访问操作,都将被透明地重定向到该存储设备上的文件系统

挂载的意义

  1. 统一的视图:对用户和程序来说,无需关心文件具体在哪个物理设备上。它们只需要遵循统一的目录树结构即可找到所有文件。/home/alice/Documents可能在一个独立的SSD上,而/var/log可能在另一块老硬盘上,但用户完全无感
  2. 极致的灵活性:

命令mount & umount

mount

基本语法:mount [选项] <设备源> <挂载点>

示例
  1. 挂载U盘(通常系统会自动完成,手动演示如下)
## 首先,创建挂载点目录
sudo mkdir /mkt/my_usb

# 然后,挂载设备(假设U盘被识别为 /dev/sdc1)
sudo mount /dev/sdc1 /mnt/my_usb

# 现在,可以通过 /mnt/my_usb 访问U盘内容
ls /mnt/my_usb
  1. 挂载光盘
sudo mount /dev/cdrom /media/cdrom
  1. 挂载一个镜像文件
sudo mount -o loop ubuntu-22.04.iso /mnt/iso

umount

卸载是为了确保所有数据都已写入设备,保证数据安全。直到拔掉设备可能导致数据损坏
语法:umount <挂载点或设备源>

# 通过挂载点卸载
sudo umount /mnt/my_usb

# 或者通过设备源卸载
sudo umount /dev/sdc1

如果遇到device is busy报错,表示有进程正在使用挂载点下的文件。需要退出所有在该目录下的终端和程序,或用lsof /mnt/my_sub命令查看并结束相关进程

自动挂载:/etc/fstab文件

每次开机都手动挂载非常麻烦。/etc/fstab(file systems table)文件就是用来配置启动时挂载的
它的每一行定义了一个需要挂载的文件系统,包含六个字段

<设备源> <挂载点> <文件系统类型> <挂载选项> <dump备份标志> <fsck检查顺序>

示例

# 将 /dev/sdb1 在启动时自动挂载到 /data 目录,使用 ext4 文件系统,默认选项
UUID=1234-abcd-5678 /data ext4 defaults 0 2

临时文件系统(tmpfs)的挂载

挂载的概念不仅用于物理设备,还可以用于内存

# 将一部分内存挂载到 /tmp目录,提高临时文件读写速度
sudo mount -t tmpfs -o size=512M tmpfs /tmp

这样,所有写入/tmp的文件实际上都写在内存里,速度极快,但重启后消失

namespace

namespace是Linux内核提供的一种资源隔离机制
它允许你把同一台机器的内核资源分成多个“视图”(view),每个进程只看到自己的那一份世界
这意味着两个进程可能:

每个namespace控制一类资源。Linux通过这些不同类型的namespace拼出“独立宇宙”

namespace的类型

Linux支持几种不同类型的namespaces,每种类型负责隔离不同的资源

  1. Mount namespace(mnt)
    • 用来隔离文件系统的挂载点。每个挂载命名空间都有自己独立的挂载表,这意味着不同的命名空间可以有不同的挂载视图
    • 举个例子,一个进程在一个命名空间中挂载的文件系统,并不会影响其他命名空间中的进程
  2. Process ID namespace(pid)
    • 用来隔离进程ID。每个命名空间中的进程有自己的进程ID号,并且在命名空间内看到的进程树是独立的
    • 比如,在一个进程ID命名空间中,进程的PID从1开始,这使得容器中的进程与主机的进程互相独立
  3. Network namespace(net)
    • 用来隔离网络资源,每个命名空间都有独立的网络设备、IP地址、路由表等网络配置
    • 这样,进程在不同命名空间中可以拥有不同的网络配置,相互之间无法直接通信
  4. IPC namespace(ipc)
    • 用来隔离进程间通信(IPC)资源。每个命名空间拥有独立的消息队列、信号量、共享内存等IPC资源
    • 这样,进程在不同命名空间中无法直接使用其他命名空间的IPC资源
  5. UTS namespace(uts)
    • 用来隔离主机名和域名,每个命名空间中的进程可以有自己独立的主机名和域名,而不会影响到其他命名空间
    • 这对于容器化应用特别有用,因为它可以让每个容器看起来像是独立的主机
  6. User namespace(user)
    • 用来隔离用户和组ID。每个命名空间中的进程可以有独立的用户ID(UID)和组ID(GID),这使得进程在不同命名空间中可以拥有不同的权限
    • 在容器中,进程可以拥有root权限,但仅限于容器内,不会影响主机系统
  7. Cgroup namespace(cgroup)
    • 用来隔离进程的控制组(cgroup)信息。每个命名空间中的进程可以有自己独立的资源限制(如CPU、内存等)

从进程的视角看隔离

每个进程都有自己的namespace集合,内核通过task_struct里的指针指向这些namespace,可以使用lsnscat /proc/$$/ns/查看当前shell所处的各个命名空间

ipc -> ipc:[4026531839]
mnt -> mnt:[4026531840]
net -> net:[4026531992]
pid -> pid:[4026531836]
user -> user:[4026531837]
uts -> uts:[4026531838]

这些数字是namespace的唯一标识符(实质上是内核对象)

多个进程如果指向同一个namespace,就共享那份“世界观”,如果不同,就各自在不同的宇宙

使用namespace

unshare --net bash 

这回启动一个新的shell,运行在一个新的网络命名空间中,意味着它无法访问主机的网络资源

ls /proc/self/ns 

这里可以看到当前进程所属于的各个命名空间

不同的Linux文件系统类型

Linux有多个文件系统实现,各有优劣:

ext4文件系统的大布局

当在磁盘上格式化一个ext4分区时,它会分成几大区域

[ Boot Block][ Superblock ][ Block Group 0 ][ Block Group 1 ]...

Block Group的内部结构

每个块组都包含以下部分

[ Superblock副本][ Group Descriptor ][ Block Bitmap ][ Inode Bitmap ]
[ Inode Table ][ Data Blocks ]

ext4改进机制

相比ext2/ext3, ext4加了很多现代特性

现代文件系统的趋势

Linux文件系统的演进方向,正在从“可靠 + 通用”转向“可验证 + 可回滚”

  1. ext4:稳定成熟,但本质是20世纪的架构
  2. Btrfs/ZFS:Copy-on-Write(COW)架构,内置校验、快照、压缩。数据安全性高,但对写入延迟敏感
  3. OverlayFS/UnionFS:容器时代的利器,可将多层文件系统叠加成一个视图(如Docker的镜像层)
  4. FUSE(Filesystem in Userspace):允许用户空间实现文件系统,典型如sshfs. AppImage, rclone 这体现了Linux哲学中的“机制优于策略”——内核只提供通用机制,不规定策略

当代Linux的存储体系已经超越了传统磁盘概念:

换句话说,Linux的文件系统并不仅仅是管理数据的,而是管理抽象和语义的

数据读写流程:从抽象到具体

结合VFS和底层实现,一个read操作的大致流程如下

  1. 系统调用:用户程序调用read(fd, buf, count)
  2. VFS层:内核通过文件描述符fd找到对应的file结构体,其中包含来指向VFS通用操作的指针
  3. 路由到具体文件系统:VFS根据文件所在的挂载点,确定其文件系统类型(如ext4),并调用ext4注册的read操作
  4. 页缓存(Page Cache):如果缓存未命中,ext4开始工作。它根据VFS传来的偏移量,通过文件的inode中的指针(或Extents)计算出数据所在的块(block)号
  5. 块设备层:ext4向块设备层发起I/O请求,读取这些块
  6. I/O调度:块设备层的I/O调度器会对多个请求进行合并和排序(如电梯算法),以优化磁盘磁头的移动路径
  7. 设备驱动:最终,请求被发送到具体的硬盘驱动程序,由驱动通过DMA等方式将数据从磁盘读入页缓存
  8. 完成:数据从页缓存复制到用户空间的buf,系统调用返回

写入(write)操作也是类似的路径,但更复杂,涉及回写(Writeback):数据通常先写到页缓存,内核线程再异步地将脏页刷回磁盘。这提升了性能,但也带来了数据一致性的考虑,这就是Journaling(日志)发挥作用的地方

日志(Journaling)机制的工作流程

日志是防止文件系统在崩溃后陷入不一致状态的关键技术(如ext3/ext4的data=ordered模式):

  1. 记录日志:在真正向数据块写入用户内容之前,先将本次写入的元数据(如要写入哪些块、inode如何更新)作为一个“事务”追加到日志区域
  2. 提交日志:将日志事务标记为提交,确保日志记录是完整的
  3. 写入数据:开始真正的数据写入(Commit)
  4. 清理日志:数据写入成功后,清理日志中对应的记录

崩溃恢复

现代特性:Copy-on-Write(COW)

这是Btrfs和ZFS等新一代文件系统的核心思想,与传统的日志式文件系统(如ext4)有根本不同:

权限与安全模型

Linux的安全模型并不是一个附加功能,而是文件系统设计的一部分
每个文件的inode都存储着以下信息:

这些字段定义了系统中不同主体对文件的操作权

基本权限

Linux将文件权限分为三组:所有者(user)/ 所属组(group)/其他用户(others)
每组有是三个权限位:

通过ls -l查看权限,显示为

-rwxr-x--- 1 user group size date filename 

前是个字符即为权限标志,每三个一组,分别对应user, group, others

当执行ls -l后,最前面的字符表示文件的类型,而不是权限本身,比如

drwxr-xr-x  2 user user 4096 Oct 23  folder
-rw-r--r--  1 user user 1024 Oct 23  file.txt
lrwxrwxrwx  1 user user   12 Oct 23  link -> /some/path
符号含义
d目录(directory)
-普通文件(regular file)
l符号链接(symbolic link)
c字符设备(character device, 比如终端、串口)
b块设备(block device, 比如硬盘、U盘)
p管道(pipe)
s套接字(socket)

权限与文件类型的交互

不同类型的文件,权限意义略有不同

文件类型rwx
普通文件读内容改内容执行
目录列出文件新增/删除文件进入目录
设备文件允许I/O操作允许写入设备无意义
管道/套接字允许读允许写无意义

权限修改

chmod(change mode)

改变权限,有两种写法:

  1. 符号式
chmod u+x file # 给用户增加执行权限
chmod g-w file # 移除组写权限
chmod o=r file # 设置其他用户只读
chmod a+x file # 给所有人加执行权限
  1. 八进制式 三组权限用三位八进制数字表示:
    • r=4, w=2, x=1
    • 组合相加得出
      • rwx=7
      • rw-=6
      • r-x=5
      • r--=4
      • -wx=3
      • -w-=2
      • --x=1
chmod 755 file 

chown(change owner)

改变文件的拥有者

sudo chown alice file.text # 改变所有者
sudo chown alice:developers file # 同时改组

chgrp(change group)

单独改组

chgrp developers file 

特殊权限位

Linux文件权限不止rwx三种,还有三种特殊位用于控制执行时的行为

  1. setuid(Set User ID) 应用于可执行文件
    当普通用户执行带setuid位的程序时,程序会以文件所有者的身份运行
    常用于需要短暂提升权限的系统工具
ls -l /usr/bin/passwd 
-rwsr-xr-x 1 root root 54256 ...

rws中的s表示setuid
这让用户能修改自己的密码(写入/etc/shadow),而不需要root权限

  1. setgid(Set Group ID) 有两种作用:
chmod g+s /shared_dir 

这样/shared_dir中的所有文件都属于同一组,方便团队协作

  1. sticky bit 常用于共享目录(如/tmp
    表示目录中的文件只有文件所有者或root才能删除
chmod +t /tmp 
drwxrwxrwt 10 root root 4096 ...

末尾的t表示sticky bit

总结

Linux权限系统体现了Unix的哲学:“最小权限原则”——默认拒绝,按需授予
系统中一切操作最终都由文件描述符驱动,权限控制是确保这个接口不会滥用的根机制
在现代系统中,这套传统权限模型还扩展为

硬连接与软链接

核心概念

创建命令

ln fileA fileB # fileB 是 fileA的硬链接
ln -s targetpath linkname # 创建符号链接

示例

假设有file1,inode假定为 1000

echo hello > file1 
ln file1 file_hard # 硬链接
ln -s file1 file_soft # 符号链接
ls -li 

关键行为区别:

技术与限制

  1. 跨文件系统

    • 硬链接不能跨文件系统(必须在同一分区/同一文件系统内)
    • 软连接可以指向任意路径(包括不同分区、网络挂载、甚至不存在的路径)
  2. 目录链接

    • 普通用户不能对目录创建硬链接(历史守丧出于放置环和破坏树结构的考虑)。root也极少使用。软连接可以指向目录且很常用(例如/ect/alternatives、指向配置目录等)
  3. link count(链接计数)

    • 硬链接会增加inode的link count(statls -l第二列显示)。只有当link count为0且没有进程持有该inode的打开文件描述符时,内核才回收数据块
    • 软连接不会影响目标文件的link count
  4. 权限与元数据

    • 硬链接与目标共享同一个inode,所有它们共享权限、所有者、时间戳等元数据(对其中一个改权限,另一个也变)
    • 软链接本身有自己的权限位(通常lrwxrwxrwx),但系统在访问时会忽略这些,实际权限由目标文件决定(注意:lstatstat的区别,可分别查看链接本身和目标)
  5. 性能与实现成本

    • 硬链接只是多了一个目录项,几乎零成本(没有额外I/O除了目录项更新)
    • 软连接需要一次额外的路径解析/跳转(一次额外查inode),成本极小且通常可忽略
  6. 符号链接的相对/绝对路径问题

    • 绝对symlink(/usr/bin/foo)在移动包含该链接的整个目录后会失效
    • 相对symlink(../lib/foo)在移动整个目录树时更稳健,推荐在项目内部/仓库使用相对链接

检测与诊断命令

ls -li file1 file_hard file_soft 
stat file1 
find /path -xdev -inum 12345 -print 

-xdev防止快文件系统搜索(可选)

find /path -type l | -exec test -e {} \; -print 

解释:找所有类型为symlink且test -e为假的(目标不存在)的项

readlink linkname # 只输出目标路径
readlink -f linkname # 输出解析后的绝对真实路径(跟随所有中间链接)

常见误区与安全隐患

何时用硬链接,何时用软连接(实战建议)