写时复制就这么几行代码,还是不会?

bfae465bf31ed77abd6c3324b1369613.gif

作者 | 闪客

来源 | 低并发编程

这里讲的是 Linux 内核里的写时复制原理。

写时复制的原理网上讲述的文章很多,今天来一篇很直接的文章,通过看看 Linux 0.11 这个最简单的操作系统,从源码层面把写时复制的原理搞清楚。

很简单哦,你可别中途就放弃了。

直接干!

哦不行,干之前先来点储备知识,如果你已经有了这一 pa 可以略过,不过我估计你没有...

储备知识

坚持看完这部分,写时复制用到的这里的知识点只有其中一个位的值而已,但我把周边也给你讲讲。

32 位模式下,Intel 设计了页目录表页表两种结构,用来给程序员们提供分页机制。

在 Intel Volume-3 Chapter 4.3 Figure 4-4 中给出了页表和页目录表的数据结构,PDE 就是页目录表,PTE 就是页表。

e41387fec29541842077c8cecbc12e67.png

大部分的操作系统使用的都是 4KB 的页框大小,Linux 0.11 也是,所以我们只看 4KB 页大小时的情况即可。

一个由程序员给出的逻辑地址,要先经过分段机制的转化变成线性地址,再经过分页机制的转化变成物理地址

Figure 4-2 给出了线性地址到物理地址,也就是分页机制的转化过程。

b032acbb1943a1a09d2595903bc44206.png

这里的 PDE 就是页目录表,PTE 就是页表,刚刚说过了。

在手册接下来的 Table 4-5 和 Table 4-6 中,详细解释了页目录表和页表数据结构各字段的含义。

Table 4-5 是页目录表。

d1f40d1ff5ae3744c50900e846b37d29.png

Table 4-6 是页表。

c89b7d8e640d521f6df1d0af7bfe4b46.png

他们几乎都是一样的含义,我们就只看页表就好了,看一些比较重要的位。

31:12 表示页的起始物理地址,加上线性地址的后 12 位偏移地址,就构成了最终要访问的内存的物理地址,这个就不说了。

第 0 位是 P,表示 Present,存在位。

第 1 位是 RW,表示读写权限,0 表示只读,那么此时往这个页表示的内存范围内写数据,则不允许。

第 2 位是 US,表示用户态还是内核态,0 表示内核态,那么此时用户态的程序往这个内存范围内写数据,则不允许。

在 Linux 0.11 的 head.s 里,初次为页表设置的值如下。

setup_paging:...movl $pg0+7,_pg_dir     /* set present bit/user r/w */movl $pg1+7,_pg_dir+4       /*  --------- " " --------- */movl $pg2+7,_pg_dir+8       /*  --------- " " --------- */movl $pg3+7,_pg_dir+12      /*  --------- " " --------- */movl $pg3+4092,%edimovl $0xfff007,%eax     /*  16Mb - 4096 + 7 (r/w user,p) */std
1:  stosl...

后三位是 7,用二进制表示就是 111,即初始设置的 4 个页目录表和 1024 个页表,都是:

存在(1),可读写(1),用户态(1)

好了,储备知识就到这里。

如果你前面没读懂,你只需要知道,页表当中有一位是表示读\写的,而 Linux 0.11 初始化时,把它设置为了 1,表示可读写。

写时复制的本质

在调用 fork() 生成新进程时,新进程与原进程会共享同一内存区。只有当其中一个进程进行写操作时,系统才会为其另外分配内存页面。

7ad40393bf07fc906d340ca977e749be.png

不过我们考虑写时复制并不用这么复杂,去掉些细节就是。

原来的进程通过自己的页表占用了一定范围的物理内存空间。

bfbb8356585cdfbaa9f2c1afa5b292e3.png

调用 fork 创建新进程时,原本页表和物理地址空间里的内容,都要进行复制,因为进程的内存空间是要隔离的嘛。

386a0f67987eda9ad94d3d4b9b6133ee.png

但 fork 函数认为,复制物理地址空间里的内容,比较费时,所以姑且先只复制页表,物理地址空间的内容先不复制

c22b64681b9928d1483faf7aeaee7ecf.png

如果只有读操作,那就完全没有影响,复不复制物理地址空间里的内容就无所谓了,这就很赚。但如果有写操作,那就不得不把物理地址空间里的值复制一份,保证进程间的内存隔离。

5212ba204684e3cee8436666c50ab565.png

有写操作时,再复制物理内存,就叫写时复制

看看代码咋写的

有上述的现象,必然是在 fork 时,对页表做了手脚,这回知道为啥储备知识里讲页表结构了吧? 

同时,只要有写操作,就会触发写时复制这个逻辑,这是咋做到的呢?答案是通过中断,具体是缺页中断

好的,首先来看 fork。

这里只看其中关键的复制页表的代码。

int copy_page_tables(...) {...// 源页表和新页表一样this_page = *from_page_table;...// 源页表和新页表均置为只读this_page &= ~2;*from_page_table = this_page;...
}

还记得知识储备当中的页表结构吧,就是把 R/W 位置 0 了。

a627c9a6a1298aa4025bd990eb048131.png

用刚刚的 fork 图表示就是。

52e887fe71cba98dbcfb87b25c78e754.png

那么此时,再次对这块物理地址空间进行写操作时,就不允许了。

但不允许并不是真的不允许,Intel 会触发一个缺页中断,具体是 0x14 号中断,中断处理程序里边怎么处理,那就由 Linux 源码自由发挥了。

Linux 0.11 的缺页中断处理函数的开头是用汇编写的,看着太闹心了,这里我选 Linux 1.0 的代码给大家看,逻辑是一样的。

void do_page_fault(..., unsigned long error_code) {...   if (error_code & 1)do_wp_page(error_code, address, current, user_esp);elsedo_no_page(error_code, address, current, user_esp);...
}

可以看出,根据中断异常码 error_code 的不同,有不同的逻辑。

那触发缺页中断的异常码都有哪些呢?

在 Intel Volume-3 Chapter 4.7 Figure 4-12 中给出。

d74ed8b8798fcf7f6c9d661172b83394.png

可以看出,当 error_code 的第 0 位,也就是存在位为 0 时,会走 do_no_page 逻辑,其余情况,均走 do_wp_page 逻辑。

我们 fork 的时候只是将读写位变成了只读,存在位仍然是 1 没有动,所以会走 do_wp_page 逻辑。

void do_wp_page(unsigned long error_code,unsigned long address) {// 后面这一大坨计算了 address 在页表项的指针un_wp_page((unsigned long *)(((address>>10) & 0xffc) + (0xfffff000 &*((unsigned long *) ((address>>20) &0xffc)))));
}void un_wp_page(unsigned long * table_entry) {unsigned long old_page,new_page;old_page = 0xfffff000 & *table_entry;// 只被引用一次,说明没有被共享,那只改下读写属性就行了if (mem_map[MAP_NR(old_page)]==1) {*table_entry |= 2;invalidate();return;}// 被引用多次,就需要复制页表了new_page=get_free_page();mem_map[MAP_NR(old_page)]--;*table_entry = new_page | 7;invalidate();copy_page(old_page,new_page);
}// 刷新页变换高速缓冲宏函数
#define invalidate() \
__asm__("movl %%eax,%%cr3"::"a" (0))

我用图直接说明这段代码的细节。

刚刚 fork 完一个进程,是这个样子的对吧?

d58d3efc97976c3ce5045e898e9a8975.png

这是我们对着这个物理空间范围,写一个值,就会触发上述函数。

假如是进程 2 写的。

显然此时这个物理空间被引用了大于 1 次,所以要复制页面。

new_page=get_free_page();

并且更改页面只读属性为可读写。

*table_entry = new_page | 7;

图示就是这样。

b4207304788c30daa0931b8f801416e5.png

是不是很简单。

那此时如果进程 1 再写呢?那么引用次数就等于 1 了,只需要更改下页属性即可,不用进行页面复制操作。

if (mem_map[MAP_NR(old_page)]==1) ...

图示就是这样。

243decff488213ea67739cce18bdf818.png

就这么简单。

是不是从细节上看,和你原来理解的写时复制,还有点不同。

缺页中断的处理过程中,除了写时复制原理的 do_wp_page,还有个 do_no_page,是在页表项的存在位 P 为 0 时触发的。

这个和进程按需加载内存有关,如果还没加载到内存,会通过这个函数将磁盘中的数据复制到内存来~

497ab64f942b9f8467f5d9b86391e1be.gif

往期推荐

如果让你来设计网络

这种本机网络 IO 方法,性能可以翻倍!

留不住客户?该从你的系统上找找原因了!

明明还有大量内存,为啥报错“无法分配内存”?

3e784b5c9b9d6e98aa11c99134269014.gif

点分享

83ed3c72e5728f46fd491e0d91c8fe9d.gif

点收藏

d2d1259484d084aba2a9fab70936a208.gif

点点赞

949e77351b18799fbae9ac323a00137f.gif

点在看

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

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

相关文章

划重点|iOS15正式发布, 全新的通知推送系统,你必须要知道

简介: 今年友盟联合达摩院决策智能实验室讲算法技术,推出国内首个智能推送功能,帮助产品运营人员实现一键式触达的精细化运营。通过精心打磨的在线学习与优化算法,对推送人群与推送文案进行精准匹配,最大化用户点击量。…

万物互联下的碎片化怎么破?UINO优锘推出物联网产业元宇宙“物联森友会”

编辑 | 宋慧 出品 | CSDN云计算 移动浪潮之后,随着5G普及,IoT物联网已经成为下一个技术聚焦的领域。不过,万物互联中的“万物”终端,一直都存在着庞杂的应用场景,品类众多、技术指标各异的传感器,以及海量…

云湖共生-释放企业数据价值

摘要:2021云栖大会云原生企业级数据湖专场,阿里云智能资深技术专家、对象存储 OSS 负责人罗庆超为我们带来《云湖共生-释放企业数据价值》的分享。本文主要从数据湖存储演进之路、数据湖存储3.0 进化亮点等方面分享了云湖共生带来的企业价值。 摘要&…

数据湖构建与计算

简介: 2021云栖大会云原生企业级数据湖专场,阿里云智能高级产品专家李冰为我们带来《数据湖构建与计算》的分享。本文主要从数据的入湖和管理、引擎的选择展开介绍了数据湖方案降本增效的特性。 摘要:2021云栖大会云原生企业级数据湖专场&am…

天天讲路由,那 Linux 路由到底咋实现的!?

作者 | 张彦飞allen来源 | 开发内功修炼容器是一种新的虚拟化技术,每一个容器都是一个逻辑上独立的网络环境。Linux 上提供了软件虚拟出来的二层交换机 Bridge 可以解决同一个宿主机上多个容器之间互连的问题,但这是不够的。二层交换无法解决容器和宿主机…

治理企业“数据悬河”,阿里云DataWorks全链路数据治理新品发布

简介: 10月19日,在2021年云栖大会上,阿里云重磅发布DataWorks全链路数据治理产品体系,基于数据仓库,数据湖、湖仓一体等多种大数据架构,DataWorks帮助企业治理内部不断上涨的“数据悬河”,释放企…

函数式编程的Java编码实践:利用惰性写出高性能且抽象的代码

简介: 本文会以惰性加载为例一步步介绍函数式编程中各种概念,所以读者不需要任何函数式编程的基础,只需要对 Java 8 有些许了解即可。 作者 | 悬衡 来源 | 阿里技术公众号 本文会以惰性加载为例一步步介绍函数式编程中各种概念,所…

WorkManager从入门到实践,有这一篇就够了

作者 | Eason来源 | 程序员巴士前言一般情况下,我们大部分的操作都是在app打开的时候进行的,但是在某些情况下,即使app关闭了,我们也可能需要执行必要的动作,或者会采取一个动作,而不是让用户等待加载&…

终端卡顿优化的全记录

简介: 目前手机SOC的性能越来越少,很多程序员在终端程序的开发过程中也不太注意性能方面的优化,尤其是不注意对齐和分支优化,但是这两种问题一旦出现所引发的问题,是非常非常隐蔽难查的,不过好在项目中用到…

brew安装指定版本mysql,Mac 系统为 Valet 开发环境安装指定版本 MySQL

Mac 系统为 Valet 开发环境安装指定版本 MySQL由 学院君 创建于1年前, 最后更新于 5个月前版本号 #31547 views1 likes0 collects在 Mac 系统下使用 Valet 作为 Laravel 本地开发环境的话,需要自行安装 MySQL 数据库,我们通过 Homebrew 来安装。如果之前…

系统架构面临的三大挑战,看 Kubernetes 监控如何解决?

简介: 随着 Kubernetes 的不断实践落地,我们经常会遇到负载均衡、集群调度、水平扩展等问题。归根到底,这些问题背后都暴露出流量分布不均的问题。那么,我们该如何发现资源使用,解决流量分布不均问题呢?今天…

JavaScript 数组你都掰扯不明白,还敢说精通 JavaScript ?| 赠书

作者 | 哪吒来源 | CSDN博客最近小编在看文章的时候,总有很多刚刚入门的小白说精通这个,精通那个技术,更有意思的是,最近看到一则简历上说精通 JavaScript ,聊一聊发现数组还不明白,就对外说精通~所以今天小…

基于消息队列 RocketMQ 的大型分布式应用上云实践

简介: Apache RocketMQ 作为阿里巴巴开源的支撑万亿级数据洪峰的分布式消息中间件,在众多行业广泛应用。在选型过程中,开发者一定会关注开源版与商业版的业务价值对比。 那么,今天就围绕着商业版本的消息队列 RocketMQ和开源版本 …

Gartner发布2022年政府行业主要技术趋势:XaaS、数字化、超自动化等

作者 | Gartner研究副总裁 Bettina Tratz-Ryan Gartner杰出研究副总裁John Kost Gartner高级研究总监 相斌斌 供稿 | Gartner 政府领导人和民选官员在2022年不仅要面对巨大的挑战,还要把握疫情与经济复苏应对措施、不断变化的政治需求和持续数字化变革所带来的机遇…

RedShift到MaxCompute迁移实践指导

简介: 本文主要介绍Amazon Redshift如何迁移到MaxCompute,主要从语法对比和数据迁移两方面介绍,由于Amazon Redshift和MaxCompute存在语法差异,这篇文章讲解了一下语法差异 1.概要 本文档详细介绍了Redshift和MaxCompute之间SQL…

数字农业WMS库存操作重构及思考

简介: 数字农业库存管理系统在2020年时,部门对产地仓生鲜水果生产加工数字化的背景下应运而生。项目一期的数农WMS中的各类库存操作均为单独编写。而伴随着后续的不断迭代,这些库存操作间慢慢积累了大量的共性逻辑:如参数校验、幂…

数字营销行业大数据平台云原生升级实战

简介: 加和科技CTO 王可攀:技术是为业务价值而服务 王可攀 加和科技CTO 本文将基于加和科技大数据平台升级过程中面临的问题和挑战、如何调整数据平台架构以及调整后的变化,为大家介绍数字营销行业大数据平台云原生升级实战经验。主要分为以…

场景模型驱动自动化测试在盒马的探索及实践

简介: 盒马业务有如下几个特点:线上线下一体化、仓储配送一体化、超市餐饮一体化、经营作业一体化、多业态与平台化。在以上的种种原因,生鲜及物流体验是盒马的特点,但仓储配送一体化作业中,如何能更高效的提升测试效率…

基于 KubeVela 的 GitOps 交付

简介: KubeVela 是一个简单、易用、且高可扩展的云原生应用管理和交付平台,KubeVela 背后的 OAM 模型天然解决了应用构建过程中对复杂资源的组合、编排等管理问题,同时也将后期的运维策略模型化,这意味着 KubeVela 可以结合 GitOp…

BCS2022大会将提前至5月 网络安全产业空间扩容将成热门话题

年度网络安全的盛会即将开启。 2022年3月30日,2022年北京网络安全大会(BCS2022)新闻发布会在北京奇安信安全中心召开,宣布2022年北京网络安全大会“提档”至5月24日至26日,并与北辰集团国家会议中心达成战略合作&#…