====== Despachante de tarefas ======
Este projeto faz parte do [[PPOS-v2:start|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:
{{ dispatcher.png | Funcionamento 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.
{{ estados.png | Estados das tarefas}}
===== 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.
Na versão anterior (1) do PPOS, o despachante era uma tarefa. Nesta versão 2 **o despachante é uma função**,
invocada ao final da inicializaçã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 ''task'' da 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 ''task'' estiver suspensa em alguma fila, retira-a dessa fila;
- ajusta o status de ''task'' tarefa para "pronta";
- insere ''task'' na 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_create'' deve 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 dispatcher
* ''kernel/scheduler.h'': interface do escalonador (**não alterar**)
* ''kernel/scheduler.c'': implementação do escalonador
* ''kernel/tcb.c'': define a estrutura do task control block
* ''kernel/task.c'': implementação da gestão básica de tarefas
* ''test/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 [[https://valgrind.org|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
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 [[https://valgrind.org/docs/manual/manual-core-adv.html|nesta página]].
===== Outras informações =====
* Duração estimada: 5 horas.
* Dependências:
* [[Filas genéricas]]
* [[Tarefas cooperativas]]