路线图

文件系统结构

星辉 2026-07-02 阅读 5 min 940 字 路线图
文件系统结构 封面

Linux 运维基础 · 第二章 目标:理解 Linux 目录布局,掌握路径、inode、链接的核心概念


1. 概述#

Windows 用盘符(C: D: E:)组织文件,插个 U 盘就多一个盘符。Linux 完全不同——一切皆文件,所有东西挂在同一个根 /。磁盘、键盘、进程信息、网络连接……统统表示为文件。

本章从运维的角度帮你看清三个关键问题:

  • /etc/var 分别放什么?万一生产出问题,知道去哪翻。
  • df -h 满了但 du -sh 不大——是不是"幽灵文件"在占空间?
  • 软链接和硬链接到底有什么区别?删了源文件链接还能用吗?

2. FHS 目录约定#

FHS(Filesystem Hierarchy Standard)规定了各目录该放什么。以下是运维必须记住的核心目录:

2.1 核心目录速查#

目录全称 / 含义放什么(一句话)
/root整个文件系统的起点,所有目录的祖先
/etcEditable Text Configuration配置文件。一切服务的配置都在这里(nginx、ssh、systemd 等)
/varVariable可变数据:日志(/var/log)、缓存、数据库文件、邮件队列等
/tmpTemporary临时文件,所有用户可写,重启可能清空
/procProcess伪文件系统,存放内核和进程的运行时信息(内存数、CPU 信息)
/homeHome普通用户的家目录,每个用户一个子目录
/usrUnix System Resources用户程序、库、文档(现代解释为"系统资源";历史上最初其实是 user 家目录,后演变为只读共享数据区)
/optOptional第三方软件的独立安装目录(如 /opt/google/chrome)
/rootroot 用户的家目录,不在 /home 下
/devDevice设备文件,磁盘、终端、键盘等都以文件形式存在
/bin, /sbinBinary / System Binary基础命令(许多发行版已合并到 /usr/bin)
/bootBoot内核镜像和引导加载器文件
/lib, /lib64Library共享库文件(/lib/modules 是内核模块)
/mnt, /mediaMount / Media临时挂载点 / 自动挂载的 U 盘等
/runRuntime系统启动以来的运行时数据(PID 文件、socket 文件等)
/sysSystem内核设备信息,与 /proc 类似但更结构化

2.2 运维常去的目录#

bash
# 查配置
/etc/nginx/          # Nginx 配置
/etc/systemd/system/ # systemd 服务文件
/etc/ssh/sshd_config # SSH 服务端配置
/etc/fstab           # 开机自动挂载表

# 查日志
/var/log/syslog      # 系统主日志(Debian/Ubuntu)
/var/log/messages    # 系统主日志(RHEL/CentOS)
/var/log/auth.log    # 认证日志(登录、sudo 记录)
/var/log/nginx/      # Nginx 日志

# 查运行时状态
/proc/cpuinfo        # CPU 详细信息
/proc/meminfo        # 内存信息
/proc/loadavg        # 系统负载
/var/run/            # PID 文件在这(或 /run/)

# 临时文件
/tmp/                # 注意:不要放重要数据

2.3 伪文件系统 /proc /sys /dev /run#

/proc 不是真实磁盘文件,读取它本质是向内核查询系统状态,所以不占磁盘空间:

bash
# 查看 CPU 信息
cat /proc/cpuinfo | grep "model name"

# 查看系统启动了多久
cat /proc/uptime

# 查看进程信息
ls /proc/          # PID 为名的目录就是进程的信息
cat /proc/1234/cmdline  # 进程 1234 的启动命令
cat /proc/1234/status   # 进程 1234 的状态和内存使用

除了 /proc,Linux 还有几个"不占磁盘"的虚拟文件系统。df -hT 里能看到它们的类型是 tmpfs / devtmpfs / proc / sysfs:

挂载点类型作用
/procproc进程与内核运行时信息(上面讲的)
/syssysfs内核设备/驱动/子系统的结构化视图,可读写内核参数(如 /sys/class/net/eth0/mtu
/devdevtmpfs设备节点(/dev/sda/dev/null),内核自动创建维护
/runtmpfs运行时数据(PID、socket),存在内存里,重启即清空
/dev/shmtmpfs共享内存,供进程间高速交换数据

tmpfs 是内存文件系统:往 /run/dev/shm 写东西不落磁盘、重启就没,但会占内存。别拿它当持久存储。


3. 绝对路径 vs 相对路径#

3.1 概念#

类型特征示例
绝对路径/ 开头,从根目录算起/home/user/doc/readme.txt
相对路径不以 / 开头,以当前目录为参考doc/readme.txt

两个特殊符号:

符号含义
.当前目录
..上级目录

3.2 使用场景#

bash
# 绝对路径:写脚本/配置文件时必须用——不依赖"当前在哪"
cat /etc/nginx/nginx.conf       # 不管在哪个目录,都能正确读取

# 相对路径:日常交互时省敲键
cd /var/log                     # 先到日志目录
less syslog                      # 相对路径,省去 /var/log/syslog

cd ../../home/alice             # 向上两级再到 alice 家目录

原则:写脚本时永远用绝对路径,或先 cd 到已知位置再操作。避免 cron 任务因为"当前目录不对"而找不到文件。


4. inode 是什么#

4.1 两层结构#

每个文件在磁盘上由两个东西描述:

结构存什么存在哪
inode(索引节点)元数据:权限、大小、时间戳、数据块指针、链接数文件系统的 inode 表
目录项(dentry)文件名 + 对应 inode 编号目录文件的数据块中

理解要点:文件名不是文件的一部分,它只是"指向 inode"的标签。 一个 inode 可以有多个文件名(硬链接)。

4.2 inode 中存什么#

text
inode 编号 (inode number)
文件类型 (普通文件/目录/链接...)
权限  (rwxr-xr-x)
UID / GID(所有者/所属组)
文件大小  (字节数)
时间戳:
  - atime:最后访问时间
  - mtime:最后修改内容时间
  - ctime:最后修改元数据时间
链接数 (link count)
数据块指针:指向真正存数据的磁盘块

atime 的现代实情:为省掉"每次读文件都写一次 atime"的开销,现在主流发行版(Rocky/Ubuntu)挂载默认带 relatime——只有当 atime 比 mtime/ctime 旧、或距上次更新超过 24h 时才更新 atime。所以别指望用 atime 精确判断"文件最后一次被读是什么时候"。想彻底关掉可用 noatime 挂载选项(数据库、高 IO 场景常用)。

4.3 查看 inode#

bash
# -i 显示 inode 编号
ls -li /etc/passwd
# 输出:1234567 -rw-r--r-- 1 root root 2540 Jan 1 12:00 /etc/passwd
#       ^ inode号

# 查看 inode 使用情况
df -i
# Filesystem     Inodes IUsed IFree IUse% Mounted on
# /dev/sda1      655360 45321 610039    7% /

运维要点df -h 显示空间没满但创建不了文件——很可能是 inode 用完了(小文件太多)。


5. 删文件的本质#

5.1 为什么 rm 不等于删除数据#

bash
# 准备一个普通文件(link count = 1)
echo "hello" > test.log

# 当文件没有被任何进程打开:
rm test.log
  → 1. 删除目录项(文件名消失)
  → 2. inode 的 link count 从 1 变成 0
  → 3. 内核释放 inode 和对应的数据块

# 当文件被进程打开时:
> app.log                # 假设有进程正在写这个文件
rm app.log               # 文件名消失了
ls -l app.log            # 显示:No such file or directory
df -h                    # 但磁盘空间没有释放!

# 为什么?因为进程还持有文件句柄(file handle)
# inode 的 link count 变 0,但 open count 不是 0
# inode 和数据块会保留到进程关闭文件才真正释放

5.2 典型故障场景:磁盘满了但找不到大文件#

运维经典故障:

bash
df -h
# /dev/sda1       50G   50G   0  100% /     ← 磁盘满了

du -sh /* | sort -rh | head
# 加起来才 30G,那 20G 去哪了?

# 真相:有进程持有已删除文件
lsof | grep deleted | awk '{print $1, $7}' | sort -nrk2 | head
# mysqld    1052672000   ← MySQL 还在写一个已删除的日志
# java      524288000    ← Java 写一个已删除的日志

# 解决方法:重启对应进程(不推荐直接杀)
systemctl restart mysqld   # 重启后空间就回来了

要点:被进程打开的文件,即使删了目录项,空间要等进程关闭文件才释放。


6. 软链接 vs 硬链接#

6.1 概念对比#

bash
# 创建硬链接
ln source.txt hard_link.txt

# 创建软链接(符号链接)
ln -s source.txt soft_link.txt

# 查看
ls -li
# 12345 -rw-r--r-- 2 user group  0 Jul  1 10:00 hard_link.txt   ← inode=12345, link count=2
# 12345 -rw-r--r-- 2 user group  0 Jul  1 10:00 source.txt       ← 同一个 inode!
# 67890 lrwxrwxrwx 1 user group 10 Jul  1 10:00 soft_link.txt -> source.txt
#       ↑ 类型是 l(link),inode 不同,大小是"目标路径名长度"

6.2 详细对比#

特性硬链接 (hard link)软链接 (symlink / symbolic link)
本质同一个 inode 的多个文件名一个独立的"指针文件",存目标路径字符串
inode相同(与源文件共享 inode)不同(自己有一个新 inode)
跨文件系统不允许(不同文件系统 inode 独立)允许(存的是路径字符串)
跨分区不允许允许
链接目录不允许(有些系统/特权除外)允许
删除源文件后链接仍可用(只要 link count > 0,数据还在)链接断掉(指向不存在的路径)
修改链接源文件同步变(同一个 inode)源文件同步变(写操作经过路径解析)
显示ls -l 看不出,"感觉"就是普通文件ls -l 第一列为 l,文件名后有 ->
用途防止误删重要数据快捷方式、路径别名、跨分区引用

6.3 典型用法#

bash
# 软链接:给长路径起个短名
ln -s /opt/application-v3.2.1-current /opt/app
cd /opt/app   # 实际进到 /opt/application-v3.2.1-current

# 软链接:版本切换(滚动更新)
rm /opt/app
ln -s /opt/application-v3.2.2-current /opt/app  # 一键切版本

# 硬链接:备份保护
ln important_file important_file.backup
# 即使有人 rm important_file,数据还在(link count > 0)

# 软链接:配置文件管理
ln -s /etc/nginx/sites-available/myapp.conf /etc/nginx/sites-enabled/
# Nginx 只加载 sites-enabled 下的配置

6.4 注意事项#

bash
# 软链接要注意相对路径陷阱
ln -s ../config/app.conf /etc/myapp/link_app.conf
# 软链接里存的是字面字符串;解析时相对「软链接自己所在的目录」(/etc/myapp/),
# 而不是你的当前目录——这里实际指向 /etc/config/app.conf。
# 坑在于:数 .. 要从软链接的位置数(容易数错),且一旦软链接被移动,目标就跟着错位

# 建议:创建软链接时目标用绝对路径
ln -s /opt/app/config/app.conf /etc/myapp/link_app.conf  # 安全

# 查看软链接的真正目标
readlink /etc/myapp/link_app.conf      # 显示目标路径
readlink -f /etc/myapp/link_app.conf   # 递归解析到最终绝对路径

7. 实战:排查"幽灵空间"#

场景#

监控告警:/data 分区使用率达到 98%。运维登录排查。

步骤#

bash
# Step 1:确认告警
df -h /data
# /dev/sdb1       100G   98G  2G  98% /data

# Step 2:定位大户
du -sh /data/* 2>/dev/null | sort -rh | head -5
# 52G     /data/mysql
# 18G     /data/logs
# 5.2G    /data/backup
# ...加起来才 75G,98G - 75G = 23G 去哪了?

# Step 3:查是不是"已删文件句柄没释放"
lsof +L1 /data  2>/dev/null | awk '{print $1, $2, $7}' | sort -nrk3 | head
# java      12345   15032385536    ← 一个 14G 的已删除文件!
# syslog-ng  8876    8589934592   ← 一个 8G 的已删除日志!

# Step 4:确认是哪个进程
ps aux | grep 12345
# appuser   12345  5.2  ... java -jar /data/app.jar

# Step 5:通知开发/业务方,重启对应的进程
systemctl restart myapp

# Step 6:确认释放
df -h /data
# /dev/sdb1       100G   75G  25G  75% /data   ← 恢复

8. 小结#

知识点一句话记忆
FHS配置去 /etc,日志去 /var/log,临时文件去 /tmp
绝对/相对路径写脚本用绝对,日常用相对
inode存元数据,文件名只是指向 inode 的标签
rm 不释放空间进程持有文件句柄时,空间不会释放——lsof | grep deleted
软链接快捷方式,删源文件就断;可跨分区、可链目录
硬链接同一 inode 的别名,删源不影响;不可跨分区、不可链目录

上一章:一、命令行入门 下一章:三、文件操作