====== Tarefas cooperativas ======
Este projeto faz parte do [[PPOS-v2:start|PPOS v2]].
O objetivo deste projeto é construir as funções básicas de gestão de tarefas usando as funções de troca de contexto vistas no projeto anterior.
===== Descritor de tarefa =====
Cada tarefa existente no sistema deve ter uma estrutura que a representa, ou seja, um descritor de tarefa (//TCB - Task Control Block//). Essa estrutura deve ser definida no arquivo ''kernel/tcb.h'':
struct task_t
{
int id; // identificador da tarefa
char *name; // nome da tarefa
ctx_t context; // contexto da tarefa
int status; // pronta, executando, terminada, ...
... // demais informações, a completar
};
===== Implementação =====
As seguintes funções estão definidas em ''kernel/task.h'' e devem ser implementadas em ''kernel/task.c'':
=== Inicia a gestão de tarefas ===
void task_init();
Esta função é chamada por ''ppos.c:ppos_init'' e inicia as variáveis necessárias à gestão de tarefas. Neste momento, sua principal responsabilidade é definir uma tarefa para o fluxo de execução do núcleo, que pode ser chamada de ''task_kernel'', com nome "kernel" e ID 0. Essa tarefa pode ser vista como o equivalente da função ''main'' no programa ''contexts.c'' do projeto anterior.
A tarefa do núcleo não deve ser criada com ''task_create'', pois não usa uma pilha separada nem uma função separada.
=== Cria uma nova tarefa ===
struct task_t *task_create(char *name,
void (*entry)(void *),
void *arg);
Parâmetros:
* ''name'': nome da tarefa (útil nas mensagens na tela), pode ser nulo
* ''entry'': função que será executada pela tarefa
* ''arg'': parâmetro recebido pela função ''entry'' ao iniciar a tarefa
* retorno: ponteiro para uma nova tarefa ou NULL, se houver erro
Esta função cria uma nova tarefa. Para isso ela deve:
* alocar e preencher um TCB
* alocar uma pilha
* criar um contexto
* definir um ID
* devolver um ponteiro para o TCB
Ao ser criada, cada tarefa recebe um ID inteiro crescente, iniciando com 0 (zero).
=== Destrói uma tarefa ===
int task_destroy(struct task_t *task);
Esta função destrói um descritor de tarefa e libera seus recursos (pilha e TCB).
=== Transfere o processador para outra tarefa ===
int task_switch(struct task_t *task);
Parâmetros:
* ''task'': tarefa que irá receber o processador, ou NULL.
* retorno: valor negativo se houver erro, ou zero
Esta é a operação básica de troca de contexto entre tarefas, que usa a função ''ctx_swap'' da biblioteca de troca de contextos. Ela será chamada sempre que for necessária uma troca de contexto.
Caso ''task'' seja nulo, a CPU deve ser transferida para a tarefa que criou a tarefa atual, ou seja, a tarefa que estava ativa quando ''task_create'' criou a tarefa que está executando neste momento.
Observe que a função ''task_switch'' recebe somente o ponteiro da **próxima** tarefa, então ela deve ter alguma forma de saber quem é a tarefa atualmente em execução, para poder efetuar a troca de contexto.
=== Informa o identificador da tarefa corrente ===
int task_id(struct task_t *task);
Esta função informa o identificador numérico (ID) de uma tarefa (ou da tarefa tarefa atual, se ''task'' for nulo). Não devem existir duas tarefas com o mesmo ID.
=== Informa o nome da tarefa corrente ===
char *task_name(struct task_t *task);
Esta função informa o nome de uma tarefa (ou da tarefa atual, se ''task'' for nulo).
===== O despachante =====
Será necessário implementar um despachante de tarefas básico na função ''dispatcher.c:dispatcher''. Ele deve executar estas ações:
- criar uma tarefa ''task_user'' para executar o código inicial da aplicação (definido na função ''pingpong-task*.c:user_main'')
- passar a CPU para essa tarefa, usando a função ''task_switch''.
- quando retornar dessa tarefa, destruí-la e encerrar.
A função ''dispatcher'' é acionada pelo programa principal do SO (função ''ppos.c:main''); quando essa função encerrar, o programa principal irá desligar o SO.
===== Observações =====
A implementação completa deste projeto compreende definir as estruturas de dados necessários para gerenciar as tarefas e implementar as funções acima descritas, comentando detalhadamente o código.
Capriche na implementação, pois esse código será a base de todos os projetos posteriores.
Avisos de compilação (//warnings//) geram **desconto** na nota!
===== Validação =====
Seu código deve funcionar adequadamente com os programas de teste, fornecendo as saídas esperadas:
* ''test/pingpong-tasks1.c'' (saída esperada ''test/pingpong-tasks1.txt'')
* ''test/pingpong-tasks2.c'' (saída esperada ''test/pingpong-tasks2.txt'')
* ''test/pingpong-tasks3.c'' (saída esperada ''test/pingpong-tasks3.txt'')
A compilação usando ''make'' irá gerar os executáveis dos programas de teste acima.
O primeiro programa de teste corresponde ao código ''contexts.c'' do projeto anterior, reescrito com as funções propostas neste projeto. Comparar os dois códigos pode ajudar a compreender o que deve ser implementado em cada função.
===== Arquivos =====
Os seguintes arquivos são relevantes para este projeto:
* ''kernel/tcb.h'': define a estrutura do //task control block//
* ''kernel/task.h'': interface da gestão básica de tarefas (**não alterar**)
* ''kernel/task.c'': implementação da gestão básica de tarefas
* ''kernel/dispatcher.c'': implementação básica do despachante de tarefas
* ''test/pingpong-tasks*.c'': programas de teste
* ''test/pingpong-tasks*.txt'': saídas esperadas dos programas de teste
===== Depuração =====
Todas as funções implementadas devem gerar mensagens de depuração, que permitam acompanhar a execução das tarefas, como no exemplo a seguir:
PPOS: system starting
DEBUG: subsystem task initiated
DEBUG: subsystem dispatcher initiated
PPOS: system started (uptime 0 ms)
DEBUG: dispatcher started
DEBUG: task 0 (kernel) create task 1 (user)
DEBUG: task 0 (kernel) switch to task 1 (user)
user: inicio
DEBUG: task 1 (user) create task 2 (ping)
DEBUG: task 1 (user) create task 3 (pong)
DEBUG: task 1 (user) switch to task 2 (ping)
ping: inicio
ping: 0
DEBUG: task 2 (ping) switch to task 3 (pong)
pong: inicio
pong: 0
DEBUG: task 3 (pong) switch to task 2 (ping)
ping: 1
DEBUG: task 2 (ping) switch to task 3 (pong)
pong: 1
DEBUG: task 3 (pong) switch to task 2 (ping)
ping: 2
DEBUG: task 2 (ping) switch to task 3 (pong)
pong: 2
DEBUG: task 3 (pong) switch to task 2 (ping)
ping: 3
DEBUG: task 2 (ping) switch to task 3 (pong)
pong: 3
DEBUG: task 3 (pong) switch to task 2 (ping)
ping: fim
DEBUG: task 2 (ping) switch to task 1 (user)
DEBUG: task 1 (user) switch to task 3 (pong)
pong: fim
DEBUG: task 3 (pong) switch to task 1 (user)
user: fim
DEBUG: task 1 (user) destroy task 2 (ping)
DEBUG: task 1 (user) destroy task 3 (pong)
DEBUG: task 1 (user) switch to task 0 (kernel)
DEBUG: dispatcher stopping, no more user tasks
DEBUG: task 0 (kernel) destroy task 1 (user)
PPOS: system stopping
PPOS: system stopped (uptime 0 ms)
Para facilitar seu trabalho, o arquivo ''kernel/macros.h'' define uma macro ''ppos_debug'' para a geração de mensagens de depuração, que funciona igual à função ''printf'':
#ifdef DEBUG
ppos_debug("switch task %d (%s) to task %d (%s)\n", ...);
#endif
Essas mensagens de depuração somente serão geradas se a macro global ''DEBUG'' estiver definida no código-fonte (''#define DEBUG'') ou na linha de comando, no momento da compilação (''make debug p1'').
===== Outras informações =====
* Duração estimada: 4 horas.
* Dependências:
* [[Trocas de contexto]]