Dlaczego w systemach uniksowych musimy jawnie otwierać () i zamykać () `pliki, aby móc` czytać () `lub` pisać () `?

50

Dlaczego istnieje open()i close()istnieje w systemie plików Unix?

Czy system operacyjny nie mógł wykryć po raz pierwszy read()lub write()został wezwany i zrobić cokolwiek open()normalnie?

użytkownik5977637
źródło
22
Warto zauważyć, że ten model nie jest częścią systemu plików, ale raczej API Unixa . System plików zajmuje się tylko tym, gdzie na dysku idą bajty i gdzie umieścić nazwę pliku itp. Byłoby całkowicie możliwe, aby mieć alternatywny model, który opisujesz na systemie plików Unix, takim jak UFS lub ext4, byłby to jądro, aby przetłumaczyć te wywołania na odpowiednie aktualizacje dla systemu plików (tak jak teraz).
marcelm
18
Jak już powiedziano, myślę, że bardziej chodzi o to, dlaczego open()istnieje. „Czy system operacyjny nie mógł po prostu wykryć pierwszego odczytu () lub zapisu () i zrobić cokolwiek, co normalnie zrobiłby open ()?” Czy istnieje odpowiednia sugestia, kiedy nastąpi zamknięcie ?
Joshua Taylor,
7
Jak byś powiedział read()lub do write()którego pliku uzyskać dostęp? Prawdopodobnie przechodząc ścieżkę. Co się stanie, jeśli ścieżka pliku zmieni się podczas uzyskiwania do niego dostępu (między dwoma read()lub write()wywołaniami)?
user253751,
2
Poza tym zwykle nie masz kontroli dostępu włączonej read()i write()wyłączonej open().
Pavel Šimerda
6
@Johnny: Być może zapominasz, jak ograniczony był sprzęt w tamtych czasach. PDP-7, na którym po raz pierwszy został wdrożony Unix, miał (wg Google) maksymalnie 64 KB pamięci RAM i zegar 0,333 MHz - mniej niż zwykły mikrokontroler. Takie zbieranie śmieci lub używanie kodu systemowego do monitorowania dostępu do plików sprowadziłoby system na kolana.
jamesqf

Odpowiedzi:

60

Dennis Ritchie wspomina w «Ewolucja Unix Time-dzielenie systemowe» że openi closewraz z read, writei creatbyły obecne w systemie od samego początku.

Wydaje mi się, że system nie byłby openi closenie byłby nie do pomyślenia, jednak uważam, że skomplikowałoby to projekt. Na ogół chcesz wykonywać wiele wywołań odczytu i zapisu, a nie tylko jedno, i było to prawdopodobnie szczególnie prawdziwe na tych starych komputerach z bardzo ograniczoną pamięcią RAM, z której pochodzi system UNIX. Posiadanie uchwytu, który utrzymuje bieżącą pozycję pliku, upraszcza to. Jeśli readlubwritegdyby zwrócili uchwyt, musieliby zwrócić parę - uchwyt i własny status zwrotu. Część uchwytu pary byłaby bezużyteczna dla wszystkich innych połączeń, co spowodowałoby, że takie ustawienie byłoby niewygodne. Pozostawienie stanu kursora w jądrze pozwala poprawić wydajność nie tylko poprzez buforowanie. Wyszukiwanie ścieżki wiąże się również z pewnymi kosztami - posiadanie uchwytu umożliwia zapłacenie go tylko raz. Co więcej, niektóre pliki w światopoglądzie UNIX nawet nie mają ścieżki systemu plików (lub nie miały - teraz robią to z takimi rzeczami /proc/self/fd).

PSkocik
źródło
7
Koszt wyszukiwania ścieżki, sprawdzania uprawnień itp. Jest bardzo znaczący. Jeśli chcesz stworzyć system bez open/ close, na pewno zaimplementujesz takie rzeczy, jak /dev/stdoutzezwalanie na potokowanie.
Peter Cordes,
5
Myślę, że innym aspektem tego jest to, że możesz zachować ten uchwyt do tego samego pliku, gdy używasz wielu odczytów, kiedy plik jest otwarty. W przeciwnym razie mogą wystąpić przypadki, w których inny proces rozłącza się i ponownie tworzy plik o tej samej nazwie, a odczytywanie pliku we fragmentach może być całkowicie niespójne. (Niektóre z nich mogą zależeć również od systemu plików.)
Bruno,
2
Zaprojektowałem jeden bez close (); przekazujesz numer i-węzła i offset do read () i write (). Nie mogę obejść się bez open () bardzo łatwo, ponieważ tam właśnie żyje rozpoznawanie nazw.
Joshua,
3
@Joshua: Taki system ma zasadniczo inną semantykę, ponieważ deskryptory plików unix nie odnoszą się do plików (i-węzłów), ale do otwierania opisów plików , których może być wiele dla danego pliku (i-węzła).
R ..
@Joshua, po prostu przemianowany open()na get_inode()i sprawił, że cały system jest bardziej sztywna (niemożliwy do odczytu / zapisu do tego samego pliku w kilku miejscach jednocześnie).
vonbrand
53

Następnie wszystkie wywołania readi writemusiałyby przekazywać te informacje o każdej operacji:

  • nazwa pliku
  • uprawnienia do pliku
  • czy dzwoniący dołącza, czy tworzy
  • czy program wywołujący skończył pracę z plikiem (aby odrzucić nieużywane bufory odczytu i zapewnić, że bufory zapisu naprawdę zakończą pisanie)

Czy uważa niezależnych połączeń open , read, writei closebyć prostsze niż jednofunkcyjnych I / O komunikat opiera się na filozofii projektowania. Programiści uniksowi postanowili używać prostych operacji i programów, które można łączyć na wiele sposobów, zamiast jednej operacji (lub programu), która robi wszystko.

Thomas Dickey
źródło
Dzwoniący musieliby również w większości przypadków określić pożądane przesunięcie w pliku. Istnieją pewne sytuacje (np. Protokół UDP, który umożliwia dostęp do danych), w których każde żądanie niezależnie identyfikuje plik i przesunięcie może być pomocne, ponieważ eliminuje potrzebę utrzymywania stanu przez serwer, ale ogólnie wygodniej jest mieć serwer śledzić pozycję pliku. Ponadto, jak zauważono w innym miejscu, kod, który zamierza zapisywać pliki, często musi wcześniej je zablokować, a następnie zablokować; czesanie tych operacji za pomocą otwierania / zamykania jest bardzo wygodne.
supercat
5
„Plik” może przede wszystkim nie mieć nazwy ani uprawnień; readi writenie są ograniczone do plików znajdujących się w systemie plików, i to jest podstawowa decyzja projektowa w Uniksie, jak wyjaśnia pjc50.
reinierpost
1
Także gdzie w pliku do odczytu / zapisu - początek, koniec lub dowolna pozycja (zwykle bezpośrednio po zakończeniu ostatniego odczytu / zapisu) - jądro śledzi to dla ciebie (w trybie przekieruj wszystkie zapisy na koniec pliku, w przeciwnym razie pliki są otwierane z pozycją na początku i przechodzą z każdym odczytem / zapisem i można je przenosić lseek)
Random832
51

Koncepcja uchwytu pliku jest ważna ze względu na wybór projektu przez UNIX, że „wszystko jest plikiem”, w tym rzeczy, które nie są częścią systemu plików. Takie jak napędy taśm, klawiatura i ekran (lub typ teletechniczny!), Czytniki perforowanych kart / taśm, połączenia szeregowe, połączenia sieciowe oraz (kluczowy wynalazek UNIX) bezpośrednie połączenia z innymi programami zwanymi „potokami”.

Jeśli spojrzysz na wiele prostych, standardowych narzędzi UNIX, takich jak grep, zwłaszcza w ich oryginalnych wersjach, zauważysz, że nie zawierają one wywołań, open()a close()tylko readi tylko write. Uchwyty plików są ustawiane przez powłokę poza programem i przekazywane podczas uruchamiania. Dlatego program nie musi dbać o to, czy zapisuje do pliku, czy do innego programu.

Jak również open, że inne sposoby na uzyskanie deskryptory plików są socket, listen, pipe, dup, i bardzo Heath Robinson mechanizm wysyłania deskryptorów plików na rurach: https://stackoverflow.com/questions/28003921/sending-file-descriptor-by-linux -gniazdo elektryczne

Edycja: kilka notatek z wykładu opisujących warstwy pośrednie i sposób, w jaki pozwala to rozsądnie działać O_APPEND. Pamiętaj, że przechowywanie danych i-węzłów w pamięci gwarantuje, że system nie będzie musiał ponownie pobierać ich do następnej operacji zapisu.

pjc50
źródło
1
Ponadto creat, i listennie tworzy fd, ale kiedy (i jeśli) przychodzi żądanie podczas nasłuchiwania, accepttworzy i zwraca fd dla nowego (podłączonego) gniazda.
dave_thompson_085
18
To jest poprawna odpowiedź. Słynny (mały) zestaw operacji na deskryptorach plików to ujednolicony interfejs API dla wszystkich rodzajów zasobów, które wytwarzają lub zużywają dane. Ta koncepcja jest OGROMNIE udana. Ciąg może mieć składnię określającą typ zasobu wraz z faktyczną lokalizacją (adres URL czy ktoś?), Ale kopiowanie ciągów, wokół których zajmuje kilka procent dostępnej pamięci RAM (co to było na PDP 7? 16 kB?) Wydaje się nadmierne .
Peter - Przywróć Monikę
Być może byłoby tak, gdyby wywołania niskiego poziomu i powłoka zostały opracowane w tym samym czasie. Ale pipezostał wprowadzony kilka lat po rozpoczęciu rozwoju Uniksa.
Thomas Dickey,
1
@Thomas Dickey: To tylko pokazuje, jak dobry był oryginalny projekt, ponieważ pozwolił na proste rozszerzenie do rur & c :-)
jamesqf
Ale podążając za tą argumentacją, ta odpowiedź nie dostarcza nic nowego.
Thomas Dickey,
10

Odpowiedź brzmi nie, ponieważ open () i close () odpowiednio tworzą i niszczą uchwyt. Są chwile (cóż, cały czas, naprawdę), w których możesz chcieć zagwarantować, że jesteś jedynym dzwoniącym o określonym poziomie dostępu, ponieważ inny dzwoniący (na przykład) zapisujący do pliku, który analizujesz, może nieoczekiwanie opuścić aplikacja w nieznanym stanie lub prowadząca do blokady lub impasu, np. lemat „Philosophers”.

Nawet bez tego rozważenia należy wziąć pod uwagę wpływ na wydajność; close () pozwala systemowi plików (jeśli jest to właściwe lub jeśli zostałeś do niego powołany) opróżnić zajmowany bufor, co jest kosztowną operacją. Kilka kolejnych edycji strumienia w pamięci jest o wiele bardziej wydajnych niż kilka zasadniczo niezwiązanych cykli odczytu-zapisu-modyfikacji w systemie plików, który, jak wiadomo, istnieje w odległości pół świata rozproszonej w centrum danych o wartości dużej pamięci masowej o dużych opóźnieniach. Nawet w przypadku pamięci lokalnej pamięć jest zwykle o wiele rzędów wielkości szybsza niż pamięć masowa.

msaunier
źródło
7

Open () oferuje sposób blokowania plików podczas ich używania. Gdyby pliki były automatycznie otwierane, odczytywane / zapisywane, a następnie ponownie zamykane przez system operacyjny, nic nie powstrzymałoby innych aplikacji zmieniających te pliki między operacjami.

Chociaż można to zarządzać (wiele systemów obsługuje niewyłączny dostęp do plików) dla uproszczenia, większość aplikacji zakłada, że ​​otwarte przez nich pliki się nie zmieniają.

あ あ あ あ あ あ あ あ あ あ あ あ あ あ あ あ あ あ あ あ あ あ あ あ あ あ あ あ あ あ あ
źródło
5

Ponieważ ścieżka pliku może się poruszać, zakładając, że pozostanie niezmieniona.

Mehrdad
źródło
4

Odczytywanie i zapisywanie w systemie plików może obejmować wiele różnych schematów buforowania, porządkowanie systemu operacyjnego, zarządzanie dyskami niskiego poziomu i wiele innych potencjalnych działań. Więc działania open()i close()służą jako konfiguracja dla tego rodzaju działań pod maską. Różne implementacje systemu plików mogą być w razie potrzeby wysoce dostosowywane i nadal pozostają przezroczyste dla programu wywołującego.

Jeśli system operacyjny nie miałby otwierać / zamykać, to przy pomocy readlub write, te działania na plikach nadal musiałyby wykonywać wszelkie inicjalizacje, opróżnianie bufora / zarządzanie itp. Za każdym razem. Jest to dużo narzutu, który należy nakładać na powtarzalne odczyty i zapisy.

PeterT
źródło
Nie zapominaj, że open () i close () zachowuje również pozycję w pliku (do następnego odczytu lub następnego zapisu). Na koniec więc read () i write () potrzebowałyby struktury do obsługi wszystkich parametrów lub argumentów dla każdego parametru. Tworzenie struktury jest równoważne (strona programisty) z otwartym, więc jeśli system operacyjny wie także o otwartym, mamy tylko dodatkowe zalety.
Giacomo Catenazzi
1

Mantra uniksowa to „oferować jeden sposób robienia rzeczy”, co oznacza „faktoring” na (wielokrotnego użytku) elementy, które można dowolnie łączyć. Tj. W tym przypadku oddziel tworzenie i niszczenie uchwytów plików od ich użycia. Ważne korzyści pojawiły się później, dzięki potokom i połączeniom sieciowym (są one również obsługiwane za pomocą uchwytów plików, ale są tworzone w inny sposób). Możliwość przesyłania uchwytów plików (np. Przekazywanie ich do procesów potomnych jako „otwartych plików”, które przetrwają exec(2), a nawet do niepowiązanych procesów przez potok) są możliwe tylko w ten sposób. Szczególnie jeśli chcesz zaoferować kontrolowany dostęp do chronionego pliku. Możesz więc np. Otworzyć/etc/passwd do pisania i przekaż to procesowi potomnemu, który nie może otworzyć tego pliku do pisania (tak, wiem, że to absurdalny przykład, możesz edytować coś bardziej realistycznego).

vonbrand
źródło