内核经常会在不同进程之间共享mem pages. 比如,程序的代码段总是共享的。当内核知道所有涉及的进程的内存内容相同时,进程之间业江共享writable pages. 当一个进程调用fork()系统调用时,所有的writable pages将会被转化为cow pages,并且同时被父进程和子进程共享。主要他们任意一个进程不修改page中的内容,这个共享就会一直持续,内存使用量就会相应减少。
fork()的cow能够生效的原因是因为, 内核知道每个进程都希望(也都能知道)在这些page中找到相同的内容。但是如果内核不知道这些进程的页面对应的内容相同,那就无法共享相同的pages。人们可能认为这个通常都不是问题,但是kvm的开发者已经提出几种情况,在这样的情况下进程是有机会共享的。
除了这些典型的系统,对于运行了多个虚拟租户的主机,这些租户之间却不会共享进程树,但是他们所使用的很大一块儿内存很有可能是持有相同的内容。如果这个主机能够有一种方法强迫内容相同的页共享。这将会节省更多的内存,这样就能够运行更多的虚机。这种情况应该引擎虚机开发者的关注。所以RedHat的开发者设计了一款机制,叫KSM。
KSM在主机上的表现形式是虚拟设备:/dev/ksm。所有想参与内存共享的进程,都可以打开这个设备,通过ioctl()调用来注册它自己的内存地址到KSM driver中。如果内存共享机制被打开了,内核就会开始查询相同的内存也。
KSM的算法也很简单,KSM driver它作为内核的一个thread,会选择其中一个注册的内存区域并开始扫描它。对于每一个在物理内存中有映射的虚拟内存,KSM会产生一个hash,然后去扫另一个pages,找到相同页内容的pages之后。就会使用memcmp()调用去查看页面的真是内容,确认真实内容确实相同后,所有的引用了“scanned pages”的进程都会指向另外一个cow pages,并且将多余的页面返回给系统。如果没有人修改这个cow page,那共享就会一直生效。
内核线程会扫描到醉倒的内存pages数,并休息一段时间,这些扫描页面数量和扫描sleep时间都会作为参数传递给ioctl()调用,用户空间进程可以通过调用ioctl来停止扫描。页面的扫描,散列、比较都会消耗CPU。
引用: https://lwn.net/Articles/306704/
参考: https://www.kernel.org/doc/html/latest/admin-guide/mm/ksm.html#ksm-daemon-sysfs-interface