Veo varios problemas potenciales en esas secciones críticas. Hay advertencias y soluciones para todos ellos, pero como resumen:
- No hay nada que impida al compilador mover el código a través de estas macros, por optimización o por otras razones aleatorias.
- Guardan y restauran algunas partes del estado del procesador que el compilador espera que el ensamblaje en línea deje en paz (a menos que se le indique lo contrario).
- No hay nada que impida que se produzca una interrupción en medio de la secuencia y que cambie el estado entre el momento de la lectura y el de la escritura.
En primer lugar, definitivamente necesitas algo de barreras de memoria del compilador . GCC los implementa como clobbers . Básicamente, es una forma de decirle al compilador "No, no puedes mover los accesos de memoria a través de esta pieza de ensamblaje en línea porque podría afectar al resultado de los accesos de memoria". Específicamente, se necesitan ambos "memory"
y "cc"
en las macros de inicio y fin. Esto evitará que otras cosas (como las llamadas a funciones) sean reordenadas en relación con el ensamblaje en línea también, porque el compilador sabe que podrían tener accesos a la memoria. He visto que GCC para ARM mantiene el estado en los registros de código de condición a través del ensamblaje en línea con "memory"
de la capa, por lo que definitivamente se necesita el "cc"
de la capa.
En segundo lugar, estas secciones críticas guardan y restauran mucho más que si las interrupciones están activadas. Específicamente, están guardando y restaurando la mayor parte del CPSR (Registro de estado del programa actual) (el enlace es para el Cortex-R4 porque no pude encontrar un buen diagrama para un A9, pero debería ser idéntico). Hay restricciones sutiles en torno a qué trozos de estado se pueden modificar realmente, pero aquí es más que necesario.
Entre otras cosas, esto incluye los códigos de condición (donde los resultados de las instrucciones como cmp
se almacenan para que las instrucciones condicionales posteriores puedan actuar sobre el resultado). Esto confundirá al compilador. Esto es fácilmente solucionable utilizando la función "cc"
de la que se habla más arriba. Sin embargo, esto hará que el código falle cada vez, por lo que no suena como lo que está viendo problemas. Sin embargo, es una especie de bomba de relojería, ya que modificar otro código al azar puede hacer que el compilador haga algo diferente que se rompa con esto.
Esto también intentará guardar/restaurar los bits de TI, que se utilizan para implementar la ejecución condicional del pulgar . Ten en cuenta que si nunca ejecutas el código del Pulgar, esto no importa. Nunca he averiguado cómo el ensamblaje en línea de GCC se ocupa de los bits IT, aparte de concluir que no lo hace, lo que significa que el compilador nunca debe poner el ensamblaje en línea en un bloque IT y siempre espera que el ensamblaje termine fuera de un bloque IT. Nunca he visto que GCC genere código que viole estas suposiciones, y he hecho algunos ensamblajes en línea bastante intrincados con fuerte optimización, así 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 es clasificado como "arquitectónicamente imprevisible" Así 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án/restaurarán (además de los que realmente desactivan las interrupciones) son los bits de modo. Estos probablemente no cambiarán, así que probablemente no importará, pero si tienes algún código que cambie deliberadamente de modo estas secciones de interrupción podrían causar problemas. Cambiar entre el modo privilegiado y el modo de usuario es el único caso de hacer esto que yo esperaría.
En tercer lugar, no hay nada que impida que una interrupción cambie otras partes de CPSR entre el MRS
y MSR
en ARM_INT_LOCK
. Cualquier cambio de este tipo podría ser sobrescrito. En la mayoría de los sistemas razonables, las interrupciones asíncronas no cambian el estado del código que interrumpen (incluyendo 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 lo más probable), así que deberías considerar si tu sistema hace esto.
Así es como yo los implementaría de manera que se resuelvan todos los problemas potenciales que señalé:
#define ARM_INT_KEY_TYPE unsigned int
#define ARM_INT_LOCK(key_) \
asm volatile(\
"mrs %[key], cpsr\n\t"\
"ands %[key], %[key], #0xC0\n\t"\
"cpsid if\n\t" : [key]"=r"(key_) :: "memory", "cc" );
#define ARM_INT_UNLOCK(key_) asm volatile (\
"tst %[key], #0x40\n\t"\
"beq 0f\n\t"\
"cpsie f\n\t"\
"0: tst %[key], #0x80\n\t"\
"beq 1f\n\t"\
"cpsie i\n\t"
"1:\n\t" :: [key]"r" (key_) : "memory", "cc")
Asegúrese de compilar con -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 soporta cpsie
y cpsid
.
Utilicé ands
en lugar de sólo and
en ARM_INT_LOCK
por lo que es una instrucción de 16 bits si se utiliza en código Thumb. El "cc"
El clobber es necesario de todos modos, así que es estrictamente un beneficio de rendimiento/tamaño de código.
0
y 1
son etiquetas locales para que sirva de referencia.
Estos 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 hacer ARM_INT_UNLOCK
con seguridad en cualquier lugar cerca de las instrucciones.
Si su sistema tiene restricciones en cuanto a cuándo se desactivan las IRQs y FIQs, esto podría simplificarse. Por ejemplo, si siempre están deshabilitadas juntas, podrías combinarlas en una sola cbz
+ cpsie if
así:
#define ARM_INT_UNLOCK(key_) asm volatile (\
"cbz %[key], 0f\n\t"\
"cpsie if\n\t"\
"0:\n\t" :: [key]"r" (key_) : "memory", "cc")
Alternativamente, si no te importan los FIQs en absoluto, es similar a dejar de habilitarlos/deshabilitarlos completamente.
Si sabes que nada más cambia ninguno de los otros bits de estado en CPSR entre el bloqueo y el desbloqueo, entonces también podrías usar continue con algo muy similar a tu código original, excepto con ambos "memory"
y "cc"
de la que se benefician los dos. ARM_INT_LOCK
y ARM_INT_UNLOCK
1 votos
Por favor, remítase a infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dht0008a/ no lo hagas en asm incrustado por cierto. hazlo en una función como lo hace el artículo.
0 votos
No sé nada acerca de ARM, pero yo esperaría que para el mutex (o cualquier función de sincronización entre hilos o entre procesos), deberías usar el clobber de "memoria" para asegurarte de que a) todos los valores de memoria actualmente almacenados en caché en los registros sean devueltos a la memoria antes de ejecutando el asm y b) cualquier valor en memoria al que se acceda después de que el asm se recargue. Tenga en cuenta que la realización de una llamada (como recomienda HuStmpHrrr) debería realizar implícitamente esta carga por usted.
0 votos
Además, aunque todavía no hablo ARM, tus restricciones para 'key_' no parecen correctas. Dado que usted dice que esto está destinado a ser utilizado para la re-entrada, declarando como "=r" en el bloqueo parece sospechoso. '=' significa que usted tiene la intención de sobrescribirlo, y el valor existente no es importante. Parece más probable que hayas querido usar '+' para indicar tu intención de actualizar el valor existente. Y de nuevo para unlock, listarlo como una entrada le dice a gcc que no tienes intención de cambiarlo, pero si no me equivoco, lo haces (cambiarlo). Supongo que esto también debería ser listado como una salida '+'.
1 votos
+1 por codificar en ensamblador para un núcleo de tan alta especificación. De todos modos, ¿podría estar relacionado con los modos de privilegio?
0 votos
Estoy bastante seguro de que tendrá que utilizar
ldrex
ystrex
para hacerlo bien. Aquí está una página web mostrando cómo utilizarldrex
ystrex
para implementar un spinlock.0 votos
¿Está usted en un solo núcleo únicamente ¿quieres protección para no adelantarte? De lo contrario, si estás tratando de sincronizar entre múltiples núcleos o periféricos DMA, entonces juguetear con las interrupciones no va a funcionar en absoluto - necesitarás las exclusivas antes mencionadas con barreras adecuadas y una cuidadosa reflexión sobre la coherencia de la caché.
0 votos
El código se ve bien. ¿Qué te hace pensar que de los millones de código que has portado esas 4 líneas son las culpables?
0 votos
Parece que la pregunta es más adecuada para el SO, que para el EE
0 votos
¿Puede escribir casos de prueba, por ejemplo, ejecutar una tarea de baja prioridad que escriba en un búfer y luego iniciar otra de mayor prioridad que la interrumpa? ¿Interrumpir un pin en algún lugar cuando se produce la contención? ¿Quitar la sección crítica y ver cómo se estropean las cosas, y luego volver a ponerla para solucionar el problema?