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, todo personalizado. Tengo una falla que parece estar relacionada con una condición de carrera que debería evitarse mediante una sección crítica del código.
Quiero algunos comentarios sobre mi enfoque para ver si mis secciones críticas pueden no implementarse correctamente para esta CPU. Estoy usando GCC. Sospecho que hay algún error sutil.
Además, ¿hay una biblioteca de código abierto que tenga este tipo de primitivas para ARM (o incluso una buena biblioteca ligera de spinlock / semephore)?
#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 usa 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, y estas se utilizan al principio y al final de las funciones para crear funciones reentrantes.
¡Gracias!
fuente
ldrex
ystrex
hacerlo correctamente. Aquí hay una página web que le muestra cómo usarldrex
estrex
implementar un spinlock.Respuestas:
La parte más difícil de manejar una sección crítica sin un sistema operativo no es realmente crear el mutex, sino descubrir qué debería suceder si el código quiere usar un recurso que actualmente no está disponible. Las instrucciones de exclusiva de carga y exclusiva de tienda condicional hacen que sea bastante fácil crear una función de "intercambio" que, dado un puntero a un entero, almacenará atómicamente un nuevo valor pero devolverá lo que el entero apuntado había contenido:
Dada una función como la anterior, se puede ingresar fácilmente un mutex a través de algo como
En ausencia de un sistema operativo, la principal dificultad a menudo radica en el código "no se pudo obtener mutex". 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 cierta información para indicar lo que quería hacer, y luego tener un código similar que adquiera el código compruebe el mutex cada vez que va a liberar el mutex para ver si una interrupción quería hacer algo mientras se mantenía el mutex y, de ser así, realizar la acción en nombre de la interrupción.
Aunque es posible evitar problemas con las interrupciones que desean utilizar recursos protegidos por mutex simplemente deshabilitando las interrupciones (y, de hecho, deshabilitar las interrupciones puede eliminar la necesidad de cualquier otro tipo de mutex), en general es deseable evitar deshabilitar las interrupciones por más tiempo del necesario.
Un compromiso útil puede ser usar un indicador como se describió anteriormente, pero tener el código de la línea principal que va a liberar las interrupciones de desactivación de mutex y verificar el indicador antes de hacerlo (volver a habilitar las interrupciones después de liberar el mutex). Tal enfoque no requiere dejar las interrupciones desactivadas 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 el mutex, existe el peligro de que entre el momento en que ve la bandera y el momento en que actúa sobre él, puede ser reemplazado por otro código que adquiere y libera el mutex y actúa sobre el indicador de interrupción; si el código de la línea principal no prueba la bandera de la interrupción después de liberar el mutex,
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 libera el recurso.
fuente
Esta es una forma dura de hacer secciones críticas; deshabilitar interrupciones. Es posible que no funcione si su sistema tiene / maneja fallas de datos. También aumentará la latencia de interrupción. El irqflags.h Linux tiene algunas macros que se encargan de esto. Las instrucciones
cpsie
ycpsid
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 ,
ldrex/strex
son más eficientes y pueden funcionar para formar un mutex para la sección crítica o pueden usarse con algoritmos sin bloqueo para deshacerse de la sección crítica.En cierto sentido,
ldrex/strex
parece un ARMv5swp
. Sin embargo, son mucho más complejos de implementar en la práctica. Necesita una memoria caché que funcione y la memoria de destino de lasldrex/strex
necesidades debe estar en la memoria caché. La documentación de ARM en elldrex/strex
es bastante nebulosa, ya que quieren mecanismos para trabajar en CPU que no sean Cortex-A. Sin embargo, para el Cortex-A, el mecanismo para mantener la caché local de la CPU sincronizada con otras CPU es el mismo que se utiliza para implementar lasldrex/strex
instrucciones. Para la serie Cortex-A, la reserva granual (tamaño de laldrex/strex
memoria reservada) es la misma 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 doblemente vinculada.Debe asegurarse de que la secuencia nunca se pueda adelantar . De lo contrario, puede obtener dos variables clave con interrupciones habilitadas y la liberación del bloqueo será incorrecta. Puede usar la
swp
instrucción con la memoria de clave para garantizar la coherencia en el ARMv5, pero esta instrucción está en desuso en el Cortex-A a favor,ldrex/strex
ya que funciona mejor para los sistemas con múltiples CPU.Todo esto depende de qué tipo de programación tenga su sistema. Parece que solo tienes líneas principales e interrupciones. A menudo necesita las primitivas de la sección crítica para tener algunos enganches al planificador dependiendo de los niveles (sistema / espacio de usuario / etc.) con los que desea que funcione la sección crítica.
Esto es difícil de escribir de forma portátil. Es decir, tales bibliotecas pueden existir para ciertas versiones de CPU ARM y para sistemas operativos específicos.
fuente
Veo varios problemas potenciales con esas secciones críticas. Hay advertencias y soluciones a todos estos, pero como resumen:
En primer lugar, definitivamente necesita algunas barreras de memoria del compilador . GCC implementa estos como clobbers . Básicamente, esta es una manera de decirle al compilador "No, no puede mover los accesos a la memoria a través de este ensamblaje en línea porque podría afectar el resultado de los accesos a la memoria". Específicamente, necesita ambos
"memory"
y"cc"
clobbers, tanto en las macros de inicio como de fin. Esto evitará que otras cosas (como llamadas a funciones) se reordenen también en relación con el ensamblado en línea, porque el compilador sabe que pueden tener accesos a la memoria. He visto GCC para el estado de retención ARM en registros de códigos de condición a través del ensamblaje en línea con"memory"
clobbers, por lo que definitivamente necesita el"cc"
clobber.En segundo lugar, estas secciones críticas están guardando y restaurando mucho más que solo si las interrupciones están habilitadas. Específicamente, están guardando y restaurando la mayor parte del CPSR (Registro de estado del programa actual) (el enlace es para Cortex-R4 porque no pude encontrar un buen diagrama para un A9, pero debería ser idéntico). Existen restricciones sutiles sobre qué partes del estado se pueden modificar realmente, pero aquí es más que necesario.
Entre otras cosas, esto incluye los códigos de condición (donde
cmp
se almacenan los resultados de instrucciones como para que las instrucciones condicionales posteriores puedan actuar sobre el resultado). El compilador definitivamente se confundirá con esto. Esto se puede solucionar fácilmente utilizando el"cc"
clobber como se mencionó anteriormente. Sin embargo, esto hará que el código falle cada vez, por lo que no suena como lo que está teniendo problemas. Sin embargo, es una bomba de tiempo, ya que modificar otro código aleatorio podría hacer que el compilador haga algo un poco diferente que se romperá con esto.Esto también intentará guardar / restaurar los bits de TI, que se utilizan para implementar la ejecución condicional de Thumb . Tenga en cuenta que si nunca ejecuta el código Thumb, esto no importa. Nunca he descubierto cómo el ensamblaje en línea de GCC trata con los bits de TI, aparte de concluir que no lo hace, lo que significa que el compilador nunca debe colocar el ensamblaje en línea en un bloque de TI y siempre espera que el ensamblaje termine fuera de un bloque de TI. Nunca he visto a GCC generar código que viole estas suposiciones, y he realizado un ensamblaje en línea bastante complejo con una gran optimización, por lo que estoy razonablemente seguro de que se mantienen. Esto significa que probablemente no intentará cambiar los bits de TI, en cuyo caso todo está bien. Intentar modificar estos bits se clasifica como "arquitectónicamente impredecible", por lo que podría hacer todo tipo de cosas malas, pero probablemente no hará nada en absoluto.
La última categoría de bits que se guardará / restaurará (además de los que realmente deshabilitarán las interrupciones) son los bits de modo. Estos probablemente no cambiarán, por lo que probablemente no importará, pero si tiene algún código que cambie deliberadamente los modos, estas secciones de interrupción podrían causar problemas. Cambiar entre los modos privilegiado y de usuario es el único caso de hacer esto que esperaría.
En tercer lugar, no hay nada que impida que una interrupción cambie otras partes de CPSR entre
MRS
yMSR
dentroARM_INT_LOCK
. Cualquiera de estos cambios podría sobrescribirse. En la mayoría de los sistemas razonables, las interrupciones asincrónicas no cambian el estado del código que están interrumpiendo (incluido CPSR). Si lo hacen, se hace muy difícil razonar sobre lo que hará el código. Sin embargo, es posible (cambiar el bit de desactivación de FIQ me parece más probable), por lo que debe considerar si su sistema lo hace.Así es como los implementaría de una manera que aborde todos los problemas potenciales que señalé:
Asegúrese de compilar
-mcpu=cortex-a9
porque al menos algunas versiones de GCC (como la mía) tienen por defecto una CPU ARM más antigua que no admitecpsie
ycpsid
.Usé en
ands
lugar de soloand
en,ARM_INT_LOCK
así que es una instrucción de 16 bits si se usa en el código Thumb. El"cc"
clobber es necesario de todos modos, por lo que es estrictamente un beneficio de rendimiento / tamaño del código.0
y1
son etiquetas locales , para referencia.Deben ser utilizables de la misma manera que sus versiones. El
ARM_INT_LOCK
es tan rápido / pequeño como el original. Desafortunadamente, no pude encontrar una manera de hacerlo deARM_INT_UNLOCK
manera segura en tan solo unas pocas instrucciones.Si su sistema tiene restricciones cuando IRQ y FIQ están deshabilitados, esto podría simplificarse. Por ejemplo, si siempre están deshabilitados juntos, puede combinarlos en uno
cbz
+cpsie if
así:Alternativamente, si no le importan los FIQ, entonces es similar a simplemente dejar de habilitarlos / deshabilitarlos por completo.
Si sabe que nada más cambia ninguno de los otros bits de estado en CPSR entre el bloqueo y el desbloqueo, entonces también puede usar continuar con algo muy similar a su código original, excepto con ambos
"memory"
y"cc"
clobbers en ambosARM_INT_LOCK
yARM_INT_UNLOCK
fuente
para secciones críticas relativamente simples, puede usar las instrucciones LDREX y STREX.
/programming/51795537/critical-sections-in-arm http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0204f/Cihbghef.html
fuente