From 67d3c4278a9c0a8540258de60db10d9f4cbf7848 Mon Sep 17 00:00:00 2001 From: Blau Araujo Date: Mon, 2 Jun 2025 11:48:54 -0300 Subject: [PATCH] =?UTF-8?q?conte=C3=BAdo=20da=20aula=208?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- curso/aula-08.org | 1350 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1350 insertions(+) create mode 100644 curso/aula-08.org diff --git a/curso/aula-08.org b/curso/aula-08.org new file mode 100644 index 0000000..9ff5b4b --- /dev/null +++ b/curso/aula-08.org @@ -0,0 +1,1350 @@ +#+title: 8 -- Fluxos de dados +#+author: Blau Araujo +#+email: cursos@blauaraujo.com + +#+options: toc:3 + +* Objetivos + +- Compreender o conceito de fluxos de dados. +- Entender a relação entre descritores de arquivos e /streams/. +- Demonstrar como os mecanismos de pipe e redirecionamento são implementados em baixo nível. +- Introduzir a criação de programas em baixo nível que recebem dados interativamente. +- Aprender a diferenciar a origem de um fluxo de entrada de dados. + +* Tabela de descritores de arquivos + +Um /fluxo de dados/ é a abstração, em software, de todas as possíveis combinações +de trocas de dados entre arquivos e processos no espaço de usuário, como a +leitura dos dados digitados no teclado do terminal, a impressão (ou exibição) +de mensagens no /display/ do terminal o acesso a um arquivo para escrita e/ou +leitura de dados e até a mediação da troca de dados entre dois processos. + +No nível do sistema operacional, esses fluxos são estabelecidos e controlados +por diversas estruturas (/structs/), associadas individualmente a cada processo, +chamadas de /tabelas de descritores de arquivos/, onde cada entrada é um inteiro +sem sinal chamado de /descritor de arquivo/ (ou FD, de /file descriptor/). + +O diagrama abaixo representa, conceitualmente, como o kernel do Linux organiza +os descritores de arquivos e suas estruturas internas para cada processo: + +#+begin_example +TABELA DE DESCRIOTES + DE ARQUIVOS ┌─────────────┐ ┌──────────────────┐ + ┌──────────────┐ │ ├───────┤ FILE OPETRATIONS │ + │ PROCESSO │ ┌──┤ STRUCT FILE ├────┐ └──────────────────┘ + ├──────────────┤ │ │ ├──┐ │ ┌──────────────────┐ + │ FD0 (STDIN) ├──┘ └─────────────┘ │ └──┤ INODE │ + ├──────────────┤ │ └──────────────────┘ + │ FD1 (STDOUT) ├── ··· │ ┌──────────────────┐ + ├──────────────┤ └────┤ DIR ENTRY │ + │ FD2 (STDERR) ├── ··· └──────────────────┘ + ├──────────────┤ ··· + │ ··· ├── ··· + └──────────────┘ +#+end_example + +Nele, nós podemos ver que cada entrada da tabela (um descritor de arquivo) está +associada a uma estrutura de dados (/struct file/), gerenciada pelo kernel, onde +são encontradas diversas informações sobre um arquivo e as operações que serão +realizadas com ele. + +** Uma nota sobre tipos de arquivos + +Sistemas parecidos com o UNIX, como o GNU/Linux, trabalham com 7 tipos +de arquivos: + +- Diretórios :: + + Arquivos que indexam a localização de outros arquivos. + +- Arquivos comuns :: + + Como os arquivos dos nossos textos, programas e imagens. + +- Ligações simbólicas :: + + Arquivos que representam outros arquivos. + +- Dispositivos caractere :: + + Abstração da comunicação com dispositivos de hardware sem o acúmulo de dados. + +- Dispositivos bloco :: + + Abstração da comunicação com dispositivos de hardware com acúmulo de dados. + +- Pipes/FIFOs :: + + Arquivos que interfaceiam a troca de dados entre processos. + +- Sockets :: + + Arquivos de interfaceiam o acesso a serviços. + +Então, quando dissermos algo como: /"dispositivo de terminal"/, ou /"ligações +simbólicas"/, nós estaremos nos referindo a diferentes tipos de arquivos. + +** Descritores de arquivos padrão + +No sistema de arquivos, já no espaço de usuário, os descritores de arquivos +são disponibilizados aos processos como /ligações simbólicas/, e todo processo +é iniciado com acesso a três deles -- os /descritores de arquivos padrão/: + +- *Descritor de arquivo =0=:* para leitura de fluxos de dados; +- *Descritor de arquivo =1=:* para escrita de dados; +- *Descritor de arquivo =2=:* para escrita de dados relacionados a erros. + +Esses descritores, assim como todos os outros criados ao longo da execução +do processo, podem ser encontrados no diretório virtual =/proc//fd= e, +em princípio, eles estão ligados a um /dispositivo de terminal/. Os descritores +de arquivos padrão também são expostos no sistema de arquivos pelas ligações +simbólicas =stdin=, =stout= e =stderr=, no diretório =/dev= (de /device/): + +#+begin_example +:~$ ls -l /dev/std* +lrwxrwxrwx 1 root root 15 mai 28 11:30 /dev/stderr -> /proc/self/fd/2 +lrwxrwxrwx 1 root root 15 mai 28 11:30 /dev/stdin -> /proc/self/fd/0 +lrwxrwxrwx 1 root root 15 mai 28 11:30 /dev/stdout -> /proc/self/fd/1 +#+end_example + +Isso mostra claramente a relação entre os nomes simbólicos e os descritores +de arquivos que representam: + +- =stdin= → Descritor de arquivo =0= +- =stdout= → Descritor de arquivo =1= +- =stderr= → Descritor de arquivo =2= + +Ainda na listagem anterior, note que as ligações apontam para os descritores +de arquivos padrão de um processo chamado =self= (/"eu mesmo"/, em inglês), que +é um /link mágico/ que expande o PID do processo que acessa o diretório =fd= +através das ligações =/dev/std*=. + +Por exemplo: + +#+begin_example +:~$ grep -E '^(Name:|Pid:)' /proc/self/status +Name: grep +Pid: 262881 +#+end_example + +Isso fez com que o arquivo =status=, no diretório do processo do próprio =grep=, +fosse filtrado para imprimir apenas as linhas iniciadas com =Nome:= e =Pid:=. Nós +também podemos usar =self= para expandir a lista de arquivos no diretório =fd= +do processo do shell em execução em um terminal: + +#+begin_example +:~$ printf '%s\n' /proc/self/fd/* +/proc/self/fd/0 +/proc/self/fd/1 +/proc/self/fd/2 +/proc/self/fd/255 +/proc/self/fd/3 +#+end_example + +#+begin_quote +No Bash, =printf= é um comando interno. +#+end_quote + +Além dos descritores padrão (=0=, =1= e =2=), nós temos os descritores =255=, criado +pelo shell para uso interno, e =3=, que é criado pelo kernel para resolver o +caminho expandido por =self=, como podemos demonstrar listando os descritores +do processo do =ls= utilizado para listar seu próprio diretório =fd=: + +#+begin_example +:~$ ls -l /proc/self/fd +total 0 +lr-x------ 1 blau blau 64 mai 30 07:16 3 -> /proc/263784/fd +lrwx------ 1 blau blau 64 mai 30 07:16 0 -> /dev/pts/0 +lrwx------ 1 blau blau 64 mai 30 07:16 1 -> /dev/pts/0 +lrwx------ 1 blau blau 64 mai 30 07:16 2 -> /dev/pts/0 +#+end_example + +Sabendo o número do PID, nós podemos listar o conteúdo do diretório =fd= +associado ao processo sem usar =fd=. Por exemplo, nós podemos descobrir o PID +do processo do shell em execução no terminal com: + +#+begin_example +:~$ echo $$ +252271 +#+end_example + +Em seguida, nós utilizamos o PID encontrado: + +#+begin_example +:~$ ls -l /proc/252271/fd +total 0 +lrwx------ 1 blau blau 64 mai 30 05:56 0 -> /dev/pts/0 +lrwx------ 1 blau blau 64 mai 30 05:56 1 -> /dev/pts/0 +lrwx------ 1 blau blau 64 mai 30 05:56 2 -> /dev/pts/0 +lrwx------ 1 blau blau 64 mai 30 05:56 255 -> /dev/pts/0 +#+end_example + +#+begin_quote +A expansão do PID do shell também poderia ser feita diretamente na linha de +comando com: =ls -l /proc/$$/fd=. +#+end_quote + +Como podemos ver, as ligações simbólicas =0=, =1= e =2= estão presentes e apontam +para o dispositivo de terminal =/dev/pts/0=. Além deles, nós também podemos +ver que o shell criou um quarto descritor de arquivo (=255=) para seu uso +interno, mas o descritor =3= não existe, porque nós não usamos =self=. + +** Abstração em alto nível (/stream/) + +Acima da interface de baixo nível, a biblioteca padrão da linguagem C (=glibc=) +introduz uma camada de abstração bastante conveniente por meio de /streams/ +(de /fluxos/, em inglês), representadas por ponteiros do tipo =FILE *= para uma +estrutura de controle que, entre outras coisas, encapsula os descritores de +arquivos. Para entender a diferença entre descritores de arquivos e /streams/, +imagine um arquivo como um livro numa biblioteca: + +- *Descritor de arquivo:* é como ter apenas o número de catálogo do livro no + sistema da biblioteca -- você sabe onde o livro está, mas precisa ir até + ele e manuseá-lo por conta própria. + +- *Stream:* é como ter um assistente que encontra o livro, traz até você, + lembra em que página você parou, faz anotações enquanto você lê, e ainda + avisa se houver algum problema com o conteúdo. + +A =glibc=, portanto, fornece diversas funções de alto nível utilizando /streams/ +para abertura e fechamento de ligações com arquivos e tarefas de leitura e +escrita, além de recursos como bufferização automática, verificação de erros e +manipulação de arquivos. Os fluxos padrão (=stdin=, =stdout= e =stderr=) também são +inicializados automaticamente como /streams/ assim que o programa é executado, +apontando para os descritores =0=, =1= e =2=, respectivamente. + +* Uso dos fluxos de dados padrão no shell + +O shell utiliza o fluxo padrão de entrada (=stdin=) para ler os comandos +digitados no terminal e os fluxos padrão de saída para imprimir, também no +terminal, as saídas de comandos (=stdout=) e mensagens de erro (=stderr=). Os +programas executados na linha de comandos, por sua vez, herdarão os +descritores de arquivos do shell e também utilizarão os fluxos padrão para +ler e escrever no mesmo terminal: + +#+begin_example +:~$ ls -l /proc/self/fd +total 0 +lr-x------ 1 blau blau 64 mai 30 07:49 3 -> /proc/266285/fd +lrwx------ 1 blau blau 64 mai 30 07:49 0 -> /dev/pts/0 +lrwx------ 1 blau blau 64 mai 30 07:49 1 -> /dev/pts/0 +lrwx------ 1 blau blau 64 mai 30 07:49 2 -> /dev/pts/0 +:~$ ls -l /proc/$$/fd +total 0 +lrwx------ 1 blau blau 64 mai 30 05:56 0 -> /dev/pts/0 +lrwx------ 1 blau blau 64 mai 30 05:56 1 -> /dev/pts/0 +lrwx------ 1 blau blau 64 mai 30 05:56 2 -> /dev/pts/0 +lrwx------ 1 blau blau 64 mai 30 05:56 255 -> /dev/pts/0 +#+end_example + +Veja que o processo do programa =ls= (primeira listagem) herdou o acesso ao +pseudo terminal =/dev/pts/0= de seu processo pai -- o processo do shell, na +segunda listagem. Contudo, existem dois mecanismos, implementados como +operadores do shell, que possibilitam alterar os arquivos associado aos +descritores de arquivo padrão: /redirecionamentos/ e /pipes/. + +** Mecanismo de redirecionamento + +Com os redirecionamentos, nós podemos indicar os arquivos que substituirão +o terminal como origem e destino de fluxos de dados. Por exemplo, em vez +de imprimir a saída do =ls= no terminal, nós podemos desviar o fluxo em +=stdout= para o arquivo =exemplo.txt= com os operadores =>= ou =>>=. Ambos criam +o arquivo de destino se ele não existir, mas lidam de formas diferentes +com arquivos existentes: + +- *Operador de escrita em =stdout= (=>=):* trunca (apaga) o conteúdo do arquivo + antes de iniciar a escrita. +- *Operador de /append/ em =stdout= (=>>=):* inclui as novas linhas após o conteúdo + existente no arquivo. + +Como o nosso arquivo ainda não existe, tanto faz o operador, portanto... + +#+begin_example +:~$ ls -l /proc/self/fd > exemplo.txt +:~$ +#+end_example + +Nada foi impresso no terminal, mas o arquivo =exemplo.txt= foi criado e a +saída do =ls= foi escrita nele: + +#+begin_example +:~$ cat exemplo.txt +total 0 +lr-x------ 1 blau blau 64 mai 30 08:04 3 -> /proc/267394/fd +lrwx------ 1 blau blau 64 mai 30 08:04 0 -> /dev/pts/0 +l-wx------ 1 blau blau 64 mai 30 08:04 1 -> /home/blau/exemplo.txt +lrwx------ 1 blau blau 64 mai 30 08:04 2 -> /dev/pts/0 +#+end_example + +Observe que o descritor de arquivo =1= (=stdout=) estava ligado ao arquivo +=/home/blau/exemplo.txt= durante a execução do =ls=. O mesmo poderia ser +feito com a saída padrão de erros, mas nós teríamos que informar o número +do descritor de arquivos (=2=) junto ao operador. Por exemplo, a tentativa +de listar um arquivo inexistente causaria um erro e imprimiria uma mensagem +no terminal via =stderr=: + +#+begin_example +:~$ ls banana.txt +ls: não foi possível acessar 'banana.txt': Arquivo ou diretório inexistente +#+end_example + +Esta saída poderia ser redirecionada para o arquivo =exemplo.txt=: + +#+begin_example +:~$ ls -l /proc/self/fd banana.txt 2> exemplo.txt +/proc/self/fd: +total 0 +lr-x------ 1 blau blau 64 mai 30 08:14 3 -> /proc/268233/fd +lrwx------ 1 blau blau 64 mai 30 08:14 0 -> /dev/pts/0 +lrwx------ 1 blau blau 64 mai 30 08:14 1 -> /dev/pts/0 +l-wx------ 1 blau blau 64 mai 30 08:14 2 -> /home/blau/exemplo.txt +#+end_example + +Aqui, a listagem em =stdout= foi impressa no terminal e a mensagem de erro +foi escrita no arquivo: + +#+begin_example +:~$ cat exemplo.txt +ls: não foi possível acessar 'banana.txt': Arquivo ou diretório inexistente +#+end_example + +Na listagem impressa, aliás, nós também podemos ver que o descritor de +arquivo =2= foi redirecionado para o arquivo. A entrada padrão também pode +ser redirecionada para a leitura de um arquivo, mesmo que o programa não +o utilize. Isso é feito com o operador de redirecionamento de leitura (=<=): + +#+begin_example +$ ls -l /proc/self/fd < exemplo.txt +total 0 +lr-x------ 1 blau blau 64 mai 30 08:20 3 -> /proc/268655/fd +lr-x------ 1 blau blau 64 mai 30 08:20 0 -> /home/blau/exemplo.txt +lrwx------ 1 blau blau 64 mai 30 08:20 1 -> /dev/pts/0 +lrwx------ 1 blau blau 64 mai 30 08:20 2 -> /dev/pts/0 +#+end_example + +Veja que o descritor de arquivo =0= estava ligado a =exemplo.txt= durante +a execução de =ls=. + +** Mecanismo de pipe + +Enquanto o mecanismo de redirecionamento conecta processos a arquivos, +o mecanismo de /pipe/ conecta o fluxo padrão de saída de um processo ao +fluxo padrão de entrada de outro processo através de um arquivo do tipo +/pipe/. + +#+begin_example + +┌────────────┐ STDOUT ┌──────┐ STDIN ┌────────────┐ +│ PROCESSO 1 ├─────────▶│ PIPE ├────────▶│ PROCESSO 2 │ +└────────────┘ └──────┘ └────────────┘ +#+end_example + +No shell, o mecanismo é implementado com o operador de pipe (=|=) e expressa +perfeitamente, na forma de interface com o usuário, todos os princípios da +Filosofia Unix. Programas como =ls= e =cat=, por exemplo, são especializados +em fazer muito bem apenas uma coisa (listar diretórios e concatenar +conteúdos de arquivos), e ambos são capazes de trabalhar juntos através de +um fluxo de texto canalizado pelo mecanismo do /pipe/: + +#+begin_example +:~$ ls -l /proc/self/fd /proc/$(pidof cat)/fd | cat +/proc/270862/fd: +total 0 +lr-x------ 1 blau blau 64 mai 30 08:48 0 -> pipe:[1689012] +lrwx------ 1 blau blau 64 mai 30 08:48 1 -> /dev/pts/0 +lrwx------ 1 blau blau 64 mai 30 08:48 2 -> /dev/pts/0 + +/proc/self/fd: +total 0 +lr-x------ 1 blau blau 64 mai 30 08:48 3 -> /proc/270861/fd +lrwx------ 1 blau blau 64 mai 30 08:48 0 -> /dev/pts/0 +l-wx------ 1 blau blau 64 mai 30 08:48 1 -> pipe:[1689012] +lrwx------ 1 blau blau 64 mai 30 08:48 2 -> /dev/pts/0 +#+end_example + +Neste exemplo, o =ls= foi executado para listar o conteúdo de seu diretório +=fd= e o diretório do processo do =cat= que, por estar encadeado por pipe na +linha do comando, foi executado simultaneamente e seu PID foi obtido com +o utilitário =pidof=. Como resultado, nós temos os dois diretórios, onde +podemos ver que a entrada padrão do processo do =cat= (=FD0=) estava ligada +ao mesmo arquivo pipe conectado à saída padrão do =ls= (=FD1=). + +** Captura da entrada padrão + +Uma terceira forma de manipulação de fluxos de dados,é a possibilidade +de pausar a execução do shell para ler a entrada padrão e armazenar os +dados lidos em uma ou mais variáveis. Isso é feito por meio do comando +=read=, implementado como um comando interno do shell: + +#+begin_example +read [-r] VAR [VARS ...] # Sintaxe POSIX +read [OPÇÕES] [VAR ...] # Sintaxe no Bash +#+end_example + +#+begin_quote +Nas versões POSIX, o =read= lê apenas a entrada padrão, mas o Bash inclui +uma opção para a leitura de qualquer descritor de arquivo (opção =-u=). +#+end_quote + +Com o =read=, é possível receber dados interativamente, ler uma linha de um +arquivo por redirecionamento de leitura ou ler uma linha da saída de outro +comando encadeado por pipe, mas em circunstâncias bem distintas: + +| Critério | Leitura interativa | Redirecionamento | Pipe | +|-------------------+--------------------+------------------+--------------| +| *Origem da entrada* | Terminal | Arquivo | Arquivo pipe | +| *Fim da leitura* | =Enter= | =\n= ou =EOF= | =\n= ou =EOF= | +| *Interrupção* | =Ctrl+D= | Não | Não | +| *Processo* | Shell corrente | Shell corrente | /Subshell/ | +| *Dados capturados* | Disponíveis | Disponíveis | Inexistentes | + +*** Origem da entrada + +Enquanto pipes e redirecionamentos alteram a ligação com o descritor de +arquivo =0= (=stdin=), a leitura interativa utiliza a ligação original com +o terminal associado ao processo corrente do shell. Em scripts, isso pode +ser detectado através de vários testes, como: + +#+begin_example +test -t 0 # Comando 'test' (POSIX e Bash) +[ -t 0 ] # Comando '[' (POSIX e Bash) +[[ -t 0 ]] # Comando composto '[[ ... ]]', do Bash +#+end_example + +Nos três casos, os comandos terminam com /sucesso/ (estado =0=) se o descritor +de arquivo =0= estiver ligado a um terminal. + +*** Fim da leitura + +O comando interno =read= lê apenas uma linha de dados, o que geralmente é +delimitado pelo caractere de nova linha (=\n= na notação ANSI-C ou =0x0a= +na tabela ASCII) ou quando não houver mais caracteres para ler (fim do +arquivo). Contudo, em leituras não interativas, o Bash possibilita a +alteração do caractere delimitador de linha =-d=. + +Numa leitura interativa, onde a linha é digitada pelo usuário no terminal, +o fim da leitura acontece quando o terminal recebe o comando para enviar +os caracteres lidos, o que é feito com a tecla =Enter=. + +*Notas:* + +- Por padrão, o delimitador de fim de linha é descartado. +- O Bash também implementa a possibilidade de finalizar o comando após a + leitura de um dado número de caracteres (opções =-n= e =-N=). + +*** Interrupção da leitura + +No modo interativo, a leitura da entrada padrão pode ser cancelada com a +combinação de teclas =Ctrl+D=, que envia para o terminal o caractere ASCII +=EOT= (fim de transmissão). Já em pipes e redirecionamentos, a leitura não +pode ser cancelada, mas o término dos processos envolvidos pode ser +forçado, por exemplo, com a combinação de teclas =Ctrl+C=, que envia para +eles o sinal =SIGINT=. + +*** Processos envolvidos na leitura + +Como o =read= é um comando interno do shell, sua execução não dá início +a um novo processo. Isso vale para seu uso em comandos simples, mesmo +que a leitura seja feita por um redirecionamento de leitura. Entretanto, +se o =read= estiver encadeado por pipe com outros comandos, haverá o início +de um novo processo do shell para executá-lo (um /subshell/), o que traz +consequências importantes quanto à disponibilidade dos dados capturados. + +*** Disponibilidade dos dados capturados + +A linha lida pelo =read= é separada em campos conforme os caracteres +definidos na variável interna =IFS=. Por padrão, esses caracteres são +o espaço e a tabulação -- o caractere de nova de linha também está +em =IFS=, mas é utilizado como delimitador de linha. O que determina +a quantidade de campos é a quantidade de argumentos passados para o +=read= como nomes de variáveis, por exemplo: + +#+begin_example +read # Toda a linha será atribuída à variável interna 'REPLY' +read var # Toda a linha será atribuída à variável 'var' +read a b c # 'a' recebe o primeiro campo, 'b' o segundo e 'c' o restante da linha +#+end_example + +De qualquer forma, todos os dados capturados serão atribuídos a variáveis +de escopo local: ou seja, disponíveis apenas para o processo do shell em +que o =read= for executado. Sendo assim, quando o =read= é executado em um +pipe, todas as variáveis definidas serão perdidas quando o processo do +subshell em que ele for executado terminar. + +* Uso dos fluxos de dados padrão em programas + +Em baixo nível, os descritores de arquivos padrão são utilizados diretamente +como argumentos de chamadas de sistema para leitura ou escrita no terminal +associado à execução do processo do programa, como as chamadas =read= e =write=, +quando passamos os descritores =0= e =1=, respectivamente, como argumentos. + +Também existem chamadas de sistema que possibilitam a escrita de programas +que redirecionam os fluxos padrão para outros arquivos ou para pipes, como +é o caso da chamada =dup= quando utilizada em conjunto com a chamada =open= (ou +a chamada =pipe=) para trocar o descritor associado ao arquivo aberto por um +dos descritores de arquivos padrão. + +** Demonstrando um redirecionamento interno + +Por exemplo, se eu quiser que a saída do meu programa (=salve-redir.asm=) seja +escrita em um arquivo, e não no terminal, eu posso redirecionar o descritor +de arquivo =1= com a chamada de sistema =dup2= após criar o arquivo de destino. + +#+begin_quote +Obviamente, essa é a demonstração de um redirecionamento para escrita em +=stdout= tal como é feita pelo shell: nós não precisamos redirecionar nada +nos nossos programas para escrever em arquivos. +#+end_quote + +Arquivo: [[exemplos/08/salve-redir.asm][salve-redir.asm]] + +#+begin_src asm :tangle exemplos/08/salve-redir.asm +; Chamadas de sistema... +%define SYS_WRITE 1 +%define SYS_OPEN 2 +%define SYS_CLOSE 3 +%define SYS_DUP2 33 +%define SYS_EXIT 60 + +; Flags da chamada SYS_OPEN... +%define O_WRONLY 0x1 ; apenas para escrita +%define O_CREAT 0x40 ; cria se não existir +%define O_TRUNC 0x200 ; trunca conteúdo existente + +; Permissões do arquivo criado... +%define F_MODE 0o644 + +; Descritores de arquivos... +%define STDOUT 1 + +section .rodata + file db "mensagem.txt", 0 ; arquivo de destino + msg db "Salve, simpatia!", 10 ; Mensagem + len equ $ - msg ; Tamanho da mensagem + +section .text + global _start + +_start: + ; open (ARQUIVO, FLAGS, MODO) + mov rax, SYS_OPEN + mov rdi, file + mov rsi, O_WRONLY | O_CREAT | O_TRUNC + mov rdx, F_MODE + syscall + + mov r12, rax ; salvar descritor de arquivo retornado + + ; dup2(ANTIGO_FD, NOVO_FD) <-- redirecionamento + mov rax, SYS_DUP2 + mov rdi, r12 + mov rsi, STDOUT + syscall + + ; write(FD, STRING, SIZE) + mov rax, SYS_WRITE + mov rdi, STDOUT + mov rsi, msg + mov rdx, len + syscall + + ; close(FD) + mov rax, SYS_CLOSE + mov rdi, r12 + syscall + + ; exit(STATUS) + mov rax, SYS_EXIT + xor rdi, rdi + syscall +#+end_src + +Montagem e execução: + +#+begin_example +:~$ nasm -f elf64 salve-redir.asm +:~$ ld -o salve-redir salve-redir.o +:~$ ./salve-redir +:~$ cat mensagem.txt +Salve, simpatia! +#+end_example + +** Demonstrando um pipe interno + +O programa abaixo (=salve-pipe.asm=) demonstra o encadeamento por pipe entre +ele mesmo e o utilitário =cat=, também utilizando os descritores de arquivos +padrão, como o shell faria. A ideia é emular o comportamento do comando: + +#+begin_example +echo 'Salve, simpatia!' | cat +#+end_example + +Arquivo: [[exemplos/08/salve-pipe.asm][salve-pipe.asm]] + +#+begin_src asm :tangle exemplos/08/salve-pipe.asm +; ---------------------------------------------------------- +; Arquivo : salve-pipe.asm +; Reproduz: echo 'Salve, simpatia!' | cat +; Montagem: nasm -f elf64 salve-pipe.asm +; Ligação : ld -o salve-pipe salve-pipe.o +; ---------------------------------------------------------- +; Chamadas de sistema... +; ---------------------------------------------------------- +%define SYS_WRITE 1 +%define SYS_CLOSE 3 +%define SYS_PIPE 22 +%define SYS_DUP2 33 +%define SYS_FORK 57 +%define SYS_EXECVE 59 +%define SYS_EXIT 60 +%define SYS_WAIT 61 +; ---------------------------------------------------------- +; Parâmetros de SYS_WAIT... +; ---------------------------------------------------------- +%define W_CHILD -1 ; filho de qualquer PID +%define W_STATUS 0 ; ignorar o estado de término (NULL) +%define W_OPTIONS 0 ; sem opções +; ---------------------------------------------------------- +; Descritores de arquivos padrão... +; ---------------------------------------------------------- +%define STDIN_FD 0 +%define STDOUT_FD 1 +; ---------------------------------------------------------- +; Estados de término... +; ---------------------------------------------------------- +%define EXIT_SUCCESS 0 +%define EXIT_ERROR 1 +; ---------------------------------------------------------- +section .rodata +; ---------------------------------------------------------- + ; Mensagem... + msg db "Salve, simpatia!", 10 + len equ $ - msg + + ; Argumentos de execve... + comm db "/bin/cat", 0 + argv dq comm, 0 + envp dq 0 +; ---------------------------------------------------------- +section .bss +; ---------------------------------------------------------- + read_end resd 1 ; Descritor da ponta de leitura do pipe + write_end resd 1 ; Descritor da ponta de escrita do pipe +; ---------------------------------------------------------- +section .text +; ---------------------------------------------------------- +global _start + +_start: + ; pipe(int pipefd[]) + ; retorna: pipefd[0] => read_end; pipefd[1] => write_end + mov rax, SYS_PIPE + mov rdi, read_end ; Retorno vai avançar em write_end + syscall + +.parada1: ; examinar FDs retornados! + + ; fork() + mov rax, SYS_FORK + syscall + + test rax, rax ; rax = 0 -> processo filho + jz .filho ; pula a execução do código do pai +; ---------------------------------------------------------- +; Código executado apenas no processo pai... +; ---------------------------------------------------------- +.pai: +; ---------------------------------------------------------- + ; dup2(write_end, STDOUT) + mov rax, SYS_DUP2 + mov edi, [write_end] ; FD original: write_end + mov rsi, STDOUT_FD ; novo descritor de escrita + syscall + + ; Fechar write_end e read_end... + call _close_all + +.parada2: ;examinar /proc//fd! + + ; write(STDOUT, msg, len) + mov rax, SYS_WRITE + mov rdi, STDOUT_FD + mov rsi, msg + mov rdx, len + syscall + + ; wait4(-1, NULL, 0) + mov rax, SYS_WAIT + mov rdi, W_CHILD + mov rsi, W_STATUS + mov rdx, W_OPTIONS + + ; exit(0) + mov rax, SYS_EXIT + mov rdi, EXIT_SUCCESS ; sai com sucesso se chegar aqui + syscall +; ---------------------------------------------------------- +; Código executado apenas no processo filho... +; ---------------------------------------------------------- +.filho: +; ---------------------------------------------------------- + ; dup2(read_end, STDIN) + mov rax, SYS_DUP2 + mov edi, [read_end] ; descritor de leitura no pipe + mov rsi, STDIN_FD ; novo descritor de leitura + syscall + + ; Fechar write_end e read_end... + call _close_all + + ; execve("/bin/cat", ["/bin/cat", NULL], [NULL]) + mov rax, SYS_EXECVE + mov rdi, comm + mov rsi, argv + mov rdx, envp + syscall + + ; exit(1) + mov rax, SYS_EXIT + mov rdi, EXIT_ERROR ; sai com erro se execve falhar + syscall +; ---------------------------------------------------------- +; Sub de fechamento dos descritores originais do pipe... +; ---------------------------------------------------------- +_close_all: +; ---------------------------------------------------------- + ; close(read_end) + mov rax, SYS_CLOSE + mov rdi, [read_end] + syscall + ; close(write_end) + mov rax, SYS_CLOSE + mov rdi, [write_end] + syscall + ret +#+end_src + +Montagem e execução: + +#+begin_example +:~$ nasm -g -felf64 salve-pipe.asm +:~$ ld -o salve-pipe salve-pipe.o +:~$ ./salve-pipe +Salve, simpatia! +#+end_example + +Não há como observar o que o programa faz apenas pelo seu resultado, então +vamos usar o =gdb= para analisar seus efeitos em dois pontos-chave (repare que +montamos o programa com a opção =-g=): + +- Após a criação do pipe (=.parada1:=); +- Antes da impressão da mensagem (=.parada2:=). + +Nossos objetivos serão: + +- Observar os descritores de arquivos retornados para as pontas de leitura + e escrita do pipe; +- Observar o diretório =/proc//fd= do processo pai após a criação do pipe; +- Observar o diretório =/proc//fd= do processo pai antes da impressão da mensagem; +- Observar o diretório =/proc//fd= do processo do =cat= antes da impressão da mensagem. + +*Abertura do programa no GDB:* + +#+begin_example +:~$ gdb ./salve-pipe +Reading symbols from ./salve-pipe... +(gdb) +#+end_example + +*Definição dos pontos de parada e execução:* + +#+begin_example +(gdb) b _start.parada1 +Breakpoint 1 at 0x401011: file salve-pipe.asm, line 64. +(gdb) b _start.parada2 +Breakpoint 2 at 0x401035: file salve-pipe.asm, line 86. +(gdb) r +Starting program: /home/blau/git/pbn/curso/exemplos/08/salve-pipe + +Breakpoint 1, _start.parada1 () at salve-pipe.asm:64 +64 mov rax, SYS_FORK +(gdb) +#+end_example + +Neste ponto, a criação do pipe acabou de ser executada: + +#+begin_src asm +mov rax, SYS_PIPE +mov rdi, read_end ; Retorno vai avançar em write_end +syscall +#+end_src + +Quando a chamada =pipe= é executada, um arquivo do tipo pipe é criado e dois +descritores de arquivos são associados a ele no processo: + +#+begin_example +┌──────────┐ +│ ├─ 0 +│ ├─ 1 +│ PROCESSO ├─ 2 ┌──────┐ +│ │◀─── READ_END ───┤ │ +│ │ │ PIPE │ +│ ├─── WRITE_END ──▶│ │ +└──────────┘ └──────┘ +#+end_example + +Os números dos descritores de arquivos são dois inteiros (4 bytes cada) que, +no nosso programa, são escritos a partir do endereço representado pelo rótulo +=read_end=. Sendo assim, vamos examinar seus valores: + +#+begin_example +(gdb) x /1gx &read_end +0x403034 : 0x0000000400000003 +(gdb) x /1wx &read_end +0x403034 : 0x00000003 +(gdb) x /1wx &write_end +0x403038 : 0x00000004 +#+end_example + +Isso mostra que os descritores de arquivos do pipe são =3= (ponta de leitura) +e =4= (ponta de escrita). Sabendo o PID do programa, nós podemos listar o +conteúdo de seu diretório =fd= em outro terminal: + +#+begin_example +:~$ pidof salve-pipe +547187 +:~$ ls -l /proc/547187/fd +total 0 +lrwx------ 1 blau blau 64 jun 1 10:03 0 -> /dev/pts/1 +lrwx------ 1 blau blau 64 jun 1 10:03 1 -> /dev/pts/1 +lrwx------ 1 blau blau 64 jun 1 10:03 2 -> /dev/pts/1 +lr-x------ 1 blau blau 64 jun 1 10:03 3 -> pipe:[3331719] +l-wx------ 1 blau blau 64 jun 1 10:03 4 -> pipe:[3331719] +#+end_example + +#+begin_quote +Observe as permissões =r= e =w= nos dois novos descritores de arquivos. +#+end_quote + +A partir daqui, o programa deve clonar o processo pai (chamada =fork=) em um +novo processo filho, em seguida... + +- No processo pai: + - Mover o descritor de escrita do pipe para a saída padrão; + - Escrever a mensagem no pipe; + - Aguardar o teŕmino do processo filho. +- No processo filho: + - Mover o descritor de leitura do pipe para a entrada padrão; + - Executar o utilitário =cat= para ler o pipe. + +O efeito sobre os descritores de arquivos pode ser visto no próximo ponto +de parada, definido para antes da impressão da mensagem: + +#+begin_example +(gdb) c +Continuing. +[Detaching after fork from child process 549470] + +Breakpoint 2, _start.parada2 () at salve-pipe.asm:86 +86 mov rax, SYS_WRITE +#+end_example + +No outro terminal, nós vamos repetir a listagem de =fd=: + +#+begin_example +:~$ ls -l /proc/547187/fd +total 0 +lrwx------ 1 blau blau 64 jun 1 10:03 0 -> /dev/pts/1 +l-wx------ 1 blau blau 64 jun 1 10:03 1 -> pipe:[3331719] +lrwx------ 1 blau blau 64 jun 1 10:03 2 -> /dev/pts/1 +#+end_example + +Como podemos ver no processo pai, os descritores =3= e =4= não existem mais e o +pipe está conectado apenas ao descritor de arquivos =1= (=stdout=). Para ver como +estão os descritores do processo do =cat=, vamos utilizar =pidof= na própria +linha do comando: + +#+begin_example +:~$ ls -l /proc/$(pidof cat)/fd +total 0 +lr-x------ 1 blau blau 64 jun 1 10:36 0 -> 'pipe:[3331719]' +lrwx------ 1 blau blau 64 jun 1 10:36 1 -> /dev/pts/1 +lrwx------ 1 blau blau 64 jun 1 10:36 2 -> /dev/pts/1 +#+end_example + +O que demonstra que o pipe está ligado ao descritor de arquivo =0= (=stdin=) e +o nosso programa conseguiu reproduzir perfeitamente o funcionamento do +mecanismo de pipe no shell. + +** Demonstrando a leitura da entrada padrão + +O kernel fornece uma chamada de sistema para leitura da entrada padrão ou +de descritores de arquivos: a chamada =read=. Assim como o comando de mesmo +nome no shell, ao atuar sobre =stdin=, essa chamada pode receber dados de +forma interativa, ler arquivos por redirecionamento ou a saída de outro +comando encadeado por pipe. + +Contudo, diferente do comando, a chamada de sistema não se limita à leitura +de uma linha. Em vez disso, ela tenta ler uma quantidade fixa de bytes e +escreve os dados lidos no endereço de memória especificado. Interativamente, +a leitura só é iniciada a partir do momento que o terminal envia os dados +digitados para o processo -- geralmente, quando o usuário tecla =Enter=. + +*** Uso da chamada de sistema 'read' + +Na arquitetura x86_64, a chamada de sistema =read= é utilizada da seguinte +forma para ler a entrada padrão: + +#+begin_src asm +mov rax, 0 ; número da chamada de sistema read +mov rdi, FD ; Descritor de arquivo (0 = entrada padrão) +mov rsi, BUF ; O endereço do buffer em que os bytes serão escritos +mov rdx, COUNT ; A quantidade de bytes a ler +syscall ; executa a chamada de sistema +#+end_src + +O retorno, passado por =rax=, é a quantidade de bytes lidos ou =-1= em caso +de erro. + +*** Exemplo de uso em Assembly + +O exemplo abaixo (=salve-read.asm=) demonstra como podemos receber dados +pela entrada padrão utilizando a chamada de sistema =read=. + +Arquivo: [[exemplos/08/salve-read.asm][salve-read.asm]] + +#+begin_src asm +; ---------------------------------------------------------- +; Arquivo : salve-read.asm +; Montagem: nasm -f elf64 salve-read.asm +; Ligação : ld -o salve-read salve-read.o +; ---------------------------------------------------------- +; Chamadas de sistema +; ---------------------------------------------------------- +%define SYS_READ 0 +%define SYS_WRITE 1 +%define SYS_EXIT 60 +; ---------------------------------------------------------- +; Descritores de arquivos padrão +; ---------------------------------------------------------- +%define STDIN_FD 0 +%define STDOUT_FD 1 +; ---------------------------------------------------------- +; Estados de término +; ---------------------------------------------------------- +%define EXIT_SUCCESS 0 +; ---------------------------------------------------------- +; Constantes simbólicas +; ---------------------------------------------------------- +%define BUF_SIZE 256 +; ---------------------------------------------------------- +section .rodata +; ---------------------------------------------------------- + msg db `Salve, simpatia!\nQual é a sua graça?\n` + msg_len equ $ - msg + + resp db "Falaê, " + resp_len equ $ - resp + + tail db `!\n` +; ---------------------------------------------------------- +section .bss +; ---------------------------------------------------------- + buf resb BUF_SIZE ; Buffer de leitura + count resd 1 ; retorno de read (int) +; ---------------------------------------------------------- +section .text +; ---------------------------------------------------------- +global _start +_start: + ; Imprime a mensagem inicial + mov rsi, msg + mov rdx, msg_len + call _print + + ; Aguarda os dados da entrada padrão + mov rax, SYS_READ + mov rdi, STDIN_FD + mov rsi, buf + mov rdx, BUF_SIZE + syscall + + ; Salva retorno da chamada (bytes lidos) + mov [count], eax + + ; Imprime prefixo da resposta + mov rsi, resp + mov rdx, resp_len + call _print + + ; Imprime resposta + mov rsi, buf + mov rdx, [count] ; total de bytes lidos + dec rdx ; desconta \n + call _print + + ; Imprime final da resposta + mov rsi, tail + mov rdx, 2 ; !\n = 2 bytes + call _print + + ; Termina o programa + mov rax, SYS_EXIT + mov rdi, EXIT_SUCCESS + syscall +; ---------------------------------------------------------- +; Sub-rotinas... +; ---------------------------------------------------------- +_print: +; ---------------------------------------------------------- + mov rax, SYS_WRITE + mov rdi, STDOUT_FD + syscall + ret +#+end_src + +*Montagem e execução:* + +#+begin_example +:~$ nasm -f elf64 salve-read.asm +:~$ ld -o salve-read salve-read.o +:~$ ./salve-read +Salve, simpatia! +Qual é a sua graça? +[Espera a digitação] +#+end_example + +Digitando, por exemplo, meu nome: + +#+begin_example +Blau +Falaê, Blau! +#+end_example + +*** Testes com pipe e redirecionamento + +O nosso exemplo também pode ler a entrada padrão se ela estiver ligada +a um arquivo, por redirecionamento, ou à ponta de leitura de um pipe, como +veremos nos próximos testes. + +*Leitura por pipe:* + +#+begin_example +:~$ echo Fulano de Tal | ./salve-read +Salve, simpatia! +Qual é a sua graça? +Falaê, Fulano de Tal! +#+end_example + +*Leitura de um arquivo:* + +#+begin_example +:~$ ./salve-read < /proc/sys/kernel/ostype +Salve, simpatia! +Qual é a sua graça? +Falaê, Linux! +#+end_example + +*Leitura em uma /here string/ (Bash):* + +#+begin_example +:~$ ./salve-read <<< 'Teste 123' +Salve, simpatia! +Qual é a sua graça? +Falaê, Teste 123! +#+end_example + +*Leitura de várias linhas (/here doc/):* + +#+begin_example +:~$ ./salve-read << FIM +> Linha 1 +> Linha 2 +> Linha 3 +> FIM +Salve, simpatia! +Qual é a sua graça? +Falaê, Linha 1 +Linha 2 +Linha 3! +#+end_example + +** Identificando a origem do fluxo de leitura + +Para determinar se a entrada padrão está ligada ou não a um terminal, nós +podemos utilizar a chamada de sistema =ioctl= (identificador =16=), que manipula +diversos parâmetros internos de dispositivos especiais, como os terminais. + +*Argumentos da chamada de sistema =ioctl=:* + +#+begin_src asm +mov rax, 16 ; número da chamada de sistema ioctl +mov rdi, FD ; descritor de arquivo (0 = stdin) +mov rsi, OP ; código da operação (TCGETS = 0x5401) +mov rdx, BUF ; NULL ou buffer para receber os parâmetros solicitados +syscall +#+end_src + +O código de operação que nos interessa é =TCGETS= (=0x5401=), que é a solicitação +das configurações do terminal associado ao descritor de arquivo em =rdi=. Se +não houver a ligação com um terminal, a chamada termina com erro =ENOTTY= +(/não é um TTY/) e retorna o inteiro =-1= em =rax=. Como só queremos testar o +retorno da chamada =ioctl=, nós não precisamos de um buffer e podemos passar +o valor =0= (=NULL=) para =rdx=. + +*Exemplo genérico de teste:* + +#+begin_src asm + mov rax, 16 ; chamada ioctl + mov rdi, 0 ; stdin + mov rsi, 0x5401 ; TCGETS + xor rdx, rdx ; 0 = NULL + syscall + + cmp rax, 0 + jl .noy_a_tty ; rax < 0 -> não é um terminal + +.is_a_tty: + ; rotina se stdin for um terminal... + ; ... + jmp .end: + +.not_a_tty: + ; rotina se stdin não for um terminal... + ; ... +.end: + ; Rotinha de término... +#+end_src + +* Criação e manipulação de fluxos de dados + +Criar um novo fluxo de dados tem o significado de estabelecer uma nova +conexão entre um processo e um arquivo. Em alto nível, com C/C++ por exemplo, +isso é feito através das abstrações da =glibc=, o que pode resultar na ligação +com arquivos via /streams/ (por exemplo, com =fopen=) ou via descritores de +arquivos (por exemplo, com =pipe=). + +Exemplos: + +| Função | Descrição | +|-----------------+--------------------------------------------------| +| =fopen= | Abre um arquivo comum | +| =fclose= | Fecha um /stream/ | +| =freopen= | Redireciona um =FILE *= existente | +| =fdopen= | Associa um descritor de arquivo a um =FILE *= | +| =popen= | Executa um comando e abre um pipe unidirecional | +| =fmemopen= | Cria um =FILE *= associado a uma região da memória | +| =open_memstream= | Cria um =FILE *= de escrita que escreve em memória | +| =open_wmemstream= | Versão wide-char de =open_memstream= | + +Já em baixo nível, como vimos nos nossos exemplos, a criação de novos fluxos +de dados é feita pelas chamadas de sistema do kernel e sempre resulta na +ligação com arquivos via descritores de arquivos. + +Exemplos (Linux x86_64): + +| Nº | Syscall | Descrição | +|-----+------------+----------------------------------------------------| +| 2 | =open= | Abre um arquivo e retorna um =file descriptor= | +| 3 | =close= | Fecha um descritor de arquivo | +| 85 | =creat= | Cria/abre arquivo para escrita | +| 41 | =dup= | Duplica um descritor de arquivo | +| 33 | =dup2= | Duplica descritor para um valor específico | +| 292 | =dup3= | Como =dup2=, mas com flags | +| 22 | =pipe= | Cria um pipe anônimo (=int pipefd[2]=) | +| 293 | =pipe2= | Versão de =pipe= com flags adicionais | +| 41 | =socket= | Cria endpoint de comunicação (AF_INET, AF_UNIX...) | +| 53 | =socketpair= | Cria par de sockets conectados | + +* Armazenamento temporário (/bufferização/) + +Bufferização (*buffering*) é a técnica de usar áreas intermediárias de memória +chamadas *buffers* para armazenar temporariamente dados durante operações de +entrada e saída, principalmente com o propósito de: + +- Reduzir o número de chamadas de sistema. +- Adaptar diferenças de velocidade entre a produção e o consumo de dados. +- Agrupar dados em blocos maiores, aumentando a eficiência da comunicação + com dispositivos ou arquivos. + +** Bufferização em alto nível + +No GNU/Linux, a =glibc= implementa três modos de bufferização: + +| Tipo | Descrição | +|------------------------+----------------------------------------------------------------| +| *Sem bufferização* | Dados são enviados diretamente, sem armazenar em buffer. | +| *Bufferização por linha* | Dados são enviados quando uma nova linha é detectada (=\n=). | +| *Bufferização total* | Dados são armazenados até o buffer ficar cheio ou ser liberado | + +Os três fluxos padrão estão associados a buffers internos mantidos pela =glibc=: + +- *stdin*: bufferizado totalmente. +- *stdout*: bufferizado por linha se conectado a um terminal; caso contrário, totalmente. +- *stderr*: sem bufferização. + +Esses buffers fazem parte da estrutura =FILE=, da biblioteca C padrão, que +aloca uma região de memória para guardar dados temporariamente. + +** Bufferização no nível do kernel + +Além da =glibc=, o kernel Linux também implementa buffers internos em diversos +contextos: + +| Tipo de buffer | Onde ocorre | Finalidade principal | +|---------------------+--------------------------+-----------------------------------------------| +| Cachê de página | Sistema de arquivos | Acelerar leitura/escrita em disco | +| Buffer de pipe | IPC (pipe) | Comunicação entre processos | +| Buffer de socket | Rede (TCP/UDP) | Armazenar pacotes antes/depois da transmissão | +| Disciplina de linha | Dispositivos de terminal | Controlar entrada interativa | +| Buffer de blocos | Dispositivos de bloco | Otimização de leitura/escrita de disco | + +*** Cachê de página + +O subsistema de arquivos do kernel utiliza cachês de página (/page cache/) +para manter dados recentemente lidos ou escritos na memória RAM. Isso evita +acessos frequentes ao disco, que são muito mais lentos. Quando um processo +executa =read=, o kernel verifica se os dados já estão no cache; quando um +processo executa =write=, os dados vão para o cache e são escritos no disco +posteriormente (/write-back/). + +#+begin_quote +Esse mecanismo é transparente para o usuário e só afeta o desempenho. +#+end_quote + +*** Buffers de pipes (IPC) + +O kernel mantém buffers internos para comunicação entre processos (IPC) +via pipes, que possuem um buffer circular (/ring buffer/) para armazenar +dados de forma contínua em um espaço fixo de memória (tipicamente, 64kB +em sistemas modernos). Ao escrever num pipe, os dados vão para esse +buffer e são consumidos quando acontece a leitura. Se o buffer estiver +cheio, a escrita é bloqueada; se estiver vazio, a leitura é bloqueada. + +#+begin_quote +Esta condição padrão é chamada de /modo bloqueante/, mas também é possível +abrir arquivos em /modo não bloqueante/ se estivermos lidando com eventos +simultâneos, escrevendo programas que não podem parar esperando operações +de entrada e saída ou se precisarmos implementar um controle fino sobre +o fluxo de dados. +#+end_quote + +*** Buffers de sockets + +Comunicações via /sockets/ também contam com buffers internos no kernel. Cada +socket possui um buffer de envio (/send buffer/) e um recepção (/receive buffer/) +para armazenar dados enquanto o pacote é processado, retransmitido ou +recebido pela outra extremidade. + +#+begin_quote +O tamanho dos buffers pode ser consultado com a chamada de sistema =getsockopt= +ou definido com a chamada =setsockopt=. +#+end_quote + +*** Buffers de terminais (TTY) + +Os dispositivos de terminal possuem buffers no kernel que são manipulados +pelo driver de TTY. No /modo canônico/ (também chamado de /modo linha/), os +dados são acumulados até que o usuário tecle =Enter=. Além disso, o terminal +é responsável pela edição a linha antes que ela seja entregue ao processo. + +#+begin_quote +Esse buffer faz parte da /disciplina de linha/ do kernel, um componente que +fica entre o driver do terminal e o processo do usuário para interpretar +e gerenciar entradas e saídas de texto. +#+end_quote + +As principais características do modo canônico, que é o modo padrão de +terminais interativos, são: + +- Entrada bufferizada por linha. +- Possibilidade de edição das linhas antes do envio. +- Interpretação de caracteres de controle. + +No modo não canônico (modo /bruto/ ou /"caractere a caractere"/), a entrada +não é bufferizada e, portanto, cada caractere digitado é disponibilizado +imediatamente para o programa. Isso é muito utilizado por programas com +interfaces em tela cheia (como o editor Vim, por exemplo), multiplexadores +de terminal (como o Tmux e o GNU Screen) e pseudo terminais remotos +(como no SSH). + +*** Buffers de blocos (bloco de disco) + +Dispositivos de bloco (como discos) usam buffers no kernel para: + +- Agrupar operações de escrita. +- Reordenar acessos. +- Sincronizar acesso com caches do sistema de arquivos. + +** Bufferização em Assembly (x86_64) + +Em Assembly, os buffers são criados manualmente com a reserva de espaço +na seção =.bss= (=resb=, =resq=, etc), utilizando a pilha ou alocando espaços +dinamicamente, por exemplo, com as chamadas de sistema =brk= e =mmap=. + +No último exemplo (=salve-read.asm=), foi o que fizemos quando reservamos +256 bytes (valor de =BUF_SIZE=) em =.bss= para armazenar os caracteres lidos +pela chamada =read=: + +#+begin_src asm +; ---------------------------------------------------------- +section .bss +; ---------------------------------------------------------- + buf resb BUF_SIZE ; Buffer de leitura + count resd 1 ; retorno de read (int) +#+end_src + +Essa mesma técnica é aplicável a programas em C, geralmente utilizando +vetores de caracteres ou alocando espaço no /heap/ com funções como =malloc=, +da biblioteca padrão. + +* Exercícios propostos + +1. Altere o tamanho do buffer do programa =salve-read.asm= para =10= (bytes), + observe seu novo funcionamento e responda: + - O que acontece quando digitamos mais de 10 caracteres? + - Como você explica o que aconteceu? +2. Ainda usando =salve-read.asm= como base, faça com que o programa só funcione + no modo interativo, imprimindo uma mensagem por =stderr= e terminando com + estado =1= caso esteja num pipe ou com a entrada padrão redirecionada para + um arquivo. +3. Pesquise e escreva um programa em C que reproduza o funcionamento do exemplo + =salve-pipe.asm=. +4. Pesquise e escreva programas em C que reproduzam o funcionamento do exemplo + =salve-read.asm= e de todas as alterações feitas nos exercícios 1 e 2. + +* Tabela das chamadas de sistema utilizadas + +| Chamada | #ID | Propósito | rax | rdi | rsi | rdx | +|---------+-----+-------------------------------------+-----+----------------------+----------------+-------------| +| =read= | 0 | Ler um descritor de arquivo | 0 | fd (ex: STDIN=0) | buffer | quantidade | +| =write= | 1 | Escrever em um descritor de arquivo | 1 | fd (ex: STDOUT=1) | buffer | tamanho | +| =open= | 2 | Abre um arquivo | 2 | nome do arquivo | flags | modo | +| =close= | 3 | Fechar descritor de arquivo | 3 | fd | - | - | +| =ioctl= | 16 | Obter parâmetros de dispositivos | 16 | fd | operação | buffer | +| =pipe= | 22 | Criar pipe (par de descritores) | 22 | ponteiro pipefd[2] | - | - | +| =dup2= | 33 | Duplicar descritor de arquivo | 33 | oldfd | newfd | - | +| =fork= | 57 | Criar novo processo | 57 | - | - | - | +| =execve= | 59 | Executar novo programa | 59 | caminho ("/bin/cat") | argv | envp | +| =exit= | 60 | Encerrar o processo | 60 | código de saída | - | - | +| =wait4= | 61 | Esperar término de processo filho | 61 | pid (-1 = qualquer) | statloc = NULL | options = 0 | + +Retorno em =rax=. + +* Referências + +- =man 2 read= +- =man 2 write= +- =man 2 open= +- =man 2 close= +- =man 2 ioctl= +- =man 2 pipe= +- =man 2 dup2= +- =man 2 fork= +- =man 2 execve= +- =man 2 wait4= +- =man 2 waitpid= +- =man 3 stdin= +- =man 5 proc_pid_fd= +- [[https://en.wikipedia.org/wiki/Pipeline_(Unix)][Wikipedia: Pipeline (Unix)]]