2012-09-26 53 views
15

我最近在自定義Linux內核(2.6.31.5,x86)驅動程序中遇到了一個問題,其中copy_to_user將定期不將任何字節複製到用戶空間。它會返回傳遞給它的字節數,表明它沒有複製任何內容。在代碼檢查後,我們發現代碼在調用copy_to_user時違反了中斷,而違反了合同。糾正後,問題停止發生。由於這個問題很少發生,我需要證明禁用中斷導致了這個問題。當x86指令禁用中斷時,mov指令導致頁面錯誤時會發生什麼情況?

如果您從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) 

的2個想法,我是:

  1. 當中斷被禁止,不會出現缺頁和 然後代表; MOVS?沒有做任何事情就跳過了。然後返回值 將爲CX,或者未複製到用戶空間的金額,如 定義指定的和所觀察到的行爲。
  2. 頁面錯誤確實發生,但linux無法處理它,因爲中斷被禁用,所以頁面錯誤處理程序跳過指令,雖然我不知道頁面錯誤處理程序如何執行此操作。同樣,在這種情況下,CX將保持不變並且返回值是正確的。

任何人都可以指向英特爾手冊中指定此行爲的部分,或者指向任何其他可能有用的Linux源代碼?

+0

你提到「代碼禁止中斷」。你可以詳細說明哪些中斷和如何?... – TheCodeArtist

+0

@TheCodeArtist:write_lock_bh();舉行,我的理解是禁用軟件中斷。 – Edward

+0

@TheCode藝術家:謝謝!您的評論讓我更加仔細地研究了write_lock_bh(),讓我看到了方向! – Edward

回答

7

我已經找到了答案。我的#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:」。

缺少的部分是在do_page_fault()在arch/86/MM/fault.c:因爲我們是在一個write_lock_bh()鎖定

/* 
* 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返回真! bad_area_nosemaphore最終做了修復。

如果發生page_fault(這不太可能,因爲工作空間的概念),那麼函數調用會失敗並跳出__copy_user宏,並且未複製的字節被設置爲大小,因爲搶先被禁用。

4

頁面錯誤不是可屏蔽的中斷。事實上,它們在技術上根本不是中斷 - 但是除了例外,儘管我同意這種區別更具語義。

當你在禁用中斷的原子上下文中調用它時,你的copy_to_user失敗的原因是因爲代碼對此有明確的檢查。

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

+0

感謝您的回答。電話大部分時間都在工作。它只是很少失敗。如果是因爲原子環境,我會期望它總是失敗。反正這個條件不應該在pentium上執行。 ,[根據Linus](http://answers.softpicks.net/answers/topic/-BUG-__copy_to_user_inatomic-broken-on-non-Pentium-machines-2056019-1.htm)boot_cpu_data.wp_works_ok應該== 0一切都大於386。 – Edward