INF1018 - Software Básico

Aulas de Laboratório

Co-rotinas para multitasking cooperativo

Um uso importante de co-rotinas é a provisão de um ambiente de multitasking, onde diversas "tarefas" (linhas de execução) podem executar simultaneamente, compartilhando o uso do processador. Diferentemente de um ambiente baseado em threads, que é tipicamente preemptivo (i.e., as tarefas perdem involuntariamente o controle do processador, em qualquer momento de sua execução), um ambiente baseado em co-rotinas é basicamente cooperativo, ou seja, as tarefas cedem voluntariamente o controle do processador.

O controle de execução de tarefas em um ambiente de multitasking é tipicamente realizado através de uma estrutura chamada de escalonador de tarefas: ele é o responsável por iniciar (ou retomar) cada uma das tarefas "ativas", recebendo o controle de volta quando essa tarefa ceder o controle.

Um escalonador de tarefas utiliza basicamente uma fila (ou lista) de tarefas ativas. Ele implementa um "loop" que retira uma tarefa da fila, inicia/retoma a execução dessa tarefa e, ao receber o controle de volta (quando a tarefa ceder o controle), verifica se a tarefa terminou. Se ela não terminou, ele a reinsere na fila. Um "esqueleto" para o escalonador poderia ser

void dispatcher() {
  while (1) {
    /****************/
    /* obtem a proxima tarefa da fila: se fila vazia, termina! */

    /****************/
    /* retoma a tarefa */
    /*   Se terminou, destroi a tarefa */
    /*   Se não terminou re-insere na fila */
  }
  printf ("acabaram todas as tarefas \n");
}

Um escalonador pode adotar diferentes políticas para escolher a próxima tarefa a executar. A política mais simples é o que chamamos de round-robin: a próxima tarefa é a primeira na fila; quando ela cede o controle, volta para o fim da fila (um escalonamento para tarefas de prioridades diferentes poderia usar uma fila com prioridades...).

  1. Crie um novo diretório e copie para lá o módulo task.c, que implementa uma fila de tarefas simples que pode ser usada por um escalonador round-robin. Copie também a interface desse módulo, descrita em task.h.

    Note que cada tarefa é descrita por uma estrutura que contém um identificador, um ponteiro para próxima tarefa e um ponteiro void, que será utilizado para armazenar a co-rotina associada à tarefa.

  2. Copie agora para o seu diretório o arquivo com o esqueleto de um programa que implementa um ambiente de multitasking cooperativo. Esse esqueleto já contém uma função main, que inicializa a fila de tarefas, cria algumas co-rotinas cujo corpo é a função boba, e insere tarefas associadas a essas co-rotinas na fila de tarefas. Em seguida, ela ativa (chama) o escalonador (dispatcher).

  3. Escreva a função boba, que recebe um único parâmetro, um int, que irá representar o número da tarefa (o escalonador obtém esse número no campo id do descritor da tarefa, e o repassa à co-rotina como o segundo parâmetro da chamada a coro_resume). boba deve conter um loop com chamadas à função mostra, que recebe dois inteiros, o número da tarefa e o valor de um contador (uma variável local de boba, como fizemos em ping e pong, no laboratório passado).

    Note que quem faz o yield das tarefas é a função mostra, que sempre devolve o valor 0, para indicar que a tarefa ainda está ativa. A função boba, ao terminar, deve retornar o valor 1, para o dispatcher saber que a tarefa terminou.

  4. Usando as funções que manipulam a fila de tarefas e as funções da biblioteca de co-rotina, complete a função dispatcher, que executa, em round-robin as tarefas inseridas na fila pela main.

  5. Copie para o seu diretório os arquivos da biblioteca de co-rotinas: corotinas.h, corotinas.c e core.s. Gere o programa executável com o comando
      gcc -m32 -o meuprog core.s corotinas.c task.c multitask.c
    
    e o execute!

EXTRA: Acabou? Tente fazer boba criar e inserir na fila uma nova tarefa (você pode criar um "corpo" diferente para ela...)