【Linux】 文件访问与路径解析:目录、文件名、dentry、路径缓冲与 dcache 全解析


Linux 文件访问与路径解析:目录、文件名、dentry、路径缓冲、挂载与软硬链接全解析

一、为什么要搞懂文件访问与路径解析

在 Linux 中"一切皆文件"。

我们每天都在调用 open、read、stat、ls,但很少思考:

当你写下 open("/home/alice/file.txt", O_RDONLY) 时,内核到底做了什么?

这一串字符是如何一步步变成内存中的文件对象、最终读到磁盘数据的?

理解这条链路,你才能真正理解:

  • 为什么文件名"不属于文件",磁盘上甚至没有"目录"这个概念;
  • 为什么打开文件后,原路径被删除/替换都不影响 fd;
  • 为什么删除一个正在使用的日志文件,磁盘空间却不释放;
  • Linux 有多个分区,路径解析时怎么知道自己在哪个分区;
  • 为什么会有 TOCTOU 竞态,为什么要发明 openat、O_PATH;
  • 为什么 chroot 不是安全边界。

这正是**路径解析(Path Resolution)**要解决的问题,也是 Linux 系统编程和面试的核心考点。


二、全局观:VFS 四大对象

VFS(Virtual File System,虚拟文件系统)是内核中对所有文件系统的抽象层。

它定义了四个核心对象:

对象 对应关系 生命周期 作用
super_block 一个已挂载的文件系统实例 挂载期间 管理整个文件系统、inode 分配
inode 磁盘上一个文件(唯一) 内核缓存期间 文件元数据 + 数据块定位
dentry 内存中一个路径分量 (如 home、alice) 内存,可被回收 把路径名和 inode 关联,组成 dcache
file 一个进程打开的文件实例 从 open 到 close 记录打开模式、当前读写偏移

关系要点:

  • 一个 inode 可以对应多个 dentry(硬链接、不同路径访问同一文件);
  • 一个 dentry 可以对应多个 file(多次 open、多进程打开同一文件);
  • file 里存的是 struct path(dentry + vfsmount),fd 最终指向 file。

三、文件名与目录的本质

3.1 文件名不属于文件,而属于目录

一个非常关键的认知:文件名并不存在 inode 里,而是存在它父目录的"目录项"中。

  • inode 只存文件的元数据(大小、权限、时间戳、数据块指针),不存名字;
  • 目录里的每条目录项记录「文件名 → inode 号」的映射;
  • 由此带来几个重要结论:
    • 重命名(rename)只改目录项,不移动数据,是原子的;
    • 硬链接 = 多个目录项指向同一个 inode;
    • 同一个文件可以有多个名字。

3.2 磁盘上没有"目录",只有"文件"

这是必须建立的第二层认知:

  • 磁盘上没有"目录"的概念,只有"文件属性 + 文件内容";
  • 目录的本质也是一个文件,它有自己的 inode 和数据块;
  • 区别只在于:普通文件的数据块存文件内容,目录的数据块存的是**"文件名 → inode 号"的映射关系****;**
  • 目录不能像普通文件那样直接 write,只能通过 mkdir/unlink/rename/link 等系统调用修改。

3.3 目录项的磁盘格式(以 ext 系列为例)

字段 长度 含义
inode 4B 该名字对应的 inode 号
rec_len 2B 本条目录项总长度(用于遍历)
name_len 1B 文件名长度
file_type 1B 文件类型(普通/目录/符号链接)
name 变长 文件名字节

ls 底层用 getdents64 读取这些目录项;

ls -l 还要对每个名字再查一次 inode,所以海量文件时 ls -l 明显变慢。

3.4 验证:用 opendir/readdir 读取目录

cpp 复制代码
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <dirent.h>
#include <sys/types.h>
#include <unistd.h>

int main(int argc, char *argv[]) {
    if (argc != 2) {
        fprintf(stderr, "Usage: %s <directory>\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    DIR *dir = opendir(argv[1]);
    if (!dir) {
        perror("opendir");
        exit(EXIT_FAILURE);
    }

    struct dirent *entry;
    while ((entry = readdir(dir)) != NULL) {
        if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
            continue;
        printf("Filename: %s, Inode: %lu\n", entry->d_name,
               (unsigned long)entry->d_ino);
    }

    closedir(dir);
    return 0;
}

运行 ./readdir / 就能看到根目录下每个文件名和它的 inode 号(如 mnt: 1048577、home: 786433),正好印证**"目录内容 = 文件名 + inode 号"****。**


四、dentry 结构与 dcache

4.1 磁盘目录项 vs 内存 dentry

用户态看到的目录项是磁盘上的持久记录,而内核在内存中用 struct dentry 表示每一个被访问过的路径分量。两者务必区分:

  • 磁盘目录项:持久、按文件系统格式存放;
  • dentry:内存对象、非持久、由 VFS 维护。

4.2 struct dentry 关键字段

字段 作用
d_inode 指向关联的 inode(负 dentry 时为 NULL)
d_parent 父目录 dentry
d_name 该分量的名字
d_hash 链入 dcache 哈希表
d_lru 链入 LRU 链表(可被回收)
d_child / d_subdirs 父目录的子链表 / 自己的子节点
d_count 引用计数
d_mounted 该路径是否是挂载点
d_op dentry 操作函数表

4.3 dcache 如何组织

  • 哈希表 dentry_hashtable:用「父 dentry + 名字」做哈希,实现 O(1) 查找;
  • LRU 链表:引用计数为 0 的 dentry 挂在 LRU 上,内存紧张时被回收;
  • 解析每个分量时先查哈希表,命中就无需读磁盘------这是路径解析最主要的加速手段;
  • 新解析出的 dentry 会被加入 dcache。

4.4 负 dentry(negative dentry)

  • 文件不存在时,内核也会缓存一个 d_inode = NULL 的 dentry;
  • 再次访问该名字时可以立刻返回 ENOENT,不必反复读磁盘;
  • 在该路径创建文件时,负 dentry 会被"转正"。

五、路径的表示与限制

5.1 绝对路径与相对路径

类型 形式 解析起点
绝对路径 以 / 开头,如 /home/alice/f.txt 根目录 /
相对路径 不以 / 开头,如 alice/f.txt、../f.txt 进程当前工作目录(cwd)

5.2 长度限制

常量 值 含义 超限错误
PATH_MAX 4096 整条路径长度(含 \0) ENAMETOOLONG
NAME_MAX 255 单个文件名长度 ENAMETOOLONG

5.3 特殊规则

  • 多个连续 / 等价于一个 /(/home//alice 同 /home/alice);
  • 文件名不能含 / 和空字符 \0;
  • 尾随 / 表示最后一个分量必须是目录,否则返回 ENOTDIR;
  • 空路径名("")是非法的;
  • Linux 文件名区分大小写,按字节比较(可含 UTF-8 编码)。

六、进程的 fs_struct:root 与 pwd

6.1 fs_struct 结构

每个进程的 task_struct 里有一个 fs 指针,指向 struct fs_struct,保存两个关键"位置":

  • root:进程的根目录(/ 对应什么);
  • pwd:进程当前工作目录(cwd)。

它们都是 struct path(= dentry + 挂载点 vfsmount)。

fork 时子进程复制 fs_struct,拥有独立的 cwd。

6.2 chdir / chroot / pivot_root

  • chdir(path):改变进程的 pwd;
  • getcwd():读取当前工作目录;
  • chroot(path):改变进程看到的根目录,但不是安全边界------若 cwd 还在 chroot 目录之外,或持有已打开的 fd,仍可能逃逸;
  • 容器真正使用的是 mount namespace + pivot_root;
  • /proc/self/cwd、/proc/self/root 是指向这两个位置的符号链接。

七、路径解析完整流程(核心)

7.1 什么是路径解析

路径解析(Path Resolution / Lookup)就是:

把一个路径字符串(如 /home/alice/f.txt)转换成内核中的 dentry / inode 对象。

它由内核 fs/namei.c 实现,核心结构是 nameidata(保存解析的中间状态)。

7.2 路径解析的"递归"推导

顺着"访问文件要打开父目录"往下追问,就会得到路径解析的本质:

  • 要访问 test.c,必须先打开它所在的目录 test;
  • 要打开 test,就得先打开它的父目录 code;
  • 如此类似"递归"一路向上,直到出口------根目录 /;
  • 根目录是递归的终点:根目录的文件名和 inode 号固定(ext 系列为 2),无需查找,开机即知。

7.3 内核函数调用链

cpp 复制代码
open()
 └─ do_sys_open()
     └─ do_filp_open()
         └─ path_openat()
             ├─ getname()              拷贝路径到内核
             ├─ path_init()            确定起点(root/pwd)
             ├─ link_path_walk()       逐分量(除最后一个)
             │   └─ walk_component()
             │       ├─ "." / ".."
             │       ├─ lookup_fast()      查 dcache(RCU)
             │       ├─ lookup_slow()      dcache miss → 读磁盘目录
             │       ├─ follow_mount()     穿越挂载点
             │       └─ may_lookup()       权限检查
             ├─ do_last() / lookup_open()  处理最后分量
             └─ complete_walk()

7.4 逐分量解析细节

  • .:忽略,留在当前目录;
  • ..:回到父目录(d_parent),注意 chroot 下不能穿越根;
  • 普通分量:
    • 先 lookup_fast(RCU 查 dcache);
    • 未命中走 lookup_slow:分配 dentry 并调用具体文件系统读目录;
  • 符号链接:follow_link 读取目标,递归解析;
  • 挂载点:follow_mount 切换到挂载文件系统的根。

7.5 符号链接处理

  • 符号链接内容是目标路径字符串,缓存在 page cache;
  • 解析时递归,受两个限制:嵌套深度(约 8)、总链接数(约 40),超限返回 ELOOP;
  • 权限检查针对目标文件,不检查符号链接本身;
  • O_NOFOLLOW 让 open 不跟随末尾符号链接。

7.6 最后一个分量的特殊处理

  • stat:必须存在;
  • open(O_CREAT):不存在则创建;
  • O_CREAT|O_EXCL:保证原子创建,已存在则失败(防竞态);
  • O_DIRECTORY:必须是目录;
  • O_NOFOLLOW:末尾符号链接不跟随。

7.7 路径由谁提供、最初路径从哪来

  • 命令/工具访问本质是进程访问,进程有 CWD,相对路径由进程提供;
  • 系统安装后有根目录和缺省目录,用户创建的文件天然处于某条路径中;
  • 是系统 + 用户共同构建了 Linux 的路径结构。

八、路径缓冲与三种遍历模式

8.1 为什么要先把路径拷进内核

系统调用传进来的路径在用户态内存里,内核在解析过程中(可能含磁盘 I/O),用户线程可能修改它,造成混乱甚至安全问题。

所以解析前先用 getname() 把路径完整拷到内核缓冲区,再基于这份固定副本解析。

8.2 路径缓冲实现

  • getname():优先从 per-cpu 名称缓存分配,否则 kmalloc;
  • 返回 struct filename(含 name、uptr、refcnt);
  • putname() 释放;
  • 这份内核副本就是路径解析的工作内存。

8.3 三种遍历模式

模式 特点 适用
RCU walk 无锁、最快,靠 RCU + seqlock 校验 dcache 全命中、无阻塞
ref walk 持有 dentry 引用计数和锁 需要读磁盘、符号链接
RCU → ref 回退 RCU 校验失败(ECHILD)自动降级 运行时动态切换

九、挂载分区与路径前缀

9.1 inode 不能跨分区

  • inode 号、块号都是以分区为单位划分的,不能跨分区;
  • 但 Linux 可以有多个分区/文件系统,那么路径解析时怎么知道自己在哪一个分区?

9.2 挂载:把文件系统和一个目录关联

分区写入文件系统后无法直接使用,必须挂载到某个目录(挂载点)才能访问。

实验流程:

cpp 复制代码
dd if=/dev/zero of=./disk.img bs=1M count=5   # 造一个 5MB 磁盘文件
mkfs.ext4 disk.img                            # 建立 ext4 文件系统
mkdir /mnt/mydisk                             # 建立挂载点
sudo mount -t ext4 ./disk.img /mnt/mydisk     # 挂载
df -h                                         # 看到 /dev/loop0 挂到 /mnt/mydisk
sudo umount /mnt/mydisk                       # 卸载

9.3 loop 回环设备

  • 挂载普通文件时,df -h 显示设备是 /dev/loop0;
  • loop(回环/loopback)设备是一种伪设备,让一个普通文件被当作块设备使用,从而把文件(如 ISO 镜像、磁盘镜像)挂载成文件系统。

9.4 关键结论:靠"路径前缀"判断分区

挂载后,每个文件系统都挂在目录树的某个目录下,因此:

可以根据访问目标文件的"路径前缀",准确判断它属于哪个分区/文件系统;

路径解析走到挂载点时,就切换到对应文件系统继续解析。


十、权限检查与 sticky bit

10.1 路径上的权限

路径上的每一级目录都必须有执行(搜索)权限 x,最后再检查目标文件的读/写权限:

权限 对目录的含义
r 能列出目录里的文件名(ls)
w 能在目录里增删改名目录项(创建/删除文件)
x 能进入/穿越目录,访问其中的文件
  • 只有 r 没有 x:能看到文件名,却无法访问文件;
  • 只有 x 没有 r:能访问知道名字的文件,但不能列出全部;
  • 删除文件需要父目录的 w 权限(配合 x),而不是文件本身的写权限------因为删除本质是修改父目录的目录项。

10.2 sticky bit

  • sticky bit(t):在 /tmp 这类公共可写目录中,只有文件属主、目录属主、root 能删除/改名别人的文件;
  • 防止公共目录里互相删除文件。

十一、软硬链接

11.1 硬链接:多个名字对应同一个 inode

cpp 复制代码
touch abc
ln abc def          # 建立硬链接 def,指向 abc 的 inode
ls -li abc def      # 两者 inode 相同,硬链接计数为 2
  • abc 和 def 地位完全相同,都是指向同一 inode 的目录项;
  • 内核在 inode 中记录硬链接计数,此时为 2。

11.2 软链接:通过名字引用,是独立文件

cpp 复制代码
ln -s abc abc.s     # 创建软链接 abc.s,指向 abc
ls -li
# abc       普通文件,inode 263563,链接数 2
# abc.s -> abc   软链接,独立 inode 261678,链接数 1
  • 软链接是独立文件,有自己的 inode,数据块里存的是目标文件的路径字符串;
  • 路径解析遇到软链接时,读出目标路径再递归解析。

11.3 软硬链接对比

对比项 硬链接 软链接
本质 文件名与 inode 的映射(目录项) 独立文件,存目标路径
inode 与目标相同 与目标不同
跨文件系统/分区 不能(inode 不跨分区) 能
链接目录 不能(普通用户) 能
目标删除后 仍可访问(链接数还在) 失效(悬空链接)
典型例子 .、.. 快捷方式

11.4 删除文件的两步操作

删除一个文件时内核做两件事:

  1. 在父目录中删除该目录项;
  2. 将 inode 的硬链接计数 −1;只有计数减到 0、且没有进程打开它时,才真正释放 inode 和数据块。

11.5 文件的三个时间

时间 含义
Access(atime) 文件内容最后被访问的时间
Modify(mtime) 文件内容最后被修改的时间
Change(ctime) 文件属性(inode)最后被改变的时间

十二、TOCTOU 与安全机制

12.1 TOCTOU 问题

经典的 TOCTOU(Time Of Check To Time Of Use):

先 access/stat 检查文件,再 open,两步之间文件可能被替换(符号链接攻击),造成越权访问。

12.2 解决方案

  • *at 系列(openat、fstatat、mkdirat、unlinkat、renameat):相对路径基于 dirfd,dirfd 可传 AT_FDCWD;
  • 打开后用 fstat 检查 fd,而不是再检查路径;
  • O_NOFOLLOW:不跟随末尾符号链接;
  • O_PATH:只获得一个路径级 fd(不真正打开),专门用作 *at 的 dirfd;
  • O_CREAT|O_EXCL:原子创建;
  • O_TMPFILE:创建无目录项的临时文件,从根本上避免路径竞态。

十三、getcwd 与 realpath

  • getcwd(buf, size):内核从 pwd 的 dentry 沿 d_parent 上溯,用 prepend_path 逐段前插,直到根,再反向拼出完整路径;
  • realpath(path, buf):库函数,解析路径中所有符号链接、.、..,返回规范化绝对路径;
  • canonicalize_file_name(path):等价于自动 malloc 结果的 realpath;
  • 底层可借助 readlink("/proc/self/fd/...")。

十四、常见错误码汇总

错误码 含义
ENOENT 路径某分量不存在
ENAMETOOLONG 路径超 4096 或文件名超 255
EACCES 某级目录无 x 权限或目标无 r/w 权限
ELOOP 符号链接嵌套过多
ENOTDIR 路径中间分量不是目录
EEXIST O_CREAT
ENOTEMPTY 删除非空目录

十五、面试题精讲(44 题)

概念与原理

1. 文件名存在哪里?为什么说文件名不属于文件?

存在父目录的目录项里,inode 不存名字。

文件名只是 "目录项 → inode 号" 的映射,所以重命名只改目录项、硬链接可以让一个文件有多个名字。


2. 磁盘上有"目录"这种东西吗?

没有,磁盘上只有"文件属性 + 文件内容"。

目录也是文件,只是它的数据块存的是"文件名 → inode 号"。


3. 目录是什么?里面存的是什么?

目录是特殊文件,有 inode 和数据块,数据块里存目录项(文件名 → inode),还含 . 和 ..。


4. VFS 四大对象及关系?

super_block(已挂载文件系统)、inode(文件)、dentry(路径分量)、file(打开实例)。

dentry 指向 inode,file 持有 path(dentry+mnt),多个 dentry/file 可对应同一 inode。


5. dentry 和磁盘目录项的区别?

dentry 是内存对象、非持久、组成 dcache,含父子关系和挂载点信息;

磁盘目录项是持久的、按文件系统格式存储。


6. 为什么需要 dcache?

路径解析每分量都要查目录,dcache 用哈希表缓存已解析的 dentry,命中即可不读磁盘,大幅加速路径解析。


7. 什么是负 dentry?有什么用?

d_inode 为 NULL 的 dentry,表示该名字查过但不存在。

缓存它可以快速返回 ENOENT,避免反复读磁盘;

创建文件时转正。


8. 一个 inode 可以对应几个 dentry?为什么?

多个。硬链接会产生多个目录项,再加上不同路径、挂载点访问,都可能产生多个 dentry 指向同一 inode。


9. 硬链接和符号链接在路径解析中的区别?

硬链接就是普通目录项,直接指向同一 inode,无额外解析;

符号链接的内容是目标路径,需要 follow_link 递归解析并可能返回 ELOOP。


10. PATH_MAX 和 NAME_MAX 是多少?

PATH_MAX 4096(整条路径),NAME_MAX 255(单文件名),

超限返回 ENAMETOOLONG。


11. 为什么单文件名限制是 255?

历史上 ext 文件系统目录项用 1 字节 name_len(最大 255),成为通用上限。


12. 目录项在磁盘上的格式?

inode(4B)、rec_len(2B)、name_len(1B)、file_type(1B)、name(变长),ls 底层用 getdents64 读取。


路径解析内核机制

13. 描述路径解析的完整流程。

getname 拷贝路径 → path_init 确定起点(root/pwd)→ link_path_walk 逐分量(查 dcache、miss 读磁盘、符号链接递归、穿越挂载点、每级查 x 权限)→ do_last 处理最后分量 → complete_walk 完成。


14. 为什么路径解析能一路递归到根?根目录为什么特殊?

打开任何文件都要先打开父目录,父目录又要打开它的父目录,递归到根目录为止;

根目录的文件名和 inode 号(ext 为 2)固定,开机即知,无需查找。


15. 为什么路径解析前要把路径拷进内核?

防止解析过程中用户态修改路径,保证一致性并避免安全漏洞(TOCTOU)。


16. RCU walk 是什么?为什么快?

在 RCU 保护下无锁遍历 dcache,配合 seqlock 检测 dentry 是否被修改,无需拿引用计数和锁,所以最快;

遇到阻塞或校验失败回退到 ref walk。


17. 路径解析中每一级需要什么权限?

路径上每一级目录都需要 x(搜索)权限,最后目标文件再检查 r/w 权限。


18. 为什么目录需要 x 权限?

x 表示可以"进入/穿越"目录、按名字访问其中的文件。

没有 x,即使有 r 也只能看到文件名而无法访问。


19. 目录 r、w、x 分别表示什么?

r 列出文件名;

w 增删改名目录项;

x 进入/穿越并访问文件。


20. 符号链接如何处理?权限检查谁?

读取目标路径递归解析,嵌套/总数超限返回 ELOOP;

权限检查针对目标文件,不检查符号链接本身。


21. ELOOP 在什么情况下出现?

符号链接自指、互相指或嵌套过深,超过内核限制(约 8 层/40 次)。


22. 路径解析遇到挂载点怎么处理?

follow_mount 检测 dentry 上的挂载,把当前 path 切换到挂载文件系统的根,再继续解析。


23. nameidata 是什么?

路径解析的状态结构,保存当前 path、起点 root、last_type、flags、链接计数(link_count/total_link_count)等。


24. dcache 如何组织与回收?dentry 引用计数有何作用?

哈希表(父 dentry+名字)用于查找,LRU 链表挂载引用计数为 0 的 dentry,内存紧张时回收;

count>0 不会被回收,降为 0 进入 LRU。

挂载与文件系统

25. inode 能跨分区吗?怎么确定在哪个分区?

不能,inode 以分区为单位编号;

文件系统挂载到目录树后,根据路径前缀判断分区,走到挂载点就切换文件系统。


26. 挂载的本质是什么?

分区写入文件系统后无法直接使用,必须挂载(mount)把文件系统和某个目录关联,之后通过该目录访问。


27. loop 设备是什么?

伪设备,让普通文件被当作块设备,从而把磁盘镜像/ISO 文件挂载成文件系统(如 /dev/loop0)。


28. 为什么硬链接不能跨文件系统、不能链接目录?

硬链接靠 inode 关联,而 inode 只在本文件系统内有效;

目录被限制硬链接是为了防止破坏目录树层级(./.. 一致性)。


29. 路径由谁提供?最初路径从哪来?

进程有 CWD,相对路径由进程提供;

系统提供根目录和缺省目录,用户新建文件天然处于路径中,由系统+用户共同构建。

系统调用与场景

30. getcwd 如何实现?

从 pwd dentry 沿 d_parent 上溯到根,逐段前插(prepend_path)拼出路径。


31. realpath 做了什么?

解析所有符号链接、.、..,返回规范化绝对路径。


32. openat 解决什么问题?

相对路径基于指定 dirfd 而非全局 cwd,避免多线程 cwd 竞态和 TOCTOU;

dirfd 为 AT_FDCWD 时等价于 open。


33. 什么是 TOCTOU?如何彻底解决?

检查与使用之间文件被替换。

用 *at 系列、打开后 fstat、O_NOFOLLOW、O_PATH、O_TMPFILE 等手段。


34. O_PATH 是什么?用途?

只获得路径级 fd,不真正打开文件,不能读写,但可作为 *at 的 dirfd 或配合 fstat/dup 使用,适合安全的路径操作。


35. O_NOFOLLOW 的作用?

让 open 不跟随末尾符号链接,若末尾是符号链接则失败,防止符号链接攻击。


36. chroot 为什么不是安全边界?

chroot 只改 root,若 cwd 仍在外部、持有已打开 fd 或进程有特权,仍可能逃逸;

容器应使用 mount namespace + pivot_root。


37. 删除一个正在被打开使用的文件会发生什么?

unlink 只删目录项并使 inode 链接数减 1;

只要有 file 打开,inode 和数据块不释放,fd 仍可读写;

全部关闭后才真正释放空间。

这也是日志文件被删后磁盘空间不立即释放的原因,可通过 /proc/pid/fd 恢复。


38. 文件的三个时间分别是什么?

Access(atime)最后访问、Modify(mtime)内容最后修改、Change(ctime)属性最后改变。

进阶场景

39. 为什么 open 之后,原路径文件被替换/删除不影响已打开的 fd?

d 直接绑定 file → inode,路径解析只在 open 时发生一次;

之后文件按 inode 访问,与路径无关。


40. 为什么 rename 是原子的?如何利用它做原子更新?

rename 只改目录项,由文件系统保证原子性和崩溃一致性**。**

写配置时先写临时文件再 rename 覆盖目标,读取方要么看到旧内容要么看到新内容,不会读到半截。


41. 为什么 rm -rf 大目录很慢?

每个文件都要单独 unlink、每个目录 rmdir,每次都要路径解析、改目录项、改位图、更新元数据,是大量元数据操作而非顺序 I/O。


42. 如何安全地创建临时文件?

用 mkstemp(内部 O_CREAT|O_EXCL 原子创建)或 O_TMPFILE(无目录项);

不要用 tmpnam/mktemp 等有竞态的函数。


43. 为什么删除文件需要父目录的写权限,而不是文件本身?

删除动作本质是修改父目录的目录项,所以需要父目录的 w 权限(配合 x);

文件本身的权限不决定能否被删除(sticky bit 另有限制)。


44. sticky bit 解决什么问题?

在 /tmp 等公共可写目录中,防止用户删除/改名别人的文件,只有文件属主、目录属主、root 可操作。


十六、总结

把整条链路串起来:

  • 路径字符串先经 getname 拷入路径缓冲;
  • 从 root(绝对)或 pwd(相对) 开始;
  • 逐分量在 dcache 中查找(命中走 RCU,未命中读磁盘),处理符号链接、挂载点和每级 x 权限;
  • 走到挂载点时,根据路径前缀切换到对应分区/文件系统;
  • 找到 dentry → inode,open 后由 fd → file 持有,此后不再依赖路径;
  • 软硬链接、删除计数、三个时间刻画了文件的"名字---inode"关系;
  • 安全问题(TOCTOU、临时文件、公共目录)由 *at、O_PATH、O_NOFOLLOW、O_TMPFILE、sticky bit 等机制解决。

核心一句话:

路径解析是"字符串 → dentry → inode"的查找过程,

而文件描述符是"解析完成后与路径无关的稳定句柄"。


相关推荐
见闻小天地1 小时前
新手入门:印刷包装设备的伺服和PLC运动控制方案各管什么?七步选型讲清楚
运维·网络·数据库·人工智能
摇滚侠1 小时前
《On Java 中文版 基础卷》阅读笔记 初始化和清理 08
java·开发语言·笔记
xiangyun612 小时前
C++ 手撕 shared_ptr:引用计数与控制块到底长什么样?
开发语言·c++·算法·面试·stl
father_12272 小时前
Greenplum 集群故障恢复与主备同步机制实战
linux·网络·greenplum
XiYang-DING2 小时前
P4009.和为K的子数组个数 & P5522.异或和为 k 的子数组个数
java·开发语言
csdn_aspnet2 小时前
Docker Prune:完整指南与上手示例
运维·docker·容器·prune
郭小李2 小时前
spring @PathVariable 注解用法
java·后端·spring
ynchyong2 小时前
linux中的 sed` 与 `seq`
linux·运维·sed·seq·文本工具
jaysee-sjc2 小时前
【苍穹外卖】Day12:工作台数据看板与 Excel 报表导出
java·开发语言·数据库·spring boot·spring·微信·excel