Registro de RT-Thread (5. Protección de sección crítica de RT-Thread)

本文聊聊临界区,以及RT-Thread对临界区的处理,
通过源码分析一下 RT-Thread 对临界区保护的实现以及与 FreeRTOS 处理的不同。

prefacio

¿Por qué hablar de secciones críticas?

Porque el apartado crítico en RT-Thread está relacionado con la ejecución secuencial de hilos, es decir, el tema de la sincronización de hilos.

Cuando se usa RTOS, varios subprocesos en ejecución a menudo necesitan acceder a recursos críticos, como algunas variables globales. Si no se toman ciertas medidas de protección, pueden ocurrir resultados inesperados en la operación del programa.

RT-Thread proporciona una variedad de formas de proteger la sección crítica.Este artículo describe principalmente los métodos para cerrar la programación del sistema y deshabilitar las interrupciones.

1. Área crítica

A menudo escuchamos términos como sección crítica y recurso crítico, entonces, ¿qué es sección crítica y recurso crítico?

1.1 ¿Qué es una sección crítica?

Echemos un vistazo a la explicación de la Enciclopedia Baidu:
inserte la descripción de la imagen aquí
El resumen simple son las dos oraciones en la imagen:

  • Recurso crítico
    Un recurso compartido que solo puede ser utilizado por un proceso a la vez
  • Sección crítica
    La pieza de código en cada proceso que accede a un recurso crítico se denomina sección crítica.

1.2 Secciones críticas en RTOS

Para nuestro RTOS multitarea, además de las interrupciones externas, su propio mecanismo de programación del sistema y subprocesos múltiples, varios subprocesos pueden acceder a los recursos compartidos Para garantizar la confiabilidad y la integridad de los datos, es necesario Para proteger, los recursos compartidos deben ser mutuamente excluyentes acceso (como variables globales).

El primero es el ejemplo más básico, ¡interrupciones externas! Esto existe no solo en RTOS, sino también en los sistemas front-end y back-end:
inserte la descripción de la imagen aquí
en el ejemplo anterior, si se agrega la protección de la sección crítica a la función del subproceso, de modo que el subproceso no responda a las interrupciones hasta la operación del crítico. se completa el recurso a, no habrá problema.

Veamos un ejemplo de acceso a recursos críticos entre subprocesos:
inserte la descripción de la imagen aquí
en el ejemplo de la figura anterior (puede ser el mismo que el delay(1)latido del reloj, puede ser un poco problemático, puede necesitar un poco más de retraso, está bien aquí, no se enrede = =!), analicé los problemas que ocurrirán si no hay protección de sección crítica (indique si hay problemas), los resultados reales del programa pueden no ser los resultados previstos originalmente por el programa, y ¡este tipo de error debe evitarse!

El siguiente contenido de este resumen incluye el análisis del código fuente de la protección de la sección crítica a continuación, que es una descripción ampliada. Saber o no comprender no afecta el aprendizaje del uso de la protección de la sección crítica RT-Thread, porque la programación RTOS involucrada Los principios, las excepciones de PendSV y otros conocimientos requieren una cierta base, aquí se recomienda que los amigos que quieran aprender RTOS deben leer detenidamente el documento "Guía autorizada de Cortex-M3 y Cortex-M4".

Comprender el ejemplo anterior está relacionado con el principio de programación de RTOS. La interrupción utilizada en la explicación anterior interrumpe el subproceso y lo guarda en el sitio, lo restaura en el sitio y programa el subproceso. Es necesario tener una cierta comprensión del principio de programación de RTOS.En RTOS, además de las interrupciones externas que interrumpen la ejecución de los hilos, también hay interrupciones de Systick y una excepción PendSV importante.

PendSV también se conoce como una llamada al sistema suspendible. Es una excepción que se puede suspender como una interrupción normal. Se utiliza especialmente para ayudar al sistema operativo en el cambio de contexto. Las excepciones de PendSV se inicializan con la excepción de prioridad más baja. Cada vez que sea necesario realizar un cambio de contexto, la excepción de PendSV se activará manualmente y el cambio de contexto se realizará en la función de manejo de excepciones de PendSV.

Para una comprensión detallada, consulte mi otra publicación de blog:
Registros de FreeRTOS (3. Análisis de los principios de programación de tareas de RTOS_Systick, PendSV, SVC)

Aquí hay una pequeña explicación con capturas de pantalla del texto:
inserte la descripción de la imagen aquíinserte la descripción de la imagen aquí

En resumen, para RTOS, al acceder a recursos críticos, se debe prestar especial atención para proteger las secciones críticas.

Para evitar los problemas que mencionamos anteriormente, RTOS adopta algunos métodos de protección correspondientes para las secciones críticas, en términos generales, son:
cierre de la programación del sistema, cierre de interrupciones, uso de semáforos y mutexes.

Explicaremos el semáforo y el mutex RT-Thread en la próxima publicación del blog.Este artículo se centra principalmente en la operación de cierre de interrupciones y la programación del sistema.

2. Protección de sección crítica RT-Thread

2.1 Deshabilitar la programación

Las funciones de bloqueo y desbloqueo del programador RT-Thread son las siguientes:

void rt_enter_critical(void);//调度器上锁,进入调度临界区,不再切换线程
void rt_exit_critical(void);//调度器解锁,退出调度临界区

Tenga en cuenta que el bloqueo de programación no evita que el sistema responda a las interrupciones, sino que solo continúa ejecutando el subproceso bloqueado después de que se completa el procesamiento de la interrupción y sale. ¡Este método no se aplica si hay acceso a recursos críticos en la interrupción! !

Las funciones de bloqueo y desbloqueo del programador se usan en pares, ¡recuerde!

Ejemplo de uso:
inserte la descripción de la imagen aquí

Un breve análisis del código fuente para prohibir la programación

Encontremos rt_enter_criticalla función y veamos cómo se implementa:
inserte la descripción de la imagen aquí

Pero la función anterior solo rt_scheduler_lock_nestincrementa automáticamente la variable y no tiene otras operaciones, entonces, ¿cómo afecta esta variable al programador?
Encontramos el lugar donde se usa la variable rt_scheduler_lock_nesty encontramos el siguiente código:
inserte la descripción de la imagen aquí

Luego lo mismo, en la rt_exit_criticalfunción, claro, se decrementa la variable:
inserte la descripción de la imagen aquí

Si observa este código detenidamente, también puede encontrar un detalle, es decir, ¡este anidamiento de soporte de programación cerrada y programación abierta! Cuando el programador se bloquea una vez, se debe desbloquear una vez y cuando se bloquea dos veces, se debe desbloquear dos veces.

¡Esto también nos dice que a veces mirar el código fuente será más intuitivo que mirar directamente la explicación de la lógica!

2.2 Enmascaramiento de interrupciones

Toda la programación de subprocesos de RTOS se basa en interrupciones. Las interrupciones de cierre no solo pueden proteger las interrupciones externas, sino también deshabilitar la programación. Es "más capaz de proteger" la sección crítica que la programación de deshabilitación anterior.

Las funciones de las interrupciones de enmascaramiento de RT-Thread y las interrupciones de habilitación son las siguientes:

/*
返回值:
中断状态 	rt_hw_interrupt_disable 函数运行前的中断状态
*/
rt_base_t rt_hw_interrupt_disable(void);//屏蔽中断
/*
参数:
level 	前一次 rt_hw_interrupt_disable 返回的中断状态
*/
void rt_hw_interrupt_enable(rt_base_t level);//中断使能

Tenga en cuenta que el bloqueo de interrupción de terminal mencionado anteriormente es el método de sincronización más poderoso y eficiente. El principal problema de este método es que el retraso de respuesta a la interrupción se extenderá. Se debe prestar atención al caso en el que el rendimiento en tiempo real es extremadamente extremo. , por lo que el uso real debe ser Úselo razonablemente de acuerdo con la aplicación.

Las funciones de máscara de interrupción y habilitación de interrupción también se usan en pares, ¡recuerde!

Ejemplo de uso:
inserte la descripción de la imagen aquí

Análisis de código fuente de bloqueo de interrupción

La función anterior encuentra la declaración, pero no puede saltar al prototipo de la función:
inserte la descripción de la imagen aquí
¿dónde está la implementación de la función? Como se muestra abajo:
inserte la descripción de la imagen aquí

Debido a que se usa el compilador gcc, las context_gcc.Sdeclaraciones antes y después del cuerpo de la función en el archivo serán diferentes de las de MDK, pero el lenguaje ensamblador implementado por la función es el mismo:

/*
 * rt_base_t rt_hw_interrupt_disable();
 */
 /*
 .global关键字用来让一个符号对链接器可见,可以供其他链接对象模块使用
 前面两句意思就类似于定义了一个全局可调用的函数rt_hw_interrupt_disable 
 */
    .global rt_hw_interrupt_disable //告诉编译器rt_hw_interrupt_disable 是一个全局可见的
    .type rt_hw_interrupt_disable, %function//告诉编译器rt_hw_interrupt_disable是一个函数
rt_hw_interrupt_disable:
    MRS     R0, PRIMASK   //读取PRIMASK寄存器的值到r0寄存器
    CPSID   I             //关闭全局中断,具体原因见博文后续说明
    BX      LR       	//函数返回,通过LR 连接寄存器 返回

/*
 * void rt_hw_interrupt_enable(rt_base_t level);
 */
    .global rt_hw_interrupt_enable   //与上面类似
    .type rt_hw_interrupt_enable, %function
rt_hw_interrupt_enable:
    MSR     PRIMASK, R0  //将 r0 的值寄存器写入到 PRIMASK 寄存器
    BX      LR   		//函数返回,通过LR 连接寄存器 返回

Incluso si escribiera un comentario y dijera el significado del código anterior, seguirá habiendo problemas. ¿Por qué CPSID Iestá desactivada la interrupción global?

Si te fijas bien en el documento "Guía definitiva de Cortex-M3 y Cortex-M4", se puede entender todo.

PRIMSK: Registro especial de máscara de interrupción. Con PRIMSK, se pueden suprimir todas las excepciones excepto HardFault y NMI. Se establece en el documento recomendado anterior:
inserte la descripción de la imagen aquí
CPSID Ieso es para deshabilitar las interrupciones, CPSIE Ieso es para habilitar las interrupciones.

Un detalle, ¿por qué rt_hw_interrupt_enablelas funciones no CPSIE Ireanudan las interrupciones?

La respuesta es CPSIE Ique los bloqueos de interrupción no se pueden anidar si se utilizan interrupciones habilitadas. Use el R0registro para guardar el PRIMASKestado actual, de modo que tenga que apagar tantas interrupciones como necesite para encender tantas interrupciones.
inserte la descripción de la imagen aquí

R0¡También vale la pena mencionar que el valor contenido en el rt_base_t levelregistro en el ejemplo anterior es esta variable!

A través del análisis anterior, debemos comprender completamente cómo se implementa el bloqueo de interrupción de RT-Thread, ¿son otros RTOS así? Echemos un vistazo a cómo FreeRTOS implementa los bloqueos de interrupción.

Diferencia de FreeRTOS

La sección crítica de FreeRTOS, presentada en mi publicación de blog:

Registro de FreeRTOS (cuatro, problema de desbordamiento de la pila de tareas de FreeRTOS y sección crítica)

Aquí solo miramos su código de implementación para compararlo con RT-Thread (también tomando M3 como ejemplo, M0 y M3 son diferentes):
inserte la descripción de la imagen aquí
Aquí analizamos la función que protege las interrupciones en las tareas. El análisis de la máscara de interrupción es similar, pero un poco más complicado.

Interrupciones de máscara:

#define portDISABLE_INTERRUPTS()				vPortRaiseBASEPRI()
/*----------------------------------------------*/
/*只需要注意操作的寄存器为 basepri*/
/*----------------------------------------------*/

portFORCE_INLINE static void vPortRaiseBASEPRI( void )
{
    
    
uint32_t ulNewBASEPRI;

	__asm volatile
	(
		"	mov %0, %1												\n" \
		"	msr basepri, %0											\n" \  
		"	isb														\n" \
		"	dsb														\n" \
		:"=r" (ulNewBASEPRI) : "i" ( configMAX_SYSCALL_INTERRUPT_PRIORITY ) : "memory"
	);
}

Para habilitar las interrupciones:

//...
#define portENABLE_INTERRUPTS()					vPortSetBASEPRI(0)
/*只需要注意操作的寄存器为 basepri*/
portFORCE_INLINE static void vPortSetBASEPRI( uint32_t ulNewMaskValue )
{
    
    
	__asm volatile
	(
		"	msr basepri, %0	" :: "r" ( ulNewMaskValue ) : "memory"
	);
}

Aquí podemos ver en el código del bloqueo de interrupción de FreeRTOS que opera basepriregistros, no PRIMSKregistros, entonces, ¿qué baseprison los registros? La respuesta todavía se puede encontrar en el documento "Guía autorizada de Cortex-M3 y Cortex-M4":
inserte la descripción de la imagen aquí
FreeRTOS usa basepriregistros . El usuario puede establecer la configuración de esta prioridad. Esto deja una salida para interrupciones muy urgentes.

Pero de todos modos, en cualquier momento, ¡el código para el procesamiento de la sección crítica es, por supuesto, lo más corto posible! !

2.3 Aplicaciones prácticas

Para resumir brevemente las situaciones que pueden ser requeridas en aplicaciones prácticas de protección de secciones críticas:

  • Código que llama a funciones públicas (funciones no reentrantes)
  • Leer o modificar variables (variables globales)
  • Usar recursos de hardware (al operar con memoria o flash)
  • Operaciones con requisitos de temporización precisos (comunicación I2C, pero debe tenerse en cuenta que la función de retraso que usa systick y el retraso de secado, etc. no se pueden usar en la comunicación)
  • Algún código que el usuario no quiere que se interrumpa (como printf imprime)

En casos generales, la protección de las secciones críticas ordinarias se puede satisfacer prohibiendo la programación, a menos que tenga acceso a los recursos críticos en la interrupción.
Por supuesto, no hay absolutos.A veces, la ocurrencia de interrupciones también puede afectar algunas tareas comunes (como el muestreo de ADC), por lo que aún es necesario usar la protección de la sección crítica razonablemente de acuerdo con la situación real.

Epílogo

El contenido de este artículo es relativamente simple para aprender el uso de la protección de sección crítica RT-Thread, solo necesita dominar algunas llamadas de función. Sin embargo, es relativamente complicado comprender el principio de implementación y es necesario tener una cierta comprensión del kernel y los principios básicos del sistema operativo.

A través de un simple análisis del código fuente de estas funciones, tenemos una comprensión más intuitiva de la implementación de sus principios, ¡y es un buen hábito desarrollar la lectura del código fuente que es útil para nuestro aprendizaje!

En el siguiente registro de RT-Thread, aprenderemos sobre los semáforos y mutexes relacionados con la sincronización entre subprocesos de RT-Thread, que es otra forma en que RT-Thread protege las secciones críticas.

¡Gracias!

Supongo que te gusta

Origin blog.csdn.net/weixin_42328389/article/details/123593592
Recomendado
Clasificación