Sem mais enrolação, vamos ao nosso escalonador. Para os programadores de coração mole e apegados a paixões humanas do tipo “nunca use goto” recomendo pegar um copo d´água antes. Lembre-se também que a rotina BRTOS_Scheduler() está pendurada na interrupção de watchdog, logo será executada em modo interrompido e o retorno dela será feito por uma instrução “reti“, que restaura automaticamente o SR e PC da pilha (leia novamente o post III caso esteja confuso). Outro detalhe é o “NAKED” presente na definição da função. Ele irá instruir o MSPGCC a criar uma função *SEM* nenhuma mudança na pilha ao ser executada (nada é salvo e variáveis não são criadas na pilha), deixando isto na responsabilidade do programador.
NAKED( BRTOS_Scheduler )
{
save_context_entry:
SaveContext();
SaveStackPointer();
RestoreSchedStackPointer();
dont_save_context_entry:
/* Update timers */
BRTOS_ProcessTimers();
/* check sleeping tasks */
BRTOS_SleepTasks();
/* Get the next task to run */
ucCurrentTask = BRTOS_RoundRobin(ucCurrentPriLevel);
if(ucCurrentTask == BRTOS_NO_TASK_TO_RUN)
{
/* nothing to do: sleep */
GoToLowPowerMode3();
goto dont_save_context_entry;
}
/* save scheduler context */
SaveSchedStackPointer();
/* restore stack pointer */
RestoreStackPointer();
/* restore other registers */
RestoreContext();
/* reti will pop PC and Status from stack (naked function) */
EnableInterrupts();
ReturnFromInterrupt();
}
A primeira ação é salvar o contexto com SaveContext(). Perceba que deve existir uma tarefa em execução e o ponteiro da pilha (SP) tem relação com esta tarefa. SaveContext() irá colocar na pilha desta tarefa os registros relacionados em SaveContext(), deixando a pilha da tarefa com a seguinte estrutura:
+------------------- + | SR | -> Status +--------------------+ | PC | -> Program counter +--------------------+ da tarefa | Contexto: | | registros | | de R3 a R15 | +--------------------+
SaveContext() é escrita em assembly e não vejo muito como fugir disso já que é preciso controle total neste momento:
#define SaveContext() __asm__ __volatile__ ("push R3\n" \
"push R4\n" \
"push R5\n" \
"push R6\n" \
"push R7\n" \
"push R8\n" \
"push R9\n" \
"push R10\n" \
"push R11\n" \
"push R12\n" \
"push R13\n" \
"push R14\n" \
"push R15" )
Com o contexto salvo, falta agora salvar a posição do SP da tarefa no TCB dela, com a chamada SaveStackPointer(). A notação é do conjunto de ferramentas do GCC e, confesso, não me agrada muito mas é bem flexível.
#define SaveStackPointer() \
asm("mov.w R1,%0" : "=m" (asBrtosTasks[ucCurrentTask].pusStackPtr))
Neste ponto estamos na condição ideal para trocar o SP da tarefa pelo SP do escalonador, devidamente guardado na variável usSchStackPtr através da função RestoreSchedStackPointer(). O SP do escalonador vai nos permitir fazer chamadas em C, criar variáveis locais dentro do escalonador, etc. Quando discutirmos a inicialização do sistema vai ficar claro onde esta variável foi inicializada.
#define RestoreSchedStackPointer() \
asm("mov.w %0,R1" :: "m" (usSchStackPtr))
Depois deste início bem dependente da plataforma e do compilador, duas tarefas de faxina são feitas. Uma delas é processar timers, ainda não implementada, e a outra verificar se as tarefas em modo “SLEEP” ainda precisam continuar dormindo ou não.
BRTOS_ProcessTimers(); BRTOS_SleepTasks();
O próximo passo é decidir quem ficará com o controle do processador, feito através da chamada BRTOS_RoundRobin(). Voltaremos nesta função num post futuro, por hora apenas aceite que ela irá devolver qual tarefa deve ter seu contexto restaurado.
ucCurrentTask = BRTOS_RoundRobin(ucCurrentPriLevel);
Se não existe nenhuma tarefa pronta para entrar em execução, o escalonar coloca o processa em modo de espera (GoToLowPowerMode3(), totalmente dependente do MSP430) e a próxima interrupção do watchdog irá acordar o processador, voltando algumas linhas através de um goto e refazendo as tarefas de rotina. Este ponto pode ser melhorado já que se o processador sair do modo de espera por outro motivo, a contagem de ticks para tarefas em SLEEP não estará correta. Uma possibilidade é ter uma tarefa para cuidar disso, ao invés de fazer isto no processador, evitando problemas.
if(ucCurrentTask == BRTOS_NO_TASK_TO_RUN)
{
/* nothing to do: sleep */
GoToLowPowerMode3();
goto dont_save_context_entry;
}
Uma vez com a tarefa que será executada definida, é hora de restaurar o contexto e devolver o processador para ela. As tarefas são relativamente simples: salvar o SP do escalonador, restaurar o SP da tarefa, restaurar os registros da tarefa e retornar da interrupção. Dê uma espiada na pilha desenhada no início do post e vai ver que a pilha vai ser consumida na ordem correta e a tarefa voltará ao seu estado anterior. Veja o código de cada chamada para entender melhor.
SaveSchedStackPointer(); RestoreStackPointer(); RestoreContext(); EnableInterrupts(); ReturnFromInterrupt();
Vamos parar por aqui, mas já deu para perceber que a rotina apresentada pode ser melhorada e é pouco portável para outros processadores ou compiladores. Também tem pontos discutíveis e o objetivo é este mesmo: sensibilizar. Com certeza você vai tomar outras decisões ao implementar a sua rotina.