Mind Bending

É curioso como certas coisas (mesmo defasadas caídas em desuso) se mantêm no mundo da tecnologia, o disquete como símbolo de salvar, as teclas HJLK no VIM, o conceito de TTY no GNU/Linux...

Outro dia eu estava desenvolvendo um programa para o GNU/Linux em modo texto e esse programa executaria algumas operações e deveria exibir o status dessa execução no terminal. Pois bem, foi aí que me deparei com algumas características interessantes do famoso "modo texto" do GNU/Linux. Acabei tropeçando também em muita história interessante que gostaria de compartilhar.

Terminal

Na verdade o correto não é modo texto, mas sim "Terminal". Terminal, Modo Texto, TTY? What the Hell?

Um pouco de história

O TTY, ou teletypewriter, era uma espécie de maquina de escrever eletrônica usada para enviar e receber mensagens ponto-a-ponto ou mensagens ponto-a-multiponto através de vários tipos de canais de comunicação. Mais tarde esses terminais TTY foram adaptados afim de serem usados como interface do usuário em sistemas de Mainframes e Minicomputadores. Vejam só! A primeira interface de usuário era um dispositivo eletro-mecânico!

Teletype Modelo 33 ASR Teleprinter

Teletype Modelo 33 ASR Teleprinter

Esses terminais TTY trabalhavam com um conjunto de caracteres e imprimiam em papel a mensagem - Por isso também eram conhecidos como Hard Copy Terminal, e também possuíam um conjunto de caracteres de controle que serviam para executar operações como mover o cursor de volta para o inicio da linha (CR - Carriage Return), avançar uma linha na mesma coluna (LF - Line Feed), e assim por diante.

A evolução dos terminais TTY se deu com a adição de um monitor de vídeo como saída padrão das informações. Esses terminais ficaram conhecidos como Computer Terminals. A partir desses terminais de texto era feita toda a entrada e saída de dados de um sistema de computador. Os terminais mais comuns na época eram o IBM 3270 e o DEC VT100.

Terminal IBM 3270

Terminal IBM 3270 (Não, este não sou eu trabalhando…)

Terminal DEC VT100

Terminal DEC VT100

Terminais de Texto

Sistemas operacionais padrão Unix (como o GNU/Linux e o FreeBSD) possuem consoles virtuais para fornecer vários terminais de texto em um único computador. A principal função do terminal de texto é de interpretador de linha de comando (ou shell), que interpreta os comandos do usuário, executa cada comando e então retorna informações.

Terminais Burros - São chamados assim porque de nada servem se não estão em conexão com um servidor. Nenhum processo é executado no terminal, mas sim no servidor. No Terminal Burro temos apenas a entrada de dados (via teclado) e a saída ou retorno de dados (via monitor). Existem também Terminais Gráficos que exibem imagens, o X11 é um servidor de terminais gráficos usados para enviar imagens à terminais gráficos.

O Problema

Agora que conhecemos um pouco da história dos TTY, vamos ver um pouco das características do TTY. Vamos imaginar um programa em C desenvolvido para GNU/Linux, que irá executar em modo texto. Esse programa irá executar algumas operações e gostaríamos de ter um feedback afim de saber em que passo estamos e quanto falta para terminar. Então podemos considerar o seguinte programa:

#include <stdio.h>

int main(void) {
        int status;
        long int d1, d2;

        for(status = 1; status <= 100; status++) {
                printf("Processando - %u%%\n", status);
                // Operacoes do programa
                for(d1 = 1; d1 <= 6500; d1++) for(d2 = 1; d2 <= 6500; d2++); // Tarefa DUMMY - Apenas para consumir tempo e simular funcionamento.
        }
        printf("\nFinalizado!\n");
}

Neste caso teríamos uma saída assim:

Processando - 1%
Processando - 2%
Processando - 3%
Processando - 4%
Processando - 5%
.
.
.
Processando - 96%
Processando - 97%
Processando - 98%
Processando - 99%
Processando - 100%

Finalizado!

Estranho não? Uma linha para cada atualização do status da execução - Nada profissional. Não existe um meio de exibir a atualização na mesma linha?

A Herança do TTY

Agora vamos perceber o seguinte: Muito foi herdado dos terminais TTY, como os caracteres de controle por exemplo.

No nosso programa exemplo o ideal é mover o cursor para o início da mesma linha a cada passo que o programa atualize o status. Usaremos o caractere especial Carriage Return (CR). No C ele é representado pelo \r.

Você deve conhecer o caractere especial chamado Line Feed (LF). No C ele é representado pelo \n e serve para mover o cursor para a próxima linha. Veja que ele está presente no nosso programa no comando printf. Vamos atualizar nosso programa, removendo o \n no final da string do printf e incluindo o \r no início.

#include <stdio.h>

int main(void) {
        int status;

        for(status = 1; status <= 100; status++) {
                printf("\rProcessando - %u%%", status);
                // Operacoes do programa
                for(d1 = 1; d1 <= 6500; d1++) for(d2 = 1; d2 <= 6500; d2++); // Tarefa DUMMY - Apenas para consumir tempo e simular funcionamento.
        }
        printf("\nFinalizado!\n");
}

Pela lógica tudo nos parece bem. Agora vamos ter a atualização do status na mesma linha. Vamos testar? Vai lá… Compile e execute o código. Eu espero.

Esperando...

Ei! O que está acontecendo? O programa está estranho… lá pelos 63% que tivemos a primeira atualização de tela e logo depois o programa terminou. Mas afinal, o que aconteceu aqui?

Bem, o que aconteceu aqui não pode ser chamado de erro, mas sim de característica. Explico: Do mesmo modo que herdamos os caracteres de controle como o Carriage Return (CR) e o Line Feed (LF), herdamos algumas outras características como a velocidade dos terminais e o conceito de buffer de dados. A atualização de dados do nosso programa foi mais rápido do que o terminal, e por isso pouco vimos da execução do nosso programa.

Para ver qual é a velocidade do nosso terminal podemos usar o comando stty do GNU/Linux. Este comando é usado para alterar ou exibir as configurações do terminal.

No entanto não adianta alterar a velocidade do nosso terminal. Isso não resolve o problema porque velocidade máxima do terminal ainda é inferior ao fluxo de dados do nosso programa. Então o problema que ocorre aqui refere-se ao fluxo de dados do terminal, portanto é será necessário atualizar o terminal manualmente, despejando todo o fluxo de dados no buffer para a tela usando a função fflush() - Flush também é o termo em inglês para dar a descarga… Vamos dar descarga no terminal!

Flushing...

Resolvendo o Problema

O terminal é considerado um arquivo (ou stream) no GNU/Linux. Então a função fflush() força uma gravação de todos os dados em buffer para a saída de dados. Se o argumento da função for NULL, fflush() dá a descarga em todos os streams abertos. Então vejamos:

#include <stdio.h>

int main(void) {
        int status;

        for(status = 1; status <= 100; status++) {
                printf("\rProcessando - %u%%", status);
                fflush(stdout); // Dando descarga de stdout...
                // Operacoes do programa
                for(d1 = 1; d1 <= 6500; d1++) for(d2 = 1; d2 <= 6500; d2++); // Tarefa DUMMY - Apenas para consumir tempo e simular funcionamento.
        }
        printf("\nFinalizado!\n");
}

Feito isso está garantido. Toda informação será descarregada do buffer para o terminal.

BONUS AREA

Que tal exibir uma barra de status ao invés de exibir a porcentagem? Vamos ao código:

#include <stdio.h>

#define BS_SZ 60 // Tamanho da barra de status

int main(void) {
    int status;
        long int d1, d2;

        for(status = 0; status <= BS_SZ; status++) {
            printf("\rProcessando: ["); // Abre a barra de status
                for(d1 = 1; d1 <= status; d1++) printf("="); // Exibe status atual
            for(d1 = (status + 1); d1 <= BS_SZ; d1++) printf(" "); // Preenche com espaco em branco
                printf("]"); // Fecha a barra de status
                fflush(stdout);
            for(d1 = 1; d1 <= 9900; d1++) for(d2 = 1; d2 <= 9900; d2++);
        }
        printf("\nFinalizado!\n");
}

Para alterar o tamanho da barra de status, altere o valor da constante BS_SZ para o tamanho que desejar. Lembre-se apenas de usar um número menor do que o número de colunas do terminal para não ficar estranho!

Para um pouco mais de história ou mais informações, seguem alguns links interessantes:

Até a próxima!

Fernando Almeida

Fernando Almeida

Formado em Gestão da Tecnologia da Informação, divide seu dia entre dois mundos: A vida de “engravatado” como analista de risco e broker para empresa de Leasing OPEX e a vida de desenvolvedor de sistemas embarcados para empresa de telecomunicações. Grande entusiasta do movimento OpenSource, aproveita suas horas vagas para um bom passeio de moto com sua esposa.


Comments

comments powered by Disqus