¿Cuáles son los beneficios de un sistema operativo no preventivo? y el precio de estos beneficios?

14

Para un MCU de metal descubierto, en comparación con el código casero con el bucle de fondo más la arquitectura de interrupción del temporizador, ¿cuáles son los beneficios de un sistema operativo no preventivo? ¿Cuáles de estos beneficios son lo suficientemente atractivos para que un proyecto adopte un sistema operativo no preventivo, en lugar de utilizar código casero con arquitectura de bucle de fondo?
.

Explicación a la pregunta:

Realmente aprecio que todos hayan respondido mi pregunta. Siento que la respuesta casi ha llegado. Agrego esta explicación a mi pregunta aquí, que muestra mi propia consideración y puede ayudar a reducir la pregunta o hacerla más precisa.

Lo que intento hacer es comprender cómo elegir el RTOS más apropiado para un proyecto en general.
Para lograr esto, ayudará una mejor comprensión de los conceptos básicos y los beneficios más atractivos de diferentes tipos de RTOS y el precio correspondiente, ya que no existe el mejor RTOS para todas las aplicaciones.
Hace unos años leí libros sobre el sistema operativo, pero ya no los tengo conmigo. Busqué en Internet antes de publicar mi pregunta aquí y encontré que esta información fue de gran ayuda: http://www.ustudy.in/node/5456 .
Hay mucha otra información útil, como las introducciones en el sitio web de diferentes RTOS, artículos que comparan la programación preventiva y la programación no preventiva, etc.
Pero no encontré ningún tema mencionado cuando elegir un RTOS no preventivo y cuando es mejor, simplemente escriba su propio código usando la interrupción del temporizador y el bucle de fondo.
Tengo ciertas respuestas, pero no estoy suficientemente satisfecho con ellas.
Realmente me gustaría saber la respuesta o la opinión de personas con más experiencia, especialmente en la práctica de la industria.

Lo que entiendo hasta ahora es:
no importa usar o no usar un sistema operativo, siempre es necesario cierto tipo de códigos de programación, incluso en forma de código como:

    in the timer interrupt which occurs every 10ms  
    if(it's 10ms)  
    {  
      call function A / execute task A;  
    }  
    if(it's 50ms)  
    {  
      call function B / execute task B;  
    }  

Beneficio 1:
un sistema operativo no preventivo designa la forma / estilo de programación para el código de programación, de modo que los ingenieros puedan compartir la misma vista incluso si no estaban en el mismo proyecto antes. Luego, con la misma visión sobre la tarea conceptual, los ingenieros pueden trabajar en diferentes tareas y probarlas, perfilarlas de forma independiente tanto como sea posible.
Pero, ¿cuánto podemos realmente ganar con esto? Si los ingenieros están trabajando en el mismo proyecto, pueden encontrar la manera de compartir bien la misma vista sin usar un sistema operativo no preventivo.
Si un ingeniero es de otro proyecto o empresa, obtendrá el beneficio si conocía el sistema operativo antes. Pero si no lo hizo, de nuevo, parece que no hace una gran diferencia para él aprender un nuevo sistema operativo o una nueva pieza de código.

Ventaja 2:
si el código del sistema operativo se ha probado correctamente, ahorra tiempo de depuración. Esto es realmente un buen beneficio.
Pero si la aplicación solo tiene alrededor de 5 tareas, creo que no es realmente complicado escribir su propio código usando la interrupción del temporizador y el bucle de fondo.

Un sistema operativo no preventivo aquí se refiere a un sistema operativo comercial / libre / heredado con un programador no preventivo.
Cuando publiqué esta pregunta, pienso principalmente en ciertos sistemas operativos como:
(1) KISS Kernel (Un pequeño RTOS no preventivo - reclamado por su sitio web)
http://www.frontiernet.net/~rhode/kisskern.html
(2) uSmartX (RTOS ligero - reclamado por su sitio web)
(3) FreeRTOS (es un RTOS preventivo, pero según tengo entendido, también se puede configurar como un RTOS no preventivo)
(4) uC / OS (similar a FreeRTOS)
(5 ) código de sistema operativo / planificador heredado en algunas compañías (generalmente hecho y mantenido internamente por la compañía)
(No se pueden agregar más enlaces debido a la limitación de la nueva cuenta StackOverflow)

Según tengo entendido, un sistema operativo no preventivo es una colección de estos códigos:
(1) un planificador que utiliza una estrategia no preventiva.
(2) facilidades para comunicación entre tareas, mutex, sincronización y control de tiempo.
(3) gestión de memoria.
(4) otras instalaciones votos / bibliotecas como sistema de archivos, pila de red, y la interfaz gráfica de usuario, etc. (FreeRTOS y uC / OS proporciona estos, pero no estoy seguro de si todavía funcionan cuando el programador está configurado como no preferente)
Algunos de ellos no siempre están ahí. Pero el planificador es imprescindible.

hailang
fuente
Eso es más o menos en pocas palabras. Si tiene una carga de trabajo que necesita ser multiproceso y puede pagar los gastos generales, use un sistema operativo de subprocesos. De lo contrario, un tiempo simple o un "programador" basado en tareas es suficiente para la mayoría de los casos. Y para determinar si la multitarea preventiva o cooperativa es la mejor ... Supongo que se trata de gastos generales y cuánto control desea tener sobre la multitarea que necesita hacer.
akohlsmith

Respuestas:

13

Esto huele un poco fuera de tema, pero intentaré volver a encaminarlo.

La multitarea preventiva significa que el sistema operativo o el núcleo pueden suspender el subproceso que se está ejecutando actualmente y cambiar a otro en función de cualquier heurística de programación que tenga en su lugar. La mayoría de las veces, los subprocesos en ejecución no tienen el concepto de que hay otras cosas que suceden en el sistema, y ​​lo que esto significa para su código es que debe tener cuidado al diseñarlo de modo que si el núcleo decide suspender un subproceso en medio de un operación de varios pasos (por ejemplo, cambiar una salida PWM, seleccionar un nuevo canal ADC, leer el estado de un periférico I2C, etc.) y dejar que otro hilo se ejecute por un tiempo, para que estos dos hilos no interfieran entre sí.

Un ejemplo arbitrario: supongamos que es nuevo en los sistemas integrados de subprocesos múltiples y tiene un pequeño sistema con un ADC I2C, un LCD SPI y una EEPROM I2C. Decidió que sería una buena idea tener dos subprocesos: uno que lee del ADC y escribe muestras en la EEPROM, y otro que lee las últimas 10 muestras, las promedia y las muestra en la pantalla LCD de SPI. El diseño sin experiencia se vería así (simplificado):

char i2c_read(int i2c_address, char databyte)
{
    turn_on_i2c_peripheral();
    wait_for_clock_to_stabilize();

    i2c_generate_start();
    i2c_set_data(i2c_address | I2C_READ);
    i2c_go();
    wait_for_ack();
    i2c_set_data(databyte);
    i2c_go();
    wait_for_ack();
    i2c_generate_start();
    i2c_get_byte();
    i2c_generate_nak();
    i2c_stop();
    turn_off_i2c_peripheral();
}

char i2c_write(int i2c_address, char databyte)
{
    turn_on_i2c_peripheral();
    wait_for_clock_to_stabilize();

    i2c_generate_start();
    i2c_set_data(i2c_address | I2C_WRITE);
    i2c_go();
    wait_for_ack();
    i2c_set_data(databyte);
    i2c_go();
    wait_for_ack();
    i2c_generate_start();
    i2c_get_byte();
    i2c_generate_nak();
    i2c_stop();
    turn_off_i2c_peripheral();
}

adc_thread()
{
    int value, sample_number;

    sample_number = 0;

    while (1) {
        value = i2c_read(ADC_ADDR);
        i2c_write(EE_ADDR, EE_ADDR_REG, sample_number);
        i2c_write(EE_ADDR, EE_DATA_REG, value);

        if (sample_number < 10) {
            ++sample_number;
        } else {
            sample_number = 0;
        }
    };
}

lcd_thread()
{
    int i, avg, sample, hundreds, tens, ones;

    while (1) {
        avg = 0;
        for (i=0; i<10; i++) {
            i2c_write(EE_ADDR, EE_ADDR_REG, i);
            sample = i2c_read(EE_ADDR, EE_DATA_REG);
            avg += sample;
        }

        /* calculate average */
        avg /= 10;

        /* convert to numeric digits for display */
        hundreds = avg / 100;
        tens = (avg % 100) / 10;
        ones = (avg % 10);

        spi_write(CS_LCD, LCD_CLEAR);
        spi_write(CS_LCD, '0' + hundreds);
        spi_write(CS_LCD, '0' + tens);
        spi_write(CS_LCD, '0' + ones);
    }
}

Este es un ejemplo muy crudo y rápido. ¡No codifiques así!

Ahora recuerde, el sistema operativo multitarea preventivo puede suspender cualquiera de estos hilos en cualquier línea del código (en realidad en cualquier instrucción de ensamblaje) y darle tiempo al otro hilo para que se ejecute.

Piénsalo. Imagine lo que sucedería si el sistema operativo decidiera suspender adc_thread()entre la configuración de la dirección EE para escribir y escribir los datos reales. lcd_thread()correría, andaría con el periférico I2C para leer los datos que necesitaba, y cuandoadc_thread() tocara volver a funcionar, la EEPROM no estaría en el mismo estado en que se encontraba. Las cosas no funcionarían muy bien en absoluto. Peor aún, incluso podría funcionar la mayor parte del tiempo, pero no todo el tiempo, ¡y te volverías loco tratando de descubrir por qué tu código no funciona cuando PARECE como debería!

Ese es el mejor ejemplo; el sistema operativo podría decidir adelantarse i2c_write()del adc_thread()contexto y comenzar a ejecutarlo nuevamente desdelcd_thread() el contexto! Las cosas pueden ponerse realmente desordenadas muy rápido.

Cuando escribe código para trabajar en un entorno multitarea preventivo, debe usar mecanismos de bloqueo para asegurarse de que si su código se suspende en un momento inoportuno, no se desata el infierno.

La multitarea cooperativa, por otro lado, significa que cada hilo tiene el control de cuándo abandona su tiempo de ejecución. La codificación es más simple, pero el código debe diseñarse cuidadosamente para asegurarse de que todos los hilos tengan suficiente tiempo para ejecutarse. Otro ejemplo artificial:

char getch()
{
    while (! (*uart_status & DATA_AVAILABLE)) {
        /* do nothing */
    }

    return *uart_data_reg;
}

void putch(char data)
{
    while (! (*uart_status & SHIFT_REG_EMPTY)) {
        /* do nothing */
    }

    *uart_data_reg = data;
}

void echo_thread()
{
    char data;

    while (1) {
        data = getch();
        putch(data);
        yield_cpu();
    }
}

void seconds_counter()
{
    int count = 0;

    while (1) {
        ++count;
        sleep_ms(1000);
        yield_cpu();
    }
}

Ese código no funcionará como piensa, o incluso si parece funcionar, no funcionará a medida que aumente la velocidad de datos del hilo de eco. Nuevamente, tomemos un minuto para mirarlo.

echo_thread()espera a que aparezca un byte en un UART y luego lo obtiene y espera hasta que haya espacio para escribirlo, luego lo escribe. Una vez hecho esto, le da a otros hilos un turno para ejecutarse. seconds_counter()incrementará un conteo, esperará 1000ms y luego dará a los otros hilos la oportunidad de ejecutarse. Si ingresan dos bytes en el UART mientras sleep()está sucediendo ese tiempo , podría perderse de verlos porque nuestro hipotético UART no tiene FIFO para almacenar caracteres mientras la CPU está ocupada haciendo otras cosas.

La forma correcta de implementar este ejemplo muy pobre sería poner yield_cpu()donde tenga un bucle ocupado. Esto ayudará a que las cosas avancen, pero podría causar otros problemas. por ejemplo, si el tiempo es crítico y usted cede la CPU a otro subproceso que demora más de lo esperado, podría perder su tiempo. Un sistema operativo multitarea preventivo no tendría este problema porque suspende los hilos a la fuerza para asegurarse de que todos los hilos estén programados correctamente.

¿Qué tiene que ver esto con un temporizador y un bucle de fondo? El temporizador y el bucle de fondo son muy similares al ejemplo cooperativo de multitarea anterior:

void timer_isr(void)
{
    ++ticks;
    if ((ticks % 10)) == 0) {
        ten_ms_flag = TRUE;
    }

    if ((ticks % 100) == 0) {
        onehundred_ms_flag = TRUE;
    }

    if ((ticks % 1000) == 0) {
        one_second_flag = TRUE;
    }
}

void main(void)
{
    /* initialization of timer ISR, etc. */

    while (1) {
        if (ten_ms_flag) {
            if (kbhit()) {
                putch(getch());
            }
            ten_ms_flag = FALSE;
        }

        if (onehundred_ms_flag) {
                    get_adc_data();
            onehundred_ms_flag = FALSE;
        }

        if (one_second_flag) {
            ++count;
                    update_lcd();
            one_second_flag = FALSE;
        }
    };
}

Esto se parece bastante al ejemplo de subprocesamiento cooperativo; tiene un temporizador que configura eventos y un bucle principal que los busca y actúa de manera atómica. No tiene que preocuparse de que los "hilos" de ADC y LCD interfieran entre sí porque uno nunca interrumpirá al otro. Todavía tiene que preocuparse de que un "hilo" tarde demasiado; Por ejemplo, ¿qué pasa si get_adc_data()tarda 30 ms? perderás tres oportunidades para buscar un personaje y repetirlo.

La implementación de loop + timer es a menudo mucho más simple de implementar que un microkernel multitarea cooperativo, ya que su código puede diseñarse más específicamente para la tarea en cuestión. En realidad, no se trata de realizar múltiples tareas, sino de diseñar un sistema fijo en el que le dé a cada subsistema algo de tiempo para realizar sus tareas de una manera muy específica y predecible. Incluso un sistema multitarea cooperativo debe tener una estructura de tareas genérica para cada subproceso y el siguiente subproceso a ejecutar está determinado por una función de programación que puede volverse bastante compleja.

Los mecanismos de bloqueo para los tres sistemas son los mismos, pero la sobrecarga requerida para cada uno es bastante diferente.

Personalmente, casi siempre codifico para este último estándar, la implementación de bucle + temporizador. Me parece que el enhebrado es algo que debe usarse con moderación. No solo es más complejo escribir y depurar, sino que también requiere más sobrecarga (un microkernel multitarea preventivo siempre será más grande que un temporizador estúpidamente simple y un seguidor de eventos de bucle principal).

También hay un dicho que cualquier persona que trabaje en hilos apreciará:

if you have a problem and use threads to solve it, yoeu ndup man with y pemro.bls

:-)

akohlsmith
fuente
Muchas gracias por su respuesta con ejemplos detallados, akohlsmith. Sin embargo, no puedo concluir de su respuesta por qué elige un temporizador simple y una arquitectura de bucle de fondo en lugar de la multitarea cooperativa . No me malinterpretes. Realmente aprecio su respuesta, que proporciona mucha información útil sobre la programación diferente. Simplemente no tengo el punto.
Hailang
¿Podría por favor trabajar un poco más en esto?
Hailang
Gracias akohlsmith. Me gusta la frase que pones al final. Me tomó un tiempo reconocerlo :) Volviendo al punto de su respuesta, casi siempre codifica la implementación del bucle + temporizador. Entonces, en los casos en que abandonaste esta implementación y recurriste a un sistema operativo no preventivo, ¿qué te hizo hacerlo?
Hailang
He utilizado sistemas cooperativos y preventivos de multitarea cuando estaba ejecutando el sistema operativo de otra persona. Ya sea Linux, ThreadX, ucOS-ii o QNX. Incluso en algunas de esas situaciones, he usado el temporizador simple y efectivo + bucle de eventos (me poll()viene a la mente de inmediato).
akohlsmith
No soy fanático de los subprocesos o la multitarea en embebidos, pero sé que para sistemas complejos es la única opción sensata. Los sistemas microoperativos enlatados le brindan una forma rápida de poner en marcha las cosas y, a menudo, también proporcionan controladores de dispositivos.
akohlsmith
6

La multitarea puede ser una abstracción útil en muchos proyectos de microcontroladores, aunque un verdadero programador preventivo sería demasiado pesado e innecesario en la mayoría de los casos. He realizado más de 100 proyectos de microcontroladores. He utilizado tareas cooperativas varias veces, pero el cambio de tareas preventivo con su equipaje asociado hasta ahora no ha sido apropiado.

Los problemas con las tareas preventivas en relación con las tareas cooperativas son:

  1. Mucho más peso pesado. Los planificadores de tareas preventivos son más complicados, ocupan más espacio en el código y toman más ciclos. También requieren al menos una interrupción. Eso es a menudo una carga inaceptable en la aplicación.

  2. Se requieren mutexes alrededor de estructuras a las que se pueda acceder simultáneamente. En un sistema cooperativo, simplemente no se llama TASK_YIELD en medio de lo que debería ser una operación atómica. Esto afecta las colas, el estado global compartido y se arrastra en muchos lugares.

En general, dedicar una tarea a un trabajo en particular tiene sentido cuando la CPU puede soportar esto y el trabajo es lo suficientemente complicado con suficiente operación dependiente del historial que sería complicado dividirlo en unos pocos eventos individuales separados. Este es generalmente el caso cuando se maneja un flujo de entrada de comunicaciones. Tales cosas generalmente dependen en gran medida del estado dependiendo de alguna entrada anterior. Por ejemplo, puede haber bytes de código de operación seguidos por bytes de datos únicos para cada código de operación. Luego está el problema de que estos bytes llegan cuando algo más se siente como enviarlos. Con una tarea separada que maneja el flujo de entrada, puede hacer que aparezca en el código de la tarea como si estuviera saliendo y obteniendo el siguiente byte.

En general, las tareas son útiles cuando hay mucho contexto de estado. Las tareas son básicamente máquinas de estado con la PC como variable de estado.

Muchas cosas que debe hacer un micro pueden expresarse como respuesta a un conjunto de eventos. Como resultado, generalmente tengo un ciclo de evento principal. Esto verifica cada posible evento en secuencia, luego vuelve a la cima y lo vuelve a hacer todo. Cuando el manejo de un evento lleva más de unos pocos ciclos, generalmente vuelvo al inicio del ciclo del evento después de manejarlo. En efecto, esto significa que los eventos tienen una prioridad implícita en función de dónde se verifican en la lista. En muchos sistemas simples, esto es lo suficientemente bueno.

A veces tienes tareas un poco más complicadas. Estos a menudo se pueden dividir en una secuencia de un pequeño número de cosas separadas para hacer. Puede usar banderas internas como eventos en esos casos. He hecho este tipo de cosas muchas veces en PIC de gama baja.

Si tiene la estructura básica de eventos como la anterior pero también tiene que responder a una secuencia de comandos a través de UART, por ejemplo, entonces es útil tener una tarea separada para manejar la secuencia UART recibida. Algunos microcontroladores tienen recursos de hardware limitados para la multitarea, como un PIC 16 que no puede leer ni escribir su propia pila de llamadas. En tales casos, uso lo que llamo una pseudo-tarea para el procesador de comandos UART. El bucle principal de eventos aún maneja todo lo demás, pero uno de sus eventos para manejar es que el UART recibió un nuevo byte. En ese caso, salta a una rutina que ejecuta esta pseudo-tarea. El módulo de comando UART contiene el código de la tarea, y la dirección de ejecución y algunos valores de registro de la tarea se guardan en la RAM en ese módulo. El código saltado por el bucle de eventos guarda los registros actuales, carga los registros de tareas guardados, y salta a la dirección de reinicio de la tarea. El código de la tarea invoca una macro YIELD que hace lo contrario, que luego eventualmente salta al inicio del ciclo del evento principal. En algunos casos, el bucle principal de eventos ejecuta la pseudo-tarea una vez por pasada, generalmente en la parte inferior para que sea un evento de baja prioridad.

En un PIC 18 y superior, uso un verdadero sistema de tareas cooperativas ya que la pila de llamadas es legible y escribible por firmware. En estos sistemas, la dirección de reinicio, algunas otras partes del estado y el puntero de la pila de datos se mantienen en un búfer de memoria para cada tarea. Para permitir que todas las demás tareas se ejecuten una vez, una tarea llama a TASK_YIELD. Esto guarda el estado actual de la tarea, busca en la lista la siguiente tarea disponible, carga su estado y luego la ejecuta.

En esta arquitectura, el ciclo de eventos principal es solo otra tarea, con una llamada a TASK_YIELD en la parte superior del ciclo.

Todo mi código multitarea para PIC está disponible de forma gratuita. Para verlo, instale la versión PIC Development Tools en http://www.embedinc.com/pic/dload.htm . Busque los archivos con "tarea" en sus nombres en el directorio SOURCE> PIC para los PIC de 8 bits, y el directorio SOURCE> DSPIC para los PIC de 16 bits.

Olin Lathrop
fuente
Los mutexes aún pueden ser necesarios en sistemas cooperativos multitarea, aunque es raro. El ejemplo típico es un ISR que necesita acceso a una sección crítica. Esto casi siempre se puede evitar mediante un mejor diseño o eligiendo un contenedor de datos apropiado para los datos críticos.
akohlsmith
@akoh: Sí, he usado mutexes en algunas ocasiones para manejar un recurso compartido, como el acceso al bus SPI. Mi punto fue que los mutexes no son inherentemente necesarios en la medida en que están en un sistema preventivo. No quise decir que nunca se necesitan o nunca se usan en un sistema cooperativo. Además, un mutex en un sistema cooperativo puede ser tan simple como girar en un bucle TASK_YIELD comprobando un solo bit. En un sistema preventivo, generalmente deben integrarse en el núcleo.
Olin Lathrop
@OlinLathrop: Creo que la ventaja más significativa de los sistemas no preventivos cuando se trata de mutexes es que solo se requieren cuando interactúan directamente con las interrupciones (que por naturaleza son preventivas) o cuando se necesita el tiempo para mantener un recurso protegido excede el tiempo que uno quiere pasar entre llamadas de "rendimiento", o uno quiere retener un recurso protegido alrededor de una llamada que "podría" ceder (por ejemplo, "escribir datos en un archivo"). En algunas ocasiones cuando tener un rendimiento dentro de una llamada "escribir datos" hubiera sido un problema, he incluido ...
supercat
... un método para verificar la cantidad de datos que se pueden escribir de inmediato, y un método (que probablemente ceda) para asegurar que haya alguna cantidad disponible (agilizar la recuperación de bloques de flash sucios y esperar hasta que se recupere un número adecuado) .
supercat
Hola Olin, me gusta mucho tu respuesta. Su información está mucho más allá de mis preguntas. Incluye muchas experiencias prácticas.
Hailang
1

Editar: (Dejaré mi publicación anterior a continuación; tal vez ayude a alguien algún día).

Los sistemas operativos multitarea de cualquier tipo y las Rutinas de servicio de interrupción no son, o no deberían ser, arquitecturas de sistemas competidoras. Están destinados a diferentes trabajos en diferentes niveles del sistema. Las interrupciones realmente están destinadas a secuencias de códigos breves para manejar tareas inmediatas como reiniciar un dispositivo, posiblemente sondear dispositivos que no interrumpen, cronometrar en software, etc. Por lo general, se supone que el fondo realizará cualquier procesamiento adicional que ya no sea crítico después del tiempo. Se han satisfecho las necesidades inmediatas. Si todo lo que necesita hacer es reiniciar un temporizador y alternar un LED o pulsar otro dispositivo, el ISR generalmente puede hacerlo todo en primer plano de manera segura. De lo contrario, debe informar al fondo (estableciendo una bandera o poniendo en cola un mensaje) que algo debe hacerse y liberar el procesador.

He visto estructuras de programa muy simples cuyo bucle de fondo es solo un bucle inactivo: for(;;){ ; } . Todo el trabajo se realizó en el temporizador ISR. Esto puede funcionar cuando el programa necesita repetir alguna operación constante que se garantiza que termine en menos de un período de tiempo; ciertos tipos limitados de procesamiento de señales vienen a la mente.

Personalmente, escribo ISR que limpian una salida, y dejo que el fondo se haga cargo de cualquier otra cosa que necesite hacer, incluso si eso es tan simple como multiplicar y agregar que podría hacerse en una fracción de un período de tiempo. ¿Por qué? Algún día, tendré la brillante idea de agregar otra función "simple" a mi programa, y ​​"diablos, solo tomará un breve ISR para hacerlo" y de repente mi arquitectura previamente simple crece algunas interacciones que no había planeado encendido y sucede inconsistentemente. Esos no son muy divertidos para depurar.


(Comparación publicada previamente de dos tipos de multitarea)

Cambio de tareas: el MT preventivo se encarga del cambio de tareas por usted, lo que incluye garantizar que ningún subproceso se quede sin CPU y que los subprocesos de alta prioridad se ejecuten tan pronto como estén listos. Cooperative MT requiere que el programador se asegure de que ningún subproceso mantenga el procesador durante demasiado tiempo a la vez. También tendrá que decidir cuánto tiempo es demasiado largo. Eso también significa que siempre que modifique el código, deberá saber si algún segmento de código ahora supera ese tiempo cuántico.

Protección de operaciones no atómicas: con un PMT, deberá asegurarse de que no se produzcan intercambios de subprocesos en el medio de operaciones que no deben dividirse. Lectura / escritura de ciertos pares de registros de dispositivos que deben manejarse en un orden particular o dentro de un período de tiempo máximo, por ejemplo. Con CMT es bastante fácil: simplemente no ceda el procesador en medio de tal operación.

Depuración: generalmente más fácil con CMT, ya que planifica cuándo / dónde ocurrirán los cambios de subprocesos. Las condiciones de carrera entre subprocesos y errores relacionados con operaciones no seguras para subprocesos con un PMT son particularmente difíciles de depurar porque los cambios de subprocesos son probabilísticos, por lo que no se pueden repetir.

Comprensión del código: los subprocesos escritos para un PMT se escriben prácticamente como si pudieran estar solos. Los hilos escritos para una CMT se escriben como segmentos y, dependiendo de la estructura del programa que elija, puede ser más difícil de seguir para un lector.

Uso de código de biblioteca no seguro para subprocesos: deberá verificar que cada función de biblioteca que llame bajo un subproceso seguro para subprocesos PMT. printf () y scanf () y sus variantes casi siempre no son seguras para subprocesos. Con una CMT, sabrá que no se producirá ningún cambio de subproceso, excepto cuando ceda específicamente el procesador.

Un sistema controlado por máquina de estado finito para controlar un dispositivo mecánico y / o rastrear eventos externos a menudo son buenos candidatos para CMT, ya que en cada evento, no hay mucho que hacer: arrancar o detener un motor, configurar una bandera, elegir el siguiente estado , etc. Por lo tanto, las funciones de cambio de estado son inherentemente breves.

Un enfoque híbrido puede funcionar realmente bien en este tipo de sistemas: CMT para administrar la máquina de estado (y, por lo tanto, la mayor parte del hardware) que se ejecuta como un subproceso, y uno o dos subprocesos más para realizar cálculos más largos iniciados por un estado cambio.

JRobert
fuente
Gracias por tu respuesta, JRobert. Pero no se adapta a mi pregunta. Compara el sistema operativo preventivo frente al sistema operativo no preventivo, pero no compara el sistema operativo no preventivo frente al sistema operativo no operativo.
Hailang
Bien, lo siento. Mi edición debería responder mejor a su pregunta.
JRobert