ULWOS – Multitarefa no RL78: Comutador de tarefas

ULWOS Multitarefa Multitarefa no RL78
Este post faz parte da série ULWOS - Multitarefa no RL78. Leia também os outros posts da série:

Agora que já temos uma ideia geral de como funciona a comutação de tarefas num sistema multitarefa, vamos tratar do nosso pequeno comutador de tarefas e da sua implementação no RL78.

A linha RL78 da Renesas é derivada da linha 78K que, por sua vez, deriva do precursor da microcomputação em geral, o Intel 8080.

A arquitetura dos RL78 inclui um conjunto de registradores de uso geral de 8 bits (A, X, B, C, D, E, H e L) que podem ser agrupados para formar registradores de 16 bits: AX, BC, DE e HL (de maneira geral o AX atua como acumulador e os registradores BC, DE e HL podem atuar como apontadores). Além disso, estão disponíveis até 4 bancos de registradores, resultando num total de 32 registradores de 8 bits ou 16 registradores de 16 bits.

Além dos registradores de uso geral, encontramos também um registrador de estado (PSW, de 8 bits), um apontador de pilha (SP, de 16 bits), dois registradores de segmentação de memória (ES para dados e CS para programa) e um contador de programa (PC, de 20 bits). Todos esses registradores (e outros) estão mapeados na memória, exceto o PC.

A arquitetura permite até 1MB de endereçamento de memória e predispõe os últimos 64KB de memória para a área de RAM e de registradores.

No que diz respeito ao nosso agendador/comutador de tarefas, o grande número de registradores significa igual quantidade de RAM para armazenamento do contexto da tarefa e um ligeiro impacto no tempo necessário para troca de contexto, já que quanto maior o número de registradores, mais tempo se gasta empilhando e desempilhando os mesmos! Felizmente este tempo não é muito elevado graças à arquitetura CISC com um set de instruções bastante otimizado e rápido, onde uma boa parcela das instruções possui tamanho de um a dois bytes e são executadas em um ciclo de clock (graças a um pipeline de 3 estágios)!

A base do nosso agendador/comutador de tarefas consiste em realizar duas operações essenciais: o salvamento do contexto da tarefa que está sendo paralisada e a recuperação do contexto da tarefa cuja execução será retomada. O salvamento de contexto utiliza instruções assembly PUSH que permitem salvar registradores na pilha (apontada por SP) e a recuperação de contexto utiliza instruções POP que retiram valores da pilha apontada por SP. Uma observação importante: nos RL78 a pilha é decrescente, ou seja, a cada empilhamento (PUSH) o SP é decrementado em 2 e a cada desempilhamento (POP) o SP é incrementado em 2!

Por padrão, o compilador GCC utiliza apenas os três primeiros bancos de registradores para os programas em C, reservando o último para o tratamento de interrupções. Sendo assim, o nosso código de salvamento e recuperação de contexto deverá preservar apenas os três primeiros bancos e poderá fazer uso dos registradores do banco 3 para as operações internas.

A seguir temos o código do nosso agendador de tarefas round-robin. O agendador utiliza a interrupção do timer de intervalo (IT) disponível em todos os RL78. O timer é configurado para gerar uma interrupção a cada 1ms, sendo este o heart-beat do ULWOS. Isto significa que a cada 1ms esta interrupção é disparada, o PC e o PSW são salvos na pilha (da tarefa) e a execução desvia para a função INT_IT().

Dentro da função de tratamento de interrupção ocorrem três operações: o salvamento do contexto (função save_context), o avanço do apontador de tarefa (ulwos_current_task) para a próxima tarefa válida e a recuperação do contexto desta tarefa. Ao final da ISR a pilha atual é a da tarefa a ser executada, os três primeiros bancos de registradores estão com os seus valores recuperados (o contexto anterior da tarefa), então a execução da instrução assembly RETI desempilha o PC e PSW da pilha e o código da nova tarefa passa a ser executado de onde havia parado anteriormente!

Note que a função INT_IT é declarada com o atributo naked. Este atributo informa ao compilador GCC que ele não deve gerar código de entrada (prólogo) e nem de saída (epílogo) para a função. Apesar da observação do manual do GCC não recomendar a utilização de extended inline assembly ou mistura de assembly e C dentro da função, acredito que esta recomendação não pode ser aplicada ao presente caso, pois todos os cuidados com salvamento e recuperação de registradores foram tomados. Além disso, o código da ISR utiliza (como veremos a seguir) apenas o banco de registradores número 3 e o simples fato de ser uma ISR já separa esta função das funções normais chamadas por software.

Observando a simplicidade do código do agendador é fácil perceber que toda a “mágica” é feita pelas funções de salvamento e recuperação de contexto. Então que tal darmos uma olhada na operação das mesmas? Mas antes de seguir, é importante conhecer as estruturas de dados utilizadas para armazenamento do contexto. O ULWOS utiliza três estruturas básicas para armazenamento do estado e contexto das tarefas:

  • ulwos_task_context é um array bidimensional que armazena o conteúdo dos registradores da CPU (AX, BC, DE e HL dos bancos 0, 1 e 2);
  • o array ulwos_taskSP armazena o conteúdo do stack pointer de cada tarefa e;
  • o array ulwos_task_stack é um array bidimensional que armazena a pilha de memória de cada tarefa.

Atualmente o ULWOS não preserva o estado dos registradores ES e CS, mas isso pode ser facilmente modificado caso necessário!

O salvamento de contexto consiste basicamente em:

  • salvar o conteúdo do SP e;
  • salvar o contexto.

A pilha da tarefa não necessita ser salva pois cada tarefa possui a sua pilha individual.

Infelizmente o salvamento do SP e do contexto não pode ser feito diretamente em C, já que não há como garantir que o compilador não irá alterar o conteúdo do SP durante a execução do código. Por isso, é melhor utilizar o assembly para termos a garantia de que o conteúdo de todos os registradores da CPU será preservado fielmente. Além disso, o uso de assembly permite escrever o código mais eficiente possível (limitado apenas à eficiência do programador, é claro!).

A seguir temos o código da função de salvamento de contexto. Ela é declarada como inline pois o objetivo é que o compilador insira todo o código da função no local da sua chamada (evitando assim o uso da pilha, o que seria prejudicial ao funcionamento do nosso agendador).

O código assembly é totalmente auto-explicativo, mas vale a pena fazer alguns comentários sobre o funcionamento do assembly inline no GCC. Para que possamos utilizar variáveis C dentro do assembly é necessário utilizar o chamado extended inline assembly do GCC (tópico 6.44.2 do manual do compilador) cujo formato é o seguinte:

O template contém o código assembly propriamente dito. Este código pode referenciar registradores da CPU ou endereços de memória. Quando é necessário acessar uma variável C, a mesma deve ser especificada nos operandos de saída (se a mesma for alterada pelo código assembly) ou nos operandos de entrada (opcionais) se a mesma somente for lida. Os Clobbers consistem numa lista de registradores alterados direta ou indiretamente pelo código assembly do template. Cada instrução assembly deve estar escrita dentro de aspas e ser encerrada com \n\t (códigos de barra invertida para linha nova e tab).

Observe que os operandos de entrada e clobbers são opcionais, mas os operandos de saída são obrigatórios. Caso o código não utilize nenhum operando de saída, é possível deixar a lista de operandos de saída em branco (como no nosso caso).

Os operandos de saída e de entrada utilizam ainda uma série de restrições (constraints) que especificam como o operando afeta ou é afetado pelo código. Estas restrições são grafadas dentro de strings e podem assumir valores como:

  • “=” - indica que o conteúdo do operando é sobrescrito;
  • “+” - indica que o conteúdo do operando tanto é lido como escrito pelo código;
  • “m” - indica que o operando é uma posição de memória;
  • “r” - indica que o operando é um registrador;
  • “i” - indica que o operando é um valor imediato (como o endereço de uma variável).

Note que o símbolo C deve seguir o constraint e estar escrito dentro de parenteses, para maiores detalhes consulte o tópico 6.44.3.1 do manual do GCC.

Note que save_regs() não é uma função, mas uma macro criada para simplificar e melhorar a legibilidade do código, ela é definida como:

Após a execução da função save_context, todo o conteúdo dos registradores dos bancos 0, 1 e 2 estará preservado na pilha ulwos_task_context da tarefa e o SP da tarefa estará preservado em ulwos_taskSP. O conteúdo do PSW e do PC já foi preservado automaticamente na pilha da tarefa quando ocorreu a interrupção!

O passo seguinte é selecionar a próxima tarefa a ser executada. O agendador round-robin simplesmente incrementa o indicator de tarefa atual (ulwos_current_task) e verifica se o valor corresponde a uma tarefa válida. Caso negativo, o indicador retorna a zero, reiniciando o ciclo de tarefas.

Após a seleção da nova tarefa a ser executada, é necessário recuperar o contexto da mesma, de forma que a execução possa ser restabelecida do ponto em que estava anteriormente. A função restore_context é encarregada de tal operação. O código assembly é autoexplicativo e dispensa maiores comentários.

Assim como no caso de save_regs(), restore_regs() nada mais é do que uma macro que desempilha os registrados da memória. Sua definição é a seguinte:

Agora que já temos código capaz de comutar tarefas em execução, é necessário uma função para criar uma nova tarefa. E isso fica para o próximo artigo!

Outros artigos da série

<< ULWOS - Multitarefa no RL78: IntroduçãoULWOS - Multitarefa no RL78: Criando tarefas >>
Licença Creative Commons Esta obra está licenciada com uma Licença Creative Commons Atribuição-CompartilhaIgual 4.0 Internacional.

Receba os melhores conteúdos sobre sistemas eletrônicos embarcados, dicas, tutoriais e promoções.

Software » ULWOS - Multitarefa no RL78: Comutador de tarefas
Comentários:
Notificações
Notificar
guest
0 Comentários
Inline Feedbacks
View all comments
Talvez você goste:

Séries



Outros da Série

Menu

WEBINAR
 

Soluções inteligentes para acionamento de MOSFETs/IGBTs com família STDRIVE

Data: 08/10 às 15:00h - Apoio: STMicroelectronics
 
INSCREVA-SE AGORA »



 
close-link