Estou tentando entender o conceito de arquivos especiais no Linux. No entanto, ter um arquivo especial em /dev
parece tolo quando sua função pode ser implementada por um punhado de linhas em C, que eu saiba.
Além disso, você pode usá-lo da mesma maneira, ou seja, canalizando em null
vez de redirecionar para /dev/null
. Existe um motivo específico para tê-lo como um arquivo? Criar um arquivo não causa muitos outros problemas, como muitos programas acessando o mesmo arquivo?
files
devices
executable
null
Ankur S
fonte
fonte
cat foo | bar
é por que é muito pior (em escala) do quebar <foo
.cat
é um programa trivial, mas mesmo um programa trivial cria custos (alguns deles específicos da semântica do FIFO - porque os programas não podem serseek()
incluídos no FIFO, por exemplo, um programa que poderia ser implementado com eficiência na busca pode acabar fazendo operações muito mais caras quando recebe um pipeline; com um dispositivo de caractere como/dev/null
ele pode falsificar essas operações ou com um arquivo real, ele pode implementá-las, mas um FIFO não permite nenhum tipo de tratamento com reconhecimento de contexto).grep blablubb file.txt 2>/dev/null && dosomething
Não foi possível trabalhar com null sendo um programa ou uma função.read
função for/dev/null
consiste em um "retorno 0" (o que significa que não faz nada e, suponho, resulta em um EOF): (De static github.com/torvalds/linux/blob/master/ drivers / char / mem.c )ssize_t read_null(struct file *file, char __user *buf, size_t count, loff_t *ppos) { return 0; }
(Ah, acabei de ver que o @JdeBP já fez esse ponto. Enfim, aqui está a ilustração :-).Respostas:
Além dos benefícios de desempenho do uso de um dispositivo especial para caracteres, o principal benefício é a modularidade . / dev / null pode ser usado em praticamente 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 o uso de um programa como fonte ou coletor seria extremamente complicado. Mesmo no caso do pipeline do shell, stdout e stderr podem ser redirecionados para arquivos de forma independente, algo difícil de fazer com executáveis como coletores:
fonte
/dev/null
apenas os comandos do shell. Você pode usá-lo em outros parâmetros fornecidos a um software --- por exemplo, em arquivos de configuração. --- Para o software, isso é muito conveniente. Não é necessário fazer a diferença entre/dev/null
e um arquivo regular.pipe
,fork
e,execve
como qualquer outro processo de tubulação, apenas com alterações nasdup2
chamadas que configuram as conexões, certo? É verdade que a maioria dos shells não oferece as maneiras mais bonitas de fazer isso, mas, presumivelmente, se não tivéssemos tanto padrão de dispositivo como arquivo e a maioria das coisas/dev
e/proc
fossem tratadas como executáveis, os shells teriam sido projetados com maneiras para fazer isso tão facilmente quanto redirecionamos agora.wait4
! Você está correto, certamente é possível canalizar stdout e stderr para programas diferentes usando apis POSIX, e pode ser possível inventar uma sintaxe inteligente do shell para redirecionar stdout e stderr para diferentes comandos. No entanto, não estou ciente de nenhum desses shell no momento, e o ponto mais importante é que / dev / null se encaixa perfeitamente nas ferramentas existentes (que funcionam amplamente com arquivos) e / bin / null não. Poderíamos também imaginar alguma API IO que o torna tão fácil para gcc para (com segurança!) Saída para um programa como para um arquivo, mas essa não é a situação em que estamos.grep localhost /dev/ /etc/hosts 2> >(sed 's/^/STDERR:/' > errfile ) > >(sed 's/^/STDOUT:/' > outfile)
, resultante é separado processadoserrfile
eoutfile
Para ser justo, não é um arquivo comum por si só; é um dispositivo especial para personagens :
Funcionar como um dispositivo, e não como um arquivo ou programa, significa que é uma operação mais simples redirecionar a entrada ou saída a partir dele, pois pode ser anexada a qualquer descritor de arquivo, incluindo entrada / saída / erro padrão.
fonte
cat file | null
teria muita sobrecarga, primeiro na criação de um canal, gerando um processo, executando "null" no novo processo, etc. Além disso,null
ele próprio usaria bastante CPU em um loop, lendo bytes em um buffer que é posteriormente acabou de ser descartado ... A implementação de/dev/null
no kernel é apenas mais eficiente dessa maneira. Além disso, e se você quiser passar/dev/null
como argumento, em vez de redirecionar? (Você pode usar<(...)
em bash, mas que é ainda muito mais pesado entregue!)null
Em vez de usar o redirecionamento para/dev/null
, haveria uma maneira simples e clara de dizer ao shell para executar um programa enquanto envia apenas seu stderr para null?/dev/zero
lugar.dd of=-
grava em um arquivo chamado-
, basta omitir oof=
para gravar em stdout, pois é nesse local quedd
grava por padrão. A tubulação parafalse
não funcionar porquefalse
não lê seu stdin, entãodd
seria morta com um SIGPIPE. Para um comando que descarta sua entrada você pode usar ...cat > /dev/null
. Também a comparação que provavelmente é irrelevante como o gargalo provavelmente seria a geração de números aleatórios aqui.dd
etc. nem sequer se incomodam em fazer um syscall de gravação quando detectam que o destino é / dev / null.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 negligenciável em não gerar 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, se você olhar para do ponto de vista do 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. De uma perspectiva de script de shell, ofoo | null
programa é realmente realmente útil e conveniente , efoo >/dev/null
demora um pouco mais para digitar e pode parecer estranho.Mas aqui estão dois exercícios:
Vamos implementar o programa
null
usando ferramentas existentes 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 descartar apenas a entrada é trivial; portanto, talvez ainda não seja óbvio por que é útil ter um arquivo virtual disponível para a tarefa.
Considere: quase todas as linguagens de programação já precisam trabalhar com arquivos, descritores e caminhos de arquivos, porque faziam parte do paradigma "tudo é um arquivo" do Unix desde o início.
Se tudo o que você tem são programas que gravam no stdout, bem, o programa não se importa se você os redirecionar 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ê possui programas que utilizam caminhos de arquivos 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,
/dev/null
isso é de graça.Observe que a elegância disso é que reduz a complexidade do código de todos os programas envolvidos - para cada caso de uso comum, mas especial, que o sistema pode fornecer como "arquivo" com um "nome do arquivo" real, seu código pode evitar adicionar comandos personalizados opções de linha e caminhos de código personalizados para manipular.
A boa engenharia de software geralmente depende da descoberta de metáforas boas ou "naturais" para abstrair algum elemento de um problema de uma maneira que se torna mais fácil de pensar, mas permanece flexível , para que você possa resolver basicamente a mesma faixa de problemas de nível superior sem precisar gaste tempo e energia mental reimplementando soluções para os mesmos problemas de nível inferior constantemente.
"Tudo é um arquivo" parece ser uma metáfora para acessar recursos: você chama
open
um determinado caminho em um espaço de nome heirárquico, obtendo uma referência (descritor de arquivo) ao objeto e poderead
ewrite
etc nos descritores de arquivo. Seu stdin / stdout / stderr também são descritores de arquivos que foram pré-abertos para você. Seus pipes são apenas arquivos e descritores de arquivos, e o redirecionamento de arquivos permite colar todas essas peças.O Unix conseguiu tanto quanto em parte por causa de quão bem essas abstrações funcionaram juntas, e
/dev/null
é melhor entendido 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
/dev/null
tiveram que ser implementados no próprio kernel, mas acontece que é útil o suficiente para expor a funcionalidade no formato de arquivo / pasta que, desde então, foram criados vários sistemas que possibilitam 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 já se espalhou para outros sistemas convencionais).
fonte
NUL
apareça em todos os diretórios, ou seja, tudo o que você precisa digitar é> NUL
.Eu acho que
/dev/null
é um dispositivo de caractere (que se comporta como um arquivo comum) em vez de um programa por razões de desempenho .Se fosse um programa, seria necessário carregar, iniciar, agendar, executar e, posteriormente, interromper 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 ao considerar um grande número (digamos milhões) de ações de redirecionamento / canalização, pois as operações de gerenciamento de processo são caras em larga escala, pois eles envolvem opções de contexto.
Outra suposição: a inserção em um programa requer que a memória seja alocada pelo programa receptor (mesmo que seja descartada diretamente posteriormente). Portanto, se você acessar a ferramenta, terá um consumo de memória dupla, uma vez no programa de envio e novamente no programa de recebimento.
fonte
read
os dados). Isso não é desprezível em um PDP-11 de núcleo único onde o Unix foi projetado! A largura de banda / cópia da memória é muito mais barata hoje do que era então. Umawrite
chamada do sistema para um FD aberto/dev/null
pode retornar imediatamente sem mesmo ler os dados do buffer.null
programa seria péssimo, mas a maioria das CPUs multi-core não roda com todos os núcleos ocupados o tempo todo. O tempo de CPU para executar onull
programa é geralmente gratuito. Em um sistema com todos os núcleos vinculados, a tubulação inútil é um problema maior. Mas não, um buffer "quente" pode ser reescrito várias vezes sem ser liberado para a RAM; portanto, estamos apenas competindo pela largura de banda L3, não pelo cache. Não é ótimo, especialmente em um sistema SMT (hyperthreading), onde outro núcleo lógico (s) no mesmo física estão competindo ...vpaddd zmm
: 16 elementos de 32 bits por instrução.Além de "tudo é um arquivo" e, portanto, a facilidade de uso em qualquer lugar em que a maioria das outras respostas se baseia, também há um problema de desempenho como o @ user5626466 menciona.
Para mostrar na prática, criaremos 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 tubos, portanto, a única maneira de drená-lo é lê-lo até que esteja vazio).
enquanto que com
/dev/null
o redirecionamento de arquivo padrão , obtemos velocidades muito melhores (devido aos fatos mencionados: menos troca de contexto, o kernel apenas ignora os dados em vez de copiá-los, etc.):(este deve ser um comentário, mas é grande demais para isso e seria completamente ilegível)
fonte
dd
buffer + buffer de recebimento não se encaixe; você provavelmente está lidando com largura de banda da memória em vez da largura de banda do cache de último nível. Você pode obter melhor largura de banda com buffers de 512k ou mesmo 64k. (De acordo comstrace
minha área de trabalhowrite
eread
retornando 1048576, acho que significa que estamos pagando apenas ao usuário <-> o custo do kernel da invalidação de TLB + liberação de previsão de ramificação uma vez por MiB, não por 64k, @sourcejedi. mitigação que tem o maior custo, eu acho)syscall
que retorna imediatamenteENOSYS
é de ~ 1800 ciclos no Skylake com a mitigação Specter ativada, sendo a maior parte awrmsr
que invalida a BPU, de acordo com os testes do @ BeeOnRope . Com a mitigação desativada, o tempo de ida e volta do usuário-> kernel-> usuário é de ~ 160 ciclos. Mas se você está tocando muita memória, a atenuação do Meltdown também é significativa. Páginas enormes devem ajudar (menos entradas TLB precisam ser recarregadas).Sua pergunta é colocada como se algo fosse ganho talvez em 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 "tubos comuns".
Mas considere, um cachimbo também é um arquivo . Eles normalmente não são nomeados e, portanto, só podem ser manipulados através de seus descritores de arquivo.
Considere este exemplo um tanto artificial:
Usando a substituição de processos do Bash, podemos realizar a mesma coisa de uma maneira mais indireta:
Substitua o
grep
forecho
e podemos ver embaixo das tampas:A
<(...)
construção é substituída apenas por um nome de arquivo, e o grep acha que está abrindo qualquer arquivo antigo, apenas recebe o nome/dev/fd/63
. Aqui/dev/fd
está um diretório mágico que cria pipes nomeados para todos os descritores de arquivos possuídos pelo arquivo que os acessa.Poderíamos tornar menos mágico
mkfifo
criar um pipe nomeado que apareça emls
tudo, como um arquivo comum:Em outro lugar:
e eis que grep será exibido
foo
.Eu acho que depois que você perceber que pipes e arquivos regulares e arquivos especiais como / dev / null são apenas arquivos, é evidente 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 soltar as gravações no chão, enquanto que com um pipe ele precisa realmente transferir os bytes para outro programa, que então precisa realmente lê-los.
fonte
/dev/fd/63
?Eu argumentaria que há um problema 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 quão simples, é um princípio fundamental da segurança do sistema. Uma substituição
/dev/null
certamente seria necessária para ter esses privilégios devido ao uso pelos serviços do sistema. As estruturas de segurança modernas fazem um excelente trabalho impedindo explorações, mas não são infalíveis. Um dispositivo controlado por kernel acessado como um arquivo é muito mais difícil de explorar.fonte
/dev/null
ou um programa de descarte de entrada proposto, o vetor de ataque seria o mesmo: obter um script ou programa que é executado como root para fazer algo estranho (como tentarlseek
em/dev/null
ou aberto várias vezes a partir do mesmo processo, ou IDK o quê. Ou invoque/bin/null
com um ambiente estranho, ou o que seja).Como outros já apontaram,
/dev/null
é um programa feito de algumas 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. Gravando para
/dev/null
chamadas write_null , enquanto lê chamadas read_null , registradas 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 nas mãos apenas se contar outras funções além de ler e escrever.
fonte
Espero que você também esteja ciente do / dev / chargen / dev / zero e outros como eles, incluindo / dev / null.
O LINUX / UNIX possui alguns deles - disponibilizados para que as pessoas possam fazer bom uso das fragmentos de códigos bem escritos.
O Chargen foi projetado para gerar um padrão específico e repetitivo de caracteres - é bastante rápido e aumentaria os limites de 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 gerar muitos zeros
/ dev / null é apenas mais uma ferramenta com a mesma idéia em mente.
Todas essas ferramentas no seu kit de ferramentas significam que você tem meia chance de fazer com que um programa existente faça algo único, sem levar em consideração a (sua necessidade específica) como dispositivos ou substituições de arquivos
Vamos criar um concurso para ver quem pode produzir o resultado mais emocionante, considerando apenas os poucos dispositivos de caracteres na sua versão do LINUX.
fonte