Linux 文件系统深度
第2章:Linux 文件系统深度解析
Linux 的"一切皆文件"哲学,让你只需理解文件系统就能掌握整个系统的行为。本章从 FHS 目录结构出发,深入 inode 内部原理,掌握硬链接与软链接的本质区别,再到 find 的高级用法和磁盘使用分析——这是所有 Shell 高手的必备知识体系。
2.1 FHS:文件系统层次结构标准
FHS(Filesystem Hierarchy Standard) 规定了 Linux 系统目录结构的标准,确保不同发行版的目录布局保持一致。理解每个目录的用途是系统管理的基础。
| 目录 | 全称/含义 | 典型内容 |
|---|---|---|
| / | 根目录 | 整个文件系统的起点,所有路径从此开始 |
| /bin | 基本用户二进制文件 | ls, cp, mv, cat, bash(现代系统常为 /usr/bin 的符号链接) |
| /sbin | 系统管理二进制文件 | fdisk, ifconfig, fsck, init(需要 root 权限的命令) |
| /usr | Unix 系统资源 | /usr/bin(用户程序),/usr/lib(库文件),/usr/share(共享数据) |
| /etc | 系统配置文件 | /etc/passwd, /etc/fstab, /etc/nginx/, /etc/ssh/(所有系统级配置) |
| /var | 可变数据 | /var/log(日志),/var/spool(队列),/var/cache(缓存),/var/www(Web 根目录) |
| /tmp | 临时文件(重启清空) | 所有用户都可写,常挂载为 tmpfs(内存) |
| /proc | 进程信息虚拟文件系统 | /proc/cpuinfo, /proc/meminfo, /proc/[PID]/(内核数据接口) |
| /sys | 系统设备虚拟文件系统 | 内核导出的设备/驱动/电源管理接口(sysfs) |
| /dev | 设备文件 | /dev/sda(磁盘),/dev/null,/dev/zero,/dev/tty(终端) |
| /home | 用户主目录 | /home/alice/, /home/bob/(每个用户的私人空间) |
| /root | root 用户的主目录 | 仅 root 可访问,不在 /home 下 |
| /opt | 可选第三方软件 | 手动安装的大型软件包(如 /opt/google/chrome) |
| /srv | 服务数据 | Web 服务、FTP 服务的数据目录(/srv/www, /srv/ftp) |
| /boot | 启动加载文件 | vmlinuz, initrd.img, grub/(内核和 GRUB 配置) |
| /lib | 基本共享库 | /lib/x86_64-linux-gnu/libc.so.6(C 标准库等) |
| /mnt | 临时挂载点 | 管理员临时挂载设备的约定目录 |
| /media | 可移动媒体挂载点 | 自动挂载的 USB、DVD 等(/media/username/USB) |
# 查看根目录结构
ls -la /
# 了解各目录大小
du -sh /* 2>/dev/null | sort -hr | head -20
# 查看当前挂载的文件系统
findmnt
# 或
mount | column -t
# 查看磁盘分区和文件系统类型
lsblk -f
2.2 inode 原理:文件的真实身份
在 Linux 文件系统中,文件名只是指向 inode 的一个标签,真正存储文件元数据的是 inode(Index Node,索引节点)。每个文件(包括目录)都对应一个唯一的 inode 编号。
inode 存储的内容(ASCII 结构图)
┌─────────────────────────────────────────────┐
│ inode │
├─────────────────────────────────────────────┤
│ inode number : 2097152 │
│ file type : regular file (-) │
│ permissions : 0644 (rw-r--r--) │
│ link count : 1 (硬链接数量) │
│ owner UID : 1000 (alice) │
│ group GID : 1000 (alice) │
│ file size : 4096 bytes │
│ atime : 2026-04-25 10:00:00 │ ← 最后访问时间
│ mtime : 2026-04-20 14:30:00 │ ← 最后修改时间(内容)
│ ctime : 2026-04-21 09:00:00 │ ← 最后改变时间(元数据)
│ block size : 4096 │
│ block count : 8 │
│ direct blocks : [ptr0][ptr1]...[ptr11] │ ← 直接块指针(12个)
│ single indirect : [ptr → block table] │ ← 一级间接块
│ double indirect : [ptr → ptr → blocks] │ ← 二级间接块
│ triple indirect : [ptr → ptr → ptr → ...] │ ← 三级间接块
└─────────────────────────────────────────────┘
↑
目录项(dentry)中存储:文件名 → inode 编号
文件名本身不在 inode 中!
关键理解: inode 不存储文件名——文件名存储在目录项(directory entry)中,目录项将文件名映射到 inode 编号。这就是为什么同一个文件可以有多个名字(硬链接),也是为什么移动文件(mv)在同一文件系统内瞬间完成——只需修改目录项,不移动数据。
# 查看文件的 inode 编号
ls -i filename.txt
# 2097152 filename.txt
# 查看目录内所有文件的 inode 号
ls -li /etc/
# 查看 inode 使用情况(每个文件系统的 inode 总数和已用数)
df -i
# Filesystem Inodes IUsed IFree IUse% Mounted on
# /dev/sda1 6553600 87234 6466366 2% /
# 查找 inode 用尽的迹象(inode 满了但磁盘空间还有)
df -i | awk '$5 == "100%"'
# 查找 inode 号为某个值的文件
find / -inum 2097152 2>/dev/null
2.3 stat 命令:读取文件元数据
stat 命令直接读取并显示文件的 inode 信息,是了解文件真实状态的最佳工具。
$ stat /etc/passwd
File: /etc/passwd
Size: 2847 Blocks: 8 IO Block: 4096 regular file
Device: fd01h/64769d Inode: 524299 Links: 1
Access: (0644/-rw-r--r--) Uid: ( 0/ root) Gid: ( 0/ root)
Access: 2026-04-25 08:12:33.421076800 +0800 ← atime:最后读取时间
Modify: 2026-04-20 14:22:10.123456789 +0800 ← mtime:内容最后修改时间
Change: 2026-04-20 14:22:10.123456789 +0800 ← ctime:元数据最后修改时间
# 三个时间戳的区别:
# atime (access time) → 每次读取文件时更新
# 注意:挂载时使用 noatime 选项可禁止更新(提升性能)
# mtime (modify time) → 文件内容改变时更新(写入数据)
# ctime (change time) → 文件元数据改变时更新(权限/所有者/链接数)
# 注意:ctime 不能被人为修改(touch 不影响 ctime)
# 只查看特定字段
stat -c "%n: inode=%i, size=%s, mtime=%y" /etc/passwd
# 格式化输出
stat --format="File: %n | Size: %s bytes | Permissions: %A | Inode: %i" /etc/passwd
# 查看目录的 stat
stat /var/log/
# 使用 touch 修改 atime 和 mtime
touch -a file.txt # 只更新 atime
touch -m file.txt # 只更新 mtime
touch -t 202601010000 file.txt # 设置为指定时间
2.4 Linux 文件类型详解
ls -l 输出第一列的第一个字符表示文件类型。Linux 共有 7 种文件类型:
# 文件类型标识符(ls -l 第一列首字符):
# - 普通文件(regular file):文本、二进制、图片等
# d 目录(directory)
# l 软链接(symbolic link)
# c 字符设备(character device):/dev/tty, /dev/null(逐字节 I/O)
# b 块设备(block device):/dev/sda, /dev/loop0(块 I/O,如磁盘)
# p 命名管道(FIFO):进程间通信
# s Unix 域套接字(socket):/run/docker.sock
# 实际示例:
ls -la /dev/null /dev/sda /tmp /run/docker.sock
# crw-rw-rw- 1 root root 1, 3 ... /dev/null ← c 字符设备
# brw-rw---- 1 root disk 8, 0 ... /dev/sda ← b 块设备
# drwxrwxrwt 20 root root ... /tmp ← d 目录
# srw-rw---- 1 root docker ... /run/docker.sock ← s 套接字
# 用 file 命令识别文件真实类型(不依赖扩展名)
file /bin/ls
# /bin/ls: ELF 64-bit LSB pie executable, x86-64 ...
file /etc/passwd
# /etc/passwd: ASCII text
file /dev/sda
# /dev/sda: block special (8/0)
# 用 find 按文件类型搜索
find /dev -type c # 所有字符设备
find /tmp -type p # 所有命名管道
find /run -type s # 所有套接字
# 创建命名管道
mkfifo /tmp/mypipe
ls -l /tmp/mypipe
# prw-r--r-- 1 user user 0 ... /tmp/mypipe ← p 管道
2.5 硬链接 vs 软链接:本质区别
链接是 Linux 文件系统中的核心概念,理解硬链接(hard link)和软链接(symbolic link)的区别,是理解 inode 原理的最佳实践场景。
# === 硬链接(Hard Link)===
# 创建硬链接:两个文件名指向同一个 inode
ln original.txt hardlink.txt
# 验证:两者 inode 号相同
ls -li original.txt hardlink.txt
# 2097152 -rw-r--r-- 2 user user 100 ... original.txt
# 2097152 -rw-r--r-- 2 user user 100 ... hardlink.txt
# ↑ 相同 inode ↑ 链接数为 2
# 删除原文件——数据依然存在(链接数减 1,不为 0 时不删除数据)
rm original.txt
cat hardlink.txt # 依然可以读取!
# 硬链接的限制:
# 1. 不能跨文件系统(inode 号仅在同一文件系统内唯一)
# 2. 不能对目录创建硬链接(防止循环引用)
# === 软链接(Symbolic Link)===
# 创建软链接:存储目标路径字符串
ln -s /path/to/original.txt symlink.txt
ln -s /usr/bin/python3 /usr/local/bin/python # 常见用法
# 验证:inode 号不同,l 类型,显示指向
ls -li original.txt symlink.txt
# 2097153 lrwxrwxrwx 1 user user 16 ... symlink.txt -> original.txt
# ↑ 不同 inode ↑ l 类型
# 软链接可以跨文件系统,可以指向目录
ln -s /mnt/data /home/user/data-link
# 软链接的注意事项:
# - 原文件删除后,软链接变为"悬空链接"(dangling link)
# - 相对路径软链接:相对于链接文件所在目录,而非当前目录
# === 相关命令 ===
# 查看软链接的真实目标
readlink symlink.txt
# /path/to/original.txt
# 解析完整绝对路径(跟随所有软链接)
realpath symlink.txt
# 找出所有悬空软链接
find /path -xtype l # -xtype l 匹配软链接,但目标不存在
# 统计一个文件的硬链接数
stat -c "%h" /bin/ls # 通常为 1
| 特性 | 硬链接 | 软链接(符号链接) |
|---|---|---|
| inode 关系 | 共享同一 inode | 独立 inode,存储路径字符串 |
| 跨文件系统 | 不支持 | 支持 |
| 指向目录 | 不支持(防止循环) | 支持 |
| 原文件删除后 | 数据仍可访问 | 变为悬空链接,无法访问 |
| ls -l 显示 | 与普通文件相同(链接数>1) | l 类型,显示 -> 目标 |
| 常见用途 | 备份不占额外 inode | 版本管理、路径别名 |
2.6 find 深度使用:强大的文件搜索
find 是 Linux 最强大的文件搜索工具,支持按名称、类型、大小、时间、权限等多种条件组合搜索,并能对每个找到的文件执行任意命令。
# === 按名称搜索 ===
find /home -name "*.log" # 查找所有 .log 文件
find /etc -name "*.conf" -type f # 只找普通文件
find / -name "passwd" 2>/dev/null # 忽略权限错误
find . -iname "*.TXT" # 忽略大小写
# === 按类型搜索 ===
find /var -type d -name "log*" # 目录
find /dev -type b # 块设备
find /tmp -type l # 所有软链接
find /run -type s # Unix 套接字
# === 按大小搜索 ===
find /var/log -size +100M # 大于 100MB 的文件
find /home -size -1k # 小于 1KB 的文件(空文件附近)
find /tmp -size +10M -size -1G # 10MB 到 1GB 之间
find / -empty # 空文件或空目录
# === 按时间搜索(单位:天)===
find /var/log -mtime -7 # 7天内修改过的文件
find /home -atime +30 # 30天以上未被访问
find /tmp -mtime +3 -type f # 3天以上的临时文件
find /etc -newer /etc/passwd # 比 passwd 更新的文件
find /var/log -mmin -60 # 60分钟内修改(-mmin,分钟)
# === 按权限搜索 ===
find / -perm 0777 -type f 2>/dev/null # 权限为 777 的文件(安全风险!)
find / -perm -u+s -type f 2>/dev/null # 有 SUID 位的文件
find / -perm -g+s -type f 2>/dev/null # 有 SGID 位的文件
find /home -perm /o+w # 其他用户可写的文件
# === 按所有者搜索 ===
find /home -user alice # alice 拥有的文件
find /tmp -nouser # 没有对应用户的文件(孤立文件)
find / -group docker 2>/dev/null # docker 组拥有的文件
# === -exec:对每个结果执行命令 ===
# {} 代表当前找到的文件;\ 是 -exec 的结束符
find /tmp -name "*.tmp" -mtime +7 -exec rm {} \;
# -exec 的更安全版本:-execdir(在文件所在目录执行)
find /home -name "*.bak" -execdir rm {} \;
# 将结果传给 xargs(效率更高,批量处理)
find /var/log -name "*.log" -mtime +30 | xargs rm -f
find /home -name "*.jpg" | xargs -I{} cp {} /backup/photos/
# === 组合条件 ===
# -a (AND,默认),-o (OR),! (NOT)
find /home -name "*.txt" -size +1M # AND:txt 且大于 1MB
find /home \( -name "*.txt" -o -name "*.md" \) # OR:txt 或 md
find /home ! -name "*.log" # NOT:不是 .log
# === 深度控制 ===
find /etc -maxdepth 1 -name "*.conf" # 只搜索一层深
find /usr -mindepth 2 -maxdepth 3 # 只搜索 2-3 层深
# === 实用组合示例 ===
# 找出过去24小时内被修改的文件,并显示它们
find /var/www -mtime -1 -type f -ls
# 找出大于 500MB 的文件,按大小排序
find / -type f -size +500M -printf "%s\t%p\n" 2>/dev/null | sort -rn | head -20
# 找出 SUID/SGID 文件(安全审计)
find / -type f \( -perm -4000 -o -perm -2000 \) -exec ls -la {} \; 2>/dev/null
# 统计目录下各类型文件数量
find /etc -type f | wc -l # 文件数
find /etc -type d | wc -l # 目录数
2.7 ls 深度:解读每一列的含义
$ ls -la /etc/passwd
-rw-r--r-- 1 root root 2847 Apr 20 14:22 /etc/passwd
^ ^^^^^^^ ^ ^^^^ ^^^^ ^^^^ ^^^^^^^^^^^^ ^^^^^^^^^^^^
│ │ │ │ │ │ │ └── 文件名
│ │ │ │ │ │ └── 最后修改时间(mtime)
│ │ │ │ │ └── 文件大小(字节)
│ │ │ │ └── 所属组
│ │ │ └── 所有者
│ │ └── 硬链接数
│ └── 权限位(rwxrwxrwx:所有者/组/其他)
└── 文件类型(- d l c b p s)
# 常用 ls 选项
ls -l # 长格式
ls -la # 包含隐藏文件(.开头)
ls -lh # 人类可读大小(K, M, G)
ls -lS # 按文件大小排序
ls -lt # 按修改时间排序(最新在前)
ls -ltr # 按修改时间反向排序(最旧在前)
ls -li # 显示 inode 号
ls -lR # 递归列出子目录
ls -ld /etc # 只显示目录本身信息(不展开内容)
ls --color=auto # 颜色区分文件类型(大多数发行版默认开启)
# 自定义颜色配置
# dircolors -p > ~/.dircolors
# 编辑 ~/.dircolors,然后在 ~/.bashrc 中加:
# eval "$(dircolors ~/.dircolors)"
# tree 命令(更直观的目录结构)
sudo apt install -y tree
tree /etc/nginx -L 2 # 显示 2 层深度
tree -sh # 显示大小,人类可读
tree -d # 只显示目录
2.8 文件系统类型:ext4 vs xfs vs 虚拟文件系统
ext4 vs xfs 对比
| 特性 | ext4 | XFS |
|---|---|---|
| 设计目标 | 通用,向后兼容 ext2/ext3 | 高性能大文件,大规模并行 I/O |
| 最大文件系统大小 | 1 EB | 8 EB |
| 最大单文件大小 | 16 TB | 8 EB |
| 日志(Journal) | 完整日志支持 | 元数据日志(默认),可选数据日志 |
| 小文件性能 | 良好(内联小文件到 inode) | 略低(专注大文件优化) |
| 大文件性能 | 良好 | 优秀(延迟分配 + 条带化) |
| fsck 速度 | 慢(大文件系统上很慢) | 快 |
| 在线缩减 | 不支持 | 不支持 |
| 默认发行版 | Ubuntu, Debian | RHEL, CentOS, Fedora |
| 推荐场景 | 通用桌面/服务器,系统分区 | 数据库、视频流、大数据存储 |
虚拟文件系统
# === tmpfs(内存文件系统)===
# 数据存在 RAM 中,重启后消失,速度极快
# /tmp 通常挂载为 tmpfs
mount | grep tmpfs
# tmpfs on /tmp type tmpfs (rw,nosuid,nodev,size=8192M)
# 手动创建 tmpfs 挂载点
sudo mkdir /mnt/ramdisk
sudo mount -t tmpfs -o size=512M tmpfs /mnt/ramdisk
df -h /mnt/ramdisk
# === /proc 虚拟文件系统(procfs)===
# 内核在运行时动态生成的文件,不占磁盘空间
cat /proc/cpuinfo # CPU 信息
cat /proc/meminfo # 内存信息
cat /proc/version # 内核版本
cat /proc/mounts # 当前挂载点
cat /proc/net/dev # 网络接口统计
cat /proc/loadavg # 系统负载
cat /proc/$$/status # 当前 Shell 进程状态($$ 是当前 PID)
ls /proc/1/ # PID 1(init/systemd)的所有信息
# === /sys 虚拟文件系统(sysfs)===
# 内核设备模型的接口,可读写来控制硬件
cat /sys/class/net/eth0/speed # 网卡速度
cat /sys/class/thermal/thermal_zone0/temp # CPU 温度(单位 millidegrees)
cat /sys/block/sda/queue/scheduler # 磁盘调度器
echo mq-deadline > /sys/block/sda/queue/scheduler # 修改调度器(需 root)
# 查看当前所有挂载的文件系统类型
cat /proc/filesystems # 内核支持的文件系统
mount -t ext4 # 只显示 ext4 类型的挂载
findmnt -t ext4,xfs # findmnt 过滤特定类型
2.9 du 与 df:磁盘使用分析
df(disk free)报告文件系统级别的磁盘使用情况,du(disk usage)报告目录或文件占用的实际空间。两者配合使用可以快速定位磁盘空间问题。
# === df 命令 ===
df -h # 人类可读格式(K/M/G)
df -H # 使用 1000 而非 1024 计算
df -T # 显示文件系统类型
df -i # 显示 inode 使用情况(而非磁盘空间)
df -h /var # 只显示 /var 所在文件系统
# df 输出解读:
# Filesystem Size Used Avail Use% Mounted on
# /dev/sda1 50G 32G 16G 67% /
# tmpfs 3.9G 1.2M 3.9G 1% /tmp
# === du 命令 ===
du -sh /var/log # 整个 /var/log 目录的总大小
du -sh /* # 根目录下各目录大小(快速定位大目录)
du -sh /home/* # 每个用户的空间占用
du -h --max-depth=2 / # 递归最多 2 层
du -h --max-depth=1 /var | sort -hr # 排序后显示(最大的在前)
# 找出当前目录中最大的 10 个文件/目录
du -h . | sort -hr | head -10
# 找出系统中最大的文件(前20个)
find / -type f -printf "%s\t%p\n" 2>/dev/null | sort -rn | head -20 | \
awk '{printf "%.1fMB\t%s\n", $1/1024/1024, $2}'
# === ncdu — 交互式磁盘使用分析器 ===
sudo apt install -y ncdu # 安装
ncdu / # 扫描根目录(交互式,可以导航进入子目录)
ncdu /var # 扫描特定目录
# === 常见磁盘满问题排查流程 ===
# 1. 确认哪个分区满了
df -h
# 2. 定位大目录
du -h --max-depth=1 / 2>/dev/null | sort -hr | head -10
# 3. 进入大目录继续深挖
du -h --max-depth=1 /var | sort -hr
# 4. 找大文件
find /var/log -type f -size +100M -ls 2>/dev/null
# 5. 检查被删除但仍被进程持有的文件(常见于日志轮转)
lsof +L1 | grep deleted
# 6. 清理旧日志(journalctl)
journalctl --vacuum-size=200M # 保留最近 200MB
journalctl --vacuum-time=7d # 保留最近 7 天
磁盘满但 df 显示有空间? 两种常见原因:(1) inode 耗尽——用
df -i检查,小文件过多会耗尽 inode;(2) 文件被删除但仍被进程持有——用lsof +L1查找,重启进程或系统即可释放。
章节总结: 你现在深刻理解了 Linux 文件系统的核心机制:FHS 目录规范告诉你每个目录的职责,inode 原理揭示了"文件名只是标签"的本质,硬链接与软链接的区别让你能合理使用这两种工具,find 的强大组合让任意文件搜索成为可能,du/df 帮你随时掌控磁盘状态。下一章将聚焦文件操作命令的精通使用。
上一章
← 第1章:Shell 环境搭建
下一章
第3章:文件操作精通 →