1. 文件系统层次化

对 Linux 系统来说一切皆文件,Linux 使用树形的层次化结构来管理所有的文件对象。

完整的Linux文件系统,是由多种设备、多种文件系统组成的一个混合的树形结构。我们首先从一个单独的块设备来分析其树形结构的构造。

1.1 块设备的层次化(superblock/inode/dentry)

对一个块设备来说要想构造文件系统树形结构,最重要的两个全局因素是:块设备(block_device)和文件系统(file_system_type)。内核使用了一个数据结构 struct super_block 把这两者结合起来,用来标识一个块设备。

确定了 super_block 以后,就可以使用文件系统提供的方法来解析块设备的内容,形成一个块设备内部的树形结构,也就是我们熟悉的文件夹、文件的层次结构。

系统使用 struct inode 结构来标识块设备内部的一个文件夹或者文件,struct inode 结构中最重要的成员是 ->i_ino 这个记录了 inode 在块设备中的偏移。

系统为了辅助 struct inode 的使用还设计了一个 struct dentry 结构,通常情况下一个 struct dentry 对应一个 struct inode,也有少数情况下多个 struct dentry 对应一个 struct inode(如硬链接)。struct dentry 中 cache 了更多的文件信息,类如文件名、层次结构,成员 ->d_parent 指向同一块设备内的父节点 struct dentry ,成员 ->d_subdirs 链接了所有的子节点 struct dentry

1.2 多设备的层次化(mount/vfsmount)

上一节描述了单个块设备构建树形文件系统的方式,如果是多个设备怎么样组成一颗更复杂的树呢?

Linux使用父子树的形式来构造,父设备树中的一个文件夹 struct dentry 可以充当子设备树的挂载点 mountpoint

  • mount/vfsmount

为了组成复杂的父子树,系统定义了一个 struct mount 结构来负责对一个设备内子树的引用。

  • mount tree

可以看到通过一个 struct mount 结构负责引用一颗子设备树,把这颗子设备树挂载到父设备树的其中一个 dentry 节点上。

如果 dentry 成为了挂载点 mountpoint,会给其标识成 DCACHE_MOUNTED。我们在查找路径的时候同样会判断 dentryDCACHE_MOUNTED 标志,一旦置位就变成了 mountpoint,挂载点文件夹下原有的内容就不能访问了,转而访问子设备树根节点下的内容。

struct mount 结构之间也构成了树形结构。

我们通常使用 mount -t fstpye devname pathname 命令来进行挂载子设备的操作。Linux 拥有非常灵活的挂载规则:

规则1、一个设备可以被挂载多次:

可以看到同一个子设备树,同时被两个 struct mount 结构所引用,被挂载到父设备树的两处不同的 dentry 处。

特别说明:虽然子设备树被挂载两次并且通过两处路径都能访问,但子设备的 dentryinode 只保持一份。

规则2、一个挂载点可以挂载多个设备:

还可以对父设备树的同一个文件夹 dentry 进行多次挂载,最后路径查找时生效的是最后一次挂载的子设备树。

  • path

因为linux提供的灵活的挂载规则,所以我们如果要标识一个路径 struct path 的话需要两个元素:vfsmountdentry

可以看到两个路径 struct path 最后引用到了同一 inode,但是路径 path 是不一样的,因为 path 指向的 vfsmount 是不一样的。

  • chroot

Linux 还支持每个进程拥有不同的根目录,使用 chroot() 系统调用可以把当前进程的根目录设置为整棵文件系统树中的任何 path

1.3 多名空间的层次化(mnt_namespace)

之前的系统中只有一棵mount树,为了支持mnt_namespace,系统把mount树叶扩展成了多棵。每个mnt_namespace拥有一棵独立的mount树:

2. 关键代码

2.1 mount()

mount() 系统调用是理解文件系统层次化的核心,它主要包含3个关键步骤:

1、解析 mount() 系统调用中的参数挂载点路径 pathname ,返回对应的 struct path 结构:

SYSCALL_DEFINE5(mount) → do_mount() → user_path() → user_path_at_empty() → filename_lookup() → path_lookupat() → link_path_walk() → walk_component() → follow_managed()

2、解析 mount() 系统调用中的参数文件系统类型 -t type 和设备路径 devname ,建立起子设备的树形结构(如果之前已经创建过引用即可),建立起新的 struct mount 结构对其引用:

SYSCALL_DEFINE5(mount) → do_mount() → do_new_mount() → vfs_kern_mount() → mount_fs() → type->mount()

3、将新建立的 struct mount 结构挂载到查找到的 struct path 结构上:

SYSCALL_DEFINE5(mount) → do_mount() → do_new_mount() → do_add_mount() → graft_tree() → attach_recursive_mnt() → commit_tree()

2.2 chroot()

更改当前进程的根目录:

SYSCALL_DEFINE1(chroot, const char __user *, filename)
{
struct path path;
int error;
unsigned int lookup_flags = LOOKUP_FOLLOW | LOOKUP_DIRECTORY;
retry:
/* (1) 解析指定的路径,返回对应的`struct path`结构 */
error = user_path_at(AT_FDCWD, filename, lookup_flags, &path);
if (error)
goto out; error = inode_permission(path.dentry->d_inode, MAY_EXEC | MAY_CHDIR);
if (error)
goto dput_and_out; error = -EPERM;
if (!ns_capable(current_user_ns(), CAP_SYS_CHROOT))
goto dput_and_out;
error = security_path_chroot(&path);
if (error)
goto dput_and_out; /* (2) 把`struct path`结构设置为当前进程的根目录 */
set_fs_root(current->fs, &path);
error = 0;
dput_and_out:
path_put(&path);
if (retry_estale(error, lookup_flags)) {
lookup_flags |= LOOKUP_REVAL;
goto retry;
}
out:
return error;
} ↓ void set_fs_root(struct fs_struct *fs, const struct path *path)
{
struct path old_root; path_get(path);
spin_lock(&fs->lock);
write_seqcount_begin(&fs->seq);
old_root = fs->root;
/* (2.1) 替换新的根目录 */
fs->root = *path;
write_seqcount_end(&fs->seq);
spin_unlock(&fs->lock);
if (old_root.dentry)
path_put(&old_root);
}

2.3 copy_mnt_ns()

在进程创建或者 unshare()/setns() 系统调用时,如果设置了 CLONE_NEWNS 标志会调用 copy_mnt_ns() 创建一个新的 mnt namespace。其中的核心是创建一颗新的 struct mount 树,首先把旧的 struct mount 树复制过来:

SYSCALL_DEFINE1(unshare) → unshare_nsproxy_namespaces() → create_new_namespaces() → copy_mnt_ns() → copy_tree() → clone_mnt()

随后如果有新的 mount() 动作,两棵树的内容就会不同。

参考文档:

1.Linux Namespace
2.Linux文件系统
3.在CentOS7上使用LXC管理容器
4.docker基础:从chroot理解namespace的隔离
5.Docker基础: Linux内核命名空间之(1)mnt namespace
6.Docker基础:文件系统之AUFS
7.docker image是什么,存储在什么位置
8.Docker镜像存储-overlayfs
9.linux文件系统之mount流程分析
10.dentry和inode的关系

最新文章

  1. Jquery Validate 正则表达式实用验证代码
  2. MS CRM 2011的自定义和开发(11)——插件(plugin)开发(三)
  3. 时序列数据库武斗大会之什么是 TSDB ?
  4. Debug Certificate expired on 11-5-8 上午 6:26
  5. weekend110(Hadoop)的 第六天笔记
  6. 使用pip install 或者easy_install安装Python的各种包出现cc failed with exit status 1
  7. Setup and Configure the vsftpd server in CentOS 7 operation system
  8. java 基本类型包装类,system类,Math类,Assrays类,大数据运算
  9. nginx+fastCGI
  10. 结构型---代理模式(Proxy Pattern)
  11. C++以const 作为返回值类型的意义
  12. Django 2.x 常用命令
  13. 选择排序的C、C++实现
  14. OCR 基本知识
  15. PHP导出excel文件的几种方式
  16. css笔记 - 张鑫旭css课程笔记之 overflow 篇
  17. mysql 塞数据
  18. Linux之virtualbox中的ubuntu虚拟机linux系统共享文件夹
  19. 用OpenSSL命令行生成证书文件
  20. myisam,innodb和memory的区别

热门文章

  1. Elasticsearch、XXLJob以及最近的学习记录
  2. Windows环境下搭建Hadoop(2.6.0)+Hive(2.2.0)环境并连接Kettle(6.0)
  3. python-matplotlib学习(1)
  4. 踩坑系列《一》数据库建表权限 CREATE command denied to user for table
  5. 11204备库升级PSU
  6. VS Code环境配置
  7. JavaScript中的函数、参数、变量
  8. Java序列初始化
  9. 记一次关于pdf 下载需求变更到 pdf 在线预览
  10. 【二食堂】Beta - Scrum Meeting 4