Named pipes (FIFOs)
LidoNesta aula, você aprenderá sobre named pipes (FIFOs) no Linux/Bash: como criar com mkfifo, como usá-los para comunicação entre processos, casos de uso práticos e exemplos completos. O conteúdo inclui explicações detalhadas, exemplos de código e exercícios com respostas.
Named pipes, também conhecidos como FIFOs (First In, First Out), são um mecanismo de comunicação entre processos (IPC) no Linux. Diferente dos pipes anônimos (usados com o operador |), que existem apenas enquanto os processos estão em execução, um named pipe é um arquivo especial no sistema de arquivos que persiste até ser removido. Isso permite que processos independentes se comuniquem, mesmo que não tenham sido iniciados em uma cadeia direta de comandos. Nesta aula, vamos explorar como criar, usar e aplicar FIFOs em scripts shell, desde o básico até casos de uso mais avançados.
FIFOs são um dos pilares da programação de sistemas Unix, oferecendo uma forma simples e eficiente de trocar dados entre processos sem a necessidade de sockets ou memória compartilhada. Para o programador de shell, dominar named pipes amplia significativamente a capacidade de orquestrar tarefas concorrentes, criar pipelines flexíveis e implementar padrões de comunicação como cliente-servidor. Vamos mergulhar nos detalhes práticos, com exemplos executáveis e explicações claras.
mkfifo
O comando mkfifo é a ferramenta padrão para criar um named pipe no Linux. Sua sintaxe básica é mkfifo [OPÇÕES] NOME..., onde NOME é o caminho do arquivo FIFO a ser criado. Após a criação, o arquivo aparece no diretório como um arquivo especial, identificado pelo tipo p no início da listagem do ls -l. Diferente de arquivos regulares, um FIFO não armazena dados no disco; ele atua como um buffer no kernel, permitindo que dados sejam escritos por um processo e lidos por outro, na ordem em que foram escritos.
O comando mkfifo aceita opções como -m para definir permissões (modo) e -p (embora não seja padrão em todas as versões) para criar diretórios pais. Na prática, usamos mkfifo -m 600 meu_pipe para criar um FIFO com permissões de leitura e escrita apenas para o dono. Também é possível usar o utilitário mknod para criar FIFOs, mas mkfifo é mais simples e direto. Ao criar um FIFO, ele fica disponível até ser removido com rm, o que permite que processos diferentes o utilizem em momentos distintos.
Exemplo de criação e verificação:
# Criar um named pipe chamado 'meu_pipe'
mkfifo meu_pipe
# Verificar o tipo de arquivo (a primeira letra deve ser 'p')
ls -l meu_pipe
# Saída: prw-r--r-- 1 user user 0 Jan 1 00:00 meu_pipe
# Remover o FIFO quando não for mais necessário
rm meu_pipe
No exemplo acima, o ls -l mostra prw-r--r--: o p indica que é um named pipe. O tamanho é 0, pois os dados não são armazenados no arquivo, mas sim no buffer do kernel.
Comunicação entre processos
A principal finalidade de um FIFO é permitir que dois ou mais processos troquem dados. Quando um processo escreve em um FIFO, os dados são enviados ao kernel, que os mantém em um buffer até que outro processo os leia. Se não houver leitor, o processo de escrita pode bloquear (dependendo do modo de abertura) até que um leitor se conecte. Da mesma forma, se um processo tenta ler um FIFO vazio, ele pode bloquear até que dados estejam disponíveis. Esse comportamento é fundamental para sincronização.
Para usar um FIFO em um script, basta redirecionar a saída ou entrada para o arquivo FIFO. Por exemplo, echo "mensagem" > meu_pipe escreve no FIFO, e cat < meu_pipe lê dele. No entanto, é importante entender que a abertura de um FIFO para leitura ou escrita pode bloquear até que o lado oposto esteja pronto. Por padrão, abrir um FIFO para leitura bloqueia até que haja um escritor, e abrir para escrita bloqueia até que haja um leitor. Isso pode ser contornado com exec e redirecionamentos em modo não bloqueante, mas em scripts simples, muitas vezes usamos processos em segundo plano ou redirecionamentos para evitar deadlocks.
Exemplo clássico de comunicação entre dois processos usando um FIFO:
# Terminal 1: leitor
cat < meu_pipe
# Terminal 2: escritor
echo "Olá do escritor!" > meu_pipe
No terminal 1, o cat fica bloqueado até que algo seja escrito. No terminal 2, o echo escreve e termina. O leitor então imprime a mensagem e também termina, pois o FIFO é fechado. Esse é o princípio básico: um processo produz dados, outro consome.
Em scripts, podemos usar FIFOs para comunicação entre um processo em segundo plano e o script principal. Por exemplo, um processo pode escrever logs em um FIFO enquanto o script principal os lê e processa. Isso permite desacoplar a produção do consumo de dados.
Casos de uso
Named pipes são versáteis e aparecem em vários cenários de programação de sistemas. Alguns casos de uso comuns incluem:
- Comunicação entre processos independentes: Dois scripts ou programas podem trocar dados sem estarem diretamente conectados via pipe anônimo, pois o FIFO existe no sistema de arquivos.
- Sincronização de tarefas: FIFOs podem ser usados como mecanismo de bloqueio ou sinalização. Por exemplo, um processo pode esperar por dados em um FIFO para saber que outro processo concluiu uma etapa.
- Implementação de padrões cliente-servidor: Um servidor pode ler comandos de um FIFO, e clientes escrevem nesse FIFO para enviar comandos. Isso é uma forma simples de IPC para aplicações de pequena escala.
- Redirecionamento de logs: Processos podem gravar mensagens de log em um FIFO, e um coletor central lê e processa essas mensagens, permitindo agregação em tempo real.
- Simulação de pipes bidirecionais: Diferente de um pipe anônimo, que é unidirecional, você pode usar dois FIFOs para criar comunicação bidirecional entre dois processos.
- Integração com ferramentas externas: Algumas ferramentas esperam um arquivo para ler ou escrever; um FIFO pode ser usado como esse arquivo, permitindo que a ferramenta se comunique com outros processos sem criar arquivos temporários.
Esses casos de uso mostram que FIFOs são uma ferramenta essencial para orquestrar processos em shell e em linguagens de programação que acessam o sistema de arquivos.
Exemplos
Vamos explorar exemplos práticos de uso de FIFOs em scripts bash. Primeiro, um exemplo simples de comunicação entre dois scripts: um escritor e um leitor.
Exemplo 1: Escritor e leitor
# Cria o FIFO
mkfifo /tmp/meu_fifo
# Escritor (script escritor.sh)
#!/bin/bash
# Escreve várias linhas no FIFO
for i in {1..5}; do
echo "Mensagem $i" > /tmp/meu_fifo
done
# Leitor (script leitor.sh)
#!/bin/bash
# Lê do FIFO e imprime
while read linha; do
echo "Recebido: $linha"
done < /tmp/meu_fifo
Se executarmos o escritor em um terminal e o leitor em outro, o leitor imprimirá as mensagens. Note que o escritor pode bloquear se o leitor não estiver pronto, pois a abertura para escrita bloqueia até que um leitor abra o FIFO. Para evitar isso, podemos abrir o FIFO em modo não bloqueante ou usar redirecionamento com exec.
Exemplo 2: Comunicação bidirecional com dois FIFOs
# Cria dois FIFOs
mkfifo /tmp/fifo1 /tmp/fifo2
# Processo A (script a.sh)
#!/bin/bash
# Lê do fifo1, responde no fifo2
while read msg; do
echo "Processo A recebeu: $msg"
echo "Resposta de A" > /tmp/fifo2
done < /tmp/fifo1
# Processo B (script b.sh)
#!/bin/bash
# Envia mensagem para A e lê resposta
echo "Olá de B" > /tmp/fifo1
read resposta < /tmp/fifo2
echo "B recebeu: $resposta"
Esse exemplo mostra como dois FIFOs podem ser usados para comunicação bidirecional. No entanto, é preciso cuidado com deadlocks: se ambos os processos tentarem escrever antes de ler, podem bloquear. Uma prática comum é usar redirecionamentos com exec para abrir os FIFOs como descritores de arquivo, permitindo leitura e escrita sem bloqueios desnecessários.
Exemplo 3: Usando FIFO como mecanismo de sincronização
# Script que espera por um sinal em um FIFO
mkfifo /tmp/sinal
echo "Aguardando sinal..."
cat < /tmp/sinal
echo "Sinal recebido!"
# Em outro terminal, envie o sinal:
echo "go" > /tmp/sinal
Esse padrão é útil para coordenar processos: um processo espera até que outro escreva algo no FIFO, funcionando como uma barreira.
Exemplo 4: Redirecionamento de saída para um FIFO
# Cria um FIFO para logs
mkfifo /tmp/log_fifo
# Processo que gera logs (em background)
while true; do
echo "Log $(date)" > /tmp/log_fifo
sleep 1
done &
# Leitor de logs (em foreground)
while read linha; do
echo "Processando: $linha"
done < /tmp/log_fifo
Esse exemplo demonstra como um produtor pode gravar logs continuamente em um FIFO, enquanto um consumidor os processa. Isso é útil para sistemas de logging em tempo real.
Esses exemplos ilustram a flexibilidade dos FIFOs. No entanto, em scripts mais complexos, é importante gerenciar os descritores de arquivo com exec para evitar bloqueios e garantir que os FIFOs sejam fechados corretamente.
Boas práticas
Ao trabalhar com FIFOs em shell, algumas boas práticas podem evitar problemas comuns:
- Sempre remover FIFOs temporários após o uso, usando
rmoutrappara limpeza em caso de interrupção. - Usar
execpara abrir FIFOs como descritores de arquivo, permitindo leitura e escrita sem bloquear o shell. - Evitar deadlocks: se dois processos precisam se comunicar, defina uma ordem clara de leitura e escrita, ou use temporizadores.
- Verificar se o FIFO existe antes de usá-lo, especialmente em scripts que podem ser executados várias vezes.
- Considere o uso de
trappara capturar sinais e limpar FIFOs, evitando que arquivos fiquem para trás.
Essas práticas ajudam a criar scripts robustos e confiáveis.
Referências
- mkfifo(1) - Linux manual page
- fifo(7) - Linux manual page
- GNU Coreutils: mkfifo invocation
- Advanced Bash-Scripting Guide: Named Pipes (FIFOs)
- Baeldung: Named Pipes in Linux
- Stack Overflow: Why do we need named pipes?
Exercícios
- Exercício 1: Crie um named pipe chamado
/tmp/meu_pipee verifique suas permissões. Em seguida, remova-o. Escreva os comandos usados.✓ Resposta:mkfifo /tmp/meu_pipe ls -l /tmp/meu_pipe rm /tmp/meu_pipe - Exercício 2: Escreva um script que crie um FIFO, escreva a mensagem "Olá, FIFO!" nele e leia a mensagem de volta, exibindo-a. (Dica: use redirecionamentos e lembre-se de que a abertura para escrita bloqueia até que haja um leitor; execute as partes em segundo plano ou use
exec.)✓ Resposta: Uma solução simples é usar um subshell em segundo plano para escrever e o script principal para ler:#!/bin/bash mkfifo /tmp/meu_fifo # Escritor em segundo plano (echo "Olá, FIFO!" > /tmp/meu_fifo) & # Leitor mensagem=$(cat < /tmp/meu_fifo) echo "Mensagem lida: $mensagem" rm /tmp/meu_fifo - Exercício 3: Explique o que acontece quando um processo tenta escrever em um FIFO que não possui nenhum leitor ativo. Como evitar que o processo fique bloqueado indefinidamente?✓ Resposta: Por padrão, abrir um FIFO para escrita bloqueia até que haja pelo menos um leitor. Se não houver leitor, o processo fica bloqueado. Para evitar isso, podemos abrir o FIFO em modo não bloqueante usando
execcom a opçãoO_NONBLOCK(não trivial em shell puro), ou usar um timeout comtimeoutno comando de escrita, ou garantir que um leitor seja iniciado em segundo plano antes de escrever. - Exercício 4: Crie um sistema simples de log usando um FIFO: um processo produtor escreve 10 mensagens de log em um FIFO, e um processo consumidor lê e imprime cada uma. Use processos em segundo plano ou terminais separados.✓ Resposta: Exemplo de produtor e consumidor:
Execute o consumidor em um terminal e o produtor em outro, ou use# Produtor (producer.sh) #!/bin/bash mkfifo /tmp/log_fifo for i in {1..10}; do echo "Log $i - $(date)" > /tmp/log_fifo done # Consumidor (consumer.sh) #!/bin/bash while read linha; do echo "Processando: $linha" done < /tmp/log_fifo./consumer.sh &antes de executar o produtor. - Exercício 5: Implemente uma comunicação bidirecional entre dois scripts usando dois FIFOs. O primeiro script deve enviar uma mensagem "ping" e o segundo deve responder "pong". Exiba as mensagens em cada lado.✓ Resposta: Exemplo:
Execute# Cria FIFOs mkfifo /tmp/fifo_a /tmp/fifo_b # Script A (a.sh) #!/bin/bash echo "ping" > /tmp/fifo_a read resposta < /tmp/fifo_b echo "A recebeu: $resposta" # Script B (b.sh) #!/bin/bash read msg < /tmp/fifo_a echo "B recebeu: $msg" echo "pong" > /tmp/fifo_b./b.shem um terminal e./a.shem outro. Cuidado com a ordem: B deve estar esperando a leitura antes que A escreva, caso contrário A pode bloquear. Uma abordagem é iniciar B em segundo plano.