2012-09-26 30 views
15

Recientemente encontré un problema en un controlador kernel Linux personalizado (2.6.31.5, x86) donde copy_to_user periódicamente no copiaba ningún byte en el espacio del usuario. Devolvería el recuento de bytes pasados, lo que indica que no había copiado nada. Después de la inspección del código, descubrimos que el código estaba deshabilitando las interrupciones al llamar a copy_to_user, que infringe su contrato. Después de corregir esto, el problema dejó de ocurrir. Debido a que el problema sucedió con poca frecuencia, necesito demostrar que la desactivación de las interrupciones causó el problema.¿Qué sucede cuando una instrucción mov causa un error de página con interrupciones deshabilitadas en x86?

Si mira el siguiente fragmento de código de arch/x86/lib/usercopy_32.c rep; movsl copia las palabras en el espacio de usuario según el conteo en CX. El tamaño se actualiza con CX en la salida. CX será 0 si el movsl se ejecuta correctamente. Debido a que CX no es cero, ¿los movs? las instrucciones no deben haberse ejecutado, para ajustarse a la definición de copy_to_user y al comportamiento observado.

/* 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) 

Los 2 ideas que tengo son:

  1. cuando las interrupciones están deshabilitadas, el error de página no se produce y luego representante; Movs? se omite sin hacer nada. El valor de retorno sería CX, o la cantidad no copiada en el espacio de usuario, como especifica la definición y el comportamiento observado.
  2. Se produce el error de página, pero Linux no puede procesarlo porque las interrupciones están deshabilitadas, por lo que el controlador de fallas de página omite las instrucciones, aunque no sé cómo lo haría el controlador de fallas de página. De nuevo, en este caso CX permanecería sin modificaciones y el valor de retorno sería correcto.

¿Alguien me puede indicar las secciones de los manuales de Intel que especifican este comportamiento o indicarme alguna fuente adicional de Linux que pueda ser útil?

+0

mencionas que "el código estaba deshabilitando las interrupciones". ¿Puede explicar qué interrupciones y cómo? ... – TheCodeArtist

+0

@TheCodeArtist: write_lock_bh(); se llevó a cabo, lo cual, según mi entender, deshabilita las interrupciones de software. – Edward

+0

@TheCodeArtist: ¡Gracias! ¡Tu comentario me hizo mirar en write_lock_bh() mucho más de cerca, mostrándome el camino! – Edward

Respuesta

7

que he encontrado la respuesta. Mi sugerencia # 2 era correcta y el mecanismo estaba justo frente a mi cara. El error de página ocurre, pero el mecanismo de excepción de fixup se usa para proporcionar un mecanismo de excepción/continuar. En esta sección se agrega entradas a la tabla de gestión de excepciones:

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

Esta dice: si la dirección IP es la primera entrada y una excepción se encuentra en un administrador de fallos, a continuación, establecer la dirección IP a la segunda dirección y continuar.

Entonces, si la excepción ocurre en "4:", salte a "5:". Si la excepción ocurre en "0:", salte a "3:" y si la excepción ocurre en "1:" salte a "6:".

La pieza que falta es en do_page_fault() en arch/x86/mm/fault.c:

/* 
* 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 regresaron cierto porque estamos en una cerradura write_lock_bh()! bad_area_nosemaphore eventualmente hace la corrección.

Si ocurriera un page_fault (que era improbable, debido al concepto de espacio de trabajo), la llamada de función fallaría y saltaría de la macro __copy_user, con los bytes no copiados ajustados porque el derecho de prioridad se deshabilitó.

4

Las fallas de página no son interrupciones de máscara. De hecho, técnicamente no son interrupciones, sino excepciones, aunque estoy de acuerdo en que la diferencia es más semántica.

El motivo por el que su copy_to_user falló cuando lo llamó en contexto atómico con interrupciones deshabilitadas es porque el código tiene una verificación explícita para esto.

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

+0

Gracias por su respuesta. La llamada funcionó la mayor parte del tiempo. Solo falló muy raramente. Si fuera por el contexto atómico, esperaría que falle siempre. Esa condición no se debe ejecutar en un pentium de todos modos. , [Según 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 debería == 0 en todo más grande que un 386. – Edward

Cuestiones relacionadas