
Los principios básicos del trabajo de los planificadores de RTOS se consideraron en el artículo "Tareas y planificación". En este artículo, analizaremos las características que ofrece Nucleus RTOS, así como las que ofrece Nucleus SE con más detalle.
Artículos anteriores de la serie:
Artículo # 8. Nucleus SE: diseño interno y despliegue
Artículo # 7. Núcleo SE: Introducción
Artículo # 6. Otros servicios RTOS
Artículo # 5. Interacción de tareas y sincronización
Artículo # 4. Tareas, cambio de contexto e interrupciones
Artículo # 3. Tareas y planificación
Artículo # 2. RTOS: estructura y modo en tiempo real
Artículo # 1. RTOS: introducción.
Planificación en Nucleus RTOS
Dado que Nucleus RTOS es un RTOS comercial completo y bien establecido, podemos asumir con seguridad que el programador se desarrolló de acuerdo con los requisitos de dicho producto. Este sistema operativo complejo y flexible proporciona al desarrollador una amplia gama de capacidades para resolver casi cualquier tarea de programación en tiempo real concebible.
El programador puede admitir un número ilimitado de tareas (limitado solo por los recursos disponibles) y trabajar con gestión de prioridades. A la tarea se le puede asignar una prioridad de 0 a 255, donde 0 es la prioridad más alta y 255 es la más baja. Una tarea tiene una prioridad dinámica, es decir, puede ser cambiada en tiempo de ejecución por la tarea misma o por otra. Se pueden asignar varias tareas al mismo nivel de prioridad. En un caso extremo, a todas las tareas se les puede asignar la misma prioridad, lo que hace posible implementar una política de planificación basada en el principio de Round Robin y Time-Slice.
Si hay varias tareas con la misma prioridad, se programarán utilizando el algoritmo Round Robin en el orden en que se prepararon. La tarea debe pausar o transferir el control para que comience la siguiente tarea. También se pueden asignar intervalos de tiempo a las tareas que proporcionan una separación más controlada del tiempo de procesador disponible.
La planificación de tareas es 100% determinista, lo cual es de esperar de un núcleo similar. Las tareas también se pueden crear y destruir dinámicamente, lo que, gracias al programador, pasa desapercibido para el usuario.
Planificación en Nucleus SE
He desarrollado todos los aspectos de Nucleus SE para que sean generalmente compatibles con Nucleus RTOS, pero también sean más simples y más eficientes en términos de memoria. El planificador no es una excepción. Proporciona muchas de las características de Nucleus RTOS Scheduler, pero es algo limitado. La flexibilidad se logra mediante la configuración durante el montaje.
Una aplicación Nucleus SE puede tener un máximo de 16 tareas (y al menos una). Aunque teóricamente este número puede aumentarse, la eficiencia de los algoritmos estará en riesgo; una serie de estructuras de datos dependen del almacenamiento del número de índice de la tarea (de 0 a 15) en un mordisco (cuatro bits), y deberán procesarse junto con el código correspondiente.
Para lograr un equilibrio entre flexibilidad y simplicidad (y tamaño), en lugar de tener un programador con varias capacidades, Nucleus SE ofrece uno de los cuatro tipos de programadores para elegir: Ejecutar a componente (RTC), Round Robin (RR), Time-Slice ( TS) y Prioridad. El planificador se selecciona estáticamente en el momento del ensamblaje. Los detalles sobre cada tipo de planificador se describen a continuación en la sección "Tipos de planificador".
Como cualquier otro aspecto de Nucleus SE, las tareas son objetos estáticos. Se determinan durante la configuración y su prioridad (índice) no se puede cambiar.
Planificadores de Nucleus SE
Como se indicó anteriormente, Nucleus SE ofrece uno de los cuatro tipos de programadores para elegir. Como la mayoría de los aspectos de la configuración de Nucleus SE, esta elección se determina escribiendo a nuse_config.h, el parámetro NUSE_SCHEDULER_TYPE debe establecerse en consecuencia, como se muestra en este fragmento del archivo de configuración:

Independientemente del planificador seleccionado, su código de inicio se llama inmediatamente después de que se inicializa el sistema. La información completa sobre la inicialización de Nucleus SE se presentará en el próximo artículo.
Ejecutar al programador de finalización
El RTC Scheduler es la solución más fácil y más adecuada si cumple con los requisitos de la aplicación. Cada tarea debe completar su trabajo antes de realizar la función de retorno y permitir que el planificador complete la siguiente tarea.
No hay necesidad de una pila separada para cada tarea. Todo el código está escrito en C, no se requiere lenguaje ensamblador. A continuación se muestra el código completo del planificador RTC.

El código es solo un ciclo sin fin que se turna para invocar cada tarea. La matriz NUSE_Task_Start_Address [] contiene punteros a la función externa de cada tarea. La macro PF0 es una conversión simple de un puntero vacío a un puntero a una función vacía sin parámetros. Está diseñado para garantizar la legibilidad del código.
La compilación condicional se utiliza para habilitar la compatibilidad con funciones adicionales: NUSE_SUSPEND_ENABLE determina si las tareas se pueden suspender; NUSE_SCHEDULE_COUNT_SUPPORT determina si se requiere un valor de contador cada vez que se programa una tarea. Se puede encontrar más información sobre esto en el próximo artículo.
Scheduler Round Robin
Si se requiere un poco más de flexibilidad de la que proporciona el planificador RTC, el planificador RR es adecuado. Permite que la tarea transfiera el control o la pausa, y luego continúe desde el mismo punto. La sobrecarga adicional, además de la complejidad del código y la no portabilidad, es que cada tarea requiere su propia pila.
El código del planificador consta de dos partes. El componente de lanzamiento es el siguiente:

Si el soporte para el estado inicial de la tarea está habilitado (utilizando el parámetro NUSE_INITIAL_TASK_STATE_SUPPOR T, consulte "Parámetros" en el siguiente artículo), la planificación comienza desde la primera tarea finalizada; de lo contrario, se utiliza una tarea con índice 0. El contexto de esta tarea se carga utilizando NUSE_Context_Load () . Para obtener más información sobre cómo guardar y restaurar un contexto, consulte la sección "Guardar contexto" en el siguiente artículo.
La segunda parte del planificador es el componente "re-planificación":

Este código se llama cuando la tarea libera el procesador central o hace una pausa.
El código selecciona la tarea con el siguiente índice para comenzar y coloca el valor en NUSE_Task_Next, teniendo en cuenta si la suspensión de la tarea está habilitada o no. La macro NUSE_CONTEXT_SWAP () se usa para invocar el cambio de contexto mediante una interrupción de software. Para obtener más información sobre cómo guardar y restaurar un contexto, consulte la sección "Guardar contexto" en el siguiente artículo.
Programador prioritario
El Programador prioritario en Nucleus SE, como otras opciones, está diseñado para proporcionar la funcionalidad requerida, a la vez que es bastante simple. Como resultado, cada tarea tiene una prioridad única, es imposible tener varias tareas con un nivel de prioridad. La prioridad está determinada por el índice de la tarea, donde 0 es el nivel de prioridad más alto. El índice de la tarea está determinado por su ubicación en la matriz NUSE_Task_Start_Address []. El siguiente artículo proporcionará información más detallada sobre la configuración de tareas.
Al igual que los planificadores RR y TS, el planificador prioritario tiene dos componentes. El componente de inicio del planificador prioritario es el mismo que los planificadores RR y TS, como se ilustra arriba. El componente de reprogramación es ligeramente diferente:

No existe un código condicional que pueda desactivar la suspensión de tareas, ya que esta función es obligatoria para el planificador prioritario; Cualquier alternativa sería ilógica. La función NUSE_Reschedule () acepta un parámetro que "le dice" qué tarea se puede programar a continuación: nueva_tarea. Este valor se establece cuando se invoca la reprogramación porque se invoca otra tarea. El índice de esta tarea se pasa como un parámetro. El planificador puede determinar si se debe realizar un cambio de contexto comparando el valor de new_task con el índice de la tarea actual (NUSE_Task_Active) . Si la reprogramación es el resultado de una pausa de tarea, el parámetro se establecerá en NUSE_NO_TASK y el planificador buscará la tarea con la prioridad más alta.
Estados de la tarea
Como regla general, todos los sistemas operativos tienen el concepto de encontrar tareas en un cierto "estado". Los detalles varían según el RTOS. En este artículo, veremos cómo Nucleus RTOS y Nucleus SE utilizan los estados de tareas.
Nucleus RTOS Task States
Nucleus RTOS admite 5 estados de tareas.
- Ejecución: la tarea que actualmente administra el procesador. Obviamente, solo una tarea puede ocupar este estado.
- Preparación: una tarea que está lista para la ejecución (o para continuar la ejecución) antes de que el planificador decida lanzarla. Por lo general, una tarea tiene una prioridad menor que la que se está realizando.
- Suspensión: la tarea de "dormir". No se tiene en cuenta durante la planificación hasta que se despierta, y en ese momento estará "listo" y puede continuar ejecutándose más tarde. Por lo general, una tarea está en un estado de "suspensión" porque está esperando algo: cuando el recurso esté disponible, cuando expire el período de tiempo establecido o cuando otra tarea lo despierte.
- Cancelar: la tarea fue "asesinada". No se tiene en cuenta durante la planificación hasta que se restablece, después de lo cual la tarea estará "lista" o "suspendida".
- Finalización: la tarea se completa y sale de su función externa simplemente abandonando la unidad externa o ejecutando la declaración de devolución. No se tiene en cuenta durante la planificación hasta que se restablece, después de lo cual la tarea estará "lista" o "suspendida".
Dado que Nucleus RTOS admite la creación y destrucción dinámica de objetos, incluidas las tareas, la tarea también puede considerarse en un estado "remoto". Sin embargo, dado que tan pronto como se elimina la tarea, todos sus recursos del sistema dejan de existir y la tarea misma ya no existe, no puede tener un estado. El código de la tarea puede estar disponible, pero el objeto de la tarea debe crearse nuevamente.
Estados de tareas en Nucleus SE
El modelo de estado de la tarea en Nucleus SE es un poco más simple. Por lo general, solo hay 3 estados: ejecución, disponibilidad y pausa. El estado de cada tarea se almacena en
NUSE_Task_Status [] , que tiene valores de tipo
NUSE_READY , aunque nunca tiene un valor que refleje el estado de Ejecución. Si la suspensión de tareas no está habilitada (consulte "Opciones" en el siguiente artículo), solo son posibles dos estados de tareas, y esta matriz está ausente.
Hay varios tipos de tareas de pausa. Si una tarea se suspende explícitamente por sí misma o por otra tarea, esto se denomina "suspensión pura" y se representa con el estado NUSE_PURE_SUSPEND. Si el estado de "suspensión" está activado y la tarea se suspende por un cierto período de tiempo, tiene el estado
NUSE_SLEEP_SUSPEND . Si la función de bloqueo de llamadas API está habilitada (a través de
NUSE_BLOCKING_ENABLE , consulte "Parámetros" en el siguiente artículo), la tarea puede suspenderse hasta que el recurso esté disponible. Cada tipo de objeto tiene su propio estado de suspensión de tarea, por ejemplo, en forma de
NUSE_MAILBOX_SUSPEND. En Nucleus SE, una tarea puede bloquearse en una partición de memoria, grupo de eventos, buzón, cola, canal o semáforo.
Estado del hilo
Cuando se discute el comportamiento de la tarea, las palabras "Estado" y "Estado" generalmente se usan con bastante libertad. Hay un factor adicional, que puede llamarse condicionalmente el "estado del flujo". Esta es la variable global
NUSE_Thread_State, que contiene una indicación de la naturaleza del código que se está ejecutando. Esto se aplica al comportamiento de muchas llamadas API. Posibles valores:
- NUSE_TASK_CONTEXT : la llamada a la API se realizó desde una tarea.
- NUSE_STARTUP_CONTEXT : la llamada a la API se realizó desde el código de inicio; el programador aún no se ha iniciado.
- NUSE_NISR_CONTEXT y NUSE_MISR_CONTEXT : la llamada API se realizó desde el controlador de interrupciones. Las interrupciones en Nucleus SE se discutirán en el próximo artículo.
El siguiente artículo detallará las características avanzadas del planificador en Nucleus SE, así como el mantenimiento del contexto.
Sobre el autor: Colin Walls ha trabajado en la industria electrónica durante más de treinta años, dedicando la mayor parte de su tiempo al firmware. Ahora es ingeniero de firmware en Mentor Embedded (una división de Mentor Graphics). Colin Walls a menudo habla en conferencias y seminarios, autor de numerosos artículos técnicos y dos libros sobre firmware. Vive en el Reino Unido.
Blog profesional
de Colin , correo electrónico: colin_walls@mentor.com