Reescrevendo o velho TSR usando Bash
Quando comecei a programar, a computação era diferente de hoje. O sistema operacional era mono-tarefa; apenas um programa podia ser executado por vez. Não havia recursos modernos, como memória protegida e mapeada. Mesmo assim, era possível criar comportamentos concorrentes nos programas, mas era preciso cuidado para evitar que alguma parte bloqueasse o processamento. Por exemplo, não podia haver loops esperando entrada do teclado ou processos muito longos. Criar um programa sem bloqueios era complicado em linguagens como Clipper.
A Arte dos Hooks em Interrupções
Outra opção para simular multitarefa era criar um pequeno programa que fizesse um hook em uma interrupção do sistema. Mas o que é um hook? Basicamente, você intercepta uma interrupção – um sinal para o processador indicando que algo importante ocorreu, como um tick do relógio ou uma tecla pressionada – e redireciona esse sinal para uma função do seu programa. Para isso, você encontra o endereço da rotina da interrupção na tabela do sistema e substitui esse endereço pelo da sua função. No final da sua função, você chama a interrupção original para manter o funcionamento do sistema.
Isso permitia usar a interrupção do relógio ou do teclado para simular multitarefa. Assim, seu programa continuava rodando em segundo plano sem interferir no funcionamento principal do sistema. Era limitado mas funcionava.
Hoje ainda uso hooks de interrupções mas só em micro-controladores.
TSR: Terminate and Stay Resident
Para que esse método funcionasse, o programa precisava permanecer na memória após a execução inicial. Isso era feito usando a interrupção 27 do MS-DOS, que permitia criar um TSR (Terminate and Stay Resident) especificando o tamanho do programa. Felizmente, o cabeçalho do Turbo C, o dos.h, facilitava essa parte para nós.
Esse programa cria um relógio no canto superior direito da tela. É um TSR que usa a interrupção do relógio e acessa diretamente a memória de vídeo para exibir o tempo. Hoje, os sistemas operacionais são mais seguros, mas sinto falta de ter aquele controle total sobre a máquina, incluindo poder escrever diretamente na memória e nos dispositivos.
#include <dos.h>
#define COLOR 0x70 // (7 << 4) | 0 // Light Gray on Black
#define TSR_MEMORY_SIZE ((_SS + (_SP / 16)) - _psp)
#define OFFSET_INIT 140 // (0 * 80 + 70) * 2
struct dostime_t t;
char far *screen;
void interrupt (*prevtimer)();
void interrupt updatetimer() {
unsigned int offset;
static unsigned char last_second = 0xFF;
_dos_gettime(&t);
if (t.second != last_second) {
last_second = t.second;
offset = OFFSET_INIT;
screen[offset++] = t.hour / 10 + '0';
screen[offset++] = COLOR;
screen[offset++] = t.hour % 10 + '0';
screen[offset++] = COLOR;
screen[offset++] = ':';
screen[offset++] = COLOR;
screen[offset++] = t.minute / 10 + '0';
screen[offset++] = COLOR;
screen[offset++] = t.minute % 10 + '0';
screen[offset++] = COLOR;
screen[offset++] = ':';
screen[offset++] = COLOR;
screen[offset++] = t.second / 10 + '0';
screen[offset++] = COLOR;
screen[offset++] = t.second % 10 + '0';
screen[offset++] = COLOR;
}
// chama a interrupção de timer anterior
(*prevtimer)();
}
void main() {
screen = (char far *)MK_FP(0xB800, 0x0000);
disable(); // Desativa interrupções
prevtimer = getvect(8);
setvect(8, updatetimer);
enable(); // Habilita interrupções
keep(0, TSR_MEMORY_SIZE);
}
A Evolução dos Sistemas Operacionais
Quando criei esse TSR, ainda não existia um UNIX para rodar nos PCs como temos hoje com Linux e BSD. A popularidade dos sistemas baseados em UNIX surgiu mais tarde, trazendo melhorias em multitarefa, gerenciamento de memória e segurança.
Com a chegada do Windows e a evolução para sistemas operacionais modernos, a necessidade de TSRs diminuiu significativamente. Hoje, o sistema operacional gerencia processos e threads, o que da mais segurança, mas, não temos mais acesso direto.
TSRs em Bash: Uma Abordagem Moderna
O script Bash a seguir é similar ao TSR anterior, mas adaptado para UNIX-Like e os emuladores de terminais modernos. Como o Bash não pode acessar diretamente a memória de vídeo, ele usa comandos ANSI para controlar o terminal. Isso inclui salvar a posição do cursor antes de exibir o relógio e restaurá-la depois, garantindo que outros programas que redesenham a tela não interfiram.
#!/bin/zsh
local last_second=0
display_time() {
seconds=$(date +"%S")
[ $seconds -eq $last_second ] && return
local current_time=$(date +"%H:%M:")
echo -ne "\e7\e[1;70H\e[30;47m$current_time$seconds\e[0m\e8"
}
while true; do
display_time
sleep 0.1
done
Para executar o script e obter o mesmo efeito do TSR, basta rodá-lo em segundo plano:
./cloch.sh&
Reflexões Finais
Hoje, temos mais recursos e sistemas mais seguros e, de certa forma, mais flexíveis. No entanto, podemos nos inspirar em técnicas como os hooks de interrupções para criar coisas novas e interessantes. A nostalgia de programar em ambientes mais restritos nos lembra da criatividade e engenhosidade que precisávamos ter na época. Talvez algumas dessas técnicas antigas ainda possam ser adaptadas ou inspirar soluções inovadoras hoje.
Além disso, entender como os sistemas funcionavam em níveis mais baixos nos dá uma maior apreciação pela sofisticação dos sistemas modernos. Então, da próxima vez que você usar um comando simples no terminal, lembre-se das camadas de abstração entre seu teclado e o silício.