Micro-Kernel de Tempo Real
Atenção: esta atividade é opcional, estritamente individual, e substitui a prova. Deve ser entregue até o dia da prova
O objetivo desta atividade é implementar um Micro-Kernel de Tempo Real para o 8086.
Sistemas embarcados usualmente possuem um micro-kernel que controla
as tarefas concorrentes do sistema. O micro-kernel faz o papel de um sistema
operacional muito simples: cria e escalona tarefas (processos), provê mecanismos de sinalização
entre tarefas, etc.
Quando o micro-kernel responde a eventos com rapidez ele se diz de tempo real.
Se o escalonamento das tarefas é feito através de um relógio de tempo real
(hardware que interrompe o processador a intervalos de tempo regulares) o micro-kernel
se diz preemptivo (pois pode interromper uma tarefa a qualquer instante e
passar o controle da CPU para outra tarefa). Caso contrário ele é dito não-preemptivo
e uma tarefa em execução libera o uso do processador através de uma função
especial do sistema (t_yield).
O escalonamento de tarefas pode usar prioridades ou ser bastante simples como
o escalonamento circular (em inglês round-robin),
que ciclicamente dá o controle da CPU a cada tarefa
em condições de executar. Será o caso desse projeto.
Se as tarefas compartilham o mesmo espaço de endereçamento elas são também chamadas
de threads ou processos leves.
Nesse caso elas têm acesso aos mesmos dados
e conflitos no seu acesso podem aparecer, requerendo técnicas de exclusão mútua entre tarefas
para acesso a dados compartilhados (também ditas de implementação de Regiões Críticas).
Este é o caso, por exemplo, das funções int 21h do DOS, que não podem ser usadas
simultâneamente por duas tarefas.
Tarefas de sistemas embarcados usualmente nunca terminam; ficam num laço
à espera de um evento sobre o qual devem atuar. No nosso caso vamos permitir
o término de uma tarefa.
Uma thread no nosso caso é simplesmente uma rotina que possui
sua própria pilha (e que pode chamar outras rotinas).
V. deve programar uma biblioteca de rotinas que permitam implementar threads num sistema com escalonamento não-preemptivo (infelizmente a máquina virtual que emula o DOS no Windows não permite o controle de interrupções, necessário à implementação de escalonamento preemptivo, que é muito mais poderoso do ponto de vista prático e mais interessante também).
No sistema que V.vai implementar haverá um número máximo prefixado de threads,
NT, (é conveniente que seja uma potência de 2, pois facilita o cálculo de índices),
e espaço em memória para controle das threads será pre-alocado para esse número.
Uma thread será identificada por um índice (tid) >=0 e < NT.
Apenas duas tabelas e algumas variáveis auxiliares
serão necessárias para implementar a biblioteca:
As seguintes funções básicas fazem parte da bibioteca:
Devido ao fato de o sistema ser não preemptivo, V. poderá depurá-lo com
o turbo-debugger. É relativamente simples tranformá-lo num micro-kernel
preemptivo: a função t_yield passaria a ser a rotina de interrupção
do relógio de tempo real!. As outras funções da biblioteca não teriam alteração
alguma, (exceto
t_create) mas elas devem executar com interrupções desablitadas por
razões óbvias (este é o problema básico da máquina virtual DOS que o
Windows 2000/NT fornece: não é possivel desabilitar interrupções).
As instruções cli e sti são inóquas
e V. não deve usá-las, pois cli
por alguma razão misteriosa atrasa a saída de mensagens enviadas via int 21h.
V. poderia fazer o micro-kernel numa máquina com o velho sistema DOS,
ou talvez num Windows mais antigo.
Descobri, no entanto, um truque (sujo) para implemetar um micro-kernel preemptivo
no ambiente Windows NT/2000.
De qualquer forma é importante implementar primeiro o micro-kernel não
preemptivo, pois V. poderá depurá-lo com o turbo debugger ao contrário do
preemptivo, especialmente as funções t_create e
t_yield que são as mais críticas.
Se V. chegar ao final desta atividade darei a dica de como transformar o seu
micro-kernel num micro-kernel preemptivo com pouquíssimas mudanças!
A título de ilustração, abaixo transcrevo algumas funções do nosso micro-kernel
preemptivo.
Org 100h ;aqui definição das constantes básicas acima mencionadas call init ; create idle task and start RTClock call debug ; send a numbered message to video mov ax,[ct] ; ct is incremented at each interrupt (my debug help) here: ; check if RTC interrupt is enabled call t_yield ; let idle task run cmp ax, [ct] jz here call debug ; interrupt is working,for we came out of the loop mov dx,task1 call t_create ; creates 1st task mov [tid],di ; save tid of task created mov dx, task2 ; creates 2nd task call t_create mov [tid+2], di ; save tid of new task mov si, [tid] call t_join ; waits for task1 to end call debug mov si, [tid+2] call t_join ; and now waits for task2 end0: call debug end: call restint ; restore original 1ch interrupt vector mov ah, 4ch ; back to DOS! int 21h ;________________________________________________________________ debug: inc byte[msg] ; trick to number a standard message mov dx, msg call prtmsg ; prints message proteced by semaphore ret ;_____________________________________________________ task1: ; my first task mov ax,4 my0: push ax mov dx,task1msg call prtmsg mov cx,18 call t_sleep pop ax dec ax jnz my0 call t_exit ; finish task jmp $ ; should never come here! ;__________________________________________________________________ task2: ; second task mov cx,18 call t_sleep ; sleep for 1 second mov dx, task2msg call prtmsg ;show message protected by semaphore call t_exit ; finish this task jmp $ ; should never come here ;******************************************************************** prtmsg: ; show message via DOS int 21h, protected by semaphore 0 ; input: dx= message address mov ax,1 lock xchg ax, [sem] ; check semaphore: 1 = locked or ax,ax jz free call t_yield ; semaphore locked, try again later jmp prtmsg free: ; now we hold the semaphore mov ah,09 ; send message to video int 21h mov word[sem],0 ; unlock semaphore ret ;*********************************************************************** t_idle: ; idle task call t_yield ; do nothing but allow others to run! jmp t_idle ;*********************************************************************** t_lock: ; locks semaphore ; input bx = semaphore to lock ; output: none lock0: mov ax, 1 ; 1: locks semaphore, 0: semaphore is unlocked lock xchg ax, [sem+bx] ; individible: locks bus in a multiprocessor system or ax, ax ; if ax is 0 jz lock1 ; then semaphore was uncloked call t_yield ; else was locked by other task, release processor jmp lock0 ; and try again lock1: ret ;********************************************************************* t_unlock: ; unlock semaphore bx mov word[sem+bx],0 ret ;*********************************************************************