Hay varias cuestiones aquí:
-
AFAIK, sólo hay un bit para controlar las interrupciones en un AVR. Así que la discusión sobre un control más preciso de las interrupciones no se aplica a esta CPU.
-
El ejemplo ilustra un fuerte caso para buscar en el ensamblador el código generado por el compilador.
Debería ser práctico detectar todos los usos de cli a sei con un pequeño script de edición aplicado a la salida de objdump (para que sea fácil de controlar). Un script basado en el programa podría resaltar todos los ejemplos de "llamada" entre ellos.
Esto podría mostrar que no hay ningún problema que resolver en su código.
-
El control de las interrupciones mediante cli()/sei() es la forma más fácil para un desarrollador de asegurar el acceso atómico, y por lo tanto mantener la consistencia de la memoria, para los valores multibyte en un AVR.
Tener que escribir en ensamblador puede ser tan propenso a los errores, o tener un impacto negativo en la optimización, que es un enfoque pobre. Yo no lo haría hasta que el resultado del paso 1 demuestre que hay un problema que resolver.
-
El ejemplo muestra que el compilador ha generado código para la división de enteros insertando una llamada a una subrutina para val = 65535U / val
y esa llamada es el código que se ha "optimizado" entre cli() y sei().
Sin embargo, lo hace no probar que las subrutinas generadas por el usuario se mueven en el código entre cli() y sei(). Así que esto puede no ser un problema para el caso, potencialmente, mucho más significativo.
-
Este ejemplo se soluciona introduciendo un nuevo volátil variable:
#define cli() __asm volatile( "cli" ::: "memory" )
#define sei() __asm volatile( "sei" ::: "memory" )
unsigned int ivar;
void test2( unsigned int val ) {
volatile unsigned int val1 = 65535U / val;
cli();
ivar = val1;
sei();
}
El código generado se convirtió en:
92: cf 93 push r28
94: df 93 push r29
96: 00 d0 rcall .+0 ; 0x98 <_Z5test2j+0x6>
98: cd b7 in r28, 0x3d ; 61
9a: de b7 in r29, 0x3e ; 62
9c: bc 01 movw r22, r24
9e: 8f ef ldi r24, 0xFF ; 255
a0: 9f ef ldi r25, 0xFF ; 255
a2: 0e 94 fb 00 call 0x1f6 ; 0x1f6 <__udivmodhi4>
a6: 7a 83 std Y+2, r23 ; 0x02
a8: 69 83 std Y+1, r22 ; 0x01
aa: f8 94 cli <----------------------- switch off interrupts
ac: 89 81 ldd r24, Y+1 ; 0x01
ae: 9a 81 ldd r25, Y+2 ; 0x02
b0: 90 93 01 01 sts 0x0101, r25
b4: 80 93 00 01 sts 0x0100, r24
b8: 78 94 sei <----------------------- switch on interrupts
ba: 0f 90 pop r0
bc: 0f 90 pop r0
be: df 91 pop r29
c0: cf 91 pop r28
c2: 08 95 ret
Esto no es bonito, pero es relativamente fácil, y puede ser suficiente. Por supuesto, evita las optimizaciones prematuras; no hagas cambios sutiles para controlar el compilador hasta que sea importante, y preferiblemente después de que el código esté funcionando y sea estable.
Yo revisaría el código generado, y esperaría a ver un problema, para luego resolver ese caso específico.
Creo que levantaría un informe de error si mi código a nivel de usuario se mueve entre el cli()/sei(). Eso daría a los desarrolladores del compilador la oportunidad de identificar soluciones o desarrollar correcciones. Los desarrolladores de compiladores son libres de inventar soluciones, a menudo utilizando pragmas, y podrían responder a un informe de error ofreciendo una solución sólida.
Mientras tanto, sería más fácil continuar por el camino fácil, en lugar de dificultar el desarrollo, hasta que haya pruebas de un problema significativo.
0 votos
¿Por qué cree que no se pueden realizar operaciones costosas en el montaje en línea?
4 votos
@IgnacioVazquez-Abrams: El objetivo es asegurar que dado algo como
cli(); counter++; sei();
El código generado sólo desactivará las interrupciones durante unos pocos ciclos de instrucción. Si el compilador reordena el código paracli(); ExpensiveOperation(); counter++; sei();
y costosa operación tardara muchos milisegundos en completarse, eso podría ser desastroso.0 votos
@IgnacioVazquez-Abrams: Desgraciadamente, el diseño de C nunca ha incluido ninguna característica que impida a un optimizador hacer esas cosas porque en el momento en que se diseñó C no se esperaba que los compiladores hicieran esas cosas, independientemente de que una directiva lo prohibiera.
2 votos
Sólo es una mala idea si hay algo mejor. Yo tendría cuidado con lo que va entre la desactivación/activación. Nada más que el mínimo de lectura o escritura, preferiblemente sólo declaraciones de asignación en lugar de cualquier cosa que implique memoria asignada dinámicamente o cualquier otro tipo de indirección. Por ejemplo, ponlo en una variable asignada estáticamente (incluso en un registro) primero, y luego desactívalo. O hazlo todo en ensamblador inline (más seguro).
1 votos
Cualquier compilador que reorganice las cosas de tal manera que un optimizador mueva cosas a través de una barrera de memoria tendría todo tipo de errores archivados contra él. Aunque no esté en el estándar, es algo que no deberían hacer.
1 votos
@IgnacioVazquez-Abrams El ejemplo del enlace que he puesto, en el que una llamada a la división lenta se mueve a través del cli, sigue ocurriendo en avr-gcc 5.2.0. La barrera de memoria no protege contra ella porque la división implica una variable local y la llamada no tiene efectos secundarios.
0 votos
Obviamente, usted querrá desmontar el código y ver lo que realmente está haciendo - puede no ser un problema.
0 votos
Aparte de los problemas de barrera de memoria que pueda haber, jugar con la máscara de interrupción global suele ser una mala idea. Si haces esto para propósitos de lectura atómica, es mejor deshabilitar sólo la(s) interrupción(es) específica(s) que pueda(n) causar un conflicto con el programa principal. De lo contrario, crearás un estrecho vínculo entre tu lectura atómica y cada una de las interrupciones del MCU, posiblemente no relacionadas.
0 votos
Una pregunta muy interesante. (Me gustaría poder votar más veces :-). Me pregunto cómo los desarrolladores embebidos limitados por MISRA C evitan el problema. Recurrir a ensamblador para cualquier código de tiempo crítico parece raro, como parece raro que tengan que desactivar las optimizaciones por completo. ¡Y la mayor parte del código incrustado en un coche es de tiempo crítico, al menos cuando se trata de circuitos de control del coche!
1 votos
@Lundin Pero si la interrupción específica que deshabilitas es de alta prioridad y de tiempo crítico, tu escritura/lectura podría ser interrumpida por otras interrupciones de menor prioridad, aumentando el tiempo que la interrupción más importante está deshabilitada.
0 votos
@cmatteri Y a la inversa: si necesitas garantizar el acceso atómico a una variable compartida con una interrupción de baja prioridad, no puedes utilizar la máscara de interrupción global, ya que deshabilitará también las interrupciones de alta prioridad. La creación de prioridades es un asunto diferente, no directamente relacionado con la deshabilitación de interrupciones para lidiar con problemas de reentrada.
0 votos
@LorenzoDonati La forma de secuenciar las instrucciones de ensamblador por parte de un compilador concreto está fuera del alcance de la especificación del lenguaje C y, por tanto, también está fuera del alcance de MISRA-C. Recurrir al ensamblador para el código crítico es muy común, y no está restringido por MISRA, que sólo establece que todo el código de ensamblador debe ser encapsulado y aislado. Un ejemplo de ello son las macros cli()/sei().