只有光头才能变强
在读《Redis设计与实现》关于哈希表扩容的时候,发现这么一段话:
执行BGSAVE命令或者BGREWRITEAOF命令的过程中,Redis需要创建当前服务器进程的子进程,而大多数操作系统都采用写时复制(copy-on-write)来优化子进程的使用效率,所以在子进程存在期间,服务器会提高负载因子的阈值,从而避免在子进程存在期间进行哈希表扩展操作,避免不必要的内存写入操作,最大限度地节约内存。
触及到知识的盲区了,于是就去搜了一下copy-on-write写时复制这个技术究竟是怎么样的。发现涉及的东西蛮多的,也挺难读懂的。于是就写下这篇笔记来记录一下我学习copy-on-write的过程。
本文力求简单讲清copy-on-write这个知识点,希望大家看完能有所收获。
在说明Linux下的copy-on-write机制前,我们首先要知道两个函数:fork()
和exec()
。需要注意的是exec()
并不是一个特定的函数, 它是一组函数的统称, 它包括了execl()
、execlp()
、execv()
、execle()
、execve()
、execvp()
。
首先我们来看一下fork()
函数是什么鬼:
fork is an operation whereby a process creates a copy of itself.
fork是类Unix操作系统上创建进程的主要方法。fork用于创建子进程(等同于当前进程的副本)。
如果接触过Linux,我们会知道Linux下init进程是所有进程的爹(相当于Java中的Object对象)
下面以例子说明一下fork吧:
#include
#include
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),所以子进程同样是会执行fork()
之后的代码。所以说:
fpid变量
的值是子进程的pidfpid变量
的值是0从上面我们已经知道了fork会创建一个子进程。子进程的是父进程的副本。
exec函数的作用就是:装载一个新的程序(可执行映像)覆盖当前进程内存空间中的映像,从而执行不同的任务。
我去画张图来理解一下:
参考资料:
fork()会产生一个和父进程完全相同的子进程(除了pid)
如果按传统的做法,会直接将父进程的数据拷贝到子进程中,拷贝完之后,父进程和子进程之间的数据段和堆栈是相互独立的。
但是,以我们的使用经验来说:往往子进程都会执行exec()
来做自己想要实现的功能。
exec()
,原有的数据会被清空)既然很多时候复制给子进程的数据是无效的,于是就有了Copy On Write这项技术了,原理也很简单:
另外的表达方式:
在fork之后exec之前两个进程用的是相同的物理空间(内存区),子进程的代码段、数据段、堆栈都是指向父进程的物理空间,也就是说,两者的虚拟空间不同,但其对应的物理空间是同一个。
当父子进程中有更改相应段的行为发生时,再为子进程相应的段分配物理空间。
如果不是因为exec,内核会给子进程的数据段、堆栈段分配相应的物理空间(至此两者有各自的进程空间,互不影响),而代码段继续共享父进程的物理空间(两者的代码完全相同)。
而如果是因为exec,由于两者执行的代码不同,子进程的代码段也会分配单独的物理空间。
Copy On Write技术实现原理:
fork()之后,kernel把父进程中所有的内存页的权限都设为read-only,然后子进程的地址空间指向父进程。当父子进程都只读内存时,相安无事。当其中某个进程写内存时,CPU硬件检测到内存页是read-only的,于是触发页异常中断(page-fault),陷入kernel的一个中断例程。中断例程中,kernel就会把触发的异常的页复制一份,于是父子进程各自持有独立的一份。
Copy On Write技术好处是什么?
Copy On Write技术缺点是什么?
几句话总结Linux的Copy On Write技术:
exec()
把当前进程映像替换成新的进程文件,完成自己想要实现的功能。参考资料:
基于上面的基础,我们应该已经了解COW这么一项技术了。
下面我来说一下我对《Redis设计与实现》那段话的理解:
参考资料:
下面来看看文件系统中的COW是啥意思:
Copy-on-write在对数据进行修改的时候,不会直接在原来的数据位置上进行操作,而是重新找个位置修改,这样的好处是一旦系统突然断电,重启之后不需要做Fsck。好处就是能保证数据的完整性,掉电的话容易恢复。
参考资料:
最后我们再来看一下写时复制的思想(摘录自维基百科):
写入时复制(英语:Copy-on-write,简称COW)是一种计算机程序设计领域的优化策略。其核心思想是,如果有多个调用者(callers)同时请求相同资源(如内存或磁盘上的数据存储),他们会共同获取相同的指针指向相同的资源,直到某个调用者试图修改资源的内容时,系统才会真正复制一份专用副本(private copy)给该调用者,而其他调用者所见到的最初的资源仍然保持不变。这过程对其他的调用者都是透明的(transparently)。此作法主要的优点是如果调用者没有修改该资源,就不会有副本(private copy)被建立,因此多个调用者只是读取操作时可以共享同一份资源。
至少从本文我们可以总结出:
其实在Java里边,也有Copy On Write技术。
这部分留到下一篇来说,敬请期待~
如果大家有更好的理解方式或者文章有错误的地方还请大家不吝在评论区留言,大家互相学习交流~~~
参考资料:
一个坚持原创的Java技术公众号:Java3y,欢迎大家关注
3y所有的原创文章: