Estou tentando entender o conceito de arquivos especiais no Linux. No entanto, ter um arquivo especial /dev
parece bobo quando sua função pode ser implementada por um punhado de linhas em C, pelo que sei.
Além disso, você pode usá-lo praticamente da mesma maneira, ou seja, canalizando em null
vez de redirecionar para /dev/null
. Existe uma razão específica para tê-lo como um arquivo? Torná-lo um arquivo não causa muitos outros problemas, como muitos programas acessando o mesmo arquivo?
Além dos benefícios de desempenho do uso de um dispositivo especial de personagem, o principal benefício é a modularidade . /dev/null pode ser usado em quase qualquer contexto em que um arquivo é esperado, não apenas em pipelines de shell. Considere programas que aceitam arquivos como parâmetros de linha de comando.
Esses são todos os casos em que usar um programa como fonte ou coletor seria extremamente complicado. Mesmo no caso do shell pipeline, stdout e stderr podem ser redirecionados para arquivos de forma independente, algo que é difícil de fazer com executáveis como coletores:
Para ser justo, não é um arquivo regular em si; é um dispositivo especial de personagem :
Funcionar como um dispositivo e não como um arquivo ou programa significa que é uma operação mais simples redirecionar a entrada ou a saída dele, pois pode ser anexado a qualquer descritor de arquivo, incluindo entrada/saída/erro padrão.
Suspeito que o porquê tenha muito a ver com a visão/design que moldou o Unix (e consequentemente o Linux) e as vantagens decorrentes dele.
Sem dúvida, há um benefício de desempenho não desprezível em não ativar um processo extra, mas acho que há mais: o Unix antigo tinha uma metáfora "tudo é um arquivo", que tem uma vantagem não óbvia, mas elegante se você olhar para de uma perspectiva de sistema, em vez de uma perspectiva de script de shell.
Digamos que você tenha seu
null
programa de linha de comando e/dev/null
o nó do dispositivo. Do ponto de vista do shell script, ofoo | null
programa é realmente genuinamente útil e conveniente , efoo >/dev/null
leva um pouco mais de tempo para digitar e pode parecer estranho.Mas aqui estão dois exercícios:
Vamos implementar o programa
null
usando as ferramentas existentes do Unix e/dev/null
- fácil:cat >/dev/null
. Feito.Você pode implementar
/dev/null
em termos denull
?Você está absolutamente certo de que o código C para apenas descartar a entrada é trivial, então pode não ser óbvio por que é útil ter um arquivo virtual disponível para a tarefa.
Considere: quase toda linguagem de programação já precisa trabalhar com arquivos, descritores de arquivo e caminhos de arquivo, porque eles faziam parte do paradigma "tudo é um arquivo" do Unix desde o início.
Se tudo o que você tem são programas que gravam em stdout, bem, o programa não se importa se você os redireciona para um arquivo virtual que engole todas as gravações ou um canal para um programa que engole todas as gravações.
Agora, se você tiver programas que usam caminhos de arquivo para leitura ou gravação de dados (o que a maioria dos programas faz) - e deseja adicionar a funcionalidade "entrada em branco" ou "descartar esta saída" a esses programas - bem, com
/dev/null
isso vem de graça.Observe que a elegância disso reduz a complexidade do código de todos os programas envolvidos - para cada caso de uso comum, mas especial, que seu sistema pode fornecer como um "arquivo" com um "nome de arquivo" real, seu código pode evitar a adição de comando personalizado -line opções e caminhos de código personalizados para manipular.
A boa engenharia de software geralmente depende de encontrar metáforas boas ou "naturais" para abstrair algum elemento de um problema de uma maneira que se torne mais fácil de pensar , mas permaneça flexível , de modo que você possa resolver basicamente a mesma gama de problemas de nível superior sem ter que gaste tempo e energia mental reimplementando constantemente soluções para os mesmos problemas de nível inferior.
"Tudo é um arquivo" parece ser uma dessas metáforas para acessar recursos: você chama
open
um determinado caminho em um namespace hierárquico, obtendo uma referência (descritor de arquivo) para o objeto, e você poderead
ewrite
, etc nos descritores de arquivo. Seu stdin/stdout/stderr também são descritores de arquivo que foram pré-abertos para você. Seus pipes são apenas arquivos e descritores de arquivo, e o redirecionamento de arquivo permite que você junte todas essas peças.O Unix teve tanto sucesso quanto em parte por causa de quão bem essas abstrações funcionaram juntas e
/dev/null
é melhor compreendida como parte desse todo.PS Vale a pena olhar para a versão Unix de "tudo é um arquivo" e coisas como
/dev/null
os primeiros passos para uma generalização mais flexível e poderosa da metáfora que foi implementada em muitos sistemas que se seguiram.Por exemplo, no Unix, objetos especiais semelhantes a arquivos como
/dev/null
tiveram que ser implementados no próprio kernel, mas é útil o suficiente para expor a funcionalidade no formato de arquivo/pasta que, desde então, vários sistemas foram criados para fornecer uma maneira para os programas fazer isso.Um dos primeiros foi o sistema operacional Plan 9, feito por algumas das mesmas pessoas que criaram o Unix. Mais tarde, o GNU Hurd fez algo semelhante com seus "tradutores". Enquanto isso, o Linux acabou recebendo o FUSE (que se espalhou para os outros sistemas convencionais também).
Acho que
/dev/null
é um dispositivo de caractere (que se comporta como um arquivo comum) em vez de um programa por motivos de desempenho .Se fosse um programa, seria necessário carregar, iniciar, agendar, executar e depois parar e descarregar o programa. É claro que o programa C simples que você está descrevendo não consumiria muitos recursos, mas acho que faz uma diferença significativa ao considerar um grande número (digamos milhões) de ações de redirecionamento / canalização, pois as operações de gerenciamento de processos são caras em grande escala, pois eles envolvem trocas de contexto.
Outra suposição: canalizar para um programa requer que a memória seja alocada pelo programa receptor (mesmo que seja descartada logo em seguida). Portanto, se você canalizar a ferramenta, terá o dobro do consumo de memória, uma vez no programa de envio e novamente no programa de recebimento.
Além de "tudo é um arquivo" e, portanto, facilidade de uso em todos os lugares em que a maioria das outras respostas se baseia, também há problemas de desempenho, como @ user5626466 menciona.
Para mostrar na prática, vamos criar um programa simples chamado
nullread.c
:e compilá-lo com
gcc -O2 -Wall -W nullread.c -o nullread
(Nota: não podemos usar lseek(2) em pipes, então a única maneira de drenar o pipe é ler dele até que esteja vazio).
considerando que com o redirecionamento de arquivo padrão
/dev/null
obtemos velocidades muito melhores (devido aos fatos mencionados: menos troca de contexto, kernel apenas ignorando os dados em vez de copiá-los, etc.):(isso deveria ser um comentário lá, mas é muito grande para isso e seria completamente ilegível)
Sua pergunta é feita como se algo fosse ganho talvez em simplicidade usando um programa nulo em vez de um arquivo. Talvez possamos nos livrar da noção de "arquivos mágicos" e, em vez disso, ter apenas "tubos comuns".
Mas considere, um pipe também é um arquivo . Eles normalmente não são nomeados e, portanto, só podem ser manipulados por meio de seus descritores de arquivo.
Considere este exemplo um tanto artificial:
Usando a substituição de processo do Bash, podemos realizar a mesma coisa de uma maneira mais indireta:
Substitua o
grep
forecho
e podemos ver embaixo das cobertas:A
<(...)
construção é apenas substituída por um nome de arquivo e o grep pensa que está abrindo qualquer arquivo antigo, apenas por acaso é nomeado/dev/fd/63
. Aqui/dev/fd
está um diretório mágico que cria canais nomeados para cada descritor de arquivo possuído pelo arquivo que o acessa.Poderíamos torná-lo menos mágico
mkfifo
para fazer um pipe nomeado que aparecels
e tudo, como um arquivo comum:Em outro lugar:
e eis que o grep produzirá
foo
.Eu acho que uma vez que você percebe que pipes e arquivos regulares e arquivos especiais como /dev/null são apenas arquivos, é aparente que a implementação de um programa nulo é mais complexa. O kernel tem que lidar com gravações em um arquivo de qualquer maneira, mas no caso de /dev/null ele pode simplesmente descartar as gravações no chão, enquanto com um pipe ele tem que transferir os bytes para outro programa, que então tem que realmente lê-los.
Eu diria que há uma questão de segurança além dos paradigmas históricos e do desempenho. Limitar o número de programas com credenciais de execução privilegiadas, não importa o quão simples seja, é um princípio fundamental da segurança do sistema.
/dev/null
Certamente seria necessário um substituto para ter tais privilégios devido ao uso pelos serviços do sistema. Estruturas de segurança modernas fazem um excelente trabalho na prevenção de exploits, mas não são infalíveis. Um dispositivo controlado pelo kernel acessado como um arquivo é muito mais difícil de explorar.Como outros já apontaram,
/dev/null
é um programa feito de um punhado de linhas de código. É que essas linhas de código fazem parte do kernel.Para deixar mais claro, aqui está a implementação do Linux: um dispositivo de caractere chama funções quando é lido ou gravado. Escrever para
/dev/null
chamadas write_null , enquanto lê chamadas read_null , registrado aqui .Literalmente um punhado de linhas de código: essas funções não fazem nada. Você precisaria de mais linhas de código do que dedos em suas mãos apenas se contasse outras funções além de ler e escrever.
Espero que você também esteja ciente do /dev/chargen /dev/zero e outros como eles, incluindo /dev/null.
O LINUX/UNIX tem alguns deles - disponibilizados para que as pessoas possam fazer bom uso de FRAGMENTOS DE CÓDIGO BEM ESCRITO.
Chargen é projetado para gerar um padrão específico e repetitivo de caracteres - é bastante rápido e ultrapassaria os limites dos dispositivos seriais e ajudaria a depurar protocolos seriais que foram escritos e falharam em algum teste ou outro.
Zero é projetado para preencher um arquivo existente ou produzir um monte de zeros
/dev/null é apenas outra ferramenta com a mesma ideia em mente.
Todas essas ferramentas em seu kit de ferramentas significam que você tem meia chance de fazer um programa existente fazer algo único sem levar em conta (sua necessidade específica) como dispositivos ou substituições de arquivos
Vamos criar um concurso para ver quem consegue produzir o resultado mais emocionante, considerando apenas os poucos dispositivos de personagem em sua versão do LINUX.