Red de conocimiento informático - Conocimiento informático - Cómo crear un análisis de código de tarea en un sistema operativo en tiempo real

Cómo crear un análisis de código de tarea en un sistema operativo en tiempo real

La programación de tareas más sencilla

Desde un punto de vista moderno, un sistema operativo de computadora personal estándar debería proporcionar las siguientes funciones:

Gestión de procesamiento (Gestión de procesamiento)

Gestión de memoria

Sistema de archivos

Redes

Seguridad

Interfaz de usuario (Interfaz de usuario)

Controlador (controladores de dispositivo)

Pero el sistema operativo integrado más simple puede contener mucho menos. Los sistemas operativos más simples suelen girar en torno a la gestión de procesos. Entonces, ahora puede probar el siguiente "sistema operativo" más simple, que solo puede realizar una programación manual simple de tareas. En aras de la simplicidad, se utiliza el programa en ejecución AT89S52 más simple: la memoria es pequeña, la cantidad de bytes limpios, los periféricos solo tienen unas pocas IO, la estructura es simple y es muy conveniente escribir el sistema operativo.

1. Tareas de ejecución desnuda y tareas en el sistema operativo

Creo que todo el mundo está familiarizado con ello. Cuando se utiliza un microcontrolador para ejecutarlo desnudo, el programa generalmente se escribe como un tiempo grande. bucle infinito de la siguiente manera:

void main (void)

{

while (1) /* repetir para siempre */

{

do_something();

}

}

O como:

void main (void)

{

mientras (1) /* repetir para siempre */

{

hacer_algo1();

do_something2(); / /Captura de entrada de datos

do_something3();

.

.

.

}

}

Aquí cada función completa una operación o tarea independiente. Estas funciones (también pueden llamarse tareas) se ejecutan en un orden determinado, una tras otra. El cambio de tarea aquí es simplemente ejecutar una y luego ejecutar otra. El ciclo continúa.

Sin embargo, una vez que se agregan más tareas, el orden de ejecución se convierte en un problema. En el ejemplo anterior, una vez que la función do_something1() se ejecuta durante demasiado tiempo, el bucle principal tardará mucho en ejecutar do_something2(). Si do_something2() es una función que recibe datos de entrada, es probable que los datos se pierdan. Por supuesto, también podemos insertar más llamadas a la función do_something2() en el bucle, o dividir do_something1() en varias partes más pequeñas. Pero esto pondrá a prueba la habilidad del programador. Si hay demasiadas tareas, escribir un programa se convertirá en un problema bastante complicado.

En este momento es necesario un sistema operativo que te ayude a asignar el tiempo de ejecución de cada tarea.

En el sistema operativo, la tarea generalmente se ve así:

void check_serial_io_task (void) _task_ 1

{

/* Esta tarea verifica las E/S serie * /

}

void process_serial_cmds_task (void) _task_ 2

{

/* Esta tarea procesa comandos seriales */

}

void check_kbd_io_task (void) _task_ 3

{

/* Esta tarea comprueba las E/S del teclado */

}

El cambio entre tareas se ha dejado en manos del sistema operativo, y la conocida función principal y while(1) generalmente han desaparecido.

2. Cómo cambiar de tarea

O hablemos de la ejecución desnuda del microcontrolador. Cuando se ejecuta desnudo, el archivo en lenguaje C se compila en ensamblador. Puede ver la instrucción CALL. se utiliza para transferir una tarea. Después de ejecutar la función, salga con RET. Sin embargo, este método indudablemente no es adecuado cuando se usa en sistemas operativos con cambios frecuentes, porque no podemos predecir cuándo salir, es decir, llamar a RET.

El cambio de tarea parece muy misterioso, pero de hecho, para decirlo sin rodeos, consiste en cambiar el valor del puntero del programa PC. La _task_ 1 y la _task_ 2 escritas anteriormente se almacenan en la ROM después de la compilación. Apunte la PC a esta ROM y se ejecutará. Si desea cambiar a otra tarea, simplemente apunte la PC a esa tarea. Es así de simple. De esta forma, ¿significa que PC = una dirección? No, porque la mayoría de los microcontroladores no permiten la asignación directa de valores al registro de la PC. Si lo escribe de esa manera, el compilador informará un error. Los sistemas operativos generales utilizan el siguiente método para cambiar el valor de la PC:

unsigned char Task_Stack1[3]

Task_Stack1[1] = (uint16) Task_1;

Task_Stack1[2] = (uint16) Task_1 gt;

SP = Task_Stack1

}//Compilar en RET

PC El valor no se puede cambiar directamente, pero se puede solucionar para cambiar el valor de la PC de otras maneras. Después de ejecutar una función, siempre es necesario cambiar el PC. ¿Así es como cambia la PC? Antes de ejecutar la función, la PC se coloca en la pila. Cuando finaliza la función, se llama a la instrucción RET, lo que significa que la PC sale de la pila. El valor de PC original insertado en la pila luego se extrae de la pila y el programa regresa a su posición original. Aquí se imita este proceso: simule la estructura de una pila, cargue en ella la dirección de entrada de la función que se ejecutará (nombre de función en lenguaje C) y apunte SP a la parte superior de la pila que creó. Una instrucción RET introduce [SP] y [SP-1] en la PC. De esta manera, la PC cambia a la dirección de entrada de la función a ejecutar y comienza a ejecutar la función objetivo. (La PC de AT89s52 tiene 16 bits y se colocan dos bytes en la pila)

3. El sistema de programación manual más simple

Aplique las ideas anteriores y escriba las 3 tareas más simples. Sistema de programación manual.

El código es el siguiente:

typedef unsigned char uint8

typedef unsigned int uint16

#include

sbit led0 = P0; ^0;

sbit led1 = P0^1;

sbit led2 = P0^2;

uint8 Cur_TaskID //Número de tarea actualmente en ejecución

uint8 Task_Stack0[10]; // Pila de tarea 0

uint8 Task_Stack1[10]

uint8 Task_Stack2[10]; Task_StackSP [3]; //Apila los punteros superiores de 3 pilas

//Task_StackSP[0] -gt; Task_Stack0

//Task_StackSP[1] -gt; >

//Task_StackSP[2] -gt; Task_Stack2

void Task_0(); //Tarea 0

void Task_1() //Tarea 1

void Task_2(); //Tarea 2

void Task_Scheduling(uint8 Task_ID); //Programación de tareas

void main (void)

{

Task_Stack0[1] = (uint16) Task_0; // Según el modo little-endian, la dirección de entrada de la función de la tarea se carga en la pila de tareas

Task_Stack0[2] = (uint16) Tarea_0 gt;

Tarea_Stack1[1] = (uint16) Tarea_1;

Tarea_Stack1[2] = (uint16) Tarea_1 gt; /p>

Task_Stack2[1] = (uint16) Task_2;

Task_Stack2[2] = (uint16) Task_2 gt;

Task_StackSP[0] = Task_Stack0;

Task_StackSP[0] = 2; //Simplemente empujé dos elementos a la pila.

Aquí se obtiene la dirección superior de la pila, a saber, Task_Stack0[2]

Task_StackSP[1] = Task_Stack1;

Task_StackSP[1] = 2;

Task_StackSP[2] = Task_Stack2;

Task_StackSP[2] = 2;

Cur_TaskID = 0

SP = Task_StackSP[0]; número de tarea 0 Dirección superior de la pila

}//Utilice la instrucción de retorno RET de main para que la PC obtenga la dirección de entrada de la tarea No. 0

//Función de programación de tareas

void Task_Scheduling( uint8 Task_ID)

{

Task_StackSP[Cur_TaskID] = SP;

Cur_TaskID = Task_ID;

SP = Task_StackSP[Cur_TaskID];

}

//Función de tarea n.º 0

void Task_0()

{

mientras(1 )

{

led0 = 0;

Tarea_Programación(1); /p>

}

//Función de tarea nº 1

void Task_1()

{

while(1 )

{

led1 = 0;

Task_Scheduling(2);

}

}

//Función de tarea número 2

void Task_2()

{

while(1)

{

led2 = 0;

Task_Scheduling(0);

}

}

Qué necesita el código Lo que hay que hacer es ejecutar las tres tareas de forma secuencial. La idea de la función de programación de tareas Task_Scheduling es la mencionada anteriormente. Puede ejecutar el código en Keil y puede ver que el programa se ejecuta secuencialmente en tres tareas.