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;
}
相关文章:

Linux内核中的overlay文件系统
一、简介 Docker 内核实现容器的功能用了linux 内核中的三个特性 Namespace、Cgroup、UnionFs,今天我们来说一下UnionFs。 linux UnionFs 实现的是overlay 文件系统 OverlayFs 文件系统分为三层, lower 是只读层 Upper 是可读写 Merged 是 lower 和U…...

archery修改为不能自提自审核上线SQL
目录 背景修改代码效果参考 背景 我和同事都可以提交上线SQL,但是不能自己提交的SQL自己去审核通过。目前的情况是可以自提自审。 修改代码 找到/opt/archery/sql/utils/workflow_audit.py文件 ...省略...# 判断用户当前是否是可审核staticmethoddef can_revie…...

如何处理git多分支
本篇文章主要处理以下两种多分支问题 如何将自己在本地的修改上传到一个新的Git分支(比如用于测试,不合并进main分支)?如何在一个新的本地仓库拉取一个项目的非main分支,并处理他们关联关系? 1. 将自己在…...

Proteus仿真--基于DS1302与数码管设计的可调电子钟
本文主要介绍基于51单片机的DS1302的可调式电子钟实验(完整仿真源文件及代码见文末链接) 仿真图如下 其中数码管显示电子钟时间信息,按键用于调节时间,时间芯片选用DS1302芯片 仿真运行视频 Proteus仿真--基于DS1302与数码管设…...

ESP32网络开发实例-远程Web串口监视器
远程Web串口监视器 文章目录 远程Web串口监视器1、应用介绍2、软件准备3、硬件准备4、代码实现在本文中,我们将构建一个 ESP32 网络服务器,用作远程串行监视器。 基于 Web 的串行监视器的工作方式与通常用于调试目的的 Arduino IDE 串行监视器的工作方式相同。 1、应用介绍 …...

xadmin后台在每一行记录增加一个复制链接按钮
xadmin后台在每一行记录增加一个复制链接按钮 1、效果 点击复制后,自动把url链接复制到粘贴板,按Ctrl+v即可显示复制内容。 2、实现代码 adminx.py # 用户管理 class UserWhiteListAdmin(object):search_fields = [name, mobile] # 检索字段list_display...

LVS+Keepalived 高可用群集
一、一.Keepalived工具介绍 专为LVS和HA设计的一款健康检查工具 • 支持故障自动切换(Failover) • 支持节点健康状态检查(Health Checking) • 官方网站:http://www.keepalived.org/ 二、Keepalived工作原理 • …...
数据传输的思考
Wi-Fi:Wi-Fi是一种无线网络技术,可以用于无线互联网接入、局域网通信和数据传输等。Wi-Fi基于IEEE 802.11标准,通过无线信号传输数据,提供高速的无线网络连接。Wi-Fi可用于连接设备与路由器或者设备之间的直接通信,可以…...

ETL-使用kettle批量复制sqlserver数据到mysql数据库
文章标题 1、安装sqlserver数据库2、下载kettle3、业务分析4、详细流程(1)转换1:获取sqlserver所有表格名字,将记录复制到结果(2)转换2:从结果设置变量(3)转换3ÿ…...

交流充电桩与直流充电桩的区别
1、背景 直流充电桩的学名是非车载充电机,是相对于交流充电桩而言的。交流充电桩是采用传导方式为具备车载充电机的电动汽车提供交流电能的专用装置。 2、交流充电桩和直流充电桩 1.1、交流充电桩 交流充电桩包括单相和三相交流充电桩。 图一是交流充电桩原理框…...

基于单片机公交安全预警系统仿真设计
**单片机设计介绍, 基于单片机公交安全预警系统仿真设计 文章目录 一 概要二、功能设计设计思路 三、 软件设计原理图 五、 程序六、 文章目录 一 概要 基于单片机的公交安全预警系统可以被设计成能够实时监测公交车辆的行驶状态,并在发生异常情况时进行…...

HarmonyOS基础组件之Button三种类型的使用
简介 HarmonyOS在明年将正式不再兼容Android原生功能,这意味着对于客户端的小伙伴不得不开始学习HarmonyOS开发语言。本篇文章主要介绍鸿蒙中的Button使用。 HarmonyOS中的Button相较于Android原生来说,功能比较丰富,扩展性高,减…...
Using the File Class使用文件类
Using the File Class 使用文件类 The file layout class enables you to perform file input and output operations with Application Engine using PeopleCode. A file object enables you to open a file (for reading or writing), read data from a file, or write da…...
【Kafka】Java整合Kafka
1.引入依赖 <dependency><groupId>org.apache.kafka</groupId><artifactId>kafka-clients</artifactId><version>2.3.1</version></dependency> 2.搭建生产者 package com.wen.kafka;import org.apache.kafka.clients.produ…...
所里网连不上,我服了
所里网连不上,我服了所里网连不上,我服了所里网连不上,我服了...

Yakit工具篇:WebFuzzer模块之热加载技术
简介 官方定义: 什么是热加载? 广义上来说,热加载是一种允许在不停止或重启应用程序的情况下,动态加载或更新特定组件或模块的功能。这种技术常用于开发过程中,提高开发效率和用户体验。 在Yakit 的Web Fuzzer中&…...

Linux基本指令(前篇)
目录 1.ls指令 2.pwd指令 3.cd 指令 4.touch指令 5.mkdir指令(重要) 6.rmdir指令 && rm 指令(重要) 7.man指令(重要) 1.ls指令 ls 选项 目录或文件 对于目录,该命令列出该目录下的所…...

[网鼎杯 2020 青龙组]singal
一道VM题目 可以看到长度是15 跟踪调用read函数的函数 分析一下switch中每个指令的含义、 在scanf下面打断点 在关键跳转处下断点 打开Ponce插件 GitHub - illera88/Ponce: IDA 2016 plugin contest winner! Symbolic Execution just one-click away! 然后开始动调 输入15个…...
Qt/QML编程学习之心得:一个QML工程的学习笔记(十)
前言: 到底什么是Qt Quick呢?因为Qt Quick是Qt新引入的,Qt Quick由Qt Quick模块提供,它是一个编写QML应用的标准库。Qt Quick模块提供了两种接口:使用QML语言创建应用的QML接口和使用C++语言扩展QML的C++接口。使用Qt Quick模块,设计人员和开发人员可以轻松地构建流畅的…...

LeetCode OJ循环队列(C语言)
1.题目的初步分析 我们分析上述题目的时候会发现题目非常的长,不好整理思路,我这里可以大致的将本题的几个核心点说出来: 1.队列的思路 循环队列说来说去不还是队列嘛,那么队列的基本操作增删查改、以及队列的基本结构肯定都是不能…...

springboot 百货中心供应链管理系统小程序
一、前言 随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱,百货中心供应链管理系统被用户普遍使用,为方…...
ES6从入门到精通:前言
ES6简介 ES6(ECMAScript 2015)是JavaScript语言的重大更新,引入了许多新特性,包括语法糖、新数据类型、模块化支持等,显著提升了开发效率和代码可维护性。 核心知识点概览 变量声明 let 和 const 取代 var…...
STM32+rt-thread判断是否联网
一、根据NETDEV_FLAG_INTERNET_UP位判断 static bool is_conncected(void) {struct netdev *dev RT_NULL;dev netdev_get_first_by_flags(NETDEV_FLAG_INTERNET_UP);if (dev RT_NULL){printf("wait netdev internet up...");return false;}else{printf("loc…...

Keil 中设置 STM32 Flash 和 RAM 地址详解
文章目录 Keil 中设置 STM32 Flash 和 RAM 地址详解一、Flash 和 RAM 配置界面(Target 选项卡)1. IROM1(用于配置 Flash)2. IRAM1(用于配置 RAM)二、链接器设置界面(Linker 选项卡)1. 勾选“Use Memory Layout from Target Dialog”2. 查看链接器参数(如果没有勾选上面…...

佰力博科技与您探讨热释电测量的几种方法
热释电的测量主要涉及热释电系数的测定,这是表征热释电材料性能的重要参数。热释电系数的测量方法主要包括静态法、动态法和积分电荷法。其中,积分电荷法最为常用,其原理是通过测量在电容器上积累的热释电电荷,从而确定热释电系数…...
iOS性能调优实战:借助克魔(KeyMob)与常用工具深度洞察App瓶颈
在日常iOS开发过程中,性能问题往往是最令人头疼的一类Bug。尤其是在App上线前的压测阶段或是处理用户反馈的高发期,开发者往往需要面对卡顿、崩溃、能耗异常、日志混乱等一系列问题。这些问题表面上看似偶发,但背后往往隐藏着系统资源调度不当…...
腾讯云V3签名
想要接入腾讯云的Api,必然先按其文档计算出所要求的签名。 之前也调用过腾讯云的接口,但总是卡在签名这一步,最后放弃选择SDK,这次终于自己代码实现。 可能腾讯云翻新了接口文档,现在阅读起来,清晰了很多&…...
MySQL 部分重点知识篇
一、数据库对象 1. 主键 定义 :主键是用于唯一标识表中每一行记录的字段或字段组合。它具有唯一性和非空性特点。 作用 :确保数据的完整性,便于数据的查询和管理。 示例 :在学生信息表中,学号可以作为主键ÿ…...
探索Selenium:自动化测试的神奇钥匙
目录 一、Selenium 是什么1.1 定义与概念1.2 发展历程1.3 功能概述 二、Selenium 工作原理剖析2.1 架构组成2.2 工作流程2.3 通信机制 三、Selenium 的优势3.1 跨浏览器与平台支持3.2 丰富的语言支持3.3 强大的社区支持 四、Selenium 的应用场景4.1 Web 应用自动化测试4.2 数据…...
嵌入式常见 CPU 架构
架构类型架构厂商芯片厂商典型芯片特点与应用场景PICRISC (8/16 位)MicrochipMicrochipPIC16F877A、PIC18F4550简化指令集,单周期执行;低功耗、CIP 独立外设;用于家电、小电机控制、安防面板等嵌入式场景8051CISC (8 位)Intel(原始…...