Cómo implementar secciones críticas en ARM Cortex A9

14

Estoy transfiriendo un código heredado de un núcleo ARM926 a CortexA9. Este código es baremetal y no incluye un sistema operativo o bibliotecas estándar, todas personalizadas. Estoy teniendo una falla que parece estar relacionada con una condición de carrera que debe evitarse mediante un corte crítico del código.

Quiero algunos comentarios sobre mi enfoque para ver si mis secciones críticas pueden no estar implementadas correctamente para esta CPU. Estoy usando GCC. Sospecho que hay algún error sutil.

Además, ¿existe una biblioteca de código abierto que tenga estos tipos de primitivas para ARM (o incluso una buena biblioteca de spinlock / semephore de peso ligero)?

#define ARM_INT_KEY_TYPE            unsigned int
#define ARM_INT_LOCK(key_)   \
asm volatile(\
    "mrs %[key], cpsr\n\t"\
    "orr r1, %[key], #0xC0\n\t"\
    "msr cpsr_c, r1\n\t" : [key]"=r"(key_) :: "r1", "cc" );

#define ARM_INT_UNLOCK(key_) asm volatile ("MSR cpsr_c,%0" : : "r" (key_))

El código se utiliza de la siguiente manera:

/* lock interrupts */
ARM_INT_KEY_TYPE key;
ARM_INT_LOCK(key);

<access registers, shared globals, etc...>

ARM_INT_UNLOCK(key);

La idea de la "clave" es permitir secciones críticas anidadas, que se usan al principio y al final de las funciones para crear funciones reentrantes.

¡Gracias!

    
pregunta CodePoet

3 respuestas

13

La parte más difícil de manejar una sección crítica sin un sistema operativo no es realmente crear el mutex, sino más bien averiguar qué debería pasar si el código quiere usar un recurso que no está disponible actualmente. Las instrucciones de carga exclusiva y de tienda condicional hacen que sea bastante fácil crear una función de "intercambio" que, dado un puntero a un entero, almacenará de forma atómica un nuevo valor, pero devolverá lo que contenía el entero apuntado a:

int32_t atomic_swap(int32_t *dest, int32_t new_value)
{
  int32_t old_value;
  do
  {
    old_value = __LDREXW(&dest);
  } while(__STREXW(new_value,&dest);
  return old_value;
}

Dada una función como la anterior, uno puede ingresar fácilmente una exclusión mutua a través de algo como

if (atomic_swap(&mutex, 1)==0)
{
   ... do stuff in mutex ... ;
   mutex = 0; // Leave mutex
}
else
{ 
  ... couldn't get mutex...
}

En ausencia de un sistema operativo, la principal dificultad a menudo reside en el código "no se pudo obtener la exclusión". Si se produce una interrupción cuando un recurso protegido por mutex está ocupado, puede ser necesario que el código de manejo de interrupciones establezca un indicador y guarde alguna información para indicar lo que quería hacer, y luego tenga cualquier código de tipo principal que adquiera el control de exclusión cada vez que se va a liberar la exclusión para ver si una interrupción quería hacer algo mientras se mantenía la exclusión y, si es así, realizar la acción en nombre de la interrupción.

Aunque es posible evitar problemas con las interrupciones que deseen utilizar recursos protegidos por mutex simplemente deshabilitando las interrupciones (y, de hecho, las interrupciones que deshabilitan pueden eliminar la necesidad de cualquier otro tipo de exclusión mutua), en general, es conveniente evitar por más tiempo las deshabilitaciones. de lo necesario.

Un compromiso útil puede ser usar una bandera como se describió anteriormente, pero tener el código de la línea principal que va a liberar las interrupciones de desactivación de exclusión mutua y verificar la bandera antes mencionada (vuelva a habilitar las interrupciones después de liberar la exclusión) ). Este enfoque no requiere dejar las interrupciones deshabilitadas por mucho tiempo, pero protegerá contra la posibilidad de que si el código de la línea principal prueba la bandera de la interrupción después de liberar la exclusión mutua, existe el peligro de que entre el momento en que ve la bandera y el momento actúa sobre él, puede ser reemplazado por otro código que adquiere y libera el mutex y actúa sobre la bandera de interrupción; Si el código de la línea principal no prueba el indicador de la interrupción después de liberar el mutex, una interrupción que ocurre justo antes de que se libere el código de la línea principal, el mutex podría bloquearse pero no ser notada por la línea principal.

En cualquier caso, lo más importante será tener un medio por el cual el código que intenta usar un recurso protegido por mutex cuando no está disponible tendrá un medio de repetir su intento una vez que se libere el recurso.

    
respondido por el supercat
7

Esta es una forma de mano dura para hacer secciones críticas; desactivar las interrupciones. Puede que no funcione si su sistema tiene / maneja fallas de datos. También aumentará la latencia de interrupción. El Linux irqflags.h tiene algunas macros que manejan esto. Las instrucciones cpsie y cpsid pueden ser útiles; Sin embargo, no guardan el estado y no permitirán el anidamiento. cps no utiliza un registro.

Para la serie Cortex-A , los ldrex/strex son más eficientes y pueden funcionar para formar un mutex para la sección crítica o pueden usarse con algoritmos de bloqueo para deshacerse de la sección crítica.

En cierto sentido, el ldrex/strex parece un ARMv5 swp . Sin embargo, son mucho más complejos de implementar en la práctica. Necesita un caché de trabajo y la memoria de destino del ldrex/strex debe estar en el caché. La documentación ARM en el ldrex/strex es bastante nebulosa, ya que quieren que los mecanismos funcionen en CPU que no sean Cortex-A. Sin embargo, para el Cortex-A, el mecanismo para mantener la memoria caché local de la CPU sincronizada con otras CPU es el mismo que se utilizó para implementar las instrucciones ldrex/strex . Para la serie Cortex-A, reserve granual (tamaño de ldrex/strex de memoria reservada) es lo mismo que una línea de caché; también debe alinear la memoria con la línea de caché si tiene la intención de modificar varios valores, como con una lista con doble enlace.

  

Sospecho que hay algún error sutil.

mrs %[key], cpsr
orr r1, %[key], #0xC0  ; context switch here?
msr cpsr_c, r1

Debe asegurarse de que la secuencia nunca se pueda anular . De lo contrario, puede obtener dos variables clave con las interrupciones habilitadas y la liberación del bloqueo será incorrecta. Puede utilizar la instrucción swp con la memoria clave para garantizar la coherencia en el ARMv5, pero esta instrucción está en desuso en el Cortex-A en favor de ldrex/strex , ya que funciona mejor para múltiples CPU sistemas.

Todo esto depende del tipo de programación que tenga su sistema. Parece que solo tienes líneas principales e interrupciones. A menudo necesita las primitivas sección crítica para tener algunos enganches al programador en función de los niveles (sistema / espacio de usuario / etc) con los que desea que funcione la sección crítica.

  

Además, ¿existe una biblioteca de código abierto que tenga estos tipos de primitivas para ARM (o incluso una buena biblioteca de spinlock / semephore de peso ligero)?

Esto es difícil de escribir de manera portátil. Es decir, estas bibliotecas pueden existir para ciertas versiones de CPU ARM y para sistemas operativos específicos.

    
respondido por el artless noise
0

para secciones críticas relativamente simples, puede usar las instrucciones LDREX y STREX.

enlace enlace

    
respondido por el Anthony Bachler

Lea otras preguntas en las etiquetas