文件系统

Linux 文件系统详解

要讲文件系统主要有三个方面的内容需要了解:

  • 用户视角下的文件系统,即一个树状的目录结构,包括各个不同磁盘的mount关系,各个文件的属性和权限以及文件本身(一个数组形状的字节合集)。
  • Linux系统下的文件系统,即内核中文件相关的数据结构,包括super block, inode, dentry等,还有磁盘上文件的页缓存。
  • 磁盘上物理组织文件的视角,磁盘上的各个块之间的关系以及各个inode等。

用户视角下的文件系统

用户视角下文件系统其实就是一个树状的目录,如图所示。其中主磁盘即rootfs,在系统启动阶段被装载在根目录,而其他文件系统例如U盘或者移动硬盘固态等,就会装载在一个特定的节点上,例如图中home和usb1就是两个挂载点,挂在了两个文件系统。整体形成一棵目录树。

image-20251002220629047

进程视角下的文件系统

task_struct中和文件相关内容

进程在Linux kernel中主要是用task_struct这个结构来表示,一个进程有一个task_struct,即操作系统中所谓的TCB。在task_struct中有很多字段和文件相关,这里只列出几个关键的:

1
2
3
4
5
6
7
8
9
10
11
12
13
// include/linux/sched.h
struct task_struct {

/* Filesystem information: */
struct fs_struct *fs; //存放进程的文件系统上下文:当前目录 (cwd)、根目录 (root)、umask、chdir 等信息。

/* Open file information: */
struct files_struct *files; //指向进程的打开文件表(fd table)。包含 fd 数组、close_on_exec 位图、引用计数等(在 fdtable.h 定义)

struct nameidata *nameidata; //与路径查找(lookup)相关的临时结构(lookup 操作时使用)。

......
}

首先每个进程会有自己的进程上下文,即当前的工作目录和根目录,记录在fs_struct结构体中。同时会打开多个文件,记录在files_struct结构体中。此外还有一个nameidata字段,struct nameidata 是 Linux 内核中用于路径查找的辅助结构体。它在文件系统中起着关键作用,帮助根据路径名找到目标节点的 dentry 和 inode。

fs_struct结构体

前面说到了一个进程内有一个fs_struct结构体,我们来看看它包含什么

1
2
3
4
5
6
7
8
9
10
// include/linux/fs_struct.h

struct fs_struct {
int users; //被共享(引用)的次数,即共享该fs_struct的进程数目,当该计数归0,该结构体会被释放
spinlock_t lock; //保护该结构的自旋锁
seqcount_spinlock_t seq; //读写相关的序列锁,用于对 root、pwd 的并发访问提供“乐观读取”机制。读者不加锁,只是读取并检查序号是否一致;写者需要持有 lock 并更新 seq。这样减少了对频繁读操作的锁竞争。
int umask; //进程创建文件时默认的访问权限
int in_exec;//当进程调用 execve() 执行新程序时设置,用来防止并发修改 fs_struct(比如别的线程在 exec 时改工作目录)。
struct path root, pwd; //根目录和当前目录
} __randomize_layout; //随机化成员,在编译阶段随机化部分成员的顺序

files_struct结构体

内核中还有一个files_struct结构体,同样看看里面包含了哪些内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// include/linux/fdtable.h
/*
* Open file table structure
*/
struct files_struct {
/*
* read mostly part
*/
atomic_t count; //被共享引用的次数,多个进程(例如通过 clone(CLONE_FILES))可以共享同一个文件描述符表,count 表示有多少进程在共享。为 0 时释放。
bool resize_in_progress; //文件描述符表是否正在扩容。当打开的文件数超过当前表大小时,需要扩展 fdtable。这个标志用来防止并发扩展(resize race)。
wait_queue_head_t resize_wait; //等待队列,如果有多个线程同时触发扩容,只有一个执行 resize,其余在此队列等待,避免重复扩展。

struct fdtable __rcu *fdt; //当前正在使用的文件描述符表,指针带有 RCU 保护。存放文件描述符(fd)与文件对象的映射表,指针可能在扩展时切换到新的 fdtable。
struct fdtable fdtab; //内嵌的一个小 fdtable。初始时进程直接使用这个内嵌表,避免频繁 kmalloc。通常能容纳少量 fd(比如 NR_OPEN_DEFAULT 个)。只有打开的文件数超过默认大小才会分配新的大表。
/*
* written part on a separate cache line in SMP
*/
spinlock_t file_lock ____cacheline_aligned_in_smp; //保护文件描述符表的自旋锁。控制对文件描述符(打开、关闭、dup 等)的并发修改。
unsigned int next_fd; //下一个可用的文件描述符号。提高分配 fd 的效率,每次分配时从 next_fd 开始搜索。
unsigned long close_on_exec_init[1]; //位图(初始内嵌部分),对应 FD_CLOEXEC 标志。记录哪些 fd 在 execve() 时需要自动关闭。超出范围时在 fdtable 的动态部分里扩展。
unsigned long open_fds_init[1]; //位图(初始内嵌部分),记录哪些 fd 当前是打开状态。
unsigned long full_fds_bits_init[1]; //位图(初始内嵌部分),辅助位图,用于加速 fd 分配(快速判断哪些 fd 已用/空闲)。
struct file __rcu * fd_array[NR_OPEN_DEFAULT]; //文件指针数组(初始内嵌部分)。存放默认数量的 struct file * 指针(例如前 32 个 fd)。超过时会切换到动态分配的 fdtable。
};

具体如图所示

image-20251003130430704

struct fdtable结构体

上面files_struct结构体中有一个关键数据结构fdtable,即文件描述符表,其在结构体内部有一个内嵌的fdtable结构体fdtab,同时还有一个指针fdt指向一个fdtable结构体。初始化时候fdt指针是指向内嵌在结构体files_struct中的fdtab成员的,并且该结构用了RCU同步保护机制。具体结构定义如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// include/linux/fdtable.h
struct fdtable {
unsigned int max_fds;
struct file __rcu **fd; /* current fd array */
unsigned long *close_on_exec;
unsigned long *open_fds;
unsigned long *full_fds_bits;
struct rcu_head rcu;
};

//ps 这里介绍一下RCU同步机制,因为有些指针使用了__rcu标志来标识,实际上该标志告诉了静态分析工具和编译器,这个指针读得多,写得少,且读的时候不需要加锁。__rcu 是一个 sparse 注解宏(静态分析工具用的标记,不影响编译出的代码),它告诉内核开发者和静态分析器:1. 这个指针只能通过 RCU 安全地读/写。2. 读它的时候要用 rcu_dereference(),而不是直接解引用。3. 改它的时候要用 rcu_assign_pointer(),而不是直接赋值。
/*
读取该指针:
rcu_read_lock();
struct fdtable *fdt = rcu_dereference(files->fdt);
struct file *filp = rcu_dereference(fdt->fd[fd]);
if (filp)
get_file(filp); // 增加引用计数,保证不会被释放
rcu_read_unlock();

写入该指针:
spin_lock(&files->file_lock);
rcu_assign_pointer(fdt->fd[fd], new_filp);
spin_unlock(&files->file_lock);
*/

同时类似,初始时内嵌成员fdtab中struct file __rcu **fd就指向files_struct中的fd_array成员等,具体看图,这样做的好处是不用kmalloc这样耗时的操作,先预先分配NR_OPEN_DEFAULT个。

struct file 结构体

最后就是内核视角的文件结构,即file结构体,在用户视角下一个文件其实就是一个int值,也叫文件描述符fd,但是对应内核数据结构其实就是file结构体数组的一个下表,真实的file结构体如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/*
* f_{lock,count,pos_lock} members can be highly contended and share
* the same cacheline. f_{lock,mode} are very frequently used together
* and so share the same cacheline as well. The read-mostly
* f_{path,inode,op} are kept on a separate cacheline.
*/
struct file {
union {
/* fput() uses task work when closing and freeing file (default). */
struct callback_head f_task_work;
/* fput() must use workqueue (most kernel threads).在内核线程中,不能使用 task work,所以改用 workqueue + lockless list。 */
struct llist_node f_llist;
unsigned int f_iocb_flags; //有时此字段存储异步 IO 控制块 (iocb) 的标志。
}; //这块表示关闭和回收文件时候使用的一个union。

/*
* Protects f_ep, f_flags.
* Must not be taken from IRQ context.
*/
spinlock_t f_lock //保护 f_ep 和 f_flags,避免并发修改。
fmode_t f_mode; //文件打开模式(读/写/追加等),对应用户空间的 O_RDONLY/O_WRONLY/O_RDWR。
atomic_long_t f_count; //文件对象的引用计数,每个 dup()、fork(CLONE_FILES) 都会增加计数,close() 时减少。为 0 时释放。
struct mutex f_pos_lock; //保护f_pos的锁
loff_t f_pos; //文件的当前偏移量(相当于用户空间的 file pointer)。不同进程如果独立 open 同一个 inode,会有不同的 f_pos。
unsigned int f_flags; //文件打开时候的flag
struct fown_struct f_owner; //记录信号所有者(F_SETOWN/F_SETFL 用于异步 IO/信号)。
const struct cred *f_cred; //打开文件时的凭据(UID/GID/capabilities)。即使进程之后提权/降权,这里仍保持当时的 cred。
struct file_ra_state f_ra; //readahead 状态(预读优化),内核读文件时可能会提前加载后续页。
struct path f_path; //记录该文件所在的 vfsmount + dentry(即路径位置)。
struct inode *f_inode; /* cached value, 缓存的 inode 指针,加快访问速度。 */
const struct file_operations *f_op; //文件操作函数表(open/read/write/mmap/ioctl 等)。

u64 f_version; //文件版本号,用于支持 NFS 等网络文件系统的 cache consistency。
#ifdef CONFIG_SECURITY
void *f_security;
#endif
/* needed for tty driver, and maybe others */
void *private_data;

#ifdef CONFIG_EPOLL
/* Used by fs/eventpoll.c to link all the hooks to this file, epoll 用来跟踪哪些 epoll_fd 正在监听此文件。 */
struct hlist_head *f_ep;
#endif /* #ifdef CONFIG_EPOLL */
struct address_space *f_mapping; //文件的页缓存映射,用于读写页缓存、mmap 等。通常指向 inode->i_mapping,但特殊文件(如 pipes、sockets)可能不同。
errseq_t f_wb_err;
errseq_t f_sb_err; /* for syncfs */
} __randomize_layout
__attribute__((aligned(4))); /* lest something weird decides that 2 is OK */

OK了,我们现在大概知道一个文件在用户视角和在内核视角的区别与联系了,接下来我们就可以看VFS的实现了。首先用户进程要操作文件,首先需要使用open函数对其打开,然后通过read和write对其进行处理。(这里的read, write都是库函数,也可以直接调用系统调用进行处理)

VFS虚拟文件系统

VFS其实作用其实就是隐藏了底层不同文件系统的差异,向上提供统一的数据结构和接口。就比如用户执行cp命令,把U盘的文件拷贝到本地,其无需考虑U盘的文件系统格式和操作函数,也无需考虑本地根目录或者安装的硬盘的文件系统格式和操作函数,而是统一地用write,read这样的操作来实现,极大地方便了用户。

VFS实现主要抽象出了四大组件对象,包括文件(file),目录(dentry),索引结点(inode),超级块(super block)。

  • 文件:struct file,存放进程已经打开的文件信息和对其进行操作的函数,类似户口,并不能直接看到文件数据。
    • 表示一个打开的文件实例,包含偏移量 (f_pos)、打开标志 (f_flags)、所属进程的 cred 等。
    • 每次 open()(或 dup())都会产生/引用一个新的 struct file
    • 即使两个 file 指针指向同一个 dentry,它们的偏移量、flags 可以不同。
  • 目录项:存在文件的特定识别名和关联信息。用来记录文件的名字、索引节点指针以及与其他目录项的层级关联关系。 多个目录项关联起来,就会形成目录结构,但它与索引节点不同的是,目录项是由内核维护的一个数据结构,不存放于磁盘,而是缓存在内存。简单来说说目录项纪录了已访问过的文件和目录的内存影像,是整个文件系统树结构的一个子集
    • 表示一个目录项(名字和 inode 的映射关系)。
    • 同一个文件路径对应一个唯一的 dentry(dcache 缓存)。
    • 多个 dentry 可以指向同一个 inode(硬链接)。
  • 索引结点:每个索引结点的编号可以唯一地标识本文件系统中的文件,不同文件系统上可以有相同的索引号。它纪录了文件的名字、索引节点指针以及与其他目录项的层级关联关系。 多个目录项关联起来,就会形成目录结构,因为目录也是一个特殊的文件。且索引节点其是磁盘上索引节点的内存影像和拓展。
    • 表示一个文件系统对象(文件/目录/设备节点)的元数据(权限、大小、时间戳等)。
    • 在内存中是唯一的:一个 inode number 在一个超级块里只有一个 struct inode
  • 超级块:存放安装和挂载的文件系统基本信息。超级块是磁盘上超级块的内存影像和拓展。

具体关系如图所示,并且都是多对一的关系,即多个文件描述符可以指向相同的文件对象(不同进程打开相同的文件),多个文件对象可以指向相同的目录项(比如同一个文件被打开多次,会产生多个struct file(fd), 但是dentry相同)和inode节点,多个目录项可以指向相同的索引节点(硬链接):

PS: 这里顺便说一下软链接,软链接是一个单独的文件,里面存放路径字符串。软链接文件有自己的 inode(类型 S_IFLNK),存放目标路径。软链接有独立的 dentry,dentry->d_inode 指向软链接自己的 inode。open("symlink") 通常触发路径解析,VFS 调用 ->follow_link(旧 API,内核新版本改为 ->get_link),解析目标路径,再返回目标文件的 dentry/inode。

image-20251003135608114

对于查找一个文件,我们以下图作为例子,假设需要找/home/lqm/file1,先在磁盘上找到/根目录对应的索引节点和内容,取到内存构造dentry,和inode的内存映像,然后找到下一级目录l2的索引节点,这样依次类推。

  • 起点:内核通过进程的 fs_struct 知道当前根目录(可能是 /,也可能是 chroot/jail 的某个子目录)和当前工作目录 pwd
    对于绝对路径 /home/lqm/file1,查找从根目录 inode 开始。
  • 第一级 /
    • 找到根目录 inode(假设编号 01),把它读入内存形成 struct inode
    • 内核为 / 目录项建立一个 struct dentry,并与 inode 关联。
    • 读出root的目录页,到page_cache, 然后用look_up查看对应下一级是否存在
  • 第二级 home
    • 在根目录数据块中找到 home 这个目录项(dentry 名字 -> inode 号)。
    • 读出 home 的 inode,建立 dentry("home"),挂到 / 的 dentry 树下。
  • 第三级 lqm:同样,找到 inode X3,建立 dentry("lqm")
  • 第四级 file1
    • lqm 目录数据里找到 file1 → inode Y4
    • 读出 Y4 的 inode,建立 dentry("file1")

此时路径解析完成,dentry("file1") + inode(Y4) 已经在内核内存里。

image-20251003141119081

打开文件的源码解析

看了那么多理论,show me the code,我们就看看进程调用open之后,创建file_struct等过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
//include/linux/syscalls.h
//首先syscall表处理函数跳转到sys_open,具体看文件arch/x86/entry/syscalls/syscall_64.tbl中
//2 common open sys_open

// 然后sys_open 调用一个宏嵌套

SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode)
{
if (force_o_largefile())
flags |= O_LARGEFILE;
return do_sys_open(AT_FDCWD, filename, flags, mode);
}
//SYSCALL_DEFINEx(3, open, const char __user *, filename, int, flags, umode_t, mode)
#define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__)
//__SYSCALL_DEFINEx(3, _open, const char __user *, filename, int, flags, umode_t, mode)
#define SYSCALL_DEFINEx(x, sname, ...) \
SYSCALL_METADATA(sname, x, __VA_ARGS__) \ //这个宏定义是空,直接看下面这一行
__SYSCALL_DEFINEx(x, sname, __VA_ARGS__)


/*生成一个别名函数asmlinkage long sys_open(const char __user * filename, int flags, umode_t mode)
__attribute__((alias("__se_sys_open")));
声明一个函数static inline long __do_sys_open(const char __user * filename, int flags, umode_t mode);
声明一个函数并实现
asmlinkage long __se_sys_open(const char __user * filename, int flags, umode_t mode); \
asmlinkage long __se_sys_open(const char __user * filename, int flags, umode_t mode) \
{ \
long ret = __do_sys_open(const char __user * filename, int flags, umode_t mode);\
做一些参数检查
return ret; \
}
*/
#define __SYSCALL_DEFINEx(x, name, ...) \
__diag_push(); \
__diag_ignore(GCC, 8, "-Wattribute-alias", \
"Type aliasing is used to sanitize syscall arguments");\
asmlinkage long sys##name(__MAP(x,__SC_DECL,__VA_ARGS__)) \
__attribute__((alias(__stringify(__se_sys##name)))); \
ALLOW_ERROR_INJECTION(sys##name, ERRNO); \
static inline long __do_sys##name(__MAP(x,__SC_DECL,__VA_ARGS__));\
asmlinkage long __se_sys##name(__MAP(x,__SC_LONG,__VA_ARGS__)); \
asmlinkage long __se_sys##name(__MAP(x,__SC_LONG,__VA_ARGS__)) \
{ \
long ret = __do_sys##name(__MAP(x,__SC_CAST,__VA_ARGS__));\
__MAP(x,__SC_TEST,__VA_ARGS__); \
__PROTECT(x, ret,__MAP(x,__SC_ARGS,__VA_ARGS__)); \
return ret; \
} \
__diag_pop(); \
static inline long __do_sys##name(__MAP(x,__SC_DECL,__VA_ARGS__))
#endif /* __SYSCALL_DEFINEx */

/*
最后跑到了static inline long __do_sys##name(__MAP(x,__SC_DECL,__VA_ARGS__)),再加上最开始的声明部分,实际__do_sys_open函数内容就是如下:
*/
static inline long __do_sys_open(int dfd, const char __user *filename,
int flags, umode_t mode)
{
if (force_o_largefile())
flags |= O_LARGEFILE;
return do_sys_open(AT_FDCWD, filename, flags, mode);
}

经过上面分析这些宏,其实最终是到do_sys_open函数,我们来看看这个函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// fs/open.c
//dfd:目录文件描述符(通常是 AT_FDCWD 表示当前目录)。
long do_sys_open(int dfd, const char __user *filename, int flags, umode_t mode)
{
struct open_how how = build_open_how(flags, mode); //把老式 flags + mode 参数转换成统一的 struct open_how,为 openat2 接口服务。
return do_sys_openat2(dfd, filename, &how);
}

static long do_sys_openat2(int dfd, const char __user *filename,
struct open_how *how)
{
struct open_flags op;
int fd = build_open_flags(how, &op); //解析用户传进来的 flags(如 O_CREAT, O_TRUNC, O_APPEND),填充成 open_flags 结构。成功会return 0. 不然返回-EINVAL; 这个fd命名也忒。。。
struct filename *tmp;

if (fd)
return fd;

tmp = getname(filename); //从用户空间拷贝文件名到内核空间,得到一个 struct filename 对象。用户态指针 const char __user *filename → 内核安全的 filename。
if (IS_ERR(tmp))
return PTR_ERR(tmp);

fd = get_unused_fd_flags(how->flags); //从当前进程的 fd 表里找一个空闲的 fd slot。
if (fd >= 0) {
struct file *f = do_filp_open(dfd, tmp, &op);
if (IS_ERR(f)) {
put_unused_fd(fd);
fd = PTR_ERR(f);
} else {
fd_install(fd, f);
}
}
putname(tmp);
return fd;
}

先关注如何获取空闲fd的,即get_unused_fd_flags(how->flags),就是前面说的利用file_struct结构体中的字段来获取到一个空闲的fd的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
int get_unused_fd_flags(unsigned flags)
{
return __get_unused_fd_flags(flags, rlimit(RLIMIT_NOFILE));
}
EXPORT_SYMBOL(get_unused_fd_flags);

int __get_unused_fd_flags(unsigned flags, unsigned long nofile)
{
return alloc_fd(0, nofile, flags);
}

static int alloc_fd(unsigned start, unsigned end, unsigned flags)
{
struct files_struct *files = current->files; //获取到当前task_struct中的files_struct,即所有打开的files相关内容,里面有fdtable表。
unsigned int fd;
int error;
struct fdtable *fdt;

spin_lock(&files->file_lock); //加锁
repeat:
/*
#define files_fdtable(files) \
rcu_dereference_check_fdtable((files), (files)->fdt)
*/
fdt = files_fdtable(files); //RCU读锁,读fdt指针
fd = start; //fd从0开始
if (fd < files->next_fd) //从next_fd开始搜索
fd = files->next_fd;

if (fd < fdt->max_fds) //如果找到就找到了,不然需要扩展fdt。
fd = find_next_fd(fdt, fd); //根据位图寻找

/*
* N.B. For clone tasks sharing a files structure, this test
* will limit the total number of files that can be opened.
*/
error = -EMFILE;
if (fd >= end)
goto out;

error = expand_files(files, fd);
if (error < 0)
goto out;

/*
* If we needed to expand the fs array we
* might have blocked - try again.
*/
if (error)
goto repeat;

if (start <= files->next_fd)
files->next_fd = fd + 1;

__set_open_fd(fd, fdt);
if (flags & O_CLOEXEC)
__set_close_on_exec(fd, fdt);
else
__clear_close_on_exec(fd, fdt);
error = fd;
#if 1
/* Sanity check */
if (rcu_access_pointer(fdt->fd[fd]) != NULL) {
printk(KERN_WARNING "alloc_fd: slot %d not NULL!\n", fd);
rcu_assign_pointer(fdt->fd[fd], NULL);
}
#endif

out:
spin_unlock(&files->file_lock);
return error;
}

得到空闲fd号之后,构建对应的struct file结构体

1
2
3
4
5
6
7
struct file *f = do_filp_open(dfd, tmp, &op);
if (IS_ERR(f)) {
put_unused_fd(fd); //失败:释放刚刚预留的 fd slot。
fd = PTR_ERR(f);
} else {
fd_install(fd, f); //成功则调用 fd_install(fd, f),把 struct file * 安装到当前进程的文件描述符表里。
}

那么关键就是构建file struct这个结构体的函数do_filp_open, 然后核心就是使用kmem_cache_zalloc去分配一个结构体,这个是一个slab cache,再初始化相应字段,最后就返回了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
struct file *do_filp_open(int dfd, struct filename *pathname,
const struct open_flags *op)
{
struct nameidata nd;
int flags = op->lookup_flags;
struct file *filp;

set_nameidata(&nd, dfd, pathname, NULL); //填充nameidata数据结构
filp = path_openat(&nd, op, flags | LOOKUP_RCU);
if (unlikely(filp == ERR_PTR(-ECHILD)))
filp = path_openat(&nd, op, flags);
if (unlikely(filp == ERR_PTR(-ESTALE)))
filp = path_openat(&nd, op, flags | LOOKUP_REVAL);
restore_nameidata();
return filp;
}

static void __set_nameidata(struct nameidata *p, int dfd, struct filename *name)
{
struct nameidata *old = current->nameidata;
p->stack = p->internal;
p->depth = 0;
p->dfd = dfd;
p->name = name;
p->path.mnt = NULL;
p->path.dentry = NULL;
p->total_link_count = old ? old->total_link_count : 0;
p->saved = old;
current->nameidata = p;
}

static struct file *path_openat(struct nameidata *nd,
const struct open_flags *op, unsigned flags)
{
struct file *file;
int error;

file = alloc_empty_file(op->open_flag, current_cred());
if (IS_ERR(file))
return file;

if (unlikely(file->f_flags & __O_TMPFILE)) {
error = do_tmpfile(nd, flags, op, file); //open(..., O_TMPFILE, ...),表示创建匿名临时文件(不会出现在目录树中)。交给 do_tmpfile 处理。
} else if (unlikely(file->f_flags & O_PATH)) {
error = do_o_path(nd, flags, file); //open(..., O_PATH),表示只获取一个路径句柄,不真正打开文件(不能读写)。交给 do_o_path 处理。
} else {
const char *s = path_init(nd, flags); //初始化路径起点是root还是pwd,返回下一个需要解析的路径字符串
while (!(error = link_path_walk(s, nd)) && //逐级解析路径分量,例如 /home/lqm/file1 就会依次解析 home → lqm → file1,在内存中找到相应的 dentry 和 inode。
(s = open_last_lookups(nd, file, op)) != NULL) //处理路径的最后一部分(可能是文件本身,或者符号链接)。
;
if (!error)
error = do_open(nd, file, op); //检查文件是否存在,不存在就创建。检查权限,最后调用文件系统的open函数
terminate_walk(nd); //结束路径遍历,释放临时资源(比如 nameidata 里保存的路径栈)。
}
...
}
struct file *alloc_empty_file(int flags, const struct cred *cred)
{
struct file *f;

if (get_nr_files() >= files_stat.max_files && !capable(CAP_SYS_ADMIN)) {
if (percpu_counter_sum_positive(&nr_files) >= files_stat.max_files)
goto over;
}

f = kmem_cache_zalloc(filp_cachep, GFP_KERNEL);
...
return f;
}