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

114

Estou tentando entender o conceito de arquivos especiais no Linux. No entanto, ter um arquivo especial em /devparece 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 nullvez 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?

Ankur S
fonte
20
Aliás, grande parte dessa sobrecarga também cat foo | baré por que é muito pior (em escala) do que bar <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 ser seek()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/nullele 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).
Charles Duffy
13
grep blablubb file.txt 2>/dev/null && dosomethingNão foi possível trabalhar com null sendo um programa ou uma função.
Rexkogitans
16
Você pode achar esclarecedor (ou pelo menos expansivo) ler sobre o sistema operacional Plan 9 para ver para onde estava indo a visão "tudo é um arquivo" - fica um pouco mais fácil ver o poder de ter recursos disponíveis como arquivo caminhos quando você vê um sistema abraçando completamente o conceito (em vez de principalmente / parcialmente, como o Linux / Unix moderno).
Mtraceur
25
Assim como ninguém salienta que um driver de dispositivo executando no espaço do kernel é um programa com "algumas linhas de C" , nenhuma das respostas até agora abordou a suposição de "muitos programas acessando o mesmo arquivo" na pergunta.
JdeBP
12
Re "sua função poderia ser implementada por um punhado de linhas em C": Você não acreditaria, mas é implementada por um punhado de linhas em C! Por exemplo, o corpo da readfunção for /dev/nullconsiste 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 :-).
27518 Peter S. Schneider

Respostas:

153

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.

# 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

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:

# Suppress errors, but print output.
$ grep foo * 2>/dev/null
ioctl
fonte
14
Além disso, você não usa /dev/nullapenas 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/nulle um arquivo regular.
pabouk
Não sei se entendi difícil a parte de redirecionamentos separados para afundar executáveis. Em C, você apenas faz um pipe, forke, execvecomo qualquer outro processo de tubulação, apenas com alterações nas dup2chamadas 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 /deve /procfossem tratadas como executáveis, os shells teriam sido projetados com maneiras para fazer isso tão facilmente quanto redirecionamos agora.
Aschepler
6
@aschepler É difícil não redirecionar para executáveis ​​de coletor. É que escrever aplicativos que podem gravar / ler nos dois arquivos e no coletor nulo seria mais complicado se o coletor nulo não fosse um arquivo. A menos que você esteja falando de um mundo onde, em vez de tudo ser um arquivo, tudo é um executável? Esse seria um modelo muito diferente do que você possui no * nix OS.
Cubic
1
@aschepler Você esqueceu 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.
ioctl
2
@ioctl sobre conchas; tanto zsh eo bash, pelo menos, lhe permitirá fazer coisas como grep localhost /dev/ /etc/hosts 2> >(sed 's/^/STDERR:/' > errfile ) > >(sed 's/^/STDOUT:/' > outfile), resultante é separado processados errfileeoutfile
Matija Nalis
62

Para ser justo, não é um arquivo comum por si só; é um dispositivo especial para personagens :

$ 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 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.

DopeGhoti
fonte
24
cat file | nullteria muita sobrecarga, primeiro na criação de um canal, gerando um processo, executando "null" no novo processo, etc. Além disso, nullele próprio usaria bastante CPU em um loop, lendo bytes em um buffer que é posteriormente acabou de ser descartado ... A implementação de /dev/nullno kernel é apenas mais eficiente dessa maneira. Além disso, e se você quiser passar /dev/nullcomo argumento, em vez de redirecionar? (Você pode usar <(...)em bash, mas que é ainda muito mais pesado entregue!)
filbranden
4
Se você tivesse que canalizar para um programa chamado nullEm 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?
precisa
5
Essa é uma configuração muito cara para uma demonstração de custos indiretos. Eu sugeriria usar em seu /dev/zerolugar.
usar o seguinte comando
20
Esses exemplos estão errados. dd of=-grava em um arquivo chamado -, basta omitir o of=para gravar em stdout, pois é nesse local que ddgrava por padrão. A tubulação para falsenão funcionar porque falsenão lê seu stdin, então ddseria 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.
Stéphane Chazelas 17/04/19
8
As versões AST de ddetc. nem sequer se incomodam em fazer um syscall de gravação quando detectam que o destino é / dev / null.
precisa saber é o seguinte
54

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 nullprograma de linha de comando e /dev/nullo nó do dispositivo. De uma perspectiva de script de shell, o foo | nullprograma é realmente realmente útil e conveniente , e foo >/dev/nulldemora um pouco mais para digitar e pode parecer estranho.

Mas aqui estão dois exercícios:

  1. Vamos implementar o programa nullusando ferramentas existentes Unix e /dev/null- fácil: cat >/dev/null. Feito.

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

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/nullisso é 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 openum determinado caminho em um espaço de nome heirárquico, obtendo uma referência (descritor de arquivo) ao objeto e pode reade writeetc 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/nullos 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/nulltiveram 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).

mtraceur
fonte
8
@ PeterCordes o ponto da resposta está começando de uma posição de não entender o design. Se todos já entendessem o design, essa pergunta não existiria.
OrangeDog
1
@mtraceur: Monte um arquivo de imagem sem permissão root? mostra algumas evidências de que o FUSE pode não exigir raiz, mas não tenho certeza.
Peter Cordes
1
@PeterCordes RE: "parece estranho": não é um pedido de desculpas pelo design, apenas um reconhecimento de como pode parecer se você não está pensando na implementação do sistema, e ainda não teve o momento eureka sobre o sistema vantagens de design gerais. Tentei deixar isso claro abrindo a frase com "da perspectiva de script de shell" e aludindo ao contraste entre essa perspectiva versus a perspectiva do sistema que algumas frases antes. Pensando melhor, "pode ​​parecer estranho" é melhor, então vou ajustá-lo para isso. Congratulo-me com mais sugestões de redação para torná-lo mais claro, sem torná-lo muito detalhado.
Mtraceur 17/04/19
2
A primeira coisa que me disseram como jovem engenheiro em relação ao Unix foi "Everything Is A File" e juro que você pode ouvir as capitais. E se apossar dessa idéia cedo faz com que o Unix / Linux pareça muito mais fácil de entender. O Linux herdou a maior parte dessa filosofia de design. Estou feliz que alguém tenha mencionado.
precisa saber é o seguinte
2
@PeterCordes, DOS "resolveu" o problema de digitação fazendo com que o nome do arquivo mágico NULapareça em todos os diretórios, ou seja, tudo o que você precisa digitar é > NUL.
Cristian Ciupitu 18/04/19
15

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.

user5626466
fonte
10
Não é apenas o custo de instalação, é que toda gravação em um pipe exige cópia de memória e uma troca de contexto para o programa de leitura. (Ou pelo menos uma alternância de contexto quando o buffer do tubo está cheio. E o leitor precisa fazer outra cópia quando estiver com reados 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. Uma writechamada do sistema para um FD aberto /dev/nullpode retornar imediatamente sem mesmo ler os dados do buffer.
Peter Cordes
@ PeterCordes, minha observação é tangencial, mas é possível que, paradoxalmente, as gravações de memória hoje sejam mais caras do que nunca. Uma CPU de 8 núcleos potencialmente executa 16 operações inteiras em um horário, enquanto uma gravação de memória de ponta a ponta seria concluída em, por exemplo, 16 relógios (CPU de 4GHz, RAM de 250 MHz). Esse é o fator de 256. RAM para a CPU moderna é como um RL02 para a CPU PDP-11, quase como uma unidade de armazenamento periférico! :) Naturalmente, não é tão simples, mas tudo que estiver no cache será gravado, e gravações inúteis privariam outros cálculos do sempre importante espaço em cache.
K19 de
@kkm: Sim, desperdiçar cerca de 2x 128 kB de área de cache L3 em um buffer de pipe no kernel e um buffer de leitura no nullprograma 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 o nullprograma é 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 ...
Peter Cordes
.... Mas seu cálculo de memória é muito falho. As CPUs modernas têm muito paralelismo de memória, portanto, embora a latência para DRAM seja algo como 200-400 ciclos de clock de núcleo e L3> 40, a largura de banda é de ~ 8 bytes / clock. (Surpreendentemente, a largura de banda de thread único para L3 ou DRAM é pior em um Xeon de vários núcleos com memória de quatro canais vs. uma área de trabalho de quatro núcleos, porque é limitada pela simultaneidade máxima de solicitações que um núcleo pode manter em andamento. Largura de banda = max_concurrency / latency: por que é Skylake muito melhor do que Broadwell-E para o throughput de memória single-threaded? )
Peter Cordes
... Consulte também 7-cpu.com/cpu/Haswell.html para obter os números de Haswell comparando quad-core vs. 18-core. De qualquer forma, sim, as CPUs modernas podem realizar uma quantidade ridícula de trabalho por relógio, se não ficarem presas à espera de memória. Seus números parecem ser apenas 2 operações de ALU por relógio, como talvez um Pentium de 1993 ou um moderno ARM de edição dupla de gama baixa. Um Ryzen ou Haswell potencialmente executa 4 operações ALU de número inteiro escalar + 2 operações de memória por núcleo por relógio ou muito mais com o SIMD. por exemplo, o Skylake-AVX512 possui (por núcleo) taxa de transferência de 2 por relógio em vpaddd zmm: 16 elementos de 32 bits por instrução.
Peter Cordes
7

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:

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

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).

% 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

enquanto que com /dev/nullo 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.):

% 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, mas é grande demais para isso e seria completamente ilegível)

Matija Nalis
fonte
Em que hardware você testou? 4.8GB / s é bastante baixo comparado aos 23GB / s que recebo em um Skylake i7-6700k (DDR4-2666, mas o buffer deve permanecer quente no cache L3. Portanto, uma boa parte do custo é que as chamadas de sistema são caras com o Spectre + Atenuação de derretimento ativada. Isso prejudica as tubulações, porque os buffers de tubulação são menores que 1 milhão, o que significa mais chamadas para o sistema de gravação / leitura. No entanto, no meu sistema Skylake, são 23 GB / s vs. 3,3 GB / s , correndo x86-64 Linux 4.15.8-1-ARCH, de modo que é um fator de 6,8 Uau, chamadas do sistema são. caro agora)
Peter Cordes
1
O @PeterCordes de 3 GB / s com buffers de pipe de 64k sugere 2x 103124 syscalls por segundo ... e esse número de alternâncias de contexto, heh. Em uma CPU do servidor, com 200000 syscalls por segundo, você pode esperar uma sobrecarga de aproximadamente 8% da PTI, pois há muito pouco conjunto de trabalho. (O gráfico que estou referenciando não inclui a otimização do PCID, mas talvez isso não seja tão significativo para pequenos conjuntos de trabalho). Então, eu não tenho certeza se o PTI tem um grande impacto lá? brendangregg.com/blog/2018-02-09/...
sourcejedi
1
Oh, interessante, por isso é um Silvermont com 2 MB de cache L2 , para que seu ddbuffer + 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 com straceminha área de trabalho writee readretornando 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)
Peter Cordes
1
@sourcejedi: Com a mitigação Specter ativada, o custo de uma syscallque retorna imediatamente ENOSYSé de ~ 1800 ciclos no Skylake com a mitigação Specter ativada, sendo a maior parte a wrmsrque 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).
Peter Cordes
1
@PeterCordes Em um sistema unix de núcleo único, certamente veríamos 1 comutador de contexto por 64K, ou qualquer que seja o seu buffer de pipe, e isso machucaria ... na verdade, também vejo o [mesmo número de comutadores de contexto com 2 núcleos de CPU] ( unix.stackexchange.com/questions/439260/… ); também deve contar um ciclo de suspensão / ativação para cada 64k como uma alternância de contexto (para um "processo inativo" nominal). Manter os processos de pipeline na mesma CPU realmente funcionou mais que o dobro da velocidade.
sourcejedi
6

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:

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

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

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

Substitua o grepfor echoe podemos ver embaixo das tampas:

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

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/fdestá 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 mkfifocriar um pipe nomeado que apareça em lstudo, 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 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.

Phil Frost
fonte
@Lyle Yeah? Então, por que o eco é impresso /dev/fd/63?
Phil Frost
Hum. Bom ponto. Bem, isso é implementado por shells, então talvez seu shell seja diferente do antigo shell Bourne com o qual eu cresci.
Lyle
Uma diferença é que o eco não lê do stdin, enquanto o grep lê, mas não consigo pensar em como o shell saberia isso antes de executá-los.
Lyle
1
E strace torna isso mais claro: para mim. você está exatamente certo, com o bash. A construção '<(...)' está fazendo algo bem diferente de <nome do arquivo. Hum. Eu aprendi alguma coisa
Lyle
0

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/nullcertamente 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.

NickW
fonte
Isso parece bobagem. Escrever um driver de kernel livre de erros não é mais fácil do que escrever um programa sem erros que lê + descarta seu stdin. Ele não precisa ser setuid ou nada, então para ambos /dev/nullou 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 tentar lseekem /dev/nullou aberto várias vezes a partir do mesmo processo, ou IDK o quê. Ou invoque /bin/nullcom um ambiente estranho, ou o que seja).
Peter Cordes
0

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/nullchamadas 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.

Matthieu Moy
fonte
Talvez eu devesse ter formulado com mais precisão. Eu quis dizer por que implementá-lo como um dispositivo char em vez de um programa. Seria algumas linhas de qualquer maneira, mas a implementação do programa seria decididamente mais simples. Como as outras respostas apontaram, há alguns benefícios nisso; chefe de eficiência e portabilidade entre eles.
Ankur S
Certo. Acabei de adicionar essa resposta porque ver a implementação real foi divertida (eu a descobri recentemente), mas a verdadeira razão é o que os outros apontaram.
Matthieu Moy
Eu também! Eu recentemente comecei a dispositivos em linux aprendendo e as respostas eram bastante informativo
Ankur S
-2

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.

útil
fonte