O UNIX sempre foi um sistema operacional multiusuário desde sua concepção no final dos anos 70. Era um sistema originariamente utilizado em grandes mainframes, que eram computadores de grande porte utilizados concorrentemente por dezenas ou até centenas de usuários. Cada usuário utilizava o mainframe através de um terminal, que eram dispositivos físicos muito simples, consistindo simplesmente de um tubo de raios catódicos (CRT) e um teclado. Em épocas ainda mais remotas, havia uma impressora e não um monitor (telétipo). Foi, aliás, da palavra teletype que surgiu a sigla tty, utilizada no UNIX como sinônimo de terminal.
Os terminais eram conectados ao mainframe através de cabos seriais. Não havia portanto, stricto sensu, uma rede e sim um único computador (mainframe) com muitos periféricos (terminais) espalhados por diversas salas e às vezes até por diversos edifícios.
No mainframe, os cabos advindos dos terminais eram conectados a placas controladoras UART, isto é, placas de comunicação através das quais o mainframe podia manipular o sinal desses cabos. Do ponto de vista do sistema operacional e dos programas do mainframe, o cenário era razoavelmente simples: há uma série de portas seriais e cada uma delas se presta para somente duas operações: (a) escrever bytes; (b) ler bytes. Nenhuma outra interação é possível (tecnicamente o driver pode, além de enviar e receber dados, efetuar operações de controle em baixo nível na linha serial como por exemplo ajustar bits de paridade e controle de fluxo, mas isso não nos importa no momento).
Em outras palavras, o mainframe não tem controle nenhum sobre o funcionamento do terminal, limitando-se somente a lhe enviar bytes e dele recebê-los. Diferentemente do que ocorre num PC, por exemplo, o hardware do mainframe não tem controle nenhum sobre o tubo de raios catódicos (CRT) do terminal; na verdade o mainframe nem ao menos sabe se o que está conectado à porta serial é de fato um terminal com CRT, um telétipo sem CRT ou qualquer outro dispositivo. O mainframe não tem nenhuma garantia do que acontece, de fato, quando ele envia bytes ao terminal: os terminais munidos de CRT exibem os bytes na tela como caracteres; os telétipos os imprimem no papel através de uma cabeça eletromecânica, e assim por diante.
No nível do sistema operacional, portanto, os terminais não passam de "caixas-pretas" acessadas através de portas seriais. No nível de aplicativos, não há portas seriais e sim descritores de arquivo gerenciados pelo sistema operacional. Cada terminal (e portanto porta serial) pode ser "aberto" por um programa, e fazendo isso o sistema operacional atribuir-lhe-á um descritor de arquivo. Quando dados são escritos nesse descritor através da chamada de sistema write do Unix, eles são enviados através da porta serial para o terminal correspondente; de forma análoga, quando o terminal envia dados (digitações do usuário, por exemplo), o sistema operacional as disponibiliza no descritor de arquivos correspondente.
A título de exemplo, quando um programa rodando no mainframe deseja imprimir "alea jacta est" no terminal, o que o programa faz é solicitar ao sistema operacional, através da chamada de sistema write, a escrita dos bytes "a", "l", "e", "a", " ", "j", "a", ..., no descritor de arquivos correspondente ao terminal desejado (que deve ter sido previamente "aberto" para receber um descritor de arquivo). O sistema operacional por sua vez, por intermédio do seu driver de terminal, emite os comandos para que a placa de comunicação UART produza os sinais elétricos que carregarão esses bytes ("a", "l", "e", "a", ...) para o terminal que se encontra na outra ponta da linha. O terminal recebe esses bytes e, através do seu canhão de elétrons, bombardeia a matriz fosfórica do CRT para nela produzir formas brilhantes, que, quanto atingem a retina de um ser vivo previamente treinado para reconhecê-las (como por exemplo um ser humano de razoável conhecimento histórico ou um babuíno previamente adestrado para esse fim), são identificadas como a célebre frase de Júlio César.
Note que o desenho das letras, bem como a decisão sobre o que fazer ao receber um caractere de "newline", são tarefas que cabem exclusivamente ao terminal; este as executa da maneira como bem entende, sem que o mainframe tenha delas controle ou sequer notícia.
Quando o usuário pressiona teclas no terminal, o hardware do terminal as envia através da linha serial para o mainframe. Lá chegando, são recebidas pela placa de comunicação (UART), e de lá são captadas pelo driver de terminal. O driver vai acumulando as digitações num buffer interno (há um desses para cada porta serial), até que receba uma digitação de um "Enter", geralmente expresso como um caractere de "newline". Até que isso ocorra, as digitações recebidas são retidas nesse buffer. Quando o "Enter" é recebido, o driver disponibiliza os caracteres acumulados no buffer através do descritor de arquivo do terminal, de onde poderão ser lidos pelo programa interessado.
Note portanto que o driver de terminal tem uma certa inteligência embutida, isto é, não se limita a simplesmente enviar e receber bytes. Além da funcionalidade descrita supra, o driver de terminal também reage ao recebimento de digitações ecoando-as de volta para o terminal (para que elas apareçam para o usuário), reage ao recebimento de uma digitação da tecla "backspace", deletando a última digitação do seu buffer interno; da tecla "clear line", limpando o buffer interno, etc. O programa cliente já recebe as digitações "finais", isto é processadas.
Apesar de não poder controlar o terminal físico em si, o mainframe tem controle sobre o driver de terminal, então através de chamadas de sistema (as da família TIOC* e TC*) os programas podem alterar o funcionamento do driver de terminal, instruindo-o por exemplo a não enviar o eco dos caracteres recebidos, a não bufferizar a entrada, a reconhecer uma outra tecla como a tecla de "delete", etc.
Nota-se, portanto, uma evidente falta de funcionalidade para controle dos terminais em si no Unix, tanto na parte visual quanto na parte de comportamento. Por exemplo, o sistema operacional não fornece nenhuma maneira de um programa solicitar que a tela de um terminal seja limpa, ou que certos caracteres sejam impressos em determinada posição ou numa determinada intensidade/cor, entre outros.
Esta falta de controle não é um defeito de design, mas meramente vem de uma compreensão incorreta sobre o conceito de "terminal" segundo o entendimento do UNIX. Para o UNIX, um "terminal" é somente isso: um dispositivo de entrada e saída. Não tem nenhuma semântica visual, como talvez alguns achariam natural. Não tem nem sequer uma semância de contenimento, isto é, no UNIX o terminal não tem nenhum "conteúdo"; é um dispositivo puramente serial que não armazena nenhum conteúdo e não tem nenhum display. Ele recebe bytes e envia bytes. Nada mais. Se alguns fornecem displays, é meramente para interoperabilidade com o ser humano, que carece de portas seriais e necessita que os dados lhe sejam transmitidos através de estímulos luminosos analógicos.
Por isso, o UNIX não guarda em nenhum lugar um registro sobre o que está atualmente "aparecendo na tela" do terminal, até porque tal conceito não existe. Se alguém desligar um terminal da tomada e ligá-lo novamente, ele perderá o que estava sendo exibido e o sistema operacional não terá como restaurar o que lá havia anteriormente.
Para contornar essa falta de controle sobre o visual e comportamento do terminal, foram criadas as seqüências de escape. São seqüências de bytes que, quando recebidas pelo terminal, causam algum tipo de reação como por exemplo posicionar o cursor numa certa posição da tela, limpar a tela, mudar a cor, etc. Cada marca/modelo de terminal tem seu próprio conjunto de seqüências de escape; muitas delas são comuns a vários terminais enquanto outras são específicas e só funcionam em alguns modelos.
Convém lembrar que o sistema operacional (UNIX) não tem qualquer conhecimento sobre seqüências de escape: elas são exclusivamente interpretadas pelo terminal. Quando um programa envia uma seqüência de escape para o terminal, o UNIX não precisa ser informado do fato; para ele o programa está simplesmente enviando bytes quaisquer para o terminal, e são tratados como quaisquer outros.
Conforme os fabricantes de terminal foram desenvolvendo mais seqüências de escape, tornou-se pouco prático que um programa tivesse que detectar em qual terminal estava executando e adaptar as seqüências por ele emitidas de acordo com isso. Assim, surgiu uma biblioteca genérica que permitia que programas fizesse consultas para determinar quais os recursos ("capacidades") disponíveis em cada tipo de terminal e quais as seqüências de escape necessárias para induzir o terminal a executá-los.
Por exemplo, ao invés de enviar uma seqüência de escape específica e hard-coded para fazer o terminal limpar a tela, o programa deveria consultar a biblioteca termcap/terminfo para: (1) determinar se o terminal tem essa capacidade; (2) determinar qual seqüência de escape é necessário emitir para usar essa capacidade. Se o terminal, por exemplo, não tem uma determinada capacidade, é responsabilidade do programa usar-se de outros métodos e capacidades para efetuar algum circumlóquio que lhe equivalha.
Algum tempo depois surgiu a biblioteca curses, que reuniu a funcionalidade do termcap/terminfo na forma de uma biblioteca de mais "alto nível", permitindo por exemplo a criação e manipulação da tela através de funções de criação de "janelas", seleção de cores, impressão de texto, etc.
Talvez seja a utilização da curses que dê ao programador a ilusão de que a parte visual do terminal é parte do UNIX, quando na verdade não é. Para o UNIX, a curses nada mais é que uma biblioteca que envia seqüências ininteligíveis de bytes para o terminal. Injustificada, portanto, a frustração de alguns usuários quanto à incapacidade do sistema operacional "restaurar" o conteúdo da tela do terminal se ele for desligado e ligado.
Talvez fosse natural esperar-se que com o advento do computador desktop, o conceito de "terminal" fosse abandonado em favor de modelos mais apropriados para os novos computadores. Contudo, não foi isso que aconteceu com o UNIX.
Desde sua concepção, aliás, o UNIX nunca passou por nenhuma mudança radical no modo como funciona, conservando em grande parte todas as suas características originais, salvo melhoras quantitativas. Dizem até os mais fanáticos que o UNIX nunca mudou porque já nasceu perfeito. fnord
Os terminais continuam existindo no UNIX moderno. Por um lado, muito para a angústia de algumas companhias, os mainframes continuam existindo e o UNIX continua sendo um dos sistemas operacionais mais apropriados para eles, e ainda se utilizam terminais reais nesses sistemas.
Por outro lado, nos computadores de desktop ainda subsiste o conceito de terminal não como um fóssil do passado, mas ainda como mecanismo fundamental de interação com a máquina. A diferença é que nas máquinas desktop, os terminais não estão fisicamente separados, mas estão embutidos na máquina. Nos PC's e demais máquinas desktop, o usuário consegue ver e interagir, através do monitor e teclado, com o que para ele tem a aparência de um terminal e o comportamento de um terminal. Conforme ele digita e observa o monitor, tem a nítida impressão de estar sentando à frente de um velho e nostálgico VT-102. Mas não há VT-102 em parte alguma; o hardware do PC não lembra nem remotamente o hardware simplório de um terminal.
Essa ilusão de terminal é criada por software, e denomina-se emulação de terminal. O nome é apto, pois é exatamente isso que ocorre: há uma peça de software na máquina que procura se comportar como se fosse um terminal, imprimindo na tela do PC aquilo que o terminal estaria desenhando em seu tubo de raios catódicos, e reagindo às digitações do teclado da mesma maneira que um terminal reagiria.
Essa peça de software que faz a emulação do terminal é separada do sistema operacional em si. Em outras palavras, seria incorreto dizer que o UNIX faz emulação de terminal. O UNIX acredita e tem fé em terminais, e funciona igualmente quer sejam emulados ou verdadeiros. A emulação de terminal é meramente uma conveniência para utilizar-se o UNIX numa máquina desktop, pois do contrário seria necessário conectar um terminal verdadeiro à porta serial do PC.
Há uma série de programas que fazem emulação de terminal em PC's. Um dos exemplos é o próprio console do Linux em "modo-texto", isto é, o terminal emulado que aparece inicialmente no Linux (e que continua acessível através da combinação Ctrl+Alt+F1 após iniciar-se o X). Outro exemplo é o programa xterm (e toda sua prole de cognatos, que fazem essencialmente a mesma coisa a menos de features cosméticos diversos e idiossincrasias misteriosas).
Possivelmente há que se objetar, com relação ao console do Linux, à afirmação assertiva supra em que afirmo não ser a emulação parte do UNIX. Recorro a uma visão pragmática para sustentá-la; constata-se pois, que no Linux o código de emulação de terminal do console está no kernel por mera conveniência e não se comunica com o resto do sistema de maneira alguma que não seja o descritor de arquivo do terminal. Para todos os efeitos pragmáticos, portanto, é externa ao sistema operacional e poderia, sem prejuízo algum ser implementada como um programa externo em user-mode.
O importante é ressaltar que, os programas que rodam num terminal não têm controle nenhum sobre ele fora a emissão/recebimento de seqüências de bytes e seqüências de escape. Da mesma maneira como o mainframe não tem controle sobre os terminais físicos a ele conectados, não têm também os programas que rodam no PC controle sobre os emuladores de terminais sob os quais estão rodando.
É importante ressaltar também que os terminais mantêm hoje a mesma semântica dos primórdios do UNIX: é meramente um dispositivo de entrada e saída de bytes, desprovido de semântica visual ou de contenimento.
Um exemplo que ilustra essa separação entre emulador de terminal e programas é o caso de um programa que deseja ler o que está sendo exibido no terminal, exibir algo e depois restaurar o que lá existia antes do programa rodar. Imagine por exemplo que a intenção é fazer um "protetor de tela" animado para o console do Linux.
Apesar de parecer muito simples, o programa não pode ser feito. Isso acontece porque o programa não tem como saber o que está sendo exibido no terminal, porque o conceito de "exibição" é algo que somente o emulador de terminal compreende; o UNIX, por outro lado, é completamente obtuso quanto a esse assunto e em nada pode auxiliar. Para saber o que está sendo exibido, seria necessário que o programa entrasse em contato com o emulador de terminal. Mas como dissemos antes, essa parte da funcionalidade é isolada numa "caixa-preta" e não se comunica com o mundo exterior salvo pela entrada/saída do terminal em si.

O UNIX fornece mais um recurso importante: o conceito de pseudo-terminal (abreviado pseudo-tty ou mesmo pty). Sua invenção não surpreende do ponto de vista conceitual, pois como argumentado anteriormente, os terminais (reais ou emulados) têm duas partes: a parte não visual (descritor de arquivo e driver de terminal) e e a parte visual (hardware do terminal físico, ou software de emulação para apresentação na tela). O pseudo-terminal é simplesmente um terminal que possui só as características funcionais de um terminal, sem contudo contar com uma camada de emulação ou um terminal físico. Ele existe, portanto, só na memória do sistema e por isso é denominado pseudo-terminal.
Os pseudo-terminais têm duas partes interligadas: o pty master e o pty slave. Cada um deles é um terminal. Então poderia-se dizer que cada pseudo-terminal é, na verdade, um par de terminais. Ou, sem prejuízo pragmático, dizer que o pseudo-terminal é um terminal que tem dois descritores de arquivo.
Todos os dados escritos num deles aparecem como dados de leitura no outro; todos os dados lidos de um deles são resultantes da escrita do outro. Assim, funcionam de maneira similar a um pipe, com a diferença de que são terminais. O fato de serem terminais, para o UNIX, significa somente que entre o master e o slave, os dados passam pelo driver de terminal. Recordando, o driver de terminal fornece algum controle sobre os dados como por exemplo o armazenamento de digitações num buffer até que o usuário pressione "Enter", recursos limitados de edição de entrada (apagar o último caractere, apagar a linha toda), recursos de ecoamento de caracteres, etc.
Geralmente são utilizados como na Figura 4.6 acima, para interligar dois programas. Um deles é o programa controlador, que cria o aloca o pseudo-terminal e fica no controle do lado master do mesmo; o outro é o programa subserviente, isto é, o programa controlado, e ele roda sob o terminal slave. A diferença é que o programa controlador utiliza o terminal master conforme lhe convém, podendo por exemplo utilizar também sua própria entrada e saída padrão independentemente. Já o "programa escravo" é geralmente iniciado com todos os seus descritores apontando para o terminal slave, ou seja, ele nem sequer dá-se conta (e em geral não há meios de detectar) que está rodando como escravo de um outro programa. Para ele, o lado slave do pseudo-tty é um terminal como qualquer outro.
Note no entanto que uma característica fundamental que falta ao pseudo-terminal é justamente a emulação de terminal. O pseudo-terminal é um dispositivo puramente de entrada e saída; não há nenhuma semântica visual, nem física nem emulada. Se o programa controlador quiser conferir um aspecto visual ao pseudo-terminal, precisa implementar, por sua própria conta, a emulação de terminal.
É o caso por exemplo do programa xterm: ele cria um pseudo-terminal, retém o descritor do lado master e executa o shell (bash por exemplo) sob o terminal slave. Quando o usuário digita algo na janela do xterm (evento esse que o xterm detecta através de bibliotecas do X Window System), ele traduz a tecla para o código que um terminal VT-102 emitiria e transmite esse código para o pty master. O shell que está rodando no slave, por sua vez, percebe essa digitação na sua entrada padrão como se estivesse rodando num terminal verdadeiro e o usuário tivesse pressionado a tecla, ignorando completamente o fato de que quem pôs a tecla lá para ser lida não foi o usuário e sim o xterm. Quando o shell (ou qualquer outro programa que rode sob ele) imprime algo no terminal, o xterm percebe através de uma leitura do master. Entra aí uma das tarefas mais complicadas que o xterm deve fazer: interpretar esses caracteres para produzir a saída na tela, emulando o que um terminal VT-102 faria ao recebê-los. Em outras palavras, deve fazer emulação de terminal.

Tristemente, cada programa que precisa fazer emulação de terminal embute esta funcionalidade em si mesmo, e mantém-na acoplada ao seu funcionamento interno. Desta forma, torna-se difícil escrever um novo programa que precise de emulação de terminal (como o Omnitty), pois essa funcionalidade precisa ser implementada manualmente.
Por isso, para não criar ainda mais um programa com emulação de terminal embutida, resolvi escrever o código de emulação independetemente do Omnitty, e acondicioná-lo numa biblioteca. Chamei-a ROTE - Our Own Terminal Emulation Library.
Trata-se de uma biblioteca que permite ao programador criar "telas virtuais" em memória e conectá-las a pseudo-terminais. A biblioteca cuida de ler do pseudo-terminal e interpretar os dados que vêm através dele, executando seqüências de escape e desta forma atualizando a "tela virtual". O programa controlador desta forma não precisa implementar a funcionalidade de emulação de terminal, deixando que a ROTE se encarrege desta parte e lhe forneça uma interface de programação que lhe permite inspecionar o conteúdo das telas virtuais, utilizando a semântica visual de que tanto falamos. A integração da ROTE com aplicativos está ilustrada na Figura 4.7.