|
Desafio: Micro Executivo de Tempo Real
|
|
Atualizado em 17/ago/15 - Prof. Célio
Objetivo: Implementar um executivo de tempo real em linguagem de montagem do ARM. Ele deve ser
testado usando o simulador qemu-arm.
Principais recursos:
- Número prefixado de tarefas definidas em tempo de montagem
- Comunicação entre tarefas através de envio/recepção de mensagens de tamanho variável.
- Tarefas podem usar o clock do sistema para implementar atrasos (delays)
- Uma tarefa pode terminar mas tarefas não podem ser criadas.
- Uma tarefa pode receber várias mensagens que devem ser enfileiradas numa fila da tarefa.
- Pilhas de execução individuais para cada tarefa e para o núcleo do sistema (primitivas).
- Escalonamento circular de tarefas (opcional: com prioridades estáticas)
- Estados de uma tarefa:
- READY - pronta para executar (estado inicial)
- BLOCKED - bloqueada à espera de uma ou mais mensagens
- DELAYED - atraso não expirado
- DONE - tarefa terminou
- Primitivas (funções do sistema):
Sugestões para implementação:
- Todas as primiivas devem salvar/restaurar na/da pilha os registradores que vai usar
pois eles podem estar sendo usados pela tarefa. Este requisito é fundamental no caso de um sistema real, onde o escalonador
é invocado por interrupção.
- Use um vetor de estruturas de dados de tamanho fixo para armazenar o contexto de cada tarefa.
Chame-as de TCB (Task Control Block). É conveniente (pois facilita o endereçamento dos TCB)
que cada TCB tenha um número
de palavras que seja potência de 2: quatro palavras são suficientes para os requisitos.
Exemplo de TCB: PC, (apontador para topo da pilha (28 bits), estado da tarefa (4 bits)),
Valor do atraso (delay), Apontador para fila de mensagens da tarefa.
- A primitiva delay pode ser implementada usando a função clock da libc.
Ela devolve o número de clocks do relógio da CPU desde o início do programa (
na xaveco.lab 100 clocks correspondem a 1 segundo).
- Uma mensagem deve ter no seu início o número da tarefa que a está enviando e o tamanho da mensagem.
- Inicialmente permita apenas uma mensagem pendente por tarefa e implemente todas as primitivas citadas.
- Para implementar uma fila de mensagens para cada tarefa você deveria desenvolver e testar
separadamente um sistema de listas ligadas para alocação/liberação de nós para armazenar mensagens
(esta é a parte mais complexa da implementação, embora requeira poucas linhas de código (< 20)).
Uma forma conveniente é definir em tempo de montagem uma lista ligada, (freelist),
com, digamos, 4*N nós,
onde N é o número de tarefas e cada nó contem 2 palavras: um apontador para o próximo nó
da lista e um apontador para uma mensagem quando o nó for alocado.
As seguintes funções para manipular esta lista devem ser implementadas :
- getnode - remove um nó da freelist e devolve um apontador para o mesmo num registrador.
- freenode - insere na freelist um nó, passando um apontador como parâmetro.
A função getnode é interna (só usada pelas primitivas); ela deverá ser invocada pela primitiva sendmsg
A função freenode é interna; ela deverá ser invocada pela primitiva getmsg
(dependendo da implementação getmsg também pode ser uma função interna).
- Introduza testes de robustês como: (i) deteção da freelist vazia ao invocar getnode
(todos os nós foram alocados),
(ii) deadlock: não há tarefas no estado READY para escalonar;
(iii) estouro da pilha do sistema ou de uma tarefa
- Prioridades estáticas podem ser implementadas, se desejado, considerando a posição da tarefa no vetor TCB como sua prioridade (menor a posição, maior a prioridade).
Opcional: pode ser interessante escrever as tarefas de uma aplicação na linguagem C:
elas invocariam as primitivas do sistema escritas em assembler.
É relativamente simples passar parâmetros para rotinas em assembler (e receber resultados)
a partir de um programa em C: parâmetros são passados
nos registradores r0, r1, etc, e resultado devolvido em r0. Detalhes serão vistos em aula.