Por que / dev / null é um arquivo? Por que sua função não é implementada como um programa simples?

111

Eu sou novo em entender o conceito de arquivos especiais no Linux. No entanto, ter um arquivo especial em /dev parece simples quando sua função pode ser implementada por um punhado de linhas em C até onde eu sei.

Além disso, você pode usá-lo praticamente da mesma maneira, ou seja, canalizar para null em vez de redirecionar para /dev/null . Existe uma razão específica para tê-lo como um arquivo? Não torná-lo um arquivo causa muitos outros problemas como muitos programas acessando o mesmo arquivo?

    
por Ankur S 16.04.2018 / 17:28

9 respostas

150

Além dos benefícios de desempenho do uso de um dispositivo especial de caractere, o principal benefício é modularidade . / dev / null pode ser usado em quase qualquer contexto onde um arquivo é esperado, não apenas em pipelines de shell. Considere programas que aceitam arquivos como parâmetros de linha de comando.

# We don't care about log output.
$ frobify --log-file=/dev/null

# We are not interested in the compiled binary, just seeing if there are errors.
$ gcc foo.c -o /dev/null  || echo "foo.c does not compile!".

# Easy way to force an empty list of exceptions.
$ start_firewall --exception_list=/dev/null

Estes são todos os casos em que o uso de um programa como fonte ou coletor seria extremamente incômodo. Mesmo no caso do pipeline de shell, stdout e stderr podem ser redirecionados para arquivos de forma independente, algo que é difícil fazer com executáveis como pias:

# Suppress errors, but print output.
$ grep foo * 2>/dev/null
    
por 16.04.2018 / 22:55
62

Para ser justo, não é um arquivo regular em si; é um dispositivo especial de caracteres :

$ file /dev/null
/dev/null: character special (3/2)

Funcionar como um dispositivo e não como um arquivo ou programa significa que é uma operação mais simples para redirecionar a entrada ou saída dela, pois ela pode ser anexada a qualquer descritor de arquivo, incluindo entrada / saída / erro padrão.

    
por 16.04.2018 / 17:56
54

Eu 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 para não criar um processo extra, mas acho que há mais: o Early Unix tinha uma metáfora "tudo é um arquivo", que tem uma vantagem não óbvia, mas elegante. você olha para isso de uma perspectiva de sistema, ao invés de uma perspectiva de script de shell.

Digamos que você tenha seu programa de linha de comando null e /dev/null do nó do dispositivo. A partir de uma perspectiva de script de shell, o programa foo | null é realmente útil e conveniente , e foo >/dev/null demora um pouco mais para ser digitado e pode parecer estranho. / p>

Mas aqui estão dois exercícios:

  1. Vamos implementar o programa null usando as ferramentas existentes do Unix e /dev/null - easy: cat >/dev/null . Feito.

  2. Você pode implementar /dev/null em termos de null ?

Você está absolutamente certo de que o código C apenas para descartar a entrada é trivial, então talvez ainda não seja óbvio porque é ú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 começo.

Se tudo que você tem são programas que escrevem para stdout, bem, o programa não se importa se você os redireciona para um arquivo virtual que engula todas as gravações, ou um pipe para um programa que engula todas as gravações.

Agora, se você tiver programas que usam caminhos de arquivo para ler ou gravar dados (o que a maioria dos programas faz) - e você deseja adicionar a funcionalidade "entrada em branco" ou "descartar esta saída" a esses programas - bem, com /dev/null que vem de graça.

Observe que a elegância é a de reduzir a complexidade do código de todos os programas envolvidos - para cada usecase comum, mas especial, que seu sistema pode fornecer como um "arquivo" com um "nome de arquivo" real, seu código pode evitar adicionando opções de linha de comando personalizadas 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 forma que se torne mais fácil de pensar mas permaneça flexível , para que você possa resolver basicamente a mesma gama de problemas de alto nível sem ter que gastar tempo e energia mental na reimplementação de soluções para os mesmos problemas de nível mais baixo constantemente.

"Tudo é um arquivo" parece ser uma dessas metáforas para acessar recursos: você chama open de um determinado caminho em um namespace heirárquico, obtendo uma referência (descritor de arquivo) para o objeto e você pode read e write , etc nos descritores de arquivos. Seu stdin / stdout / stderr também são descritores de arquivos que acabaram de ser pré-abertos para você. Seus canais são apenas arquivos e descritores de arquivos, e o redirecionamento de arquivos permite que você cole todas essas partes juntas.

O Unix teve sucesso tanto quanto em parte devido ao bom desempenho dessas abstrações, e /dev/null é melhor entendido como parte desse todo.

P.S. Vale a pena olhar para a versão Unix de "tudo é um arquivo" e coisas como /dev/null como 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 como /dev/null tiveram que ser implementados no próprio kernel, mas é útil o suficiente para expor a funcionalidade na forma de arquivo / pasta que, desde então, vários sistemas foram criados. que fornecem uma maneira de os programas fazerem isso.

Um dos primeiros foi o sistema operacional Plan 9, feito por algumas das mesmas pessoas que fizeram o Unix. Mais tarde, o GNU Hurd fez algo similar com seus "tradutores". Enquanto isso, o Linux acabou ficando FUSE (que se espalhou para os outros sistemas mainstream até agora).

    
por 17.04.2018 / 00:05
15

Eu 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. O programa C simples que você está descrevendo obviamente não consumiria muitos recursos, mas acho que faz uma diferença significativa quando consideramos um grande número (digamos milhões) de ações de redirecionamento / tubulação, já que as operações de gerenciamento de processos são caras

Outra suposição: Pipar em um programa requer que memória seja alocada pelo programa receptor (mesmo que seja descartado diretamente depois). Então, se você canaliza para a ferramenta, você tem o dobro de consumo de memória, uma vez no programa de envio e novamente no programa de recepção.

    
por 16.04.2018 / 18:21
7

Além de "tudo é um arquivo" e, portanto, a facilidade de uso em todos os lugares em que a maioria das outras respostas se baseia, também há um problema de desempenho, conforme mencionado por @ user5626466.

Para mostrar na prática, criaremos um programa simples chamado nullread.c :

#include <unistd.h>
char buf[1024*1024];
int main() {
        while (read(0, buf, sizeof(buf)) > 0);
}

e compile com gcc -O2 -Wall -W nullread.c -o nullread

(Nota: não podemos usar lseek (2) nos tubos, portanto, somente a maneira de drenar o tubo é ler até que esteja vazio).

% time dd if=/dev/zero bs=1M count=5000 |  ./nullread
5242880000 bytes (5,2 GB, 4,9 GiB) copied, 9,33127 s, 562 MB/s
dd if=/dev/zero bs=1M count=5000  0,06s user 5,66s system 61% cpu 9,340 total
./nullread  0,02s user 3,90s system 41% cpu 9,337 total

Considerando que com o redirecionamento padrão do arquivo /dev/null , obtemos velocidades muito melhores (devido aos fatos mencionados: menos troca de contexto, kernel ignorando dados em vez de copiá-los, etc.):

% time dd if=/dev/zero bs=1M count=5000 > /dev/null
5242880000 bytes (5,2 GB, 4,9 GiB) copied, 1,08947 s, 4,8 GB/s
dd if=/dev/zero bs=1M count=5000 > /dev/null  0,01s user 1,08s system 99% cpu 1,094 total

(este deve ser um comentário lá, mas é muito grande para isso e seria completamente ilegível)

    
por 19.04.2018 / 13:32
6

Sua pergunta é colocada como se algo fosse ganho, talvez na simplicidade, usando um programa nulo no lugar de um arquivo. Talvez possamos nos livrar da noção de "arquivos mágicos" e, em vez disso, ter apenas "canais 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 arquivos.

Considere este exemplo um pouco inventado:

$ echo -e 'foo\nbar\nbaz' | grep foo
foo

Usando a substituição de processos do Bash, podemos realizar a mesma coisa de uma forma mais indireta:

$ grep foo <(echo -e 'foo\nbar\nbaz')
foo

Substitua o grep por echo e podemos ver abaixo das capas:

$ echo foo <(echo -e 'foo\nbar\nbaz')
foo /dev/fd/63

A construção <(...) acabou de ser substituída por um nome de arquivo, e o grep acha que está abrindo qualquer arquivo antigo, que por acaso é chamado de /dev/fd/63 . Aqui, /dev/fd é um diretório mágico que faz pipes nomeados para cada descritor de arquivo possuído pelo arquivo que o acessa.

Poderíamos fazer menos mágica com mkfifo para criar um pipe nomeado que aparece em ls e tudo, como um arquivo comum:

$ mkfifo foofifo
$ ls -l foofifo 
prw-rw-r-- 1 indigo indigo 0 Apr 19 22:01 foofifo
$ grep foo foofifo

Em outro lugar:

$ echo -e 'foo\nbar\nbaz' > foofifo

e eis que o grep irá gerar foo .

Eu acho que quando você percebe canos e arquivos regulares e arquivos especiais como / dev / null são apenas arquivos, é evidente que implementar um programa nulo é mais complexo. O kernel tem que manipular gravações em um arquivo de qualquer forma, mas no caso de / dev / null ele pode simplesmente soltar as gravações no chão, enquanto que com um pipe ele tem que realmente transferir os bytes para outro programa, que então realmente lê-los.

    
por 20.04.2018 / 00:07
0

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, por mais simples que seja, é um princípio fundamental da segurança do sistema. Um /dev/null de substituição certamente seria obrigado a ter tais privilégios devido ao uso pelos serviços do sistema. As estruturas de segurança modernas fazem um excelente trabalho evitando explorações, mas não são infalíveis. Um dispositivo acionado pelo kernel acessado como um arquivo é muito mais difícil de explorar.

    
por 20.04.2018 / 18:13
0

Como outros já apontaram, /dev/null é um programa feito de um punhado de linhas de código. É só 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. Escrevendo para /dev/null chama write_null , durante a leitura de chamadas < href="https://elixir.bootlin.com/linux/v4.2/source/drivers/char/mem.c#L598"> read_null , registrado aqui .

Literalmente algumas 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.

    
por 23.04.2018 / 17:14
-2

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 dos BEM COMPREENDIDOS DOS CÓDIGOS ESCRITA.

Chargen é projetado para gerar um padrão específico e repetitivo de caracteres - é bastante rápido e iria empurrar os limites de série dispositivos e ajudaria a depurar protocolos seriais que foram escritos e falharam em algum teste ou outro.

O Zero foi projetado para preencher um arquivo existente ou produzir um lote inteiro de zero

/ dev / null é apenas mais uma ferramenta com a mesma ideia em mente.

Todas essas ferramentas no seu kit de ferramentas significam que você tem metade da chance de fazer um programa existente fazer algo único sem considerar suas (sua necessidade específica) como dispositivos ou substituições de arquivos

Vamos configurar um concurso para ver quem pode produzir o resultado mais interessante, considerando apenas os poucos dispositivos de caractere da sua versão do LINUX.

    
por 20.04.2018 / 15:45