如何找到底层Linux内核是否支持Copy on Write?

时间:2012-03-06 15:28:13

标签: c linux process linux-kernel fork

例如,我正在研究一个古老的内核,并想知道它是否真的实现了Copy on Write。找出方法(最好用C编程)?

2 个答案:

答案 0 :(得分:1)

不,没有一种可靠的编程方式可以在用户空间进程中找到它。

COW背后的想法是它应该对用户代码完全透明。您的代码触及各个页面,调用页面错误,内核复制相应的页面,您的进程将恢复,就像没有发生任何事情一样。

答案 1 :(得分:0)

我偶然发现了这个相当古老的问题,并且我看到其他人已经指出,“检测CoW”没有太大意义,因为Linux已经暗示了CoW。

但是我发现这个问题很有趣,尽管从技术上讲,这应该不能检测对用户空间进程完全透明的这种内核机制,但是实际上存在一些特定于体系结构的方式(例如,旁通道)利用以确定是否发生“写时复制”。

在支持Restricted Transactional Memory的x86处理器上,您可以利用以下事实:当发生诸如页面错误之类的异常时,内存事务将中止。给定一个有效的地址,此信息可用于检测页面是否驻留在内存中(类似于使用minicore(2)),甚至可以检测写入时复制。

这是一个可行的例子。注意:通过查看/proc/cpuinfo的{​​{1}}标志来检查处理器是否支持RTM,并使用GCC 无优化rtm标志进行编译。

-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;
}

如您所见,写入标记为CoW的页面(在本例中为fork之后)会导致事务失败,因为触发了页面错误异常并导致事务中止。事务中止之前,更改将由硬件还原。写入页面后,再次尝试执行相同的操作将导致事务正确终止,并且函数返回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

当然,不应真正认真地使用此方法,而应将其视为有趣而有趣的练习。由于RTM事务因任何类型的异常以及上下文切换而中止,因此可能会产生否定错误(例如,如果进程在事务中间被内核抢占了该进程)。保持事务代码非常短(在上述情况下,仅是分支和分配1)至关重要。还可以进行多次测试以避免误报。