Quais são os benefícios de um sistema operacional não preventivo? e o preço desses benefícios?

14

Para um MCU de metal descoberto, comparando o código caseiro com loop de segundo plano e arquitetura de interrupção de timer, quais são os benefícios de um sistema operacional não-preventivo? Quais desses benefícios são atraentes o suficiente para um projeto adotar um sistema operacional não preventivo, em vez de usar código caseiro com arquitetura de loop em segundo plano?
.

Explicação da pergunta:

Eu realmente aprecio todos os que responderam à minha pergunta. Eu sinto que a resposta está quase lá. Acrescento esta explicação à minha pergunta aqui, que mostra minha própria consideração e pode ajudar a restringir a questão ou torná-la mais precisa.

O que estou tentando fazer é entender como escolher o RTOS mais apropriado para um projeto em geral.
Para isso, será melhor entender os conceitos básicos e os benefícios mais atraentes dos diferentes tipos de RTOS e o preço correspondente, uma vez que não há melhor RTOS para todas as aplicações.
Li livros sobre OS há alguns anos, mas não os tenho mais comigo. Pesquisei na internet antes de postar minha pergunta aqui e achei essa informação muito útil: http://www.ustudy.in/node/5456 .
Existem muitas outras informações úteis, como as introduções no site de diferentes RTOS, artigos que comparam agendamento preventivo e não preventivo e etc.
Mas não encontrei nenhum tópico mencionado quando escolher um RTOS não preventivo e quando for melhor, basta escrever seu próprio código usando a interrupção do timer e o loop de segundo plano.
Tenho certas respostas próprias, mas não estou satisfeito o suficiente com elas.
Eu realmente gostaria de saber a resposta ou a opinião de pessoas mais exageradas, especialmente nas práticas da indústria.

Meu entendimento até agora é:
não importa o uso ou não de um sistema operacional, certos tipos de códigos de agendamento são sempre necessários, mesmo na 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;  
    }  

Benefício 1:
Um sistema operacional não-preemptivo designa o caminho / estilo de programação para o código de agendamento, para que os engenheiros possam compartilhar a mesma visão, mesmo que não estivessem no mesmo projeto antes. Então, com a mesma visão sobre a tarefa conceitual, os engenheiros podem trabalhar em diferentes tarefas e testá-las, analisá-las independentemente, tanto quanto possível.
Mas quanto somos realmente capazes de ganhar com isso? Se os engenheiros estiverem trabalhando no mesmo projeto, poderão encontrar uma maneira de compartilhar bem a mesma visão sem usar um sistema operacional não preventivo.
Se um engenheiro for de outro projeto ou empresa, ele obterá o benefício se conhecer o SO anteriormente. Mas se ele não o fez, parece que não faz muita diferença para ele aprender um novo sistema operacional ou um novo trecho de código.

Benefício 2:
se o código do SO foi bem testado, economiza tempo na depuração. Este é realmente um bom benefício.
Mas se o aplicativo tiver apenas cinco tarefas, acho que não é realmente complicado escrever seu próprio código usando a interrupção do timer e o loop de segundo plano.

Um sistema operacional não-preventivo aqui é chamado de sistema operacional comercial / gratuito / herdado com um agendador não-preventivo.
Quando postei essa pergunta, penso principalmente em certos sistemas operacionais como:
(1) KISS Kernel (um pequeno RTOS não preemptivo - reivindicado por seu site)
http://www.frontiernet.net/~rhode/kisskern.html
(2) uSmartX (RTOS leve - reivindicado por seu site)
(3) FreeRTOS (é um RTOS preemptivo, mas pelo que entendi, também pode ser configurado como um RTOS não preemptivo)
(4) uC / OS (semelhante ao FreeRTOS)
(5 ) código herdado de SO / agendador em algumas empresas (geralmente criadas e mantidas pela empresa internamente)
(não é possível adicionar mais links porque a limitação da nova conta StackOverflow)

Pelo que entendi, um sistema operacional não-preemptivo é uma coleção desses códigos:
(1) um agendador usando uma estratégia não-preemptiva.
(2) facilidades para comunicação entre tarefas, mutex, sincronização e controle de tempo.
(3) gerenciamento de memória.
(4) outras instalações votos / bibliotecas como arquivos do sistema, pilha de rede, GUI e etc. (FreeRTOS e uC / OS fornece estes, mas eu não tenho certeza se eles ainda trabalho quando o programador é configurado como não-preferência)
Alguns dos eles nem sempre estão lá. Mas o agendador é uma obrigação.

hailang
fonte
É praticamente isso em poucas palavras. Se você possui uma carga de trabalho que precisa ser multithread e pode pagar a sobrecarga, use um sistema operacional de threading. Caso contrário, um tempo simples ou um "agendador" baseado em tarefas é suficiente para a maioria dos casos. E para descobrir se a multitarefa preemptiva ou cooperativa é a melhor ... Acho que tudo se resume a sobrecarga e quanto controle você deseja ter sobre a multitarefa que precisa fazer.
precisa saber é o seguinte

Respostas:

13

Isso cheira um pouco fora do tópico, mas vou tentar orientá-lo de volta aos trilhos.

A multitarefa preventiva significa que o sistema operacional ou o kernel pode suspender o encadeamento atualmente em execução e alternar para outro com base nas heurísticas de agendamento existentes. Na maioria das vezes, os threads em execução não têm o conceito de que há outras coisas acontecendo no sistema, e o que isso significa para o seu código é que você deve ter cuidado ao projetá-lo para que, se o kernel decidir suspender um thread no meio de um operação de várias etapas (por exemplo, alterar uma saída PWM, selecionar um novo canal ADC, ler o status de um periférico I2C, etc.) e deixar outro thread funcionar por um tempo, para que esses dois threads não interfiram entre si.

Um exemplo arbitrário: digamos que você é novo em sistemas embarcados multithread e possui um pequeno sistema com um I2C ADC, um SPI LCD e um I2C EEPROM. Você decidiu que seria uma boa idéia ter dois threads: um que lê no ADC e grava amostras na EEPROM e outro que lê os últimos 10 exemplos, calcula a média e exibe-os no LCD da SPI. O design inexperiente ficaria assim (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 é um exemplo muito rude e rápido. Não codifique assim!

Agora lembre-se, o sistema operacional multitarefa preventivo pode suspender qualquer um desses threads em qualquer linha do código (na verdade, em qualquer instrução de montagem) e dar tempo para que o outro thread seja executado.

Pense sobre isso. Imagine o que aconteceria se o sistema operacional decidisse suspender adc_thread()entre a configuração do endereço EE para gravar e gravar os dados reais. lcd_thread()funcionaria, mexeria no periférico I2C para ler os dados necessários e, quando adc_thread()chegasse a sua vez de executar novamente, a EEPROM não estaria no mesmo estado em que restava. As coisas não funcionariam muito bem. Pior, pode até funcionar a maior parte do tempo, mas não o tempo todo, e você fica louco tentando descobrir por que seu código não está funcionando quando parece que deveria!

Esse é o melhor exemplo; o sistema operacional pode decidir se antecipar i2c_write()ao adc_thread()contexto do e começar a executá-lo novamente no lcd_thread()contexto do! As coisas podem ficar muito bagunçadas muito rápido.

Ao escrever um código para trabalhar em um ambiente multitarefa preventivo, você deve usar mecanismos de bloqueio para garantir que, se o seu código for suspenso em um momento inoportuno, o inferno não se esvairá.

A multitarefa cooperativa, por outro lado, significa que cada thread está no controle de quando abre mão do tempo de execução. A codificação é mais simples, mas o código deve ser projetado com cuidado para garantir que todos os threads tenham tempo suficiente para executar. Outro exemplo 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();
    }
}

Esse código não funcionará como você pensa ou, mesmo que pareça funcionar, não funcionará à medida que a taxa de dados do encadeamento de eco aumentar. Mais uma vez, vamos dedicar um minuto para analisar.

echo_thread()espera que um byte apareça em um UART e o obtém e aguarda até que haja espaço para escrevê-lo e, em seguida, grava-o. Depois que isso é feito, é possível executar outros threads. seconds_counter()aumentará uma contagem, espere 1000ms e, em seguida, dê aos outros threads a chance de executar. Se dois bytes chegarem ao UART enquanto esse tempo sleep()estiver acontecendo, você poderá deixar de vê-los porque nosso hipotético UART não possui FIFO para armazenar caracteres enquanto a CPU está ocupada fazendo outras coisas.

A maneira correta de implementar esse exemplo muito ruim seria colocar yield_cpu()onde quer que você tenha um loop ocupado. Isso ajudará as coisas a progredir, mas poderá causar outros problemas. por exemplo, se o tempo é crítico e você fornece a CPU para outro segmento que leva mais tempo do que o esperado, você pode ter seu tempo perdido. Um sistema operacional multitarefa preventivo não teria esse problema porque suspende forçosamente os threads para garantir que todos os threads fossem agendados corretamente.

Agora, o que isso tem a ver com um loop de timer e segundo plano? O timer e o loop de segundo plano são muito semelhantes ao exemplo de multitarefa cooperativa acima:

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;
        }
    };
}

Isso parece muito próximo ao exemplo de rosqueamento cooperativo; você tem um cronômetro que configura eventos e um loop principal que os procura e age sobre eles de maneira atômica. Você não precisa se preocupar com os "fios" do ADC e do LCD que interferem entre si, porque um nunca interrompe o outro. Você ainda precisa se preocupar com um "fio" demorando muito; por exemplo, o que acontece se get_adc_data()demorar 30ms? você perderá três oportunidades para procurar um personagem e repeti-lo.

A implementação do loop + timer é muitas vezes muito mais simples de implementar do que um microkernel cooperativo multitarefa, já que seu código pode ser projetado mais específico para a tarefa em questão. Você não é realmente multitarefa, mas projeta um sistema fixo em que você concede a cada subsistema algum tempo para executar suas tarefas de uma maneira muito específica e previsível. Mesmo um sistema multitarefa cooperativamente precisa ter uma estrutura de tarefas genérica para cada thread, e o próximo thread a ser executado é determinado por uma função de agendamento que pode se tornar bastante complexa.

Os mecanismos de bloqueio para todos os três sistemas são os mesmos, mas a sobrecarga necessária para cada um é bem diferente.

Pessoalmente, quase sempre codifico para esse último padrão, a implementação do loop + timer. Acho que o encadeamento é algo que deve ser usado com moderação. Não é apenas mais complexo escrever e depurar, mas também exige mais sobrecarga (um microkernel preventivo multitarefa sempre será maior do que um timer estupidamente simples e um seguidor de evento de loop principal).

Há também um ditado que diz que qualquer pessoa que trabalhe em threads irá apreciar:

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

:-)

akohlsmith
fonte
Muito obrigado pela sua resposta com exemplos detalhados, akohlsmith. No entanto, não posso concluir da sua resposta por que você escolheu uma arquitetura simples de timer e loop de segundo plano em vez da multitarefa cooperativa . Não me interpretem mal. Eu realmente aprecio sua resposta, que fornece muitas informações úteis sobre diferentes agendas. Eu simplesmente não entendi.
Hailang
Você poderia trabalhar um pouco mais nisso?
Hailang
Obrigado, akohlsmith. Eu gosto da frase que você coloca no final. Demorei um pouco para reconhecê-lo :) Voltando ao ponto de sua resposta, você quase sempre codifica para a implementação do loop + timer. Então, nos casos em que você desistiu dessa implementação e passou para o sistema operacional não-preventivo, o que o fez fazer isso?
Hailang
Eu trabalhei com sistemas multitarefa cooperativos e preventivos quando estava executando o sistema operacional de outra pessoa. Linux, ThreadX, ucOS-ii ou QNX. Mesmo em algumas dessas situações, usei o timer simples e eficaz + loop de eventos ( poll()vem imediatamente à mente).
akohlsmith
Não sou fã de threading ou multitarefa no incorporado, mas sei que para sistemas complexos é a única opção sensata. Os sistemas operacionais micro enlatados oferecem uma maneira rápida de colocar as coisas em funcionamento e, muitas vezes, também fornecem drivers de dispositivo.
akohlsmith
6

A multitarefa pode ser uma abstração útil em muitos projetos de microcontroladores, embora um verdadeiro planejador preventivo seja muito pesado e desnecessário na maioria dos casos. Eu fiz mais de 100 projetos de microcontroladores. Eu usei tarefas cooperativas várias vezes, mas a troca preventiva de tarefas com sua bagagem associada até agora não foi apropriada.

Os problemas com tarefas preventivas em oposição às tarefas cooperativas são:

  1. Muito mais pesado. Agendadores de tarefas preventivas são mais complicadas, ocupam mais espaço de código e levam mais ciclos. Eles também exigem pelo menos uma interrupção. Isso geralmente é um fardo inaceitável para o aplicativo.

  2. Mutexes são necessários em estruturas que podem ser acessadas simultaneamente. Em um sistema cooperativo, você simplesmente não chama TASK_YIELD no meio do que deveria ser uma operação atômica. Isso afeta as filas, o estado global compartilhado e cria muitos lugares.

Em geral, dedicar uma tarefa a um trabalho específico faz sentido quando a CPU pode dar suporte a isso e o trabalho é complicado o suficiente com operação dependente do histórico suficiente que dividi-lo em alguns eventos individuais separados seria complicado. Geralmente, esse é o caso ao manipular um fluxo de entrada de comunicações. Geralmente, essas coisas são muito controladas pelo estado, dependendo de algumas informações anteriores. Por exemplo, pode haver bytes de código de operação seguidos por bytes de dados exclusivos para cada código de operação. Depois, há o problema desses bytes que ocorrem quando outra coisa parece enviá-los. Com uma tarefa separada manipulando o fluxo de entrada, você pode fazer com que apareça no código da tarefa como se estivesse saindo e obtendo o próximo byte.

No geral, as tarefas são úteis quando há muito contexto de estado. As tarefas são basicamente máquinas de estado, com o PC sendo a variável de estado.

Muitas coisas que um micro precisa fazer podem ser expressas como resposta a um conjunto de eventos. Como resultado, geralmente tenho um loop de evento principal. Isso verifica cada evento possível em sequência, depois volta ao topo e faz tudo de novo. Quando o tratamento de um evento leva mais do que apenas alguns ciclos, geralmente volto ao início do loop de eventos após manipular o evento. Com efeito, isso significa que os eventos têm uma prioridade implícita, com base no local em que são verificados na lista. Em muitos sistemas simples, isso é bom o suficiente.

Às vezes, você recebe tarefas um pouco mais complicadas. Geralmente, eles podem ser divididos em uma sequência de um pequeno número de coisas separadas a serem feitas. Você pode usar sinalizadores internos como eventos nesses casos. Eu já fiz esse tipo de coisa muitas vezes em PICs de baixo custo.

Se você possui a estrutura básica de eventos como acima, mas também precisa responder a um fluxo de comandos pelo UART, por exemplo, é útil ter uma tarefa separada para lidar com o fluxo UART recebido. Alguns microcontroladores têm recursos limitados de hardware para multitarefas, como um PIC 16 que não pode ler ou gravar sua própria pilha de chamadas. Nesses casos, eu uso o que chamo de pseudo-tarefa para o processador de comandos UART. O loop do evento principal ainda lida com todo o resto, mas um de seus eventos é que um novo bytes foi recebido pelo UART. Nesse caso, ele pula para uma rotina que executa essa pseudo-tarefa. O módulo de comando UART contém o código da tarefa e o endereço de execução e alguns valores de registro da tarefa são salvos na RAM desse módulo. O código saltado para o loop de eventos salva os registros atuais, carrega os registros de tarefas salvos, e pula para o endereço de reinicialização da tarefa. O código da tarefa chama uma macro YIELD que faz o inverso, que eventualmente retorna ao início do loop do evento principal. Em alguns casos, o loop do evento principal executa a pseudo-tarefa uma vez por passagem, geralmente na parte inferior para torná-lo um evento de baixa prioridade.

Em um PIC 18 e superior, eu uso um verdadeiro sistema de tarefas cooperativas, pois a pilha de chamadas é legível e gravável por firmware. Nesses sistemas, o endereço de reinicialização, algumas outras partes do estado e o ponteiro da pilha de dados são mantidos em um buffer de memória para cada tarefa. Para permitir que todas as outras tarefas sejam executadas uma vez, uma tarefa chama TASK_YIELD. Isso salva o estado atual da tarefa, examina a lista da próxima tarefa disponível, carrega seu estado e a executa.

Nessa arquitetura, o loop do evento principal é apenas outra tarefa, com uma chamada para TASK_YIELD na parte superior do loop.

Todo o meu código multitarefa para PICs está disponível gratuitamente. Para vê-lo, instale a versão PIC Development Tools em http://www.embedinc.com/pic/dload.htm . Procure arquivos com "task" em seus nomes no diretório SOURCE> PIC para os PICs de 8 bits e no diretório SOURCE> DSPIC para os PICs de 16 bits.

Olin Lathrop
fonte
mutexes ainda podem ser necessários em sistemas multitarefa cooperativamente, embora sejam raros. O exemplo típico é um ISR que precisa acessar uma seção crítica. Isso quase sempre pode ser evitado através de um design melhor ou da escolha de um contêiner de dados apropriado para os dados críticos.
akohlsmith
@akoh: Sim, eu usei mutexes em algumas ocasiões para lidar com um recurso compartilhado, como o acesso ao barramento SPI. Meu argumento era que os mutexes não são inerentemente necessários na medida em que estão em um sistema preventivo. Eu não quis dizer que eles nunca são necessários ou nunca são usados ​​em um sistema cooperativo. Além disso, um mutex em um sistema cooperativo pode ser tão simples quanto girar em um loop TASK_YIELD verificando um único bit. Em um sistema preventivo, eles geralmente precisam ser incorporados ao kernel.
amigos estão dizendo
@OlinLathrop: Eu acho que a vantagem mais significativa dos sistemas não preventivos quando se trata de mutexes é que eles só são necessários quando se interage diretamente com interrupções (que por natureza são preventivas) ou quando é necessário o tempo necessário para manter um recurso protegido excede o tempo que se deseja passar entre chamadas de "rendimento" ou se deseja reter um recurso protegido em torno de uma chamada que "possa" render (por exemplo, "gravar dados em um arquivo"). Em algumas ocasiões, quando se tem um rendimento dentro do chamado "dados write" teria sido um problema, eu incluí ...
supercat
... um método para verificar a quantidade de dados que pode ser gravada imediatamente e um método (que provavelmente pode render) para garantir que alguma quantidade esteja disponível (agilizando a recuperação de blocos de flash sujos e aguardando até que um número adequado seja recuperado) .
Supercat
Oi Olin, eu gosto muito da sua resposta. Suas informações estão muito além das minhas perguntas. Inclui muitas experiências práticas.
precisa saber é
1

Edit: (Vou deixar meu post anterior abaixo; talvez isso ajude alguém algum dia.)

Sistemas operacionais multitarefa de qualquer tipo e Rotinas de serviço de interrupção não são - ou não deveriam ser - arquiteturas de sistema concorrentes. Eles são destinados a diferentes trabalhos em diferentes níveis do sistema. As interrupções são realmente destinadas a sequências breves de código para lidar com tarefas imediatas, como reiniciar um dispositivo, possivelmente pesquisar dispositivos sem interrupção, cronometrar no software etc. Geralmente, presume-se que o plano de fundo faça outro processamento que não é mais crítico após o processo. necessidades imediatas foram atendidas. Se tudo o que você precisa é reiniciar um timer e alternar um LED ou acionar outro dispositivo, o ISR geralmente pode fazer tudo isso em primeiro plano com segurança. Caso contrário, ele precisará informar o plano de fundo (definindo um sinalizador ou enfileirando uma mensagem) que algo precisa ser feito e liberar o processador.

Eu vi estruturas programa muito simples cujo ciclo fundo é apenas um loop ocioso: for(;;){ ; }. Todo o trabalho foi realizado no temporizador ISR. Isso pode funcionar quando o programa precisar repetir alguma operação constante que termine em menos de um período de tempo; certos tipos limitados de processamento de sinal vêm à mente.

Pessoalmente, escrevo ISRs que limpam um problema e deixo o plano de fundo assumir tudo o que precisa ser feito, mesmo que seja tão simples quanto uma multiplicação e adição que possa ser feito em uma fração de um período de tempo. Por quê? Um dia, terei a brilhante idéia de adicionar outra função "simples" ao meu programa e "diabos, isso levará apenas um pequeno ISR" e, de repente, minha arquitetura anteriormente simples aumenta algumas interações que eu não havia planejado. e acontecer de forma inconsistente. Isso não é muito divertido de depurar.


(Comparação publicada anteriormente de dois tipos de multitarefa)

Alternância de tarefas: o MT preventivo cuida da alternância de tarefas para você, incluindo a garantia de que nenhum thread fique sem CPU e que os threads de alta prioridade sejam executados assim que estiverem prontos. O MT cooperativo exige que o programador verifique se nenhum thread mantém o processador por muito tempo. Você também terá que decidir quanto tempo é muito longo. Isso também significa que, sempre que você modificar o código, precisará saber se algum segmento de código agora excede esse quantum de tempo.

Protegendo operações não atômicas: Com uma PMT, você deve garantir que as trocas de encadeamentos não ocorram no meio das operações que não devem ser divididas. Leitura / gravação de certos pares de registradores de dispositivos que devem ser manipulados em uma ordem específica ou dentro de um período máximo de tempo, por exemplo. Com o CMT, é muito fácil - apenas não produza o processador no meio dessa operação.

Depuração: Geralmente mais fácil com o CMT, pois você planeja quando / onde as alternâncias de threads ocorrerão. As condições de corrida entre threads e erros relacionados a operações não seguras com thread com um PMT são particularmente difíceis de depurar porque as alterações de thread são probabilísticas, portanto, não repetíveis.

Entendendo o código: Os threads escritos para uma PMT são praticamente escritos como se pudessem ficar sozinhos. Os tópicos escritos para um CMT são escritos como segmentos e, dependendo da estrutura do programa que você escolher, pode ser mais difícil para o leitor seguir.

Usando código de biblioteca não thread-safe: você precisará verificar se cada função de biblioteca que você chama em um PMT thread-safe. printf () e scanf () e suas variantes quase sempre não são seguras para threads. Com um CMT, você saberá que nenhuma alteração de thread ocorrerá, exceto quando você fornecer especificamente o processador.

Um sistema acionado por máquina de estado finito para controlar um dispositivo mecânico e / ou rastrear eventos externos geralmente são bons candidatos para o CMT, pois em cada evento não há muito o que fazer - iniciar ou parar um motor, definir uma bandeira, escolher o próximo estado , etc. Assim, as funções de mudança de estado são inerentemente breves.

Uma abordagem híbrida pode funcionar muito bem nesses tipos de sistemas: CMT para gerenciar a máquina de estado (e, portanto, a maior parte do hardware) em execução como um encadeamento, e um ou mais dois encadeamentos para executar cálculos mais longos iniciados por um estado mudança.

JRobert
fonte
Obrigado pela sua resposta, JRobert. Mas não está adaptado à minha pergunta. Ele compara sistema operacional preemptivo versus sistema não-preemptivo, mas não compara sistema operacional não-preemptivo versus sistema não-preventivo.
Hailang
Certo - desculpe. Minha edição deve resolver sua pergunta melhor.
JRobert 12/08