4. I/O


[Home] [Dissertação] [Biba] [Linux] [Conjugue] [br.ispell] [axw3] [uplink]

Alguns conceitos abordados:

Algumas ferramentas abordadas:

4.1 [pipes e processos] Ao longo do curso já vimos várias vezes pipes entre processos criados pelo shell:

  hal:~$ ls|wc

No exemplo a saída do ls está sendo usada como entrada do wc. Isso é bastante diferente de se enviar a saída do ls para um arquivo e ler esse arquivo como entrada do wc.

O shell dispara os dois processos ao mesmo tempo. Se o primeiro estiver produzindo saída numa taxa superior à que o segundo lê, o sistema operacional paralizará o primeiro sempre que o buffer que armazena o tráfego estiver cheio. Se o segundo estiver lendo numa taxa mais rápida do que o primeiro consegue produzir, o sistema operacional paralizará o sengundo sempre que o buffer estiver vazio.

Isso significa, por exemplo, que um pipe pode ser usado mesmo quando o volume total da saída do primeiro processo é extraordinariamente grande, maior até talvez do que o espaço livre disponivel em disco. Um exemplo em que isso pode ocorrer é quando se tenta localizar informações de arquivos deletados através de um dump de todos os setores do disco (ferramentas aplicáveis para operações desse tipo são o dd e o hexdump).

Exercício 4.2: Experimente o comando "(ls /usr/bin|wc&);ps", observe a saída dele e tente explicar o seu funcionamento.

4.3 [redirecionamento da entrada ou da saída] Processos unix habitualmente possuem três dispositivos de I/O "padrões", a "saída padrão", a "entrada padrão" e a "saída de erros". O sistema unix permite que esses dispositivos sejam definidos no momento da execução, podendo ser o console, um "pipe", a impressora, um circuito virtual de rede conectando duas máquinas, uma linha física serial ou outras coisas para as quais haja suporte no sistema.

Usuários de msdos talvez estejam habituados a fazer coisas como

  C> DIR >PRN:

para imprimir a saída de um comando. A sintaxe dos shells unix é a mesma (na verdade o msdos herdou-a do unix):

  hal:~$ ls -l >/dev/lp1

Shells do estilo "bourne" como o bash permitem redirecionamento da saída de erros através da seguinte sintaxe:

  hal:~$ rm /bin/ls 2>/tmp/error

A entrada pode ser redirecionada de forma semelhante:

  hal:~$ wc </tmp/error

4.4 [device special files] Você já viu um diretório chamado /dev? listou o conteúdo dele? sabe do que se trata? Vejamos em detalhe algumas das suas entradas, que são chamadas "device special files". Tais arquivos podem residir em qualquer diretório, mas habitualmente etão no /dev.

  hal:~$ ls -l /dev/{fd0H1440,st0,hda,cua0,ttyp1,null}
  brw-rw----   1 root  floppy  2,  28 Apr 27  1995 /dev/fd0H1440
  brw-rw----   1 root  disk    3,   0 Apr 27  1995 /dev/hda
  crw-rw----   1 root  disk    9,   0 Jul 18  1994 /dev/st0
  crw-rw----   1 root  uucp    5,  64 Jul 17  1994 /dev/cua0
  crw--w--w-   1 ueda  bin     4, 193 Jul 10 11:04 /dev/ttyp1
  crw-rw-rw-   1 root  sys     1,   3 Jul 17  1994 /dev/null

Vejamos o caso do /dev/fd0H1440, que corresponde à interface que o sistema operacional oferece para um "primeiro" drive de disquetes no "formato" 3.5 polegadas e alta densidade.

Fisicamente um disquete está organizado em trilhas e setores. No caso de um disquete 3.5 polegadas de alta densidade, são 80 trilhas e 17 setores por trilha. Como ele tem duas faces, isso dá um total de 80 * 17 * 2 = 2720 setores, ou 1440 * 1025 bytes (cada setor são 512 bytes).

Imagine que esses 2720 setores estejam dispostos numa seqüência, de forma que possamos falar de "primeiro" setor, "segundo" setor e assim por diante até o setor 2720. Isso nos daria uma seqüência de 1440 * 1024 bytes, que podem ser imaginadas como sendo um arquivo. Esse arquivo é o /dev/fd0H1440. De fato, a operação

  hal:~$ cp /dev/fd0H1440 /tmp/disco

irá funcionar, e criará no diretório /tmp um arquivo chamado disco de exatamente 1440 kilobytes (o "cp" não é a ferramenta usual nestes casos, e em algumas plataformas poderá não funcionar, melhor usar dd ou, em alguns casos, tar).

É através desta abstração que em unix todos os dispositivos de entrada e saída são acessados; todos eles são "arquivos". Os outros que listamos acima são o disco mestre IDE da "primeira" controladora (hda), a "primeira" unidade de fita (st0), a "primeira" interface serial, e dois dispositivos virtuais que não correspondem a dispositivos físicos, a saber, o pseudo-terminal ttyp1 e o dispositivo nulo (null).

Antes de se poder usar um disquete, é necessário marcar magneticamente sobre a superfície dele as trilhas e setores. Esse processo às vezes é chamado de "formatação física". No unix habitualmente há uma ferramenta específica para a formatação física (por exemplo fdformat). No msdos o "FORMAT" faz tanto a formatação física quanto a criação do filesystem, de que falaremos na seqüência.

Exercício 4.5 Tente usar o dd para ler o primeiro setor de algum disquete ou disco rígido da máquina que você estiver usando. Isso pode ou não ser uma operação restrita ao superusuário. Para fazer a tentativa, execute "dd if=/dev/fd0H1440 bs=512 count=1".

Exercício 4.6 Teste o device nulo fazendo "ls >/dev/null". Você não verá nenhuma saída. Enviar algo ao device nulo significa descartar esses dados.

4.7 [o conceito de filesystem] O termo filesystem, que tem vários significados, será abordado aqui de forma particular.

O uso habitual de disquetes não se faz da forma que descrevemos no parágrafo anterior. O que se faz com eles é copiar deles ou para eles "arquivos" identificados pelos seus nomes. Pode-se também criar diretórios para melhor organizar os arquivos.

Como o que existe fisicamente são apenas os setores de que falamos, essas abstrações de diretório e arquivos identificados pelos nomes exige uma estrutura de dados manipulada por comandos como cp, mkdir, etc. Essa estrutura de dados é chamada "filesystem".

Cada sistema operacional implementa um ou mais tipos de "filesystems". No msdos usa-se o "FAT". No os/2 usa-se "FAT" ou "HPFS". Diferentes implementações do unix usam diferentes filesystems. O linux implementa vários tipos, entre eles "FAT", "HPFS", "EXT2", "MINIX".

No unix um filesystem é criado sobre uma mídia já formatada fisicamente através do comando mkfs, que possui variantes dependendo da plataforma. No linux, é muito comum usar-se o mke2fs, para criação de filesystems "EXT2". Os passos para isso seriam:

  hal:~$ fdformat /dev/fd0H1440
  hal:~$ mke2fs -c /dev/fd0H1440

É comum haver "apelidos" para devices usados com freqüência, por exemplo "fd0" ao invés de "fd0H1440".

Exercício 4.8 Tente criar um filesystem ext2 seguindo a receita dada acima. Para isso você precisará de um disquete e, talvez, de provilégios adicionais.

4.9 [os "drives"] Um dos maiores desafios para uma pessoa acostumada ao msdos que começa a usar unix é descobrir porque não conseguem acessar os floppies usando A: e B:.

No entanto os unices costumam oferecer interfaces para uso de disquetes "formatados" no msdos. Uma família de ferramentas popular e presente em várias plataformas é o "mtools". Seguem alguns exemplos de equivalência:

  unix                         msdos
  --------------------------------------
  mdir a:                      DIR A:
  mdel a:                      DEL A:
  mcopy a:autoexec.bat .       COPY A:AUTOEXEC.BAT

Duas diferenças notáveis é que o mcopy, seguindo o modelo do cp exige a explicitação da origem e destino e o mformat, ao contrário do FORMAT do msdos, não faz a formatação física (para tanto deve-se usar alguma ferramenta específica, por exemplo fdformat).

4.10 [uso de disquetes sem filesystem] Não se pode usar um disquete não formatado fisicamente, mas pode-se usar um disquete sem filesystem, e isso é relativamente comum no unix. Equivale mais ou menos a usar uma fita. A idéia é muito simples, vamos supor: se você quiser armazenar um arquivo chamado "relat" de 749 kilobytes, basta usar os "primeiros" 749 bytes do disquete, por exemplo:

  hal:~$ dd if=relat of=/dev/fd0H1440

Note que o dd não marca o final do arquivo. É mais comum usar-se "tar". Com ele preserva-se informações como a data do arquivo e o proprietário de forma portável, e pode-se copiar uma coleção de arquivos, por exemplo uma subárvore inteira:

  hal:~$ tar cvf /dev/fd0H1440 relat

A recuperação do arquivo seria de uma das seguintes formas:

  hal:~$ tar xvf /dev/fd0H1440
  hal:~$ tar xvf /dev/fd0H1440 relat

O tar é muito usado também para concatenar os arquivos de uma subárvore, de forma semelhante a que ferramentas como o zip fazem (sem entretanto comprimir). Por exemplo:

  hal:~$ tar cvf /bin bin.tar

irá concatenar todos os arquivos da subárvore /bin gerando o bin.tar no diretório corrente. Pode-se depois listá-la, explodi-la num outro lugar ou extrair arquivos individuais. Por exemplo:

  hal:~$ tar tvf bin.tar
  hal:~$ tar xvf bin.tar
  hal:~$ tar xvf bin.tar bin/ls

Exercício 4.11 Tente reproduzir os exemplos de uso do tar dados acima no caso da subárvore /bin.

Exercício 4.12 O uso de fitas no unix é semelhante ao que fizemos acima no caso de disquetes. Havendo a possibilidade, tente fazer testes com fitas.

4.13 [impressão] A discussão anterior sobre arquivos especiais já deve dar uma idéia de como se pode fazer um uso elementar de impressoras no unix. Impressoras via de regra usam portas de comunicação seriais ou paralelas, às quais correspondem devices special files como /dev/cua0 ou /dev/lp1, os nomes dependem da plataforma (ultimamente estão se popularizando as impressoras com interface ethernet, o modo de tratá-las é bastante diferente).

Da mesma forma que no msdos se fazem coisas como

  C> COPY ARQUIVO.TXT LPT1:

no unix pode-se fazer

  hal:~$ cat arquivo.txt >/dev/lp1

com efeito semelhante. O modo habitual de trabalhar entretanto não é esse. O serviço de impressão deve administrar o eventual enfileiramento de jobs e a eventual necessidade de submeter o arquivo a conversões de formato antes da impressão.

Tudo isso no unix é feito pelo servidor de impressão lpd. Se você der um "ps ax" provavelmente irá vê-lo rodando como um processo. O cliente desse servidor é o programa lpr. A impressão que fizemos acima "na mão" seria feita da seguinte forma:

  hal:~$ lpr arquivo.txt

A questão da escolha da impressora (quando houver várias) e do preprocessamento do arquivo (por exemplo conversão para postscript) é tratada no unix (e em outros sistemas) através do conceito de fila de impressão, que é especificada pela opção -P. Exatamente quais filas existem numa determinada instalação depende de cada caso. Um exemplo sugestivo seria

  hal:~$ lpr -Pps arquivo.ps

no caso de postscript, desde que haja necessidade de conversão (por exemplo para pcl).

Exercício 4.14 Examine no arquivo /etc/printcap quais filas de impressão existem atualmente na máquina que você está usando. O formato desse arquivo costuma ser descrito na man page printcap(5).

4.15 [emulação de terminais] Há não muito tempo atrás o modo habitual de se usar um computador era através de terminais burros conectados em interfaces seriais. A conexão serial estabelece uma "via" única para dados e controle. Na categoria de "dados" entra por exemplo o texto correspondente à saída de um comando como o ls. Na categoria de "controle" pode entrar por exemplo o posicionamento do cursor quando se pressiona "seta para cima" ao usar um editor de textos visual.

Não existe um padrão único para a codificação de controles. Os fabricantes de terminais adotaram inúmeros padrões diferentes, por isso quando o computador envia um comando para um terminal, precisa saber de antemão que tipo de terminal que é para escolher a codificação correta.

Apesar dos terminais burros serem equipamentos de certa forma obsoletos, o problema continua exatamente o mesmo quando se usa um emulador de terminal como o xterm. Ao acessar-se um sistema remoto (por exemplo) através dele, pode ser necessário avisar o sistema do outro lado que você está emulando um DEC vt100 ou um tektronix 4014 (quando o emulador de terminal tem o recurso de emular vários tipos diferentes, pode ser necessário também setar localmente o tipo).

No unix, isso costuma ser feito através da variável de environment TERM. Vejamos um exemplo. O xterm emula por default o vt102. Vamos setar incorretamente o tipo de terminal:

  hal:~$ TERM=apple-80

Tente usar agora algum editor de textos como o pico ou o vi, e observe o que acontece.

Os valores que a variável TERM pode assumir dependem do sistema remoto. Eles podem estar armazenados em um de dois bancos de dados diferentes, o "termcap" ou o "terminfo" (ou ambos). Valores habitualmente reconhecidos e que cobre boa parte dos casos são "vt100", "ansi" e "xterm".

Poderá ser necessário também avisar o sistema operacional das dimensões do seu [emulador de] terminal. No unix isso é feito através do comando stty:

  hal:~$ stty rows 36 cols 80

Exercício 4.16: Especifique através do comando stty um número de linhas errado e observe como os paginadores (more, less) passam a se comportar por exemplo na leitura de uma man page.

4.17 [expressões regulares] Neste ponto encerramos já o que tínhamos a dizer sobre I/O, e a título de complementação passaremos a tratar de um conceito fundamental do unix.

Não foi o unix quem inventou a noção de expressão regular, mas que eu saiba ele é o único sistema operacional que traz esse conceito como algo incorporado às libraries, interfaces e ferramentas de configuração.

Daremos exemplos baseados no utilitário grep, mas eles poderiam ser reproduzidos por exemplo na leitura de man pages através do comando "/".

1. busca de ocorrências de algum dos anos da década de 80:

  hal:~$ grep 198[0-9] *

2. busca de uma ocorrência da palavra "yellow" seguida de uma ocorrência da palavra "page", separadas por zero ou mais caracteres quaisquer:

  hal:~$ grep -i "page.*version" *

3. busca de uma das palavras "roget" ou "thesaurus":

  hal:~$ grep -i "roget\|thesaurus" *

Nota: a sintaxe das expressões regulares pode variar um pouco dependendo da plataforma e da ferramenta.