Despachante de tarefas
Este projeto faz parte do PPOS v2.
Você irá construir um despachante de tarefas baseado em duas entidades: uma função dispatcher, responsável pelo controle dos estados das tarefas, e uma função scheduler, responsável por determinar qual a próxima tarefa a executar a cada troca de contexto.
A figura abaixo ilustra o funcionamento geral do despachante:
Essencialmente o despachante funciona como um gerenciador dos estados das tarefas, conforme pode ser observado na figura abaixo. Por isso, a estrutura de controle de cada tarefa deve possuir um campo “estado”, que é mantido atualizado pelas funções implementadas neste projeto.
Implementação
As seguintes funções estão definidas em dispatcher.h e devem ser implementadas em dispatcher.c:
Inicia o despachante
void dispatcher_init () ;
Esta função inicia as estruturas de controle do despachante. Neste projeto, sua ação mais importante é criar a fila de tarefas prontas.
O despachante
void dispatcher () ;
Esta função, ativada imediatamente após a inicialização do sistema (em ppos.c:main) contém o código principal da função despachante, que executará as tarefas existentes até sua conclusão.
O código da função dispatcher deve seguir +/- o seguinte modelo:
dispatcher()
início
// cria a tarefa inicial de usuário, que executará user_main()
task_user = task_create(...)
// enquanto houver tarefas de usuário
enquanto ( userTasks > 0 )
// escolhe a próxima tarefa a executar
próxima = scheduler()
// escalonador escolheu uma tarefa?
se próxima ≠ NULL então
// transfere controle para a próxima tarefa
task_run(próxima)
// ao voltar ao dispatcher, trata a tarefa de acordo com seu estado
caso o estado da tarefa "próxima" seja:
PRONTA : ...
TERMINADA : ...
SUSPENSA : ...
(etc)
fim caso
fim se
fim enquanto
fim
Executa uma tarefa
void task_run (struct task_t *task) ;
Esta função transfere a CPU para a tarefa task, através das seguintes ações:
- retira a tarefa
taskda fila de prontas; - muda o estado da tarefa para “rodando”;
- transfere a CPU para ela usando
task_switch.
Libera o processador
void task_yield () ;
Esta função interrompe a tarefa atual e retorna a execução ao dispatcher, através das seguintes ações:
- muda o estado da tarefa atual para “pronta”;
- coloca a tarefa atual no fim da fila de prontas;
- retorna ao dispatcher usando
task_switch.
Suspende uma tarefa
void task_suspend(struct queue_t *queue);
Esta função suspende a tarefa atual através das seguintes ações:
- ajusta o status da tarefa atual para “suspensa”;
- insere a tarefa atual na fila
queue(se não for nula); - retorna ao dispatcher usando
task_switch.
Acorda uma tarefa
void task_awake(struct task_t *task);
Esta função acorda/ativa a tarefa task através das seguintes ações:
- se a tarefa
taskestiver suspensa em alguma fila, retira-a dessa fila; - ajusta o status de
tasktarefa para “pronta”; - insere
taskna fila de tarefas prontas; - continua a tarefa atual (não retorna ao dispatcher)
Encerra uma tarefa
void task_exit(int exit_code);
Esta função encerra a execução da tarefa atual, através das seguintes ações:
- muda o estado da tarefa para “terminada”;
- retorna ao dispatcher usando
task_switch.
O parâmetro exit_code é um valor devolvido pela tarefa atual; ele deve ser ignorado por enquanto, pois somente será usado mais tarde.
Outras funções
As funções task_wait e task_sleep estão definidas no arquivo kernel/dispatcher.h mas não precisam ser implementadas neste momento. Elas serão implementadas em um projeto posterior.
Observações
- O dispatcher deve ser implementado na função
dispatcher.c:dispatcher, que é chamada durante a inicialização do sistema. - A função dispatcher só encerra quando não existirem mais tarefas de usuário a executar.
- Será necessário criar uma fila de tarefas prontas em
dispatcher.c:dispatcher_init, usando a biblioteca de filas genéricas desenvolvida anteriormente. - A função
task_createdeve inserir cada tarefa criada na fila de tarefas prontas. - A política de escalonamento é definida pela função
scheduler.c:scheduler, chamada pelo dispatcher para escolher a próxima tarefa a ativar. Neste projeto, deve ser implementada uma política FCFS (ou seja, apenas informar qual é a primeira tarefa da fila).
Sua implementação deve funcionar com o código de teste em pingpong-dispatcher.c. A saída da execução deve ser igual ao conteúdo de pingpong-dispatcher.txt.
Arquivos
Os seguintes arquivos são relevantes para este projeto:
kernel/dispatcher.h: interface do dispatcher (não alterar)kernel/dispatcher.c: implementação do dispatcherkernel/scheduler.h: interface do escalonador (não alterar)kernel/scheduler.c: implementação do escalonadorkernel/tcb.c: define a estrutura do task control blockkernel/task.c: implementação da gestão básica de tarefastest/pingpong-dispatcher.c: código de teste do dispatcher (não alterar)test/pingpong-dispatcher.txt: saída esperada do teste (não alterar)
Uso do Valgrind
O Valgrind é uma ferramenta poderosa para a depuração de problemas de memória, que pode ser muito útil neste projeto. Entretanto, o uso de trocas de contexto em modo usuário confunde esse depurador e gera uma imensa quantidade de avisos errôneos.
Para poder usar o Valgrind corretamente neste projeto, deve-se informar ao Valgrind que estão sendo usados vários contextos com pilhas separadas. Isso é feito através de algumas alterações no código-fonte.
Primeiro, deve-se incluir o arquivo de cabeçalhos do Valgrind:
#include <valgrind/valgrind.h>
No descritor de cada tarefa, deve-se incluir um campo para uso do Valgrind:
struct task_t { ... int vg_id ; // ID da pilha da tarefa no Valgrind ... }
Após a alocação da pilha de cada tarefa, deve-se registrar essa pilha junto ao Valgrind:
// aloca a pilha da tarefa task->stack = malloc (STACKSIZE) ; if (!task->stack) return (-1) ; // registra a pilha da tarefa no Valgrind task->vg_id = VALGRIND_STACK_REGISTER (task->stack, task->stack + STACKSIZE);
Finalmente, quando a pilha da tarefa for liberada, deve-se desfazer o registro dessa pilha no Valgrind:
// libera a pilha da tarefa free (task->stack) ; // dezfaz o registro da pilha no Valgrind VALGRIND_STACK_DEREGISTER (task->vg_id);
Mais informações sobre essas macros e o uso avançado do Valgrind podem ser obtidas nesta página.
Outras informações
- Duração estimada: 5 horas.
- Dependências:

