Linux内核中的overlay文件系统

一、简介

Docker 内核实现容器的功能用了linux 内核中的三个特性 Namespace、Cgroup、UnionFs,今天我们来说一下UnionFs。

linux UnionFs 实现的是overlay 文件系统

OverlayFs 文件系统分为三层,

lower 是只读层

Upper 是可读写

Merged 是 lower 和Upper 合并的目录

挂载方式可以使用mount 命令挂载:

mount -t overlay overlay -o lowerdir=lower1:lower2,upperdir=upper,workdir=work merged

二、源码分析

1.挂载overlay 设备初始化

当我们使用

mount -t overlay overlay -o lowerdir=lower1:lower2,upperdir=upper,workdir=work merged

linux 内核层,overlay 结构体声明类型

static struct file_system_type ovl_fs_type = {.owner		= THIS_MODULE,.name		= "overlay",.fs_flags	= FS_USERNS_MOUNT,.mount		= ovl_mount,.kill_sb	= kill_anon_super,
};

当我们使用overlay设备的时候,会触发结构体上挂载的mount函数指针,这个函数触发linux内核中的ovl_mount

static struct dentry *ovl_mount(struct file_system_type *fs_type, int flags,const char *dev_name, void *raw_data)
{return mount_nodev(fs_type, flags, raw_data, ovl_fill_super);
}

核心是使用ovl_fill_super,填充overlay 文件系统的超级块,申请一个ovl_fs,然后填充到

sb->s_fs_info = ofs;

详细代码:

static int ovl_fill_super(struct super_block *sb, void *data, int silent)
{struct path upperpath = { };struct dentry *root_dentry;struct ovl_entry *oe;struct ovl_fs *ofs;struct ovl_layer *layers;struct cred *cred;char *splitlower = NULL;unsigned int numlower;int err;// 如果当前用户的namespace不是超级块的ns那么返回错误 -EIOerr = -EIO;if (WARN_ON(sb->s_user_ns != current_user_ns()))goto out;// 目录操作结构体赋值sb->s_d_op = &ovl_dentry_operations;err = -ENOMEM;// 申请ovl_fs,并且对ovl_fs进行填充ofs = kzalloc(sizeof(struct ovl_fs), GFP_KERNEL);if (!ofs)goto out;err = -ENOMEM;ofs->creator_cred = cred = prepare_creds();if (!cred)goto out_err;/* Is there a reason anyone would want not to share whiteouts? */ofs->share_whiteout = true;ofs->config.index = ovl_index_def;ofs->config.uuid = true;ofs->config.nfs_export = ovl_nfs_export_def;ofs->config.xino = ovl_xino_def();ofs->config.metacopy = ovl_metacopy_def;// 装载选项err = ovl_parse_opt((char *) data, &ofs->config);if (err)goto out_err;err = -EINVAL;if (!ofs->config.lowerdir) {if (!silent)pr_err("missing 'lowerdir'\n");goto out_err;}err = -ENOMEM;splitlower = kstrdup(ofs->config.lowerdir, GFP_KERNEL);if (!splitlower)goto out_err;err = -EINVAL;numlower = ovl_split_lowerdirs(splitlower);if (numlower > OVL_MAX_STACK) {pr_err("too many lower directories, limit is %d\n",OVL_MAX_STACK);goto out_err;}err = -ENOMEM;layers = kcalloc(numlower + 1, sizeof(struct ovl_layer), GFP_KERNEL);if (!layers)goto out_err;ofs->layers = layers;/* Layer 0 is reserved for upper even if there's no upper */ofs->numlayer = 1;sb->s_stack_depth = 0;sb->s_maxbytes = MAX_LFS_FILESIZE;atomic_long_set(&ofs->last_ino, 1);/* Assume underlying fs uses 32bit inodes unless proven otherwise */if (ofs->config.xino != OVL_XINO_OFF) {ofs->xino_mode = BITS_PER_LONG - 32;if (!ofs->xino_mode) {pr_warn("xino not supported on 32bit kernel, falling back to xino=off.\n");ofs->config.xino = OVL_XINO_OFF;}}/* alloc/destroy_inode needed for setting up traps in inode cache */sb->s_op = &ovl_super_operations;if (ofs->config.upperdir) {struct super_block *upper_sb;err = -EINVAL;if (!ofs->config.workdir) {pr_err("missing 'workdir'\n");goto out_err;}err = ovl_get_upper(sb, ofs, &layers[0], &upperpath);if (err)goto out_err;upper_sb = ovl_upper_mnt(ofs)->mnt_sb;if (!ovl_should_sync(ofs)) {ofs->errseq = errseq_sample(&upper_sb->s_wb_err);if (errseq_check(&upper_sb->s_wb_err, ofs->errseq)) {err = -EIO;pr_err("Cannot mount volatile when upperdir has an unseen error. Sync upperdir fs to clear state.\n");goto out_err;}}err = ovl_get_workdir(sb, ofs, &upperpath);if (err)goto out_err;if (!ofs->workdir)sb->s_flags |= SB_RDONLY;sb->s_stack_depth = upper_sb->s_stack_depth;sb->s_time_gran = upper_sb->s_time_gran;}oe = ovl_get_lowerstack(sb, splitlower, numlower, ofs, layers);err = PTR_ERR(oe);if (IS_ERR(oe))goto out_err;/* If the upper fs is nonexistent, we mark overlayfs r/o too */if (!ovl_upper_mnt(ofs))sb->s_flags |= SB_RDONLY;if (!ofs->config.uuid && ofs->numfs > 1) {pr_warn("The uuid=off requires a single fs for lower and upper, falling back to uuid=on.\n");ofs->config.uuid = true;}if (!ovl_force_readonly(ofs) && ofs->config.index) {err = ovl_get_indexdir(sb, ofs, oe, &upperpath);if (err)goto out_free_oe;/* Force r/o mount with no index dir */if (!ofs->indexdir)sb->s_flags |= SB_RDONLY;}err = ovl_check_overlapping_layers(sb, ofs);if (err)goto out_free_oe;/* Show index=off in /proc/mounts for forced r/o mount */if (!ofs->indexdir) {ofs->config.index = false;if (ovl_upper_mnt(ofs) && ofs->config.nfs_export) {pr_warn("NFS export requires an index dir, falling back to nfs_export=off.\n");ofs->config.nfs_export = false;}}if (ofs->config.metacopy && ofs->config.nfs_export) {pr_warn("NFS export is not supported with metadata only copy up, falling back to nfs_export=off.\n");ofs->config.nfs_export = false;}if (ofs->config.nfs_export)sb->s_export_op = &ovl_export_operations;/* Never override disk quota limits or use reserved space */cap_lower(cred->cap_effective, CAP_SYS_RESOURCE);sb->s_magic = OVERLAYFS_SUPER_MAGIC;sb->s_xattr = ofs->config.userxattr ? ovl_user_xattr_handlers :ovl_trusted_xattr_handlers;sb->s_fs_info = ofs;sb->s_flags |= SB_POSIXACL;sb->s_iflags |= SB_I_SKIP_SYNC;// 把 overlay 文件系统的根目录设置到 upperDir里err = -ENOMEM;// 创建root的inode并且指向新建的inode对象root_inoderoot_dentry = ovl_get_root(sb, upperpath.dentry, oe);if (!root_dentry)goto out_free_oe;mntput(upperpath.mnt);kfree(splitlower);sb->s_root = root_dentry;return 0;out_free_oe:ovl_entry_stack_free(oe);kfree(oe);
out_err:kfree(splitlower);path_put(&upperpath);ovl_free_fs(ofs);
out:return err;
}

操作overlay 文件系统的目录操作结构体实现:

static const struct dentry_operations ovl_dentry_operations = {.d_release = ovl_dentry_release,.d_real = ovl_d_real,.d_revalidate = ovl_dentry_revalidate,.d_weak_revalidate = ovl_dentry_weak_revalidate,
};

数据结构图:

参考网址:

Linux源码剖析——OverlayFS 源码分析_linux overlay-CSDN博客

2、描述符操作结构体 

如果你做过kernel module ,读过linux设计实现.就很容易理解了

描述符操作结构体定义:

const struct file_operations ovl_dir_operations = {.read		= generic_read_dir,.open		= ovl_dir_open,.iterate	= ovl_iterate,.llseek		= ovl_dir_llseek,.fsync		= ovl_dir_fsync,.release	= ovl_dir_release,
};

当我们使用linux 系统调用打开overlay 设备文件的时候会触发操作结构体的函数,

open 函数:

static int ovl_dir_open(struct inode *inode, struct file *file)
{struct path realpath;struct file *realfile;struct ovl_dir_file *od;enum ovl_path_type type;od = kzalloc(sizeof(struct ovl_dir_file), GFP_KERNEL);if (!od)return -ENOMEM;type = ovl_path_real(file->f_path.dentry, &realpath);realfile = ovl_dir_open_realfile(file, &realpath);if (IS_ERR(realfile)) {kfree(od);return PTR_ERR(realfile);}od->realfile = realfile;od->is_real = ovl_dir_is_real(file->f_path.dentry);od->is_upper = OVL_TYPE_UPPER(type);file->private_data = od;return 0;
}

struct ovl_dir_file {bool is_real; // 是否需要合并bool is_upper; // 是否需要从upper读取struct ovl_dir_cache *cache; // 缓存目录struct list_head *cursor; // 遍历游标struct file *realfile; // 真实文件struct file *upperfile; // overlay 里 在upper目录所在位置
};

这里主要做的操作是初始化ovl_dir_file,并且把他挂载到万能指针private_data中。

读的操作是通过getdents,我们看迭代器:

static int ovl_iterate(struct file *file, struct dir_context *ctx)
{struct ovl_dir_file *od = file->private_data;struct dentry *dentry = file->f_path.dentry;struct ovl_cache_entry *p;const struct cred *old_cred;int err;old_cred = ovl_override_creds(dentry->d_sb);if (!ctx->pos)ovl_dir_reset(file);//是否需要读取真实路径if (od->is_real) {// 不需要合并直接读取真实路径/** If parent is merge, then need to adjust d_ino for '..', if* dir is impure then need to adjust d_ino for copied up* entries.*/if (ovl_xino_bits(dentry->d_sb) ||(ovl_same_fs(dentry->d_sb) &&(ovl_is_impure_dir(file) ||OVL_TYPE_MERGE(ovl_path_type(dentry->d_parent))))) {err = ovl_iterate_real(file, ctx);} else {err = iterate_dir(od->realfile, ctx);}goto out;}// 创建目录缓存if (!od->cache) {struct ovl_dir_cache *cache;cache = ovl_cache_get(dentry);err = PTR_ERR(cache);if (IS_ERR(cache))goto out;od->cache = cache;ovl_seek_cursor(od, ctx->pos);}// 直接把合并后的目录缓存,遍历返回用户层while (od->cursor != &od->cache->entries) {p = list_entry(od->cursor, struct ovl_cache_entry, l_node);if (!p->is_whiteout) {if (!p->ino) {err = ovl_cache_update_ino(&file->f_path, p);if (err)goto out;}}/* ovl_cache_update_ino() sets is_whiteout on stale entry */if (!p->is_whiteout) {if (!dir_emit(ctx, p->name, p->len, p->ino, p->type))break;}od->cursor = p->l_node.next;ctx->pos++;}err = 0;
out:revert_creds(old_cred);return err;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/171321.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

OD机考真题搜集:叠积木1

题目 有一堆长方体积木,它们的高度和宽度都相同,但长度不一。 小橙想把这堆积木叠成一面墙,墙的每层可以放一个积木,或将两个积木拼接起来,要求每层的长度相同。若必须用完这些积木,叠成的墙最多为多少层?如下是叠成的一面墙的图示,积木仅按宽和高所在的面进行拼接。 …

【数据结构】树与二叉树(廿六):树删除指定结点及其子树(算法DS)

文章目录 5.3.1 树的存储结构5. 左儿子右兄弟链接结构 5.3.2 获取结点的算法1. 获取大儿子、大兄弟结点2. 搜索给定结点的父亲3. 搜索指定数据域的结点4. 删除结点及其左右子树a. 逻辑删除与物理删除b. 算法DSTc. 算法解析d. 代码实现递归释放树算法DS e. 算法测试 5. 代码整合…

PPT 遇到问题总结(修改页码统计)

PPT常见问题 1. 修改页码自动计数 1. 修改页码自动计数 点击 视图——>幻灯片母版——>下翻找到计数页直接修改——>关闭母版视图

vue+springboot读取git的markdown文件并展示

前言 最近,在研究一个如何将我们git项目的MARKDOWN文档获取到,并且可以展示到界面通过检索查到,于是经过几天的摸索,成功的研究了出来 本次前端vue使用的是Markdown-it Markdown-it 是一个用于解析和渲染 Markdown 标记语言的 …

Cache学习(3):Cache地址映射(直接映射缓存组相连缓存全相连缓存)

1 Cache的与存储地址的映射 以一个Cache Size 为 128 Bytes 并且Cache Line是 16 Bytes的Cache为例。首先把这个Cache想象成一个数组,数组总共8个元素,每个元素大小是 16 Bytes,如下图: 现在考虑一个问题,CPU从0x0654…

城市生命线丨桥梁结构健康监测系统的作用

在城市建设当中,有非常多的城市基本建设,建设当中,桥梁作为不可忽视的一环,也需要有很多桥梁建设的智能监测系统,在这个桥梁结构健康监测系统中,桥梁的各个数值都能被监测得到。 WITBEE万宾使用城市生命线智…

高并发内存池

1.什么是内存池 内存池动态内存分配与管理技术,对于程序员来说,通常情况下,动态申请内存需要使用new,delete,malloc,free这些API来申请,这样导致的后果是,当程序长时间运行之后,由于程序运行时所申请的内存…

探索 Rollup:简化你的前端构建流程

🤍 前端开发工程师(主业)、技术博主(副业)、已过CET6 🍨 阿珊和她的猫_CSDN个人主页 🕠 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 🍚 蓝桥云课签约作者、已在蓝桥云…

Linux 命令vim(编辑器)

(一)vim编辑器的介绍 vim是文件编辑器,是vi的升级版本,兼容vi的所有指令,同时做了优化和延伸。vim有多种模式,其中常用的模式有命令模式、插入模式、末行模式:。 (二)vim编辑器基本操作 1 进入vim编辑文件 1 vim …

排序算法:归并排序、快速排序、堆排序

归并排序 要将一个数组排序,可以先将它分成两半分别排序,然后再将结果合并(归并)起来。这里的分成的两半,每部分可以使用其他排序算法,也可以仍然使用归并排序(递归)。 我看《算法》…

电源的纹波

电源纹波的产生 我们常见的电源有线性电源和开关电源,它们输出的直流电压是由交流电压经整流、滤波、稳压后得到的。由于滤波不干净,直流电平之上就会附着包含周期性与随机性成分的杂波信号,这就产生了纹波。 在额定输出电压、电流的情况下…

leetCode 1080.根到叶路径上的不足节点 + 递归 + 图解

给你二叉树的根节点 root 和一个整数 limit ,请你同时删除树中所有 不足节点 ,并返回最终二叉树的根节点。假如通过节点 node 的每种可能的 “根-叶” 路径上值的总和全都小于给定的 limit,则该节点被称之为 不足节点 ,需要被删除…

SQL Injection (Blind)`

SQL Injection (Blind) SQL Injection (Blind) SQL盲注,是一种特殊类型的SQL注入攻击,它的特点是无法直接从页面上看到注入语句的执行结果。在这种情况下,需要利用一些方法进行判断或者尝试,这个过程称之为盲注。 盲注的主要形式有…

Python之内置函数和模块

学习的最大理由是想摆脱平庸,早一天就多一份人生的精彩;迟一天就多一天平庸的困扰。各位小伙伴,如果您: 想系统/深入学习某技术知识点… 一个人摸索学习很难坚持,想组团高效学习… 想写博客但无从下手,急需…

基于单片机的可升降助眠婴儿床(论文+源码)

1.系统设计 本课题为基于单片机的可升降助眠婴儿床系统,在设计目标上确定如下: 1. 可以实现婴儿床的升降,摇床功能控制; 2. 具有音乐播放功能,并且有多首曲目; 3. 用户可以通过按键或者红外遥控&#x…

Runloop解析

RunLoop 前言 ​ 本文介绍RunLoop的概念,并使用swift和Objective-C来描述RunLoop机制。 简介 ​ RunLoop——运行循环(死循环),它提供了一个事件循环机制在程序运行过程中处理各种事件,例如用户交互、网络请求、定…

Xshell连接VMware虚拟机中的CentOS

Xshell连接VMware虚拟机中的CentOShttps://www.cnblogs.com/niuben/p/13157291.html 步骤: 1. 检查Linux虚拟机的网络连接模式,确保它是NAT模式。(由于只在本机进行连接,所以没有选择桥接模式。当然,桥接模式的配置会…

利用ngrok实现内网穿透(全网最详细教程)

准备工具: 1、phpstudy 用于在本地搭建网站 2、ngrok 用于将自己的本地端口暴露到公网上,从而实现内网穿透 文章开始前给大家分享一个学习人工智能的网站,通俗易懂,风趣幽默 人工智能https://www.captainbed.cn/myon/ ~~~~~…

【教学类-06-12】20231126 (一)二位数 如何让加减乘除题目从小到大排序(以1-20之间加法为例,做正序排列用)

结果展示 优化后 优化前 背景需求: 生成列表 单独抽取显示题目排序方法 存在问题: 我希望 00 01 02……这样排序,但是实际上,除了第一个加数会从小到大排序,第二个被加数的第十位数和个位数都会从小到大排序,也就是…

提示工程-Prompt Engineering

提示工程 提示工程 1、概述 Prompt Engineering: 提示工程 通过自然语言(英语、汉语等)来给AI下达指示,从而让AI完成你指定给他的工作的过程都可以称之为提示工程。(面向自然语言编程) 提示词要素 指令&…