内核OverlayFS—文件读写

内核版本4.4.68

文件(一般文件或目录)的读写和文件的创建删除不同,文件的创建删除作用于inode结构,需要用到inode操作。而文件的读写则是作用于file结构,因此需要用到file操作

通常,读写文件之前需要先打开这个文件,通过open系统调用,传入表示文件的路径字符串,随后内核会根据路径字符串进行路径查找,最终得到一个表示文件的file结构,并使之与当前进程相关联,之后的读写便会根据这个file结构来进行,这个结构的f_op字段是一个file操作的指针,file操作包含了文件读写函数的指针。OverlayFS中文件的读写也不例外,但是在OverlayFS中,一般文件的读写和目录的读写又有区别:

  • 一般文件的读写:在OverlayFS中,文件来源于upper层或lower层。在打开文件时,OverlayFS实际上打开的是upper层或者lower层的文件,因此随后发生的读写也就是对upper层或lower层的文件。
  • 目录的读写:与一般文件不同的是,目录的读写相对复杂。因为对于一个同时存在upper层和lower层的目录,在对该目录进行搜索时,OverlayFS会对upper层和lower层的目录分别进行搜索,然后做一些合并处理。但是对于同时存在于upper层或lower层的文件来说,由于”上层覆盖下层“,因此只需要读取upper层中文件就行了

正因如此,对于目录的读取,OverlayFS定义了自己的file操作,而对于一般文件的读写,OverlayFS不需要定义自己的操作

这篇文章重点在分析文件读写如何获得自己的读写操作,而不会深入的某个具体的读写函数。比如目录搜索是一个目录读写的例子,这部分另一篇博文有介绍,这里主要是分析OverlayFS中文件读写的本质


获取file操作

文件的读写函数由file操作中的函数指针来索引,因此要了解文件读写到底调用了什么函数就需要弄清楚文件何时、如何获得自己的file操作。而文件的file操作又由file结构的f_op字段指向,所以可以看看在路径查找过程中,获取表示文件的file结构时,如何为f_op字段赋值

在文件打开过程中,解析完路径字符串时,会调用到vfs_open()函数:

1
2
3
4
5
6
7
8
9
10
11
int vfs_open(const struct path *path, struct file *file,
const struct cred *cred)
{
struct inode *inode = vfs_select_inode(path->dentry, file->f_flags);

if (IS_ERR(inode))
return PTR_ERR(inode);

file->f_path = *path;
return do_dentry_open(file, inode, NULL, cred);
}

这个函数会选取一个inode,设置file的f_path字段,最后调用函数do_dentry_open(),这个函数会进一步设置file的其它字段,包括f_op,最后执行文件打开操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
static int do_dentry_open(struct file *f,
struct inode *inode,
int (*open)(struct inode *, struct file *),
const struct cred *cred)
{
...
f->f_inode = inode;
f->f_mapping = inode->i_mapping;

...
f->f_op = fops_get(inode->i_fop);

...
if (!open)
open = f->f_op->open;
if (open) {
error = open(inode, f);
if (error)
goto cleanup_all;
}
...
}

从代码中可以看出,file结构的f_op字段使用inode的i_fop字段进行赋值。因此文件的inode决定了file操作。通过函数vfs_open()发现,传入的inode由vfs_select_inode()决定:

1
2
3
4
5
6
7
8
9
10
static inline struct inode *vfs_select_inode(struct dentry *dentry,
unsigned open_flags)
{
struct inode *inode = d_inode(dentry);//dentry->d_inode

if (inode && unlikely(dentry->d_flags & DCACHE_OP_SELECT_INODE))
inode = dentry->d_op->d_select_inode(dentry, open_flags);

return inode;
}

dentry表示路径字符串最后一个分量的目录项,也就是OverlayFS中,所需打开的文件的目录项。对于OverlayFS中的目录项,函数的if条件成立,因此会进一步调用dentry操作的d_select_inode()函数,OverlayFS实现了对应的函数:

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
struct inode *ovl_d_select_inode(struct dentry *dentry, unsigned file_flags)
{
int err;
struct path realpath;
enum ovl_path_type type;

if (d_is_dir(dentry))
return d_backing_inode(dentry);

//如果不是一个dir,则会执行后面的代码

type = ovl_path_real(dentry, &realpath);//获取真实路径
if (ovl_open_need_copy_up(file_flags, type, realpath.dentry)) {
err = ovl_want_write(dentry);
if (err)
return ERR_PTR(err);

if (file_flags & O_TRUNC)
err = ovl_copy_up_truncate(dentry);
else
err = ovl_copy_up(dentry);
ovl_drop_write(dentry);
if (err)
return ERR_PTR(err);

ovl_path_upper(dentry, &realpath);
}

if (realpath.dentry->d_flags & DCACHE_OP_SELECT_INODE)
return realpath.dentry->d_op->d_select_inode(realpath.dentry, file_flags);

return d_backing_inode(realpath.dentry);
}

通过这个函数,我们可以发现,对于目录和一般文件,返回的inode不同:

  • 目录:如果是一个目录,则直接返回dentry->d_inode。因为dentry是OverlayFS中所要打开文件的目录项,所以其指向的inode为OverlayFS下,所要打开文件对应的inode
  • 一般文件:如果是一个一般文件,则会根据打开标志以及对应upper或lower层文件的目录项决定是否要执行copy-up操作。比方说,如果是一个lower层的一般文件,以读写方式打开,则会将这个文件拷贝至upper层。如果是以只读方式打开,则不需要。然后根据最终upper层或lower层相应文件的目录项的d_flags字段决定是否需要进一步调用d_select_inode()函数。如果不需要则返回upper层或lower层相应文件的inode

分析到这已经可以得出结论了,对于一个目录,函数返回OverlayFS中该目录的inode,然后根据inode的i_fop字段来决定文件的读写操作。对于一个一般文件,返回的并不是OverlayFS中文件的inode,通常情况下,返回的是upper或lower层对应文件的inode


一般文件读写

假设要对OverlayFS下的file1进行读写,在文件打开过程中,会得到一个表示file1的file结构,函数vfs_select_inode()会返回upper目录下file1的inode,然后根据这个inode设置file结构的f_inode字段,根据inode的i_fop字段来定义file操作,因此,实际上打开的是upper目录下的file1文件。由于upper目录下的file1是ext4下的一个文件,因此file操作由ext4实现,今后读写OverlayFS中的file1,实际上是对upper目录中的file1进行读写


目录读写

假设要对OverlayFS下的目录d进行读写,在文件打开过程中,会得到一个表示d的file结构。和一般文件不同,函数vfs_select_inode()返回的inode就是OverlayFS下目录d对应的inode。接着会根据这个inode设置file结构的f_inode字段,根据inode的i_fop字段来定义file操作,因此,实际上打开的文件就是OverlayFS下的目录d,而不是upper目录下的目录d。今后读写OverlayFS中的目录d并不会转化为读写upper下目录d,相应的file操作由OverlayFS实现

通过OverlayFS的inode分配函数也可以看出,如果不是为目录分配inode,则不会设置inode的i_fop字段。因为实际的读写操作作用于upper层或lower层的相应文件,因此不需要OverlayFS中文件的inode。但是为目录分配inode时,会设置i_fop字段,在目录读写时,为file结构的f_op字段赋值:

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
struct inode *ovl_new_inode(struct super_block *sb, umode_t mode,
struct ovl_entry *oe)
{
struct inode *inode;

inode = new_inode(sb);//在sb下分配一个inode
if (!inode)
return NULL;

inode->i_ino = get_next_ino();//设置inode号
inode->i_mode = mode;//设置mode
inode->i_flags |= S_NOATIME | S_NOCMTIME;//设置flag

mode &= S_IFMT;
//根据inode类型设置inode操作
switch (mode) {
case S_IFDIR:
inode->i_private = oe;
inode->i_op = &ovl_dir_inode_operations;
inode->i_fop = &ovl_dir_operations;
break;

case S_IFLNK:
inode->i_op = &ovl_symlink_inode_operations;
break;

case S_IFREG:
case S_IFSOCK:
case S_IFBLK:
case S_IFCHR:
case S_IFIFO:
inode->i_op = &ovl_file_inode_operations;
break;

default:
WARN(1, "illegal file type: %i\n", mode);
iput(inode);
inode = NULL;
}

return inode;
}
文章目录
  1. 1. 获取file操作
  2. 2. 一般文件读写
  3. 3. 目录读写
|