Ao contrário do caso do sistema operativo ADEOS, os pontos do escalonamento no FreeRTOS não consistem numa chamada explicita de uma rotina nos pontos
relevantes do código do kernel, mas sim baseiam-se na salvaguarda e restauro do contexto da tarefa aquando atendimento a um pedido de interrupção.
A explicação do mecanismo utilizado pode ser feita em termos genéricos mas, para tornar a descrição mais apelativa e informativa, esta será feita com base no exemplo do porting do FreeRTOS para o processador PPC405 presente no dispositivo FPGA da família Virtex4 do produtor Xilinx (FreeRTOS, b).
Recorrendo às macros definidas no BSP (board support package) gerado pelas fer- ramentas Xilinx aquando definição do SoC em causa, no ficheiro port.c as rotinas
vPortYield(), vPortTickISR() e vPortISRWrapper() são assinadas como sendo han- dlers (rotinas de atendimento) para os casos de exceção system call, interrupção
do temporizador programável e uma interrupção externa não crítica, respetiva- mente. As linhas do código que correspondem a afirmação anterior e que foram retiradas do código associado ao ficheiro portável port.c relativo ao porting para o processodor PPC405 podem ser encontradas na Listagem 4.12.
// ... X E x c _ R e g i s t e r H a n d l e r ( X E X C _ I D _ S Y S T E M _ C A L L , ( X E x c e p t i o n H a n d l e r ) v P o r t Y i e l d , (v o i d*) 0); // ... X E x c _ R e g i s t e r H a n d l e r ( X E X C _ I D _ P I T _ I N T , ( X E x c e p t i o n H a n d l e r ) v P o r t T i c k I S R , (v o i d*) 0); // ... X E x c _ R e g i s t e r H a n d l e r ( X E X C _ I D _ N O N _ C R I T I C A L _ I N T , ( X E x c e p t i o n H a n d l e r ) v P o r t I S R W r a p p e r , N U L L );
Listagem 4.12: Assinatura dos handlers das exceções/interrupções no porting para processador PPC405
As rotinas vPortYield(), vPortTickISR() e vPortISRWrapper() são implementadas na linguagem assembly no ficheiro portasm.S. Em três casos, o corpo da rotina é de-
limitado com as macros portSAVE_STACK_POINTER_AND_LR e
portRESTORE_STACK_POINTER_AND_LR. No caso das rotinas vPortYield() e vPortTickISR(), dentro do corpo da rotina ocorre uma chamada explicita da rotina vTaskSwitchContext() (implementada na linguagem C no ficheiro task.c), enquanto no caso da rotina vPortISRWrapper() fica a responsabilidade do utiliza- dor a utilização ou não de uma chamada explicita da comutação de contexto. Uma exceção system call pode ser emitida explicitamente e, sendo assim, é possível efetuar uma chamada explicita do ponto de escalonamento. As linhas de código que definem a rotina que emita uma exceção system call foram recolhidas dos ficheiros task.h e portmacro.h e podem ser vistas na Listagem 4.13
# d e f i n e t a s k Y I E L D () p o r t Y I E L D () // f r o m t a s k . h // ...
# d e f i n e p o r t Y I E L D () asm v o l a t i l e ( " SC \ n \ t NOP " ) // f r o m p o r t m a c r o . h
Listagem 4.13: Definição da macro taskYIELD() que emite exceção system call
A rotina vPortTickISR() é assinada como sendo o handler da interrupção do PIT (Programmable Interval Timer ) do processador. Este temporizador pro- gramável é usado pelo porting como a entidade responsável pelo system tick. Ou seja, a referida interrupção ocorre com a frequência definida pelo parâme-
tro configTICK_RATE_HZ e resulta num ponto de escalonamento, que pode
efetivamente acabar com a comutação de contexto nos seguintes casos: se desde o último system tick tinha ocorrido uma alteração na tarefa mais prioritária sem uma chamada explicita de comutação de contexto; no caso da empate da prioridade que resulta na execução da próxima tarefa da fila FIFO da respetiva prioridade. A implementação das rotinas vPortYield() e vPortTickISR() pode ser consultada na Listagem 4.14. v P o r t Y i e l d : p o r t S A V E _ S T A C K _ P O I N T E R _ A N D _ L R bl v T a s k S w i t c h C o n t e x t p o r t R E S T O R E _ S T A C K _ P O I N T E R _ A N D _ L R blr v P o r t T i c k I S R : p o r t S A V E _ S T A C K _ P O I N T E R _ A N D _ L R bl x T a s k I n c r e m e n t T i c k #if c o n f i g U S E _ P R E E M P T I O N == 1 bl v T a s k S w i t c h C o n t e x t # e n d i f /* C l e a r the i n t e r r u p t */ lis R0 , 2 0 4 8 m t t s r R0 p o r t R E S T O R E _ S T A C K _ P O I N T E R _ A N D _ L R blr
Listagem 4.14: Imlementação da rotina vPortYield() e vPortTickISR()
Segue-se uma análise das etapas que ocorrem aquando comutação de contexto perante uma chamada explicita do taskYIELD() ou ocorrência do system tick. Uma tarefa se encontra na execução, tendo uma pilha associada. Quando ocorre a interrupção é executado o prólogo da interrupção que é da responsabilidade do compilador, pode ser dito que o referido prólogo acaba por guardar o contexto atual do processador dentro da pilha da tarefa a executar. A seguir são executadas
instruções que constituem a macro portSAVE_STACK_POINTER_AND_LR que
.m a c r o p o r t S A V E _ S T A C K _ P O I N T E R _ A N D _ L R /* Get the a d d r e s s of the TCB . */
xor R0 , R0 , R0 a d d i s R2 , R0 , p x C u r r e n t T C B @ h a lwz R2 , p x C u r r e n t T C B @ l ( R2 ) /* S t o r e the s t a c k p o i n t e r i n t o the TCB */ stw SP , 0( R2 ) /* S a v e the l i n k r e g i s t e r */ s t w u R1 , -24( R1 ) m f l r R0 stw R31 , 20( R1 ) stw R0 , 28( R1 ) mr R31 , R1 .e n d m
Listagem 4.15: Definição da macro portSAVE_STACK_POINTER_AND_LR
As primeiras três instruções servem para compor no registo R2 o valor da variável global pxCurrentTCB que é apontador para o TCB (Task Control Block) da tarefa que está atualmente em execução, ou seja, da tarefa cuja pilha está sob operação de momento. De seguida, o atual apontador para a pilha é guardado na posição da memória apontada pelo pxCurrentTCB (a primeira posição na estrutura de dados
tskTaskControlBlock é exatamente pxTopOfStack). As restantes instruções servem
para preservar link register.
Se estamos perante system tick com escalonador no modo preemptivo ou perante exceção system call, a rotina vTaskSwitchContext será invocada. Esta é imple- mentada na linguagem C no ficheiro task.c. Ao seu cargo fica a atualização do apontador pxCurrentTCB para o TCB da tarefa mais prioritária ou da próxima tarefa da prioridade atual, em caso de esta ser a máxima e houver mais do que uma tarefa associada. Esta atualização pode resultar ou não na alteração efetiva do apontador.
A seguir, são executadas instruções que constituem a macro
portRESTORE_STACK_POINTER_AND_LR que pode ser consultada na Lista- gem 4.16.
As primeiras cinco instruções restauram linker register previamente preservado. As próximas três instruções compõem no registo R1 (cujo alias é SP) o valor da variável global pxCurrentTCB , previamente atualizada na rotina vTaskSwitch-
Context(). De seguida, o apontador da pilha assume o valor guardado na primeira
posição do TCB da tarefa escolhida no vTaskSwitchContext() como sendo a pró- xima para executar.
.m a c r o p o r t R E S T O R E _ S T A C K _ P O I N T E R _ A N D _ L R /* R e s t o r e the l i n k r e g i s t e r */ lwz R11 , 0( R1 ) lwz R0 , 4( R11 ) m t l r R0 lwz R31 , -4( R11 ) mr R1 , R11
/* Get the a d d r e s s of the TCB . */
xor R0 , R0 , R0
a d d i s SP , R0 , p x C u r r e n t T C B @ h a
lwz SP , p x C u r r e n t T C B @ l ( R1 )
/* Get the t a s k s t a c k p o i n t e r f r o m the TCB . */
lwz SP , 0( SP ) .e n d m
Listagem 4.16: Definição da macro portRESTORE_STACK_POINTER_AND_LR
O processo do atendimento à interrupção acaba com o epílogo da interrupção, que é da responsabilidade do compilador e que, a partir da pilha para qual aponta o
stack pointer, recupera o estado do processador correspondente a ultima vez que
a tarefa proprietária desta pilha foi interrompida.
Pode ser resumido, que a comutação de contexto no sistema operativo FreeRTOS se baseia no prólogo e epílogo do atendimento à interrupção (que é da responsa- bilidade do compilador) e na manipulação do stack pointer entre o acontecimento destes. Existem três cenários para a comutação de contexto: a chamada explicita do taskYIELD() a partir de uma tarefa, isto, por sua vez, provoca system call; ocor- rência do system tick; a chamada explicita da macro taskYIELD_FROM_ISR(), a partir da interrupção do utilizador, que nem é mais nem menos do que a redefinição do vTaskSwitchContext() .