Site menu Terminais UNIX

Terminais UNIX

Copyright © 2001 Conectiva S/A, (c) 2004, 2025 EPx

1. O driver de terminal

No mundo UNIX, o driver de terminal é a interface para portas seriais, console e pseudo-terminais.

As portas seriais não precisariam de uma interface tão complexa. Mas o padrão UNIX achou por bem sincretizar portas seriais com terminais. Num passado distante, a principal utilidade das portas seriais era justamente a conexão com os chamados "terminais burros".

O driver de terminal é um aspecto pouco estimado do padrão UNIX, por ser complexo, barroco, e cheira a mofo. Além disso, é uma grande fonte de incompatibilidade entre UNIXes (problema menos relevante hoje, pois todo mundo usa Linux ou MacOS). Nos primórdios, todo o controle de terminal era feito com ioctl(). Hoje os sistemas compatíveis com POSIX implementam funções dedicadas à tarefa. Quem precisa escrever programas altamente portáveis, tem de prever ambas as possibilidades.

Hoje em dia, via de regra, quem confeccionar novos programas para modo texto usará uma bibioteca como ncurses ou similar. Apenas quem for escrever programas de nível mais baixo terá de lidar com o driver de terminal, e mesmo assim só o suficiente para realizar seu intento. Mas sempre é interessante conhecer o panorama geral.

É importante entender que o driver de terminal intermedia a comunicação entre o programa e o dispositivo. Ele não controla o dispositivo em si. Isso fica a cargo do driver de dispositivo.

Observe o seguinte diagrama, que ilustra o caso de uso "clássico" do driver de terminal:

Programa ----> Driver de ----> Driver da    ----> Porta  ----> Terminal
         <----  terminal <---- porta serial <---- serial <---- burro
                                                               remoto

Entre a porta serial e o terminal burro, poderia ainda haver modems, uma linha telefônica. etc. Mas isto não muda a essência. Para ser franco, como os terminais burro estão praticamente extintos, vai ser difícil encontrar esse caso de uso no mundo real.

Outra configuração possível é a comunicação serial entre dois computadores. Como dito antes, a comunicação serial em UNIX também é controlada pelo driver de terminal:

(computador 1)
Programa ---> Driver de ---> Driver da    ---> Porta  -------+
         <---  terminal <--- porta serial <--- serial <---+  |
                                                          |  |
                                                          |  |
                                                          |  |
Programa ---> Driver de ---> Driver da    ---> Porta  ----+  |
         <---  terminal <--- porta serial <--- serial <------+
(computador 2)

No diagrama acima, entre uma porta serial e outra, também poderia haver modems e linhas telefônicas, e a conexão pode ser bem mais precária do que sugere o diagrama.

Uma terceira situação é quando o terminal é o próprio console, ou seja, a tela e o teclado estão plugados no próprio computador em que roda o UNIX:

Programa ---> Driver de ---> Driver do ---> Driver de vídeo -----> Vídeo
         <---  terminal <--- console <--+
                                        |
                                        |
                                        |
                                        +--- Driver de teclado <-- Teclado

O ponto é o seguinte: para o programa, do lado esquerdo dos três gráficos, não faz diferença se o terminal é burro, emulador, local, remoto, ou console. Ele só precisa interagir com o driver de terminal, que é sempre o mesmo.

Um quarto arranjo possível é o pseudo-terminal, mas vamos abordá-lo numa seção dedicada.

2. Conceitos básicos

2.1. Modos canônico e 'cru'

Os terminais UNIX podem trabalhar em dois modos:

Modo canônico ou padrão: o programa recebe os dados apenas quando o usuário pressiona ENTER. Nesse modo, o usuário pode digitar, usar backspace para corrigir o que estiver errado, e quando estiver satisfeito, envia a linha para o programa.

No modo canônico, o driver de terminal faz o meio-de-campo entre vídeo e teclado. (Lembrando que o driver reside no kernel.) O programa não toma conhecimento disso, ele "dorme" até receber uma linha de caracteres quando o usuário pressiona ENTER.

Mesmo nesse modo, cada caractere digitado "viaja" do teclado até o kernel da máquina onde o programa roda — mesmo que o terminal em questão esteja muito distante da máquina. Então, ainda existirá um atraso entre o usuário apertar uma tecla e o caractere aparecer na tela. (Isso pode ser evitado com eco local. Falaremos dele um pouco mais adiante.)

Muito embora o driver de terminal defina uns poucos caracteres de controle (backspace, etc.) para permitir alguma edição antes do envio, é uma interface bem primitiva para os padrões atuais. É uma herança do tempo dos teletipos — terminais de rolo de papel em vez de tela — e só funciona com programas que tratem o terminal como um teletipo.

A vantagem desse modo é ser muito eficiente em termos de processamento. Porém, suas limitações fazem com que nem mesmo o shell bash o utilize, por padrão.

Modo cru ou direto: neste modo, o driver de terminal simplesmente repassa ao programa tudo que vem do terminal. O programa é 100% responsável pela experiência de usuário.

A única opção que o driver dá ao programa é: entregar os caracteres um a um, ou em blocos de "n" caracteres. Neste último caso, ainda é possível definir um tempo máximo de espera, após o qual o driver de terminal libera o que houver no buffer.

A grande maioria dos aplicativos atuais de linha de comando trabalha neste modo. E dispositivos de comunicação serial também sempre trabalharão neste modo, para que o canal de comunicação seja 100% transparente.

2.2. Eco local e remoto

Quando você digita um caractere em um terminal, ele é (quase) imediatamente impresso na tela. Esse feedback, ou "eco" pode ser de responsabilidade do próprio terminal (eco local), ou do driver de terminal (eco remoto).

Se o terminal e o driver forem configurados para eco local, então o próprio terminal fará eco do que o usuário digitar; não é necessário que o driver de terminal retransmita cada caractere recebido.

O eco local é desejável em terminais ligados via modem analógico, pois elimina o "delay" entre o pressionamento da tecla e a aparição do caractere na tela. A experiência do usuário será muito mais agradável. (Imagine um link telefônico via satélite onde o atraso é de vários segundos.)

Porém, há um problema: não temos certeza de que o driver de terminal, lá do outro lado da linha, recebeu mesmo o caractere. O eco remoto proporciona um feedback mais concreto do que está acontecendo.

É necessário configurar tanto o terminal quanto o driver para eco local ou remoto, sob pena de ocorrer eco duplo (quando tanto o computador remoto quanto o terminal ecoam o que foi digitado), ou a ausência de eco (quando nenhuma das duas pontas faz eco).

Note ainda que configurar o driver não implica na configuração automática do terminal remoto! O lado remoto terá de ser configurado manualmente, ou receber uma cadeia de caracteres especiais com esse fim, e isso é responsabilidade do programa (ou de alguma biblioteca que o programa empregue para lidar com o terminal).

Em dispositivos de comunicação serial, o eco local será sempre desligado.

O eco local é uma relíquia que tende à extinção, pois a Internet atual possui "ping" muito baixo, mesmo em conexões que cubram milhares de quilômetros, tornando esse recurso totalmente desnecessário.

2.3. Terminal controlador e programa em "foreground"

Todo programa em modo texto deveria ter um terminal controlador.

Por exemplo, quando o usuário pressionar Control-C, isto faz o driver de terminal gerar o sinal SIGINT. Este sinal será enviado ao programa que tenha eleito aquele terminal como seu controlador. (E em geral o programa termina quando recebe o sinal SIGINT.)

Algumas poucos caracteres de controle são definidos como geradores de sinais. E sim, o driver de terminal permite redefiní-los ou mesmo desligá-los. Com certeza a primeira coisa que um programa de comunicação serial faz é desligar essas funções antes de começar a usar a porta.

O sinal não é enviado para todos os programas controlados por aquele terminal, mas apenas para o(s) programa(s) em "foreground". Para isso existe a função tcsetpgrp() que permite a um programa apontar a si mesmo como "foreground".

Esse controle de processos em "foreground" ou "background" está fora do escopo deste artigo. Aqui, basta dizer que tal controle é normalmente feito pelo shell, de modo que não precisemos cuidar disso em nossos próprios programas.

Agora, se você escreve programas que iniciam subprocessos, que por sua vez precisam lidar com o terminal (talvez você esteja escrevendo um novo shell?) precisará buscar esse conhecimento adicional. Recomendo o "GNU C Library Reference", que chega ao ponto de dissecar cada parte de um mini-shell para ilustrar o assunto.

2.4. Características de hardware do terminal

Em UNIX, a interface para todo dispositivo serial é o driver de terminal, haja ou não um terminal conectado na outra ponta.

Portanto, as configurações da linha serial — paridade, handshake, número de bits, velocidade, etc. — figuram entre as funções fornecidas pelo driver de terminal.

2.5. Arquivos de dispositivo

Classicamente, tudo em UNIX é um arquivo e pode ser manipulado como se fosse um arquivo. É uma metáfora cada vez mais desrespeitada (infelizmente o Plan 9, que seria o sucessor do UNIX e dobrou a aposta nessa metáfora, não "pegou"), mas ela ainda vale para os dispositivos controlados pelo driver de terminal.

Tecnicamente, os arquivos que representam os terminais, geralmente na forma /dev/ttyXPTO, são "dispositivos de caractere", visto que o hardware correspondente transfere dados byte a byte.

Segue alguns grupos comuns de arquivos de terminal. Note que, apesar da interface ser uniforme a todos, existe uma separação pelo tipo de hardware subjacente:

/dev/ttyN (N > 0): Os diversos consoles do Linux.
/dev/ttySN (N >= 0): As portas seriais padrão IBM PC
/dev/ttyACMN (N >= 0): Dispositivo serial USB padrão CDC
/dev/ttyUSBN (N >= 0): Dispositivo serial USB não-padrão como FTDI
/dev/ttyXY (X >= 'p', Y um dígito hexadecimal): Pseudo-terminais

O programa que vai lidar com o terminal precisa enviar e receber informações de configuração. Essas informações não podem trafegar através das operações "normais" de arquivo (ler, gravar, abrir, fechar) pois estas são ocupadas pelo teclado e pela tela.

Sobra apenas o ioctl(), que é uma espécie de "vala comum" para tudo que não caiba na metáfora de abrir-ler-gravar-fechar arquivos. Mas ioctl() não é ergonômico, então o padrão POSIX definiu funções adicionais, específicas para configuração do terminal. (Por baixo dos panos, pode ser que essas funções ainda usem ioctl().)

O Linux e a maioria dos UNIXes modernos implementam as funções POSIX. Porém, se você for obrigado a escrever programas portáveis para UNIXes ancestrais, vai ter de usar ambas as interfaces.

3. Interface C para o driver de terminal

3.1. A saída padrão é um terminal?

Programas bem-feitos modificam seu comportamento, conforme a entrada padrão e/ou a saída padrão seja um terminal, um pipe ou um arquivo comum.

Por exemplo, muitos programas GNU emitem conteúdo colorido, mas apenas se a saída for um terminal. Se a saída for um pipe ou arquivo comum, eles emitem texto puro.

A função isatty() permite determinar se um arquivo é na verdade um terminal. O programa a seguir determina se a saída padrão é um terminal:

#include <stdio.h>
#include <unistd.h>

int main()
{
    printf("A saída padrão %s terminal\n",
           isatty(1) ? "é" : "não é");
}

Esta função também serve para um programa de comunicação serial detectar se realmente está lidando com uma porta serial, e não com outra coisa qualquer.

3.2. O nome do terminal corrente

Todo programa UNIX típico "nasce" com três arquivos abertos, cujos descritores são 0, 1 e 2, correspondentes à entrada padrão, saída padrão e saída de erro, respectivamente. Os três geralmente apontam para o mesmo dispositivo de terminal, que será seu terminal controlador.

É possível descobrir o nome desse dispositivo com a função ttyname(n) onde "n" é o descritor de arquivo.

Se o programa precisar fechar e depois reabrir seu terminal controlador, não precisa memorizar seu nome. O arquivo /dev/tty é um "apelido" que sempre corresponde ao terminal controlador de quem o está abrindo.

3.3. A estrutura termios

Nota: o livro "Beginning Linux Programming", da Wrox Press, foi usado extensivamente para a composição desta seção.

Praticamente todo o controle do terminal é feito através da estrutura termios abaixo:

#include <termios.h>

struct termios {
    // o tipo tcflag_t é um mapa de bits
    tcflag_t c_iflag;
    tcflag_t c_oflag;
    tcflag_t c_cflag;
    tcflag_t c_lflag;
    cc_t c_cc[NCCS];
}

Para obter a estrutura de controle, usa-se a função

tcgetattr(int fildes, struct termios *termios_p);

onde fildes é o arquivo de terminal, e termios_p é o ponteiro para uma estrutura termios a ser preenchida.

Para enviar essa mesma estrutura ao driver, usa-se analogamente

tcseattr(int fildes, int actions, const struct *termios_p);

O parâmetro actions pode levar um dos seguintes valores:

TCSANOWmude o comportamento do terminal imediatamente
TCSADRAINmude apenas quando não houver mais nada no buffer de saída
TCSAFLUSHlimpe o buffer de entrada e então mude

Importante: se um programa muda a configuração do terminal, é responsabilidade dele restaurar a configuração original antes de sair. Programas bem-feitos como o shell do UNIX costumam fazer isso por você, mas é boa prática não contar com isso.

Para mudar a configuração do terminal, o procedimento padrão é obter a estrutura termios, alterar apenas o que for necessário, e enviá-la de volta.

Dificilmente um programa cria essa estrutura do zero. O livro "GNU C Library Reference" desaconselha usar estruturas zeradas, pois pode haver bits de configuração que você não sabe para que servem, que não estão documentados, e/ou que passaram a existir depois que seu programa foi criado, e aí a melhor aposta é deixá-los com o valor default em vez de passar bits zero.

Vejamos agora o que significam alguns bits de cada membro da estrutura termios. Os valores, especificados por macros, devem ser ligados ou desligados com operações bitwise:

// para ligar um bit
tc.c_iflags |= BRKINT;
// para desligar um bit
tc.c_iflags &= ~BRKINT;

3.3.1. Bits de tratamento de entrada

Estes bits referem-se ao tratamento de caracteres vindos do lado remoto, ou seja, classicamente vindos do terminal burro.

BRKINTGerar uma interrupção a um sinal de "break" (*)
IGNBRKIgnora quaisquer sinais de "break"
ICRNLConverter CR recebidos em LF
IGNCRDescartar CR recebidos
INLCRConverter LF recebidos em CR
IGNPARDescartar LF recebidos
INPCKFazer checagem de paridade nos caracteres recebidos
PARMRKMarcar erros de paridade
ISTRIPZerar o oitavo bit dos caracteres recebidos
IXOFFDesligar o controle de fluxo por software
IXONLigar o controle de fluxo por software

(*) Trivia: numa linha serial, o sinal de "break" é um start bit anormalmente longo (bem mais que o suficiente para não ser confundido com um caractere 0x00) e sinaliza condições excepcionais.

Para um terminal normal, é provável que seu programa vá deixar todos esses bits como estavam. Já em programas de comunicação serial, todos eles deverão ser desligados, exceto talvez pelo IGNBRK.

No UNIX, o caractere especial LF termina a linha *e* retorna o cursor para o canto esquerdo, enquanto CR *apenas* retorna o cursor para o canto esquerdo. Porém, historicamente, cada modelo de terminal burro adotava uma convenção diferente. Os bits ICRNL, IGNCR, INLCR e IGNPAR permitem adaptar o driver a qualquer terminal, mas são uma grande dor-de-cabeça quando configurados erradamente.

3.3.2. Bits de tratamento de saída

Estes bits especificam transformações que o driver de terminal pode fazer aos dados que estão sendo enviados ao terminal burro.

OPOSTHabilitar o tratamento da saída
ONLCRConverter LF para CR+LF
OCRNLConverter CR para LF
ONOCRDesabilitar CR se a coluna atual do cursor for 0
ONLRETO terminal converte LF para CR+LF por conta própria

Novamente, aparece a questão de adaptar a convenção CR/LF do UNIX à do terminal, desta vez na direção de envio.

Desligar o flag OPOST tem o efeito de desabilitar qualquer tipo de interferência na transmissão de caracteres, útil para programas de comunicação serial.

Existem ainda outros flags que referem-se a inserção de caracteres de "enchimento" (nulos) para terminais burros antigos e lentos.

3.3.3. Bits de controle

Os bits desse grupo fornecem os meios de se configurar a porta serial.

CLOCALIgnorar quaisquer sinais de status de modem. Importante quando a comunicação é feita por cabo serial com apenas 3 fios
CREADHabilitar a recepção de caracteres
CS5..CS8Caracteres possuem "n" bits
CSTOPBUsar dois "stop bits" em vez de um
HUPCLFazer o "hang up" do modem quando o arquivo for fechado
PARENBUsar bit de paridade
PARODDUsar paridade ímpar em vez de par
CRTSCTSHabilita handshaking por hardware

3.3.4. Bits de modo local

Estes bits regulam o "modo canônico", onde o próprio driver de terminal lida com parte da interação com o usuário.

ECHOHabilita o eco dos caracteres recebidos
ECHOEExecuta Backspace+Espaço+Backspace ao receber o caractere 'erase'. (Isto tem o efeito de apagar positivamente, em vez de apenas voltar o cursor.)
ECHOKApaga a linha inteira ao receber o caractere 'kill'
ECHONLEcoa caracteres LF
ICANONHabilita o modo canônico
IEXTENHabilita funções específicas da implementação UNIX
ISIGHabilita sinais
NOFLSH Não limpar buffers ao receber sinais. (Normalmente, os sinais INTR, QUIT e SUSP limparão os buffers de entrada e saída.)
TOSTOPMandar um sinal quando um processo em background tenta escrever no terminal

Em programas de comunicação serial, via de regra todos esses bits de modo local serão desligados.

3.3.5. Tabela de caracteres especiais

Os caracteres especiais (especiais do ponto de vista do driver do terminal, frise-se) estão listados todos na estrutura termios, e podem ser modificados à vontade.

Por exemplo, o caractere que gera o sinal SIGINT é o que estiver especificado em termios.c_cc[VINTR]. Por padrão, o valor lá contido é igual a 0x03, que corresponde ao caractere de controle Control-C.

Segue a lista de caracteres configuráveis. A primeira coluna é o vetor da array c_cc. A segunda coluna indica se o caractere é válido para o modo canônico (c) e/ou para o modo cru (n).

VEOF     c     Caractere EOF (padrão=Control-D)
VEOL     c     Caractere EOL (padrão=Control-L)
VERASE   c     Caractere ERASE (padrão=Control-H ou Backspace)
VINTR    cn    Caractere INTR (padrão=Control-C)
VKILL    c     Caractere KILL, deleta a linha inteira
VQUIT    cn    Caractere QUIT
VSUSP    cn    Caractere SUSP
VSTART   cn    Caractere START (padrão=Control-Q)
VSTOP    cn    Caractere STOP (padrão=Control-S)

Adicionalmente, na mesma array, há dois valores configuráveis para o modo cru:

VMIN Número mínimo de caracteres que o buffer deve acumular para então enviá-los ao programa
VTIME Tempo máximo que o driver vai esperar até enviar o buffer de entrada ao programa, mesmo que a contagem de caracteres não tenha atingido VMIN. VTIME=0 significa que o driver vai esperar indefinidamente, até coletar VMIN caracteres.

Os valores mais comumente utilizados para VMIN e VTIME são 1 e 0, respectivamente. Ou seja, o programa sempre vai receber imediatamente qualquer byte que aparecer.

3.4. Outras funções

3.4.1. Configuração de velocidade

speed_t cfgetispeed(const struct termios *);
speed_t cfgetospeed(const struct termios *);
int cfsetispeed(struct termios *, speed_t speed);
int cfsetospeed(struct termios *, speed_t speed);

Em sistemas que usam biblioteca C GNU (caso da maioria das distribuições Linux), e no MacOS, o tipo speed_t é um inteiro numericamente igual ao baud rate, portanto as funções acima dão acesso a qualquer velocidade suportada pelo hardware.

O valor zero (macro B0) sinaliza "hang up". Se houver um modem conectado à porta serial, ele desconecta da linha telefônica. (Me senti na obrigação de dizer isso em detalhes porque muitos GenZ talvez nunca realizaram o ato físico de colocar um telefone no gancho...)

Se for escrever software para Linux ou MacOS, o assunto termina aqui. Agora, se tiver de escrever código altamente portável, fique esperto.

Classicamente, o tipo speed_t não é um número, é um mapa de bits. Só se pode usar as velocidades especificadas por macros (B50, B75, B110... até B38400). Cada UNIX contorna essa limitação de uma forma diferente. Trata-se de um cantinho xexelento do padrão POSIX. Esta discussão a respeito do assunto começou em 2009 e continua rolando, sem conclusão à vista.

3.4.2. Controle do buffer

int tcdrain(int fildes);

Faz o programa aguardar até que todo o buffer de saída tenha sido transmitido.

int tcflow(int fildes, int flowtype);

Controla o fluxo. O parâmetro flowtype pode assumir um dos seguintes valores:

TCOOFF     Suspende a transmissão
TCOON      Reinicia a transmissão
TCIOFF     Suspende a recepção
TCION      Reinicia a recepção
int tcflush(int fildes, int in_out_selector);

Limpa o buffer de entrada, de saída, ou ambos, conforme o parâmetro in_out_selector:

TCIFLUSH     Limpa o buffer de entrada
TCOFLUSH     Limpa o buffer de saída
TCIOFLUSH    Limpa ambos os buffers

4. Pseudo-terminais

Imagine a seguinte situação: você quer controlar o funcionamento de um programa a partir de outro programa. A princípio, isso parece fácil em UNIX — basta passar, como entrada e saída padrão, um pipe ou um soquete TCP/IP ao programa-escravo.

Porém, boa parte dos programas exige que a entrada/saída padrão seja um terminal, caso em que a estratégia sugerida acima não funcionaria. É, por exemplo, o caso do shell, ou de um programa interativo.

Para cobrir essa lacuna, existem os pseudo-terminais, onde o "teclado" e a "tela" do outro lado da linha são, na verdade, outro programa:

Programa- ---> Driver de ---> Driver de  ---> Programa-
escravo   <--- terminal  <--- pseudo-tty <--- mestre

Conectado ao pseudo-terminal, o programa-escravo tem a ilusão de que se comunica com um terminal de verdade.

Há diversos casos de uso corriqueiros para os pseudo-terminais, mais até do que para terminais "reais". Emuladores de terminal (iTerm no Mac, xterm no Linux), sessões remotas via Telnet ou SSH, e gerenciadores de sessão como screen e tmux são todos baseados em pseudo-terminais.

O pseudo-terminal precisa ser criado pelo programa-mestre antes de ser utilizado pelo programa-escravo. Esse processo de criação varia um pouco em cada 'sabor' de UNIX.

4.1. BSD (legado)

Este é o esquema clássico, e ainda disponível no MacOS que é um derivado do FreeBSD.

O processo-mestre tenta abrir um arquivo /dev/ptyXY, o "terminal mestre", onde X é uma letra e Y é um hexadecimal. Se a operação for bem-sucedida, o processo-escravo pode então usar o dispositivo /dev/ttyXY como seu terminal.

Observe o seguinte fragmento de código:

char PTY10[] = "pqrstuvwxyz";
char PTY01[] = "0123456789abcdef";
char devPTY[] = "/dev/ptyxx";
char devTTY[] = "/dev/ttyxx";

int buscatty()
    char* p10;
    char* p01;
    for (p10 = PTY10; *p10 != '\0'; p10++) {
        devPTY[8] = *p10;
        devTTY[8] = *p10;
        for(p01 = PTY01; *p01 != '\0'; p01++) {
            devPTY[9] = *p01;
            devTTY[9] = *p01;
            fd = open(devPTY, O_RDWR);
            if (fd >= 0) {
                fcntl(fd, F_SETFL, O_NDELAY);
                return fd;
             }           
        }
    }
    return -1;
}

Na função buscatty(), o programa tentará abrir todos os dispositivos /dev/ptyXY, até conseguir. É uma busca por tentativa-e-erro. Por convenção, os caracteres X e Y estão nas faixas especificadas nas primeiras linhas de código.

Um problema do esquema BSD é lotar a pasta /dev com arquivos que quase nunca serão utilizados e não são dispositivos de hardware de verdade. O outro problema é que a busca por tentativa-e-erro é sujeita a race conditions.

Há 25 anos atrás, mesmo os sistemas que ofereciam o padrão UNIX 98 (abordado a seguir) ainda ofereciam o padrão BSD por ser o mais portável. Distribuições Linux mais recentes já o abandonaram.

4.2. Padrão UNIX 98 (sistemas modernos)

Suportado pelo Linux a partir do kernel 2.2 e pelo macOS a partir da versão 10.5.

char *devTTY;

int buscatty()
{
    int pty_fd = open("/dev/ptmx", O_RDWR);
    if (pty_fd >= 0) {
        devTTY = strdup(ttyname(pty_fd));
    }
}

Para criar o terminal mestre, abre-se o arquivo /dev/ptmx. Então, para obter o nome do terminal-escravo, usa-se a função ttyname(), passando o descritor do mestre.

Em geral, o nome do terminal-escravo é algo como /dev/pts/N, sendo N um número. Mas o código não deve tentar "adivinhar" ese nome, pois sua padronagem varia por "sabor" de UNIX, e mesmo entre versões do mesmo UNIX.

4.3. AIX

A única diferença entre o AIX (5.x ou melhor) e o padrão UNIX 98 é que o dispositivo "fornecedor" de pseudo-terminais é /dev/ptc, não /dev/ptmx.

O AIX 3 usava o esquema BSD.

4.4. Sistemas UNIX modernos

A bibioteca C de UNIXes relativamente modernos (20 anos ou menos) oferece a função openpty() que providencia um par mestre/escravo de pseudo-terminais, isentando o programador de lidar com as diferenças entre UNIXes.

A função forkpty() faz mais ainda: além de criar o pseudo-terminal, cria um processo-filho com o terminal escravo no papel de stdin/stdout/terminal controlador.

4.5. Caso de uso real

Conforme conto neste texto, em 1999 escrevi um programa envolvendo pseudo-terminais. Ele interagia com uma ferramenta CLI de banco de dados, enviando consultas semelhantes a SQL, e recebendo os resultados cuspidos pela ferramenta.

Isto foi feito porque o banco de dados não tinha API, e esse foi o único jeito de fazer esse banco de dados "conversar" com uma aplicação Web. O programa tinha de ser compatível com HP-UX, AIX e Linux.

Para complicar, testamos o código no AIX 3 (esquema BSD), mas descobrimos em campo que o AIX do cliente era versão 5 (esquema UNIX 98) — e o computador do cliente não tinha compilador C (que no AIX 5 era adquirido à parte). Houve muita tentativa-e-erro até chegar num código que compilava no AIX 3 e rodava no AIX 5.

5. Bibliografia e fontes de consulta

Como bibliografia, recomendamos o excelente "Beginning Linux Programming", da Wrox Press, e o GNU C Library Reference Manual, da Free Software Foundation.

Nenhum dos dois cobre de forma suficiente os pseudo-terminais, caso em que a consulta do código-fonte de programas consagrados como in.telnetd, xterm, screen, etc. pode ser interessante.