COW奶牛!Copy On Write机制了解一下

2023-11-18

前言

只有光头才能变强

在读《Redis设计与实现》关于哈希表扩容的时候,发现这么一段话:

执行BGSAVE命令或者BGREWRITEAOF命令的过程中,Redis需要创建当前服务器进程的子进程,而大多数操作系统都采用写时复制(copy-on-write)来优化子进程的使用效率,所以在子进程存在期间,服务器会提高负载因子的阈值,从而避免在子进程存在期间进行哈希表扩展操作,避免不必要的内存写入操作,最大限度地节约内存。

触及到知识的盲区了,于是就去搜了一下copy-on-write写时复制这个技术究竟是怎么样的。发现涉及的东西蛮多的,也挺难读懂的。于是就写下这篇笔记来记录一下我学习copy-on-write的过程。

本文力求简单讲清copy-on-write这个知识点,希望大家看完能有所收获。

一、Linux下的copy-on-write

在说明Linux下的copy-on-write机制前,我们首先要知道两个函数:fork()exec()。需要注意的是exec()并不是一个特定的函数, 它是一组函数的统称, 它包括了execl()execlp()execv()execle()execve()execvp()

1.1简单来用用fork

首先我们来看一下fork()函数是什么鬼:

fork is an operation whereby a process creates a copy of itself.

fork是类Unix操作系统上创建进程的主要方法。fork用于创建子进程(等同于当前进程的副本)。

  • 新的进程要通过老的进程复制自身得到,这就是fork!

如果接触过Linux,我们会知道Linux下init进程是所有进程的爹(相当于Java中的Object对象)

  • Linux的进程都通过init进程或init的子进程fork(vfork)出来的。

下面以例子说明一下fork吧:


#include <unistd.h>  
#include <stdio.h>  
 
int main ()   
{   
    pid_t fpid; //fpid表示fork函数返回的值  
    int count=0;
	
	// 调用fork,创建出子进程  
    fpid=fork();

	// 所以下面的代码有两个进程执行!
    if (fpid < 0)   
        printf("创建进程失败!/n");   
    else if (fpid == 0) {  
        printf("我是子进程,由父进程fork出来/n");   
        count++;  
    }  
    else {  
        printf("我是父进程/n");   
        count++;  
    }  
    printf("统计结果是: %d/n",count);  
    return 0;  
}  

得到的结果输出为:


我是子进程,由父进程fork出来

统计结果是: 1

我是父进程

统计结果是: 1

解释一下:

  • fork作为一个函数被调用。这个函数会有两次返回,将子进程的PID返回给父进程,0返回给子进程。(如果小于0,则说明创建子进程失败)。
  • 再次说明:当前进程调用fork(),会创建一个跟当前进程完全相同的子进程(除了pid),所以子进程同样是会执行fork()之后的代码。

所以说:

  • 父进程在执行if代码块的时候,fpid变量的值是子进程的pid
  • 子进程在执行if代码块的时候,fpid变量的值是0

1.2再来看看exec()函数

从上面我们已经知道了fork会创建一个子进程。子进程的是父进程的副本

exec函数的作用就是:装载一个新的程序(可执行映像)覆盖当前进程内存空间中的映像,从而执行不同的任务

  • exec系列函数在执行时会直接替换掉当前进程的地址空间

我去画张图来理解一下:

exec函数的作用

参考资料:

1.3回头来看Linux下的COW是怎么一回事

fork()会产生一个和父进程完全相同的子进程(除了pid)

如果按传统的做法,会直接将父进程的数据拷贝到子进程中,拷贝完之后,父进程和子进程之间的数据段和堆栈是相互独立的

父进程的数据拷贝到子进程中

但是,以我们的使用经验来说:往往子进程都会执行exec()来做自己想要实现的功能。

  • 所以,如果按照上面的做法的话,创建子进程时复制过去的数据是没用的(因为子进程执行exec(),原有的数据会被清空)

既然很多时候复制给子进程的数据是无效的,于是就有了Copy On Write这项技术了,原理也很简单:

  • fork创建出的子进程,与父进程共享内存空间。也就是说,如果子进程不对内存空间进行写入操作的话,内存空间中的数据并不会复制给子进程,这样创建子进程的速度就很快了!(不用复制,直接引用父进程的物理空间)。
  • 并且如果在fork函数返回之后,子进程第一时间exec一个新的可执行映像,那么也不会浪费时间和内存空间了。

另外的表达方式:

在fork之后exec之前两个进程用的是相同的物理空间(内存区),子进程的代码段、数据段、堆栈都是指向父进程的物理空间,也就是说,两者的虚拟空间不同,但其对应的物理空间是同一个

当父子进程中有更改相应段的行为发生时,再为子进程相应的段分配物理空间

如果不是因为exec,内核会给子进程的数据段、堆栈段分配相应的物理空间(至此两者有各自的进程空间,互不影响),而代码段继续共享父进程的物理空间(两者的代码完全相同)。

而如果是因为exec,由于两者执行的代码不同,子进程的代码段也会分配单独的物理空间。

Copy On Write技术实现原理:

fork()之后,kernel把父进程中所有的内存页的权限都设为read-only,然后子进程的地址空间指向父进程。当父子进程都只读内存时,相安无事。当其中某个进程写内存时,CPU硬件检测到内存页是read-only的,于是触发页异常中断(page-fault),陷入kernel的一个中断例程。中断例程中,kernel就会把触发的异常的页复制一份,于是父子进程各自持有独立的一份。

Copy On Write技术好处是什么?

  • COW技术可减少分配和复制大量资源时带来的瞬间延时
  • COW技术可减少不必要的资源分配。比如fork进程时,并不是所有的页面都需要复制,父进程的代码段和只读数据段都不被允许修改,所以无需复制

Copy On Write技术缺点是什么?

  • 如果在fork()之后,父子进程都还需要继续进行写操作,那么会产生大量的分页错误(页异常中断page-fault),这样就得不偿失。

几句话总结Linux的Copy On Write技术:

  • fork出的子进程共享父进程的物理空间,当父子进程有内存写入操作时,read-only内存页发生中断,将触发的异常的内存页复制一份(其余的页还是共享父进程的)。
  • fork出的子进程功能实现和父进程是一样的。如果有需要,我们会用exec()把当前进程映像替换成新的进程文件,完成自己想要实现的功能。

参考资料:

二、解释一下Redis的COW

基于上面的基础,我们应该已经了解COW这么一项技术了。

下面我来说一下我对《Redis设计与实现》那段话的理解:

  • Redis在持久化时,如果是采用BGSAVE命令或者BGREWRITEAOF的方式,那Redis会fork出一个子进程来读取数据,从而写到磁盘中
  • 总体来看,Redis还是读操作比较多。如果子进程存在期间,发生了大量的写操作,那可能就会出现很多的分页错误(页异常中断page-fault),这样就得耗费不少性能在复制上。
  • 而在rehash阶段上,写操作是无法避免的。所以Redis在fork出子进程之后,将负载因子阈值提高,尽量减少写操作,避免不必要的内存写入操作,最大限度地节约内存。

参考资料:

三、文件系统的COW

下面来看看文件系统中的COW是啥意思:

Copy-on-write在对数据进行修改的时候,不会直接在原来的数据位置上进行操作,而是重新找个位置修改,这样的好处是一旦系统突然断电,重启之后不需要做Fsck。好处就是能保证数据的完整性,掉电的话容易恢复

  • 比如说:要修改数据块A的内容,先把A读出来,写到B块里面去。如果这时候断电了,原来A的内容还在!

参考资料:

最后

最后我们再来看一下写时复制的思想(摘录自维基百科):

写入时复制(英语:Copy-on-write,简称COW)是一种计算机程序设计领域的优化策略。其核心思想是,如果有多个调用者(callers)同时请求相同资源(如内存或磁盘上的数据存储),他们会共同获取相同的指针指向相同的资源,直到某个调用者试图修改资源的内容时,系统才会真正复制一份专用副本(private copy)给该调用者,而其他调用者所见到的最初的资源仍然保持不变。这过程对其他的调用者都是透明的(transparently)。此作法主要的优点是如果调用者没有修改该资源,就不会有副本(private copy)被建立,因此多个调用者只是读取操作时可以共享同一份资源。

至少从本文我们可以总结出:

  • Linux通过Copy On Write技术极大地减少了Fork的开销
  • 文件系统通过Copy On Write技术一定程度上保证数据的完整性

其实在Java里边,也有Copy On Write技术。

Java中的COW

这部分留到下一篇来说,敬请期待~

如果大家有更好的理解方式或者文章有错误的地方还请大家不吝在评论区留言,大家互相学习交流~~~

参考资料:

一个坚持原创的Java技术公众号:Java3y,欢迎大家关注

3y所有的原创文章:

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

COW奶牛!Copy On Write机制了解一下 的相关文章

  • 连接到 localhost:6379 时出现错误 99。无法分配请求的地址

    设置 我有一个虚拟机 并在虚拟机中运行三个容器 一个 nginx 代理 一个非常简约的 Flask 应用程序和 redis Flask 应在端口 5000 上提供服务 而 redis 应在 6379 上提供服务 这些容器中的每一个都可以作为
  • 使用brew在MacOSx上安装Redis JSON

    如何使用brew 在 macOSx 上安装 RedisJSON 如何在不编译redis的情况下启用redis上的模块 我不想使用 docker 客户端 Redis Stack 可能是最简单的方法 它不仅仅是 RedisJSON 还包括 Re
  • 如何从 python 将无穷大传递给 redis?

    我正在使用 redis py 并希望将 inf 和 inf 与 ZRANGEBYSCORE 一起使用 我尝试使用 inf 的字符串和浮点来执行此操作 但它们返回一个空集 我怎样才能做到这一点 EDIT 我尝试执行以下命令 redis Str
  • connect-redis - 如何保护会话对象免受竞争条件影响

    我使用 nodejs 和 connect redis 来存储会话数据 我将用户数据保存在会话中 并在会话生命周期中使用它 我注意到两个更改会话数据的请求之间可能存在竞争条件 我尝试过使用 redis lock 来锁定会话 但这对我来说有点问
  • Node.js 上通过套接字连接 Redis

    由于共享托管 目标主机上的我的 redis 服务器不在端口上运行 而是在非常特定的套接字上运行 可以通过套接字文件连接到该套接字 只有我的用户可以访问 但是 我还没有找到如何通过套接字指定连接node redis and connect r
  • 使用 AWS ElastiCache 请求中的 Airflow CROSSSLOT 密钥未散列到同一插槽错误

    我在 AWS ECS 上运行 apache airflow 1 8 1 并且有一个 AWS ElastiCache 集群 redis 3 2 4 运行 2 个分片 2 个启用多可用区的节点 集群 Redis 引擎 我已经验证气流可以毫无问题
  • 仅当尚未设置时才进行原子设置

    仅当尚未在 Redis 中设置时 是否有办法执行原子设置 具体来说 我正在创建一个像 myapp user user email 这样的用户 并且希望 Redis 在 user email 已被占用时返回错误 而不是默默地替换旧值 比如声明
  • PooledRedisClientManager 未释放连接

    我将 json 数据列表存储在 redis 中并使用 ServiceStack c 客户端访问它 我本质上是在管理自己的外键 我在其中存储zrangeid 我使用应用程序内部的接口从zrange然后从 Redis 获取底层 json 对象并
  • 为什么Redis中不建议使用KEYS?

    在Redis中 建议不要使用按键命令 https redis io commands KEYS 为什么会这样呢 是因为它的时间复杂度是 O N 吗 或者是别的什么原因 我做了下面的实验来证明KEYS命令有多么危险 当带有 KEYS 的一个命
  • WSL Redis 遇到系统尚未使用 systemd 作为 init 系统(PID 1)启动。无法操作[已关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 我正在尝试遵循本文中讨论的 Redis 安装过程article https www digitalocean com community
  • 库存管理系统的 SQL 与 NoSQL

    我正在开发一个基于 JAVA 的网络应用程序 主要目的是拥有在多个称为渠道的网站上销售的产品的库存 我们将担任所有这些渠道的管理者 我们需要的是 用于管理每个渠道的库存更新的队列 库存表 其中包含每个通道上分配的正确快照 将会话 ID 和其
  • Redis Docker compose无法处理RDB格式版本10

    我无法在 docker compose 文件中启动 redis 容器 我知道docker compose文件没问题 因为我的同事可以成功启动项目 我读到有一个删除 dump rdb 文件的解决方案 但我找不到它 我使用Windows机器 任
  • 如何设置和获取Redis中存储的对象?

    我试图在 redis 中存储一个对象 当我获取该对象时 它似乎不起作用 I tried u User new u name blankman redis set test u x redis get test x name error 我想
  • 如何在Redis中进行持久化存储?

    关闭redis服务器后 使用set存储的值被破坏 在这里我找到了使用持久性存储的方法 有人帮助我 如何使用javascript实现这一点 我想将客户端的一些值存储在 redis 数据库中 并且必须在其他客户端中使用该值 您需要配置 Redi
  • Java 将字节转换为二进制安全字符串

    我有一些以字节为单位的数据 我想将它们放入Redis中 但是Redis只接受二进制安全字符串 而我的数据有一些二进制非安全字节 那么如何将这些字节转换为二进制安全字符串以便将它们保存到 Redis 中呢 Base64 对我有用 但它使数据更
  • Redis、会话过期和反向查找

    我目前正在构建一个网络应用程序 并想使用 Redis 来存储会话 登录时 会话会使用相应的用户 ID 插入到 Redis 中 并且过期时间设置为 15 分钟 我现在想实现会话的反向查找 获取具有特定用户 ID 的会话 这里的问题是 由于我无
  • 2 个具有共享 Redis 依赖的 Helm Chart

    目前 我有 2 个 Helm Charts Chart A 和 Chart B Chart A 和 Chart B 对 Redis 实例具有相同的依赖关系 如Chart yaml file dependencies name redis v
  • StackExchange.Redis Get 函数抛出 TimeoutException

    我在用着StackExchange Redis与 C 和StackExchangeRedisCacheClient Get函数抛出以下异常 myCacheClient Database StringGet txtKey Text myCac
  • Redis+Docker+Django - 错误 111 连接被拒绝

    我正在尝试使用 Redis 作为使用 Docker Compose 的 Django 项目的 Celery 代理 我无法弄清楚我到底做错了什么 但尽管控制台日志消息告诉我 Redis 正在运行并接受连接 事实上 当我这样做时 docker
  • 如何将“.csv”数据文件导入Redis数据库

    如何将 csv 数据文件导入 Redis 数据库 csv 文件中包含 id 时间 纬度 经度 列 您能否向我建议导入 CSV 文件并能够执行空间查询的最佳方法 这是一个非常广泛的问题 因为我们不知道您想要什么数据结构 您期望什么查询等等 为

随机推荐

  • 3D CG软件blender入门教程:手把手教你使用方法

    翻译 BeforeDawn大家好 我是bpm 目前在做一些设计师与技术总监相关的工作 这篇文章主要以blender这个软件作为切入点来为大家讲解一下3D CG软件blender相关概要以及使用的方法 blender是什么那么 大家知道这个名
  • 【Matlab】LM迭代估计法

    简介 在最近的传感器校准算法学习中 有一些非线性的代价函数求解使用最小二乘法很难求解 使用LM算法求解会简单许多 因此学习了一下LM算法的基础记录一下 LM 优化迭代算法时一种非线性优化算法 可以看作是梯度下降与高斯牛顿法的结合 综合了两者
  • 301跳转:http跳转https不带www跳转到带www

    写在 htaccess中 一 http跳转https RewriteCond SERVER PORT 443 RewriteRule https SERVER NAME 1 R 301 L 二 不带www跳转到带www RewriteCon
  • shell脚本-统计字符串中数字字母的个数

    bin bash read p 请输入一个字符串 str count1 0 count2 0 count3 0 count4 0 num str num for i in seq 0 num do ch str i 1 echo n ch
  • Mac 不小心断开移动硬盘导致磁盘无法读取和加载(顺利解决!)

    目录 1 问题 2 解决 2 1 终端中执行 diskutil list 2 2 输入 sudo diskutil mount dev disk0 disk1 disk2 同理 情况一 情况二 情况三 1 问题 不小心碰到USB插口 导致无
  • iOS证书(.p12)和描述文件(.mobileprovision)申请

    我们在做uniapp开发的时候 打包ios应用需要自有证书 而自有证书包含 p12和 mobileprovision这两个跟证书有关的文件 但是uniapp官方的教程 却是需要使用苹果mac系统去申请 假如没有mac电脑 则它的教程就没有参
  • Python pass 语句

    Python pass 是空语句 是为了保持程序结构的完整性 pass 不做任何事情 一般用做占位语句 Python 语言 pass 语句语法格式如下 pass 测试实例 usr bin python coding UTF 8 输出 Pyt
  • Spring boot实现Rest风格请求及底层原理

    Rest风格的介绍 如今各大公司都是使用restful风格来定义接口 restful也是一套接口的规范 restful可以使我们的接口更加简洁 快捷高效 透明 常见的Rest风格 CRUD 请求方式 对应属性 使用方式 GET 查询 表单请
  • 使用markedjs预览md文件

  • 神经网络时间序列预测PyTorch-Forecastin!

    来源 数据STUDIO 深度学习初学者 本文约5200字 建议阅读8分钟 本文为你介绍了神经网络时间序列预测PyTorch Forecastin PyTorch Forecasting 1 使用神经网络的时间序列预测对数据科学工作者和研究人
  • 地推里的t1结算啥意思

    T1结算 通常是指在地推活动中 结算员工提成的时间点 在这种情况下 T1代表第一天或第一周期的结算时间 即在活动结束后的第一天或第一周进行结算 例如 如果地推活动是在一个星期内进行的 那么T1结算可能是指在活动结束后的第一周内结算员工提成
  • 二叉树的创建和遍历实现

    1 前言 提到 树 Tree 结构 很容易联想到 大树 想到这是 一对多关系 特性的数据结构 其相关的名词 概念很多 子树 SubTree 结点 Node 根结点 Root 叶子 Leaf 终端结点 分支结点 非终端结点 内部结点 孩子 C
  • 在Windows2012下配置Mercurial

    所需的安装文件 xampp win32 1 8 3 4 VC11 installer exe python 2 7 7 amd64 msi tortoisehg 3 0 1 x64 msi mercurial 3 0 1 win amd64
  • windows下使用FFmpeg生成PCM音频文件并播放(通过命令的方式)

    一 PCM文件的定义 PCM文件 模拟音频信号经模数转换 A D变换 直接形成的二进制序列 该文件没有附加的文件头和文件结束标志 Windows的Convert工具能够把PCM音频格式的文件转换成Microsoft的WAV格式的文件 将音频
  • python 历险记(五)— python 中的模块

    目录 前言 基础 模块化程序设计 模块化有哪些好处 什么是 python 中的模块 引入模块有几种方式 模块的查找顺序 模块中包含执行语句的情况 用 dir 函数来窥探模块 python 的内置模块有哪些 结语 参考文档 系列文章列表 前言
  • 大千世界无奇不有,设计师又遇一无赖暴击!

    黑客技术 点击右侧关注 了解黑客的世界 Java开发进阶 点击右侧关注 掌握进阶之路 Linux编程 点击右侧关注 免费入门到精通 有网友发文感叹道 大千世界无奇不有 设计师又遇一无赖暴击 你们的设计我很满意 但是我不会付款的 为什么 你们
  • kylin: build cube Hbase: Region Server 意外退出

    背景 跑kylin 的 build cube 任务 总是在跑任务 数据量200M 的时候挂掉 各种调节yarn的参数都不行 关键跑的时候还没涉及到hbase 因为我跑的是kylin on druid 但是总是跑着跑着 直接ERROR 查看h
  • C++顺序检索、二分检索,并统计比较次数,体现最好、最差、平均三种情况

    实验四 include
  • vscode自动化写代码插件 自动生成代码插件

    直接在vscode中搜索chatGPT中文版安装即可 在代码仓中右侧 就去搜索你想要的代码啦 比如想搜索一个深拷贝
  • COW奶牛!Copy On Write机制了解一下

    前言 只有光头才能变强 在读 Redis设计与实现 关于哈希表扩容的时候 发现这么一段话 执行BGSAVE命令或者BGREWRITEAOF命令的过程中 Redis需要创建当前服务器进程的子进程 而大多数操作系统都采用写时复制 copy on