例如,我正在研究一个古老的内核,想知道它是否真的实现了Copy on Write。有没有一种方法(最好是在C语言中编程)来发现?
没有,没有一个可靠的程序化的方法可以在用户区进程中找到。
COW背后的想法是,它应该对用户代码完全透明。你的代码接触到各个页面,页面故障被调用,内核复制相应的页面,然后你的进程就会恢复,就像什么都没发生一样。
我偶然发现了这个比较老的问题,我看到其他人已经指出,"检测CoW "的意义不大,因为Linux已经暗示了CoW。
然而我觉得这个问题非常有趣,虽然从技术上讲,人们应该无法检测到这种应该对用户空间进程完全透明的内核机制,但实际上有一些架构上的特定方式(即侧通道)可以被利用来确定Copy on Write是否发生。
在x86处理器上,支持 受限的事务性记忆当出现异常(如页面故障)时,你可以利用内存事务被中止这一事实。给定一个有效的地址,这个信息可以用来检测一个页面是否驻留在内存中(类似于在内存中使用的 minicore(2)
),甚至检测Copy on Write。
这里有一个工作实例。注意:检查你的处理器是否支持RTM,通过查看 /proc/cpuinfo
对于 rtm
标志,并使用GCC进行编译 未经优化 并与 -mrtm
标志。
#include <stdio.h>
#include <unistd.h>
#include <sys/mman.h>
#include <immintrin.h>
/* Use x86 transactional memory to detect a page fault when trying to write
* at the specified address, assuming it's a valid address.
*/
static int page_dirty(void *page) {
unsigned char *p = page;
if (_xbegin() == _XBEGIN_STARTED) {
*p = 0;
_xend();
/* Transaction successfully ended => no context switch happened to
* copy page into virtual memory of the process => page was dirty.
*/
return 1;
} else {
/* Transaction aborted => page fault happened and context was switched
* to copy page into virtual memory of the process => page wasn't dirty.
*/
return 0;
}
/* Should not happen! */
return -1;
}
int main(void) {
unsigned char *addr;
addr = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (addr == MAP_FAILED) {
perror("mmap failed");
return 1;
}
// Write to trigger initial page fault and actually reserve memory
*addr = 123;
fprintf(stderr, "Initial state : %d\n", page_dirty(addr));
fputs("----- fork -----\n", stderr);
if (fork()) {
fprintf(stderr, "Parent before : %d\n", page_dirty(addr));
// Read (should NOT trigger Copy on Write)
*addr;
fprintf(stderr, "Parent after R: %d\n", page_dirty(addr));
// Write (should trigger Copy on Write)
*addr = 123;
fprintf(stderr, "Parent after W: %d\n", page_dirty(addr));
} else {
fprintf(stderr, "Child before : %d\n", page_dirty(addr));
// Read (should NOT trigger Copy on Write)
*addr;
fprintf(stderr, "Child after R : %d\n", page_dirty(addr));
// Write (should trigger Copy on Write)
*addr = 123;
fprintf(stderr, "Child after W : %d\n", page_dirty(addr));
}
return 0;
}
在我的机器上的输出。
Initial state : 1
----- fork -----
Parent before : 0
Parent after R: 0
Parent after W: 1
Child before : 0
Child after R : 0
Child after W : 1
正如你所看到的,对标记为CoW的页面进行写入(在本例中是在fork之后) 会导致事务失败,因为会触发一个页面故障异常,并导致事务中止。在事务中止之前,硬件会将更改的内容进行还原。在对页面进行写入后,再次尝试做同样的事情,结果是事务正确终止,函数返回了 1
.
当然,这不应该被认真使用,而只是作为一个有趣的练习。因为RTM事务会因为任何类型的异常和上下文切换而被中止,所以假阴性是可能的(例如,如果进程在事务的中间被内核抢占了)。保持事务代码非常短(在上面的例子中,只有一个分支和一个赋值)。*p = 0
)是必不可少的。也可以进行多次检测,以避免假阴性。