Nos sistemas Unix, por que precisamos explicitamente `abrir ()` e `fechar ()` arquivos para poder lê-los () ou escrevê-los?

50

Por que existe open()e close()existe no design do sistema de arquivos Unix?

O sistema operacional não pôde apenas detectar a primeira vez read()ou write()foi chamado e fazer o open()que normalmente faria?

user5977637
fonte
22
Vale ressaltar que esse modelo não faz parte do sistema de arquivos, mas da API do Unix . O sistema de arquivos se preocupa apenas com o local onde os bytes vão e onde colocar o nome do arquivo, etc. Seria perfeitamente possível ter o modelo alternativo que você descreve sobre um sistema de arquivos Unix como UFS ou ext4; para traduzir essas chamadas nas atualizações apropriadas para o sistema de arquivos (como é agora).
marcelm
18
Como foi dito, acho que isso é mais sobre o porquê open()existe. "O sistema operacional não conseguiu detectar a primeira vez que lê () ou escreve () e faz o que abrir () normalmente faria?" Existe uma sugestão correspondente para quando o fechamento aconteceria?
27716 Joshua Taylor
7
Como você diria read()ou write()qual arquivo acessar? Presumivelmente, passando o caminho. E se o caminho do arquivo mudar enquanto você o acessa (entre duas read()ou write()chamadas)?
user253751
2
Além disso, você geralmente não faz controle de acesso read()e write(), apenas open().
Pavel Šimerda
6
@ Johnny: Você talvez esteja esquecendo o quão limitado o hardware era naqueles dias. O PDP-7 no qual o Unix foi implementado tinha, no máximo, 64K de RAM e um relógio de 0,333 MHz - um pouco menos do que um simples microcontrolador atualmente. Fazer essa coleta de lixo ou usar o código do sistema para monitorar o acesso a arquivos teria deixado o sistema de joelhos.
jamesqf

Respostas:

60

Dennis Ritchie menciona em «A evolução do sistema de tempo compartilhado Unix» que opene closejunto com read, writee createstavam presentes no direito sistema desde o início.

Eu acho que um sistema sem opene closenão seria inconcebível, no entanto, acredito que isso complicaria o design. Você geralmente deseja fazer várias chamadas de leitura e gravação, não apenas uma, e isso provavelmente ocorreu especialmente nos computadores antigos com RAM muito limitada na qual o UNIX se originou. Ter um identificador que mantém sua posição atual do arquivo simplifica isso. Se readouwritedevolverem a alça, teriam que retornar um par - uma alça e seu próprio status de retorno. A parte do identificador do par seria inútil para todas as outras chamadas, o que tornaria esse arranjo estranho. Deixar o estado do cursor para o kernel permite melhorar a eficiência, não apenas através de buffer. Há também algum custo associado à pesquisa de caminho - ter um identificador permite que você pague apenas uma vez. Além disso, alguns arquivos na visão de mundo UNIX nem têm um caminho de sistema de arquivos (ou não - agora eles têm coisas do tipo /proc/self/fd).

PSkocik
fonte
7
O custo da pesquisa de caminho e verificação de permissão, etc. é muito significativo. Se você quisesse criar um sistema sem open/ close, certamente implementaria coisas como /dev/stdoutpermitir a tubulação.
Peter Cordes
5
Penso que outro aspecto disso é que você pode manter esse identificador no mesmo arquivo ao usar várias leituras ao manter o arquivo aberto. Caso contrário, você poderá ter casos em que outro processo se desvincula e recriar um arquivo com o mesmo nome, e a leitura de um arquivo em partes pode efetivamente ser completamente incoerente. (Algumas destas podem depender do sistema de arquivos também.)
de Bruno
2
Eu projetei um sem close (); você passa o número do inode e desloca para ler () e escrever (). Não posso ficar sem abrir () com muita facilidade, porque é aí que reside a resolução de nomes.
Joshua
3
@ Josué: Esse sistema tem semântica fundamentalmente diferente, porque os descritores de arquivos unix não se referem a arquivos (inodes), mas a abrir descrições de arquivos , dos quais pode haver muitos para um determinado arquivo (inode).
R ..
@Joshua, você só renomeado open()para get_inode()e fez todo o sistema mais rígido (impossível de ler / escrever o mesmo arquivo em várias posições ao mesmo tempo).
vonbrand 29/02
53

Todas as chamadas reade writeteriam que passar essas informações em cada operação:

  • o nome do arquivo
  • as permissões do arquivo
  • se o chamador está anexando ou criando
  • se o chamador terminou de trabalhar com o arquivo (para descartar buffers de leitura não utilizados e garantir que os buffers de gravação realmente terminem a gravação)

Se você considerar os independentes chamadas open , read, writee closepara ser mais simples do que a de finalidade única E / S mensagem é baseada em sua filosofia de design. Os desenvolvedores do Unix escolheram usar operações e programas simples que podem ser combinados de várias maneiras, em vez de uma única operação (ou programa) que faz tudo.

Thomas Dickey
fonte
Os chamadores também precisam, na maioria dos casos, especificar o deslocamento desejado em um arquivo. Existem algumas situações (por exemplo, um protocolo UDP que permite acesso a dados) em que cada solicitação identifica um arquivo e um deslocamento independentemente pode ser útil, pois elimina a necessidade de um servidor manter o estado, mas, em geral, é mais conveniente ter o servidor acompanhar a posição do arquivo. Além disso, como observado em outro lugar, o código que irá gravar arquivos geralmente precisa travá-los antes e depois; pentear essas operações com abrir / fechar é muito conveniente.
26616
5
O "arquivo" pode não ter um nome ou permissões em primeiro lugar; reade writenão estão restritos a arquivos que vivem em um sistema de arquivos, e essa é uma decisão fundamental de design no Unix, como explica o pjc50.
Reinierpost
11
Também em que lugar do arquivo a leitura / gravação - o início, o final ou uma posição arbitrária (normalmente ocorre imediatamente após o final da última leitura / gravação) - o kernel controla isso para você (com um modo para direcionar todas as gravações para o final do arquivo, ou de outra forma arquivos são abertos com a posição no início e avançou com cada leitura / gravação e pode ser movido com lseek)
Random832
51

O conceito de manipulação de arquivo é importante devido à escolha de design do UNIX de que "tudo é um arquivo", incluindo itens que não fazem parte do sistema de arquivos. Como unidades de fita, teclado e tela (ou teletipo!), Leitores de cartão / fita perfurados, conexões seriais, conexões de rede e (a principal invenção do UNIX) conexões diretas a outros programas chamados "pipes".

Se você olhar para muitos dos utilitários UNIX padrão simples grep, como , especialmente em suas versões originais, perceberá que eles não incluem chamadas para open()e close()apenas reade write. Os identificadores de arquivo são configurados fora do programa pelo shell e transmitidos quando são iniciados. Portanto, o programa não precisa se importar se está gravando em um arquivo ou em outro programa.

Bem como open, as outras formas de obtenção de descritores de arquivos são socket, listen, pipe, dup, e um muito mecanismo de Heath Robinson para o envio de descritores de arquivo através de tubos: https://stackoverflow.com/questions/28003921/sending-file-descriptor-by-linux -socket

Editar: algumas notas de aula descrevendo as camadas de indireção e como isso permite que o O_APPEND funcione de maneira sensata. Observe que manter os dados do inode na memória garante que o sistema não precise ir buscá-los novamente para a próxima operação de gravação.

pjc50
fonte
11
Além disso creat, e listennão cria um fd, mas quando (e se) uma solicitação é recebida durante a escuta acceptcria e retorna um fd para o novo soquete (conectado).
Dave_thompson_085
18
Essa é a resposta correta. O famoso (pequeno) conjunto de operações nos descritores de arquivo é uma API unificadora para todos os tipos de recursos que produzem ou consomem dados. Este conceito é extremamente bem sucedido. Uma corda poderia concebivelmente ter uma sintaxe que define o tipo de recurso, juntamente com a localização real (URL alguém?), Mas para copiar cordas em torno da qual ocupam vários por cento da RAM disponível (o que era sobre o PDP 7? 16 kB?) Parece excessiva .
Peter - Restabelece Monica
Talvez fosse, se as chamadas de baixo nível e o shell fossem desenvolvidos ao mesmo tempo. Mas pipefoi introduzido alguns anos após o início do desenvolvimento no Unix.
Thomas Dickey
11
@Thomas Dickey: que apenas mostra o quão bom o design original, uma vez que permitiu a extensão simples de tubos & c :-)
jamesqf
Mas, seguindo essa linha de argumento, essa resposta não oferece nada de novo.
Thomas Dickey
10

A resposta é não, porque abrir () e fechar () criam e destroem um identificador, respectivamente. Há momentos (bem, o tempo todo, realmente) em que você pode querer garantir que você é o único chamador com um nível de acesso específico, pois outro chamador (por exemplo) gravando em um arquivo que você está analisando inesperadamente pode deixar uma aplicação em um estado desconhecido ou levar a um bloqueio ou impasse, por exemplo, o lema de Dining Philosophers.

Mesmo sem essa consideração, há implicações de desempenho a serem consideradas; close () permite ao sistema de arquivos (se for apropriado ou se você solicitou) liberar o buffer que você estava ocupando, uma operação cara. Várias edições consecutivas em um fluxo na memória são muito mais eficientes do que vários ciclos de leitura / gravação / modificação essencialmente não relacionados em um sistema de arquivos que, pelo que você sabe, existe a meio mundo de distância, espalhado por um datacenter com armazenamento em massa de alta latência. Mesmo com o armazenamento local, a memória é tipicamente muitas ordens de magnitude mais rápida que o armazenamento em massa.

msaunier
fonte
7

Open () oferece uma maneira de bloquear arquivos enquanto eles estão em uso. Se os arquivos fossem abertos automaticamente, lidos / gravados e depois fechados novamente pelo sistema operacional, não haveria nada para impedir que outros aplicativos alterassem esses arquivos entre as operações.

Embora isso possa ser gerenciado (muitos sistemas suportam acesso não exclusivo a arquivos) para simplificar, a maioria dos aplicativos supõe que os arquivos abertos não sejam alterados.

あ あ あ あ あ あ あ あ あ あ あ あ あ あ あ あ あ あ あ あ あ あ あ あ あ あ あ あ あ あ あ
fonte
5

Como o caminho do arquivo pode se mover enquanto você assume que permanecerá o mesmo.

Mehrdad
fonte
4

A leitura e gravação em um sistema de arquivos pode envolver uma grande variedade de esquemas de buffer, manutenção do SO, gerenciamento de disco de baixo nível e uma série de outras ações em potencial. Portanto, as ações open()e close()servem como configuração para esses tipos de atividades ocultas. Diferentes implementações de um sistema de arquivos podem ser altamente personalizadas conforme necessário e ainda permanecem transparentes para o programa de chamada.

Se o sistema operacional não tivesse aberto / fechado, com readou write, essas ações de arquivo ainda precisariam executar qualquer inicialização, liberação / gerenciamento de buffer, etc, sempre. Isso exige muita sobrecarga para leituras e gravações repetitivas.

PeterT
fonte
Para não esquecer que open () e close () mantém também a posição no arquivo (para a próxima leitura ou próxima gravação). Portanto, no final, o método read () e write () precisariam de uma estrutura para lidar com todos os parâmetros ou argumentos para cada parâmetro. Criar uma estrutura é equivalente (site do programador) a um aberto, portanto, se o SO também souber sobre o aberto, teremos apenas mais vantagens.
Giacomo Catenazzi
1

O mantra do Unix é "oferecer uma maneira de fazer as coisas", o que significa "fatorar" em partes (reutilizáveis) para serem combinadas à vontade. Ou seja, nesse caso, separe a criação e a destruição de identificadores de arquivo do seu uso. Benefícios importantes vieram mais tarde, com pipes e conexões de rede (eles também são manipulados por meio de identificadores de arquivo, mas são criados de outras maneiras). Ser capaz de enviar identificadores de arquivos (por exemplo, passá-los para processos filhos como "arquivos abertos" que sobrevivem a um processo exec(2)e até a processos não relacionados através de um canal) só é possível dessa maneira. Especialmente se você quiser oferecer acesso controlado a um arquivo protegido. Então você pode, por exemplo, abrir/etc/passwd para escrever e passar isso para um processo filho que não pode abrir esse arquivo para escrever (sim, eu sei que esse é um exemplo ridículo, fique à vontade para editar com algo mais realista).

vonbrand
fonte