15

我最近在自定义 Linux 内核(2.6.31.5,x86)驱动程序中遇到了一个问题,其中 copy_to_user 不会定期将任何字节复制到用户空间。它会返回传递给它的字节数,表明它没有复制任何东西。经过代码检查,我们发现代码在调用 copy_to_user 时禁用了中断,这违反了它的合同。更正此问题后,问题停止发生。因为这个问题很少发生,我需要证明禁用中断导致了这个问题。

如果您从 arch/x86/lib/usercopy_32.c rep 查看下面的代码片段;movsl 通过 CX 中的计数将单词复制到用户空间。大小在退出时用 CX 更新。如果 movsl 正确执行,CX 将为 0。因为 CX 不为零,所以 movs? 为了符合 copy_to_user 的定义和观察到的行为,必须没有执行指令。

/* Generic arbitrary sized copy.  */
#define __copy_user(to, from, size)                 \
do {                                    \
    int __d0, __d1, __d2;                       \
    __asm__ __volatile__(                       \
        "   cmp  $7,%0\n"                   \
        "   jbe  1f\n"                  \
        "   movl %1,%0\n"                   \
        "   negl %0\n"                  \
        "   andl $7,%0\n"                   \
        "   subl %0,%3\n"                   \
        "4: rep; movsb\n"                   \
        "   movl %3,%0\n"                   \
        "   shrl $2,%0\n"                   \
        "   andl $3,%3\n"                   \
        "   .align 2,0x90\n"                \
        "0: rep; movsl\n"                   \
        "   movl %3,%0\n"                   \
        "1: rep; movsb\n"                   \
        "2:\n"                          \
        ".section .fixup,\"ax\"\n"              \
        "5: addl %3,%0\n"                   \
        "   jmp 2b\n"                   \
        "3: lea 0(%3,%0,4),%0\n"                \
        "   jmp 2b\n"                   \
        ".previous\n"                       \
        ".section __ex_table,\"a\"\n"               \
        "   .align 4\n"                 \
        "   .long 4b,5b\n"                  \
        "   .long 0b,3b\n"                  \
        "   .long 1b,2b\n"                  \
        ".previous"                     \
        : "=&c"(size), "=&D" (__d0), "=&S" (__d1), "=r"(__d2)   \
        : "3"(size), "0"(size), "1"(to), "2"(from)      \
        : "memory");                        \
} while (0)

我的两个想法是:

  1. 当中断被禁用时,页面错误不会发生然后rep;动?被跳过而不做任何事情。然后返回值将是 CX,或未复制到用户空间的数量,如定义指定和观察到的行为。
  2. 确实发生了页面错误,但是由于禁用了中断,linux无法处理它,因此页面错误处理程序跳过了该指令,尽管我不知道页面错误处理程序会如何执行此操作。同样,在这种情况下,CX 将保持不变并且返回值将是正确的。

谁能指出英特尔手册中指定此行为的部分,或者指出任何其他可能有用的 Linux 源代码?

4

2 回答 2

9

我找到了答案。我的#2 建议是正确的,而且机制就在我面前。确实会发生页面错误,但 fixup_exception 机制用于提供异常/继续机制。本节将条目添加到异常处理程序表:

    ".section __ex_table,\"a\"\n"               \
    "   .align 4\n"                 \
    "   .long 4b,5b\n"                  \
    "   .long 0b,3b\n"                  \
    "   .long 1b,6b\n"                  \
    ".previous"                     \

这表示:如果 IP 地址是第一个条目并且在故障处理程序中遇到异常,则将 IP 地址设置为第二个地址并继续。

所以如果异常发生在“4:”,跳转到“5:”。如果异常发生在“0:”,则跳转到“3:”,如果异常发生在“1:”,则跳转到“6:”。

缺失的部分在 arch/x86/mm/fault.c 的 do_page_fault() 中:

/*
 * If we're in an interrupt, have no user context or are running
 * in an atomic region then we must not take the fault:
 */
if (unlikely(in_atomic() || !mm)) {
    bad_area_nosemaphore(regs, error_code, address);
    return;
}

in_atomic 返回 true 因为我们处于 write_lock_bh() 锁中!bad_area_nosemaphore 最终会进行修复。

如果会发生 page_fault(这不太可能,因为工作空间的概念),那么函数调用将失败并跳出 __copy_user 宏,未复制的字节设置为 size,因为抢占被禁用。

于 2012-09-27T20:53:12.350 回答
4

页面错误不是可屏蔽中断。事实上,它们在技术上根本不是中断——而是例外,尽管我同意区别更具语义。

当您在禁用中断的原子上下文中调用它时,您的 copy_to_user 失败的原因是代码对此进行了明确的检查。

http://lxr.free-electrons.com/source/arch/x86/lib/usercopy_32.c#L575

于 2012-09-27T13:45:57.613 回答