Linux - prezentacja

System plików


Spis treści

  1. Miejsce systemu plików w jądrze Linuksa
  2. Wirtualny system plików
  3. System plików ext2
    1. Logiczna struktura dysku
    2. Obsługa i-węzłów w pamięci
    3. Obsługa i-węzłów i bloków na dysku
  4. Podręczna pamięć buforowa
    1. Struktury danych
    2. Budowa nagłówka bufora
    3. Czytanie bloków dyskowych - funkcje bread() i breada()
    4. Pisanie bloków dyskowych - demon bdflush
    5. Scenariusze dostępu - getblk() i brelse()
    6. Strategia odzyskiwania buforów
    7. Zalety i wady podręcznej pamięci buforowej
  5. Katalogi w Linuxie
    1. Podręczna pamięć buforowa nazw katalogów
    2. Tłumaczenie nazwy ścieżkowej na numer i-węzła
  6. Montowanie i demontowanie systemów plików
  7. Struktury danych procesu związane z systemem plików
  8. Struktura pliku zwykłego
    1. Struct file
  9. Dostęp do danych w pliku - funkcja bmap()
  10. Interfejs systemu plików
    1. Tworzenie i otwieranie pliku
    2. Czytanie z pliku
    3. Pisanie do pliku
    4. Obsługa łączy nienazwanych
    5. Zamykanie pliku
    6. Blokowanie plików
  11. System plików proc



redakcja: Jarosław Staniek, MIM UW INF3 - jaroslaw.staniek@students.mimuw.edu.pl

 

< Spis treści

1. Miejsce systemu plików w jądrze Linuksa.




Jako system plików rozumieć będziemy zespół mechanizmów służący do manipulacji danymi użytkownika, pozwalający w "bezpieczny" sposób na ich:

Przez bezpieczeństwo rozumiemy zarówno nadawanie (ograniczanie) praw użytkownika do zmian danych, jak i odporność na awarie. Ta część sytemu operacyjnego ma największy wpływ na komfort pracy, ale i wydajność całego środowiska, ponieważ jest ona wykorzystywana również przez inne części jądra (ładowanie programów, swap, dostęp do urządzeń zewnętrznych).


Należy wyraźnie odróżnić system plików w takim rozumieniu od fizycznej reprezentacji danych na dyskach (fat, ext2), tutaj traktujemy system plików jako logiczny twór udostępniający pewne operacje, a nie jako konkretny format zapisu na nośnikach.


Popularność Linuksa wynika między innymi z jego elastyczności, co w przypadku systemu plików przejawia się w możliwości stosowania tak różnych sposobów dostępu do danych jak np.: ext2, vfat, nfs, smbfs. Efekt ten uzyskano poprzez wprowadzenie warstwowej budowy systemu plików, gdzie kolejne warstwy odwołują się tylko do warstwy poniżej, a ostatnia odpowiedzialna jest za komunikację ze sprzętem. Pozwala to na wygodną manipulację "logicznymi" obiektami oraz wygodne modyfikowanie wybranych części kodu.


Budowę systemu plików Linuksa można schematycznie przedstawić:



Użytkownik (jego procesy) otrzymają dostęp do swoich danych poprzez pliki, czyli ciągi bajtów (o swobodnym dostępie) opatrzonych nazwą, które mogą przechowywać w hierarchicznej strukturze katalogów. Wszystkie pliki w systemie umieszczone są w jednym drzewie katalogów (a nie tak jak w DOSie wiele), którego liście zawierają pliki, zaś węzły wewnętrzne to katalogi. Operacja na plikach wykonuje się przy użyciu funkcji systemowych ( open(), read(), write(), chdir()...) stanowiących interfejs do sytemu plików, nie zależny od rodzaju stosowanego nośnika danych.


Poniżej funkcji systemowych znajduje się warstwa wirtualnego systemu plików (VFS), która odpowiada za reprezentację plików w jądrze. Zastosowano jednolite podejście polegające na powiązaniu każdego pliku z i-węzłem, zawierającym informacje o pliku i wskaźniki do funkcji pozwalających na operowanie plikiem (dla różnych rodzajów nośnika lub sposobu zapisu wystarcza więc zmieniać tylko wskaźniki do właściwych implementacji).


Wydajność systemu plików Linuksa podnosi kolejna warstwa: pamieć buforowa. Obsługuje ona żądania czytania i pisania bloków bajtów z fizycznych urządzeń ( bread(), bwrite() ) udostępniając jednolity interfejs dostępu do nich. Ważniejszym zadaniem pełnionym przez pamięć buforową jest jednak umożliwianie opóźnionego zapisu i czytania z wyprzedzeniem, czyli ogólnie ograniczanie rzeczywistej ilości operacji dyskowych wykonywanych przez jądro.


Ostatnia warstwa to podsystem wejścia-wyjścia który implementuje niskopoziomowe odwołania do fizycznych urządzeń.


Podsumowując, system plików umożliwia użytkownikowi wygodny sposób dostęp do fizycznej reprezentacji jego danych. Ze względów bezpieczeństwa (danych i systemu jako całości) oraz faktu że inne części systemu operacyjnego również korzystają z sytemu plików wszelkie usługi związane z operacjami na fizycznych danych powinny być dostarczane przez jądro i wykonywane w jego kontekście.



autor: Tomasz Zieliński
 

< Spis treści

2. Wirtualny system plików

 

Informacje ogólne

W świecie systemów operacyjnych występuje olbrzymia różnorodność systemów plików. Właściwie każdy system operacyjny wprowadza przynajmniej jeden nowy system plików, zazwyczaj całkowicie niekompatybilny z dotychczas istniejącymi. Większość systemów operacyjnych umożliwia współpracę jedynie ze swoimi systemami plików, ewentualnie kilkoma dodatkowymi. Aby uniknąć tego problemu jednym z zamierzeń twórców systemu Linux było umożliwienie łatwej komunikacji z dowolnymi systemami plików. W tym celu wprowadzono mechanizm nazywany wirtualnym systemem plików, po części wzorowany na analogiczym mechaniźmie używanym w innych systemach unixowych.

Wirtualny system plików (z ang. VFS - Virtual File System lub Virtual Filesystem Switch) jest warstwą jądra umożliwiającą programom użytkowym dostęp do danych niezależnie od formatu ich przechowywania. Jest to możliwe dzięki temu, że większość systemów plików udostępnia zbliżony zestaw operacji różniących się często tylko wewnętrzną implementacją. Poprzez udostępnienie programom obsługi poszczególnych systemów plików jednolitego interfejsu VFS umożliwia różnym systemom plików wspólną pracę w ramach tego samego systemu operacyjnego ukrywając przed użytkownikiem ich rzeczywistą strukturę. Sprawia to, że użytkownik nie musi troszczyć się o to z jakim systemem plików aktualnie współpracuje.

Proces użytkowy w momencie, gdy chce uzyskać dostęp do jakiegoś pliku wywołuje jedną ze standardowych funkcji systemowych (np. open, read). Następnie żądanie to jest przekazywane do wirtualnego systemu plików i w zależności od rodzaju systemu plików w jakim jest zapisany dany plik VFS wywołuje funkcję odpowiadającą temu właśnie systemowi plików. Mechanizm ten jest wizualnie odwzorowany na rysunku w rozdziale "Miejsce systemu plików w jądrze Linuxa".

Jedną z istotnych cech VFS jest fakt, że mimo iż udostępnia on na zewnątrz ujednolicony interfejs, w rzeczywistości wewnętrzna implementacja każdego z systemów plików może być dowolna. Dzięki temu możemy korzystać z bardzo różnorodnych systemów plików, w szczególności takich, które nie korzystają z żadnego fizycznego urządzenia, jak na przykład system plików proc. Innym ciekawym systemem plików jest NFS - Network File System (z ang. Sieciowy System Plików), umożliwiający dostęp do danych przechowywanych na innych komputerach w identyczny sposób jak w przypadku dysku lokalnego.



Rejestracja systemu plików w jądrze


Pracując w Linuxie możemy używać systemów plików aktualnie zarejestrowanych w systemie. Aby dowiedzieć się, jakie systemy plików są w danej chwili zarejestrowane należy użyć polecenia

    cat /proc/filesystems

Aby dodać do jądra obsługę nowego systemu plików należy go zarejestrować używając funkcji zawartej w fs/super.c:

int register_filesystem(struct file_system_type * fs)

Każdemu zarejestrowanemu systemowi plików jest przyporządkowana struktura file_system_type zdefiniowana w include/linux/fs.h:

struct file_system_type {
	const char *name;
	int fs_flags;
	struct super_block *(*read_super) (struct super_block *, void *, int);
	struct file_system_type *next;
}
gdzie:
name : nazwa systemu plików
flags : flagi (np. FS_REQUIRES_DEV-potrzebuje urządzenia, FS_NO_DCACHE-nie używana jest pamięć podręczna)
read_super : funkcja wywoływana przy montowaniu systemu plików
next : do wewnętrznego zastosowania przez VFS, powinno być zainicjowane na NULL

Efektem wywołania register_filesystem jest dodanie podanej struktury do listy zarejestrowanych systemów plików (przechowywanej w jądrze) o początku wskazywanym przez

static struct file_system_type *file_systems

.
Funkcja read_super zawarta w file_system_type jest inna dla każdego systemu plików. Wywoływana jest w momencie żądania zamontowania konkretnego systemu plików. Jej zadaniem jest stworzenie bloku specjalnego zawierającego informacje na temat jednej instancji systemu plików. Funkcja jako parametry otrzymuje następujące informacje:

struct super_block *sb : struktura bloku specjalnego, Jest ona częściowo zainicjowana przez VFS, pozostałe pola musi wypełnić read_super
void *data : opcjonalne opcje montowania, standardowo przekazywane jako ciąg znaków ASCII
int silent : flaga oznaczająca czy informować o błędach podczas montowania

read_super sprawdza czy podane w sb urządzenie zawiera system plików odpowiedni dla tej funkcji, jeżeli tak, zwraca wskaźnik do uzupełnionego bloku specjalnego, w przeciwnym wypadku zwraca NULL.

Struktura super_block i operacje na niej



Blok specjalny (struct super_block) zawiera różnorodne informacje na temat zamontowanego systemu plików, m.in. urządzenie obsługujące dany system plików, rodzaj systemu plików, rozmiar bloku, struktury do synchronizacji dostępu, czas dokonanej ostatnio zmiany a także informacje specyficzne dla konkretnej implementacji. Jego pełna struktura jest zdefiniowana w include/linux/fs.h.

Najciekawszym elementem super_block jest pole s_op zawierające kolejny poziom implementacji systemu plików, czyli operacje na bloku specjalnym.

Funkcje dostępne na bloku specjalnym są zawarte w strukturze:
struct super_operations {
    void (*read_inode) (struct inode *);
    void (*write_inode) (struct inode *);
    void (*put_inode) (struct inode *);
    void (*delete_inode) (struct inode *);
    int (*notify_change) (struct dentry *, struct iattr *);
    void (*put_super) (struct super_block *);
    void (*write_super) (struct super_block *);
    int (*statfs) (struct super_block *, struct statfs *, int);
    int (*remount_fs) (struct super_block *, int *, char *);
    void (*clear_inode) (struct inode *);
    void (*umount_begin) (struct super_block *);
};
read_inode - odczytuje z zamontowanego systemu plików inode o numerze zawartym w inode->i_ino (pole to jest zainicjowane przez VFS). Dodatkowo ustawia pole inode->i_op na odpowiedni zestaw funkcji operujący na inode.
write_inode - zapisuje strukturę inode na urządzenie.
put_inode - wywoływane przez VFS gdy inode przestaje być potrzebne. Jeżeli wartość i_nlink wyniesie zero, nastąpi fizyczne usunięcie pliku i zwolnienie zajmowanych przez niego bloków
delete_inode - usunięcie inode
notify_change - informuje, że nastąpiła zmiana w strukturze inode. Przydatne np. przy implementacji NFS, kiedy zmiana inode lokalnego powinna pociągnąć za sobą zmianę na zdalnym systemie
put_super - wywoływana gdy VFS chce zwolnić blok specjalny np. przy odmontowywaniu. Dodatkowo jeżeli implementacja obsługi danego systemu plików była załadowana jako moduł, jest zmniejszony licznik jego użyć.
write_super - zapisanie bloku specjalnego na dysk. Używana jest m.in. do synchronizowania urządzenia (nie dotyczy systemów plików tylko do odczytu). Również w momencie odmontowywania systemu plików, o ile pole s_dirt struktury super_block jest ustawione, wywoływana jest ta funkcja.
statfs - wywoływana w celu pobrania statystyk systemu plików. Struktura statfs zawiera informacje o rozmiarze bloków, liczbie wolnych bloków, liczbie plików itp.
remount_fs - wywoływana w momencie przemontowywania systemu plików. Zazwyczaj jej rola sprowadza się do zmiany statusu systemu plików oraz odtworzenia jego spójności.
clear_inode - wywoływane przez system plików kiedy struktura inode przestaje być potrzebna.
umount_begin - wywoływana w momencie gdy chcemy odmontować system plików.

Funkcje te pozwalają na operacje na poszczególnych strukturach inode, na zapisywanie bloku specjalnego oraz na uzyskiwanie informacji na temat systemu plików. To właśnie ta warstwa zajmuje się przekształcaniem konkretnej reprezentacji bloku specjalnego i struktur inode do ujednoliconej postaci w pamięci i na odwrót. Dokładnie rzecz biorąc struktury inode i super_block (albo ich odpowiedniki) nie muszą fizycznie istnieć, implementacja danego systemu plików może tworzyć je w zależności od wewnętrznych potrzeb (np. system plików proc).


Struktura inode i operacje na niej


Każdemu używanemu aktualnie plikowi w dowolnym systemie plików odpowiada przechowywana w pamięci struktura inode. Zawiera ona szczegółowe informacje o tym pliku, m.in. o właścicielu, prawach dostępu, rozmiarze, czasie ostatniej modyfikacji oraz adresy bloków z danymi. Dodatkowo zawiera pole i_sb będące wskaźnikiem na odpowiedni blok specjalny, opisany wcześniej, oraz pole i_op typu struct inode_operations zawierające wskaźnik na zbiór funkcji umożliwiających manipulacje na strukturze inode. Pełna struktura inode jest zawarta w pliku include/linux/fs.h.

Struktura inode_operations zawiera zestaw funkcji umożliwiających operacje na strukturze inode w sposób odpowiedni dla danego systemu plików:
struct inode_operations {
    struct file_operations * default_file_ops;
    int (*create) (struct inode *,struct dentry *,int);
    struct dentry * (*lookup) (struct inode *,struct dentry *);
    int (*link) (struct dentry *,struct inode *,struct dentry *);
    int (*unlink) (struct inode *,struct dentry *);
    int (*symlink) (struct inode *,struct dentry *,const char *);
    int (*mkdir) (struct inode *,struct dentry *,int);
    int (*rmdir) (struct inode *,struct dentry *);
    int (*mknod) (struct inode *,struct dentry *,int,int);
    int (*rename) (struct inode *, struct dentry *,
            struct inode *, struct dentry *);
    int (*readlink) (struct dentry *, char *,int);
    struct dentry * (*follow_link) (struct dentry *, struct dentry *, unsigned int);
    int (*readpage) (struct file *, struct page *);
    int (*writepage) (struct file *, struct page *);
    int (*bmap) (struct inode *,int);
    void (*truncate) (struct inode *);
    int (*permission) (struct inode *, int);
    int (*smap) (struct inode *,int);
    int (*updatepage) (struct file *, struct page *, unsigned long, unsigned int, int);
    int (*revalidate) (struct dentry *);
};
default_file_ops - jest to wskaźnik na strukturę typu file_operations, która zawiera zbiór operacji dostępnych na otwartych plikach.
create - używana przy fizycznym tworzeniu plików. Wyszukuje wolną strukturę inode poprzez get_empty_inode, wypełnia ją danymi odpowiednimi dla konkretnego systemu plików, uzupełnia strukturę katalogu.
lookup - odnalezienie dla podanego pliku struktury inode. Nazwa pliku jest podana w strukturze dentry. Po odnalezieniu jest wywoływana funkcja d_add() wstawiająca inode do dentry. Dodatkowo w strukturze inode zwiększany jest licznik odwołań i_count. Jeżeli nie odnaleziono odpowiedniej nazwy do struktury dentry jest wstawiany inode równy NULL. VFS w sposób specjalny traktuje wywołanie tej funkcji dla nazwy "..". W momencie przejścia przez punkt zamontowania systemu plików wywołuje odpowiednią implementację.
link - używana przy tworzeniu tzw. sztywnych łączy (hard links). Przed jej wywołaniem sprawdza się czy obydwie struktury dentry znajdują się na tym samym urządzeniu i czy proces bieżący ma prawo zapisu do danego katalogu.
unlink - używana do usunięcia inode. Przed wywołaniem sprawdza się czy bieżący proces ma odpowiednie uprawnienia.
symlink - używana przy tworzeniu łącza symbolicznego.
mkdir - używana przy tworzeniu podkatalogów. Funkcja po sprawdzeniu możliwości utworzenia podkatalogu w danym miejscu musi przydzielić nową strukturę inode oraz blok na fizycznym nośniku, oraz inicjuje wpisy dla pozycji "." i "..". Podobnie jak w przypadku większości funkcji VFS sprawdza uprawnienia bieżącego procesu.
rmdir - używana przy usuwaniu podkatalogów. Funkcja sprawdza czy aktualnie usuwany katalog jest pusty oraz czy nie jest przypadkiem używany przez jakiś proces. Standardowo sprawdzane są prawa dostępu.
mknod - używana przy tworzeniu urządzeń blokowych lub znakowych, łącz nazwanych FIFO lub gniazd.
rename - używana przy zmianie nazwy pliku.
readlink - używana przy odczytywaniu łączy symbolicznych. Zadaniem jej jest umieszczenie w buforze podanym przy wywołaniu ścieżki do pliku, na który wskazuje łącze. Przy przekroczeniu rozmiaru bufora ścieżka jest ucięta do zakresu podanego przy wywołaniu. Funkcja sprawdza czy podana struktura dentry zawiera łącze symboliczne, jeżeli nie, zwraca błąd.
follow_link - rozwinięcie łącza symbolicznego - funkcja umieści w docelowej strukturze dentry pozycję, na którą wskazuje łącze.
bmap - używana do zamapowania bloku pliku pod adres w przestrzeni adresowej użytkownika.
truncate - skrócenie, ale też wydłużenie pliku do dowolnej( dozwolonej ) wielkości. W podanej strukturze inode jest już zawarta nowa długość pliku. Funkcja dodatkowo zwalnia bloki nie używane przez plik.
permission - sprawdza czy są spełnione podane prawa dostępu. Jako argument może otrzymać kombinacje wartości MAY_READ, MAY_WRITE lub MAY_EXEC.
smap - używana przy tworzeniu plików wymiany w systemie plików UMSDOS

Inne operacje na systemach plików


W pliku fs/super.c występują też inne funkcje służące do obsługi systemów plików. Przykładowe to:

int unregister_filesystem(struct file_system_type * fs)

Funkcja wyrejestrowujaca system plików

struct file_system_type *get_fs_type(const char *name)

Funkcja podaje dane na temat systemu plików na podstawie jego nazwy oraz globalnej listy systemów plików file_systems

int get_filesystem_list(char * buf)

Funkcja wpisuje do bufora buf informacje tekstowe na temat poszczególnych systemów plików( nazwa, flaga FS_REQUIRES_DEV ), zwraca liczbę elementów.


 

< Spis treści

3. System plików ext2

3.1. Logiczna struktura dysku.


W systemie plików Ext2 dysk jest podzielony na blok startowy oraz grupy bloków.
  1. Blok startowy.
  2. System plików zaczyna sie od bloku startowego, w którym mogą być przechowywane informacje wykorzystywane przez system operacyjny podczas uruchamiania. Istnieje on niezależnie od tego, czy będzie używany podczas ładowania systemu, czy też nie.
  3. Grupy bloków.

  4. W skład każdej grupy bloków wchodzą:
    Część danych w bloku specjalnym dotyczy całego systemu plików, co w razie potrzeby może umożliwić jego odtworzenie. Pozostałe informacje dotyczą grupy bloków, do której blok specjalny przynależy, są to m.in.: liczba i-węzłów oraz bloków w grupie, czas ostatniego zapisu.
    W deskryptorach grupy znajduja się wiadomości na temat grup bloków, m.in. liczba wolnych bloków i i-węzłów w grupie, liczba katalogów w grupie (dzięki tej ostatniej informacji katalogi są rozmieszczane po grupach równomiernie).
    Bloki zawierające tablicę i-węzlow zawierają i-węzły. Każdemu plikowi odpowiada dokładnie jeden i-węzeł, mający swój unikatowy (w danym urządzeniu) numer, oraz pola wskazujące na bloki danych, pośrednie bloki danych, podwójnie pośrednie bloki danych i potrójnie pośrednie bloki danych, które wskazują na bloki z danymi pliku lub na bloki w których znajdują się informacje gdzie danych szukać. Poza tym w i-węźle znajduja sie wiadomości o prawach dostępu do pliku.
    W blokach danych zapisane są pliki oraz bloki pośrednie.

 

< Spis treści

3.2. Obsługa i-węzłów w pamięci.

Przechowywane w pamięci kopie i-węzłów posiadają dodatkowe - w stosunku do i-węzłów na dysku - informacje. Pole i_lock określa, czy została założona blokada; pole i_count mówi, jaka jest liczba przydzielonych procesom wcieleń plików (licznik odwołań); na podstawie pola i_dirt można stwierdzić, czy i-węzeł znajdujący sie w pamięci był modyfikowany. Każdy i-węzeł znajduje się na dwukierunkowej liście cyklicznej, w związku z tym posiada pola i_next, i_prev, które wskazujś odpowiednio na jego następnika i poprzednika. Poza tym i-węzły są przechowywane w tablicy mieszającej (dzięki temu zwiększą się dostęp do i-węzła); miejsce w tablicy wyznacza numer urządzenia oraz numer i-węzła. Kolizje obsługiwane są przy pomocy listy dwukierunkowej (pola i_hash_next, i_hash_prev).
Są dwa stany i-węzła: wolny oraz aktywny. W tablicy mieszającej znajdują się tylko i_węzły aktywne, czyli takie, które są aktualnie wykorzystywane przez jakiś proces. Pozostałe są nazwane wolnymi - mają licznik odwolań równy zero.
Poniższe funkcje slużą do operowania i-węzłami w pamięci:

Funkcja i_get() służy do przydziału struktury i_węzła znajdującej się w pamięci. Odbywa się to według następujących zasad:

  1. Korzystając z numeru i-węzła oraz numeru urządzenia jądro przegląda tablicę mieszającą.
  2. Jeśli i-węzeł został znaleziony, czyli znajduje się już w pamięci,to zostaje zwiększony jego licznik odwołań o 1.
  3. W przeciwnym przypadku sprawdza się czy istnieje już kopia szukanego i-węzła na liście wolnych. Jeśli tak, to jest on przenoszony z listy wolnych do tablicy mieszającej, a jego licznik odwołań zostaje zwiększony o 1. Jeśli nie, to jądro wybiera wolną strukturę z listy, zapisuje w niej nowy numer i-węzła i systemu plików, przenosi do tablicy mieszającej oraz wczytuje odpowiedni i-węzeł z dysku.
    Gdy lista wolnych jest pusta, wówczas i_get() zwraca błąd.
W celu wczytania i-węzła z dysku jądro oblicza numer bloku oraz jego w nim polożenie:
numer bloku = [X/Y] + (numer bloku zawierającego początek listy i-węzłow)
położenie w bloku = (X mod Y) * (rozmiar i-węzła na dysku)
Gdzie:
X=numer i-węzła -1;
Y=liczba i-węzłów w bloku;
symbol [x] oznacza cześć całkowitą liczby x.
Powyższe wzory zostały podane za M. Bachem.

Funkcją i_put() można zwolnić i-węzeł znajdujżcy się w pamięci:

  1. Zmniejszamy licznik odwołań danego i-węzła o 1.
  2. Jeśli licznik odwołań wynosi 0, to sprawdzamy czy liczba dowiazań do pliku jest równa 0. Jeśli tak, zwalniane są wszystkie bloki danych związanych z plikiem oraz i-węzeł. Wprzeciwnym przypadku sprawdzamy pole i_dirt, w którym zapisana jest informacja, czy i-węzeł byl modyfikowany i jeśli byly zmiany, to zapisujemy i-węzeł na dysk.
Funkcja namei() przekształca nazwę scieżkową pliku na i-węzeł. W tym celu wykorzystuje strukturę katalogu - czyli pliku, w ktorym zapisane są pary: numer i-węzła i nazwa pliku znajdującego się w tym katalogu.
  1. Na początku jądro sprawdza, czy nazwa scieżkowa pliku rozpoczyna się od korzenia, czy od symbolu bieżącego katalogu, a następnie funkcja i_get() pobiera odpowiednią kopie i-węzeła z pamięci; kopia ta jest przechowywana na zmiennej XXX.
  2. Dopóki w nazwie scieżkowej znajdują się jeszcze składowe, pobiera składową, a następnie przgląda zawierający ją katalog (jego i-węzeł mamy na zmiennej), w poszukiwaniu składowej. Jeśli jej nie znajdzie, to koniec. W przeciwnym przypadku pobiera numer jej i-węzła, zwalnia i-węzeł znajdujący się na zmiennej XXX ( funkcja i_put() ), pobiera i-węzeł z pamięci dla składowej (numer dla niej znamy z katalogu), który będzie odtąd pamiętany na zmiennej XXX.
  3. Nie ma już więcej składowych w nazwie scieżkowej, a zatem szukany i-węzeł znajduje się na zmiennej XXX.

 

< Spis treści


3.3/4. Obsługa i-węzłów i bloków na dysku.


Spis treści
 1. Informacje ogólne
 2. Algorytm przydziału i-węzła na dysku - funkcja ext2_new_inode()
 3. Algorytm zwolnienia i-węzła z dysku - funkcja ext2_free_inode()
 4. Algorytm przydziału bloku dyskowego - funkcja ext2_new_block()
 5. Algorytm zwalniania bloku dyskowego - funkcja ext2_free_block()
 

1. Informacje ogólne

1 plik reprezentowany jest przez 1 i-węzeł.

W EXT2:
I-węzeł na dysku składa się z części pól takich jak i-węzeł w pamięci:
typ,id właściciela,rozmiar pliku,czas dostępu,id grupy,il.linków,
il.bloków,
tablica z adresami bloków dyskowych: (ilości podane w ext2_fs.h)
12 adresów bloków zawierających dane
1 adres bloku zawierającego adresy bloków zaw.dane
1 adr.bl.zaw.adr. j.w.
1 adr.bl.zaw.adr. j.w.
+prawa dostępu do pliku.

Wygląd partycji EXT2:

Boot sector / grupa 1 / grupa 2 / grupa 3 / ... / grupa i / ...

Gdzie grupa to:
super_blok / Deskryptory wszystkich grup / Bitmapa zajętości bloków grupy /
Bitmapa zajętości i-węzłów grupy / tablica i-węzłów / bloki z danymi...

Fizycznie kazda grupa zawiera informacje o całym systemie plików:
super_blok - zawiera informacje o systemie jak np.liczba wolnych bloków
lub i-węzłów,oraz pole s_lock - semafor binarny służący do blokowania
dostępu do danych grupy (patrz funkcje przydziału i_wezła i bloku).
Do tego mamy deskryptory wszystkich grup.

Deskryptor grupy zawiera informacje o
-nr bloku z bitmapą zajętości bloków grupy
-nr bloku z bitmapą zajętości i-węzłow
-nr pierwszego bloku z tabl.i-węzłów
-liczniki wolnych zasobów.
 

2. Algorytm przydziału i-węzła na dysku - funkcja ext2_new_inode()

1. Sprawdzenie,czy pliku nie wstawiamy do nieistniejącego katalogu.
  Jeśli tak - funkcja zwraca NULL,zmienna err przyjmuje odp.wartość.

2. Pobranie pustego i-węzła z pamięci.Jeśli nie ma wolnego -
  wyjście j.w.

3. Założenie blokady na super_blok

4. Szukanie i-węzła dyskowego do przydziału.
  Czy przydzielamy i-wezeł dla katalogu?
  Nie - patrz 5.
  WPP: przejdź liniowo grupy znajdując tę o największej
  ilości wolnych i-wezłów.Jeśli znaleziono - patrz 9,
  WPP - patrz 8.

5. Przydzielenie i-w.dla pliku.
  Czy w grupie,w której jest katalog tego pliku są jakieś wolne i-w?
  Tak - patrz 9.

6. Szukanie przez haszowanie kwadratowe grupy z wolnym i-węzłem.
  Jeśli znaleziono - patrz 9.

7. Szukanie liniowo grupy z wolnym i-węzłem.
  Jeśli znaleziono - patrz 9.

8. Odblokowanie super_bloku,zwrot wolnego i-węzła do pamięci,
  funkcja zwraca NULL.

9. Znalezienie pierwszego wolnego i-węzła w znalezionej grupie.
  Jeśli się nie uda - wypisanie komunikatu o uszkodzonym deskryptorze
  grupy,zwrot i-węzła do pamięci,odblokowanie s_b,
  funkcja zwraca NULL.

10. Zaznaczenie,ze dokonano zmiany w grupie i super_bloku.

11. Jeśli i-węzeł poza zasięgiem grupy - wypisanie błędu,zwrot i-w itd.

12. Wypełnienie i-węzła

13. Uaktualnienie struktur grupy

14. Odblokowanie super_bloku

15. Sprawdzenie quoty.Jeśli przekroczono - cofnij wszystko,
   funkcja zwraca NULL.

16. Funkcja zwraca i-węzeł.

Diagram algorytmu ext2_new_inode



3. Algorytm zwolnienia i-węzła z dysku - funkcja ext2_free_inode()

1. Sprawdzenie poprawności (czy i-węzeł ma przydzielony dysk itd.)
2. Założenie blokady na super_blok
3. Znalezienie numeru grupy i-węzła i jego numeru w tej grupie
4. Zwolnienie i-węzła
5. Zmiana w mapie zajętości i-węzłów w grupie
6. Zmiany w deskryptorze grupy (powiększenie il.wolnych i-węzłów
  i jeśli zwalniany i-w oznaczał katalog, zmniejszenie liczby katalogów
  w grupie).
7. Oznaczenie dokonania zmiany w systemie.
8. Odblokowanie super_bloku
 

4. Algorytm przydziału bloku dyskowego - funkcja ext2_new_block()

Jeden z parametrów - goal - blok "chciany"

1. Założenie blokady na super_blok

2. Jeśli ilość zarezerwowanych bloków >= ilość wolnych i nie ma
  dostępu do zasobów systemowych - zwolnienie s_b i wyjście z błędem.

3. Sprawdzenie poprawności goal - czy w zasięgu bloków grup.Jesli nie -
  goal:=nr pierwszego bloku grupy.

4. Czy w grupie,w której jest goal istn.wolny blok w okolicy goal?
  Jesli tak - patrz 8

5. Czy w pozostałej części grupy jest wolny blok?
  (najpierw szukamy wolnego ciągu 8 bloków,potem bl.pojedynczych)
  Jesli tak - patrz 8

6. Czy w innych grupach są wolne bloki? Jesli tak - patrz 8

7. Sprawdzenie integralności danych (licznik bloków),odblokowanie s_b,
  wyjście z funkcji.

8. Znaleziono wolny blok.Szukanie początku ciągu wolnych bloków.

9. Sprawdzenie quoty.Przekroczona - wyjście z błędem.

10. Jeśli było takie zadanie - próba przydzielenia bloków z wyprzedzeniem
  (prealloc)

11. Przydzielenie bloku i zaznaczenie zmian (w licznikach,mapach bitowych
   grupy i super_bloku)

12. Odblokowanie super_bloku

Diagram algorytmu ext2_new_block



5. Algorytm zwalniania bloku dyskowego - funkcja ext2_free_block()

1 .Założenie blokady na super_blok

2.Sprawdzenie poprawności - czy zwalniamy bloki w obrębie grupy,
  czy nie zwalniamy potrzebnych danych itd.

3. Wczytanie mapy bitowej bloków

4. Drugie sprawdzenie poprawności - czy nie zwalniamy bloków z danymi
  systemowymi.

5.Uaktualnienie liczników i mapy bitowej blokow,zaznaczenie zmian

6.Odblokowanie super_bloku


Autor: Bartosz Kapturkiewicz , bk174730@students.mimuw.edu.pl


< Spis treści


4. Podręczna pamieć buforowa


4.1. Struktury danych


Tablica haszująca

Deklaracja tablicy
static struct buffer_head ** hash_table;
Znajdowanie odpowiedniej listy w tablicy hash_table
#define _hashfn(dev,block) (((unsigned)(HASHDEV(dev)^block)) & bh_hash_mask)
#define hash(dev,block) hash_table[_hashfn(dev,block)]
Tablica list LRU
Deklaracja tablicy
#define NR_LIST 3
static struct buffer_head * lru_list[NR_LIST] = {NULL, };
Rodzaje kolejek LRU
Stała Wartość Opis kolejki
BUF_CLEAN
0
Kolejka buforów, których bloki mają zawartość zgodną z zawartością odpowiednich bloków na dysku
BUF_LOCKED
1
Kolejka buforów zablokowanych
BUF_DIRTY
2
Kolejka buforów, wktórych bloki danych mają zawartość niezgodną z odpowiadającymi im blokami dyskowymi

Tablica buforów wolnych

Deklaracja tablicy
#define NR_SIZES 7
static struct buffer_head * free_list[NR_SIZES] = {NULL, };
Możliwe rozmiary buforów (indeksy tablicy free_list):
512, 1024, 2048, 4096, 8192, 16384, 32768



< Spis treści


4.2. Budowa nagłówka bufora




struct buffer_head { struct buffer_head * b_next; /* wskaźnik do następnego bufora w kolejce haszującej */ unsigned long b_blocknr; /* numer bloku */ unsigned long b_size; /* rozmiar bloku */ kdev_t b_dev; /* urządzenie (B_FREE -> bufor wolny) */ kdev_t b_rdev; /* urządzenie rzeczywiste */ unsigned long b_rsector; /* rzeczywista pozycja bufora na dysku */ struct buffer_head * b_this_page; /* cykliczna lista buforów w obrębie jednej strony pamięci */ unsigned long b_state; /* bitmapa statusu bufora */ struct buffer_head * b_next_free; /* wskaźnik do następnego bufora w dwukierunkowej kolejce (buforów wolnych lub LRU)*/ unsigned int b_count; /* liczba użytkowników bloku */ char * b_data; /* wskaźnik do bloku danych */ unsigned int b_list; /* numer listy, na której bufor się znajduje */ unsigned long b_flushtime; /* czas, po którym dane z bufora (brudnego) powinny zostać zapisane */ struct wait_queue * b_wait; /* kolejka dla procesów oczekujących na bufor */ struct buffer_head ** b_pprev; /* wskaźnik do kolejki haszującej */ struct buffer_head * b_prev_free; /* wskaźnik do poprzedniego bufora w dwukierunkowej kolejce */ struct buffer_head * b_reqnext; /* wskaźnik do kolejki żądań */ /* pola używane do zakończenia obsługi we/wy */ void (*b_end_io)(struct buffer_head *bh, int uptodate); /* wskażnik do funkcji obsługującej we/wy */ void *b_dev_id; /* identyfikator urządzenia */ };



Flagi statusu bufora



Flaga Wartość Znaczenie
BH_Uptodate
0
1 jeśli zawartość bufora jest zgodna z zawartością odpowiedniego bloków na dysku
BH_Dirty
1
1 jeśli bufor zawiera modyfikowane dane
BH_Lock
2
1 jeśli dostęp do bufora jest zablokowany (procesy oczekują w kolejce b_wait)
BH_Req
3
0 jeśli dane w buforze zastały unieważnione
BH_Protected
6
1 jeśli bufor jest chroniony
BH_LowPrio
7
1 jeśli bufor ma niski priorytet



czytanie


< Spis treści


4.3. Czytanie bloków dyskowych


funkcja bread()

Parametry wejściowe: Zwracana wartość: Algorytm:


Funkcja breada()

Parametry wejściowe: Zwracana wartość: Algorytm: #define NBUF 16 /* masymalna liczba buforów do wczytania z wyprzedzeniem */


< Spis treści


4.4. Pisanie bloków dyskowych


Demon bdflush


Funkcja bdlush() jest zrealizowana w postaci wątku jądra i uruchamiana podczas jego inicjalizacji. Jej zadaniem jest zapisanie pewnej liczby bloków (określonej za pomocą parametrów) na dysk.

Demon bdflush może być obudzony przy użyciu funkcji wakeup_dbflush. Jest on uaktywniany podczas odzyskiwania wolnych buforów (w funkcji refill_freelist), oraz przez funkcję brelse (zwalnianie bufora).

Wątek bdflush może być konfigurowany w czasie działania przy użyciu funkcji systemowej sys_bdflush.


Parametry wątku bdflush
Parametr Domyślna wartość Znaczenie
nfract
40
Procentowa część buforów z zawartością niezgodną z odpowiednim blokiem na dysku (po jej przekroczeniu uaktywniany jest proces bdflush)
ndirty
500
Maksymalna liczba bloków, które demon dbflush może zapisać przy jednokrotnym uaktywnienu
nrefill
64
Liczba buforów zawierających dane zgodne odpowiednimi blokami na dysku generowana przy jednokrotnym wywołaniu refill_freelist()
nref_dirt
256
Liczba buforów zawierających dane niezgodne z danymi na dysku, po przekroczeniu której uaktywniany jest demon dbflush w funkcji refill_freelist
interval
5*HZ
opóźnienie zapisu w procesie kupdate
age_buffer
30*HZ
czas, o który opóźniony jest zapis bloku zawierającego nieuaktualnione dane
age_super
5*HZ
czas, o który opóźniony jest zapis bloku specjalnego zawierającego nieuaktualnione dane
dummy2
1884
parametr nie jest używany
dummy3
2
parametr nie jest używany


Ograniczenia na minimalne i maksymalne wartości dla każdego parametru (kolejność jak w tabeli wyżej):
int bdflush_min[N_PARAM] 
	= {  0,  10,    5,   25,  0,   1*HZ,   1*HZ,   1,    1};
int bdflush_max[N_PARAM] 
	= {100,5000, 2000, 2000,60*HZ, 600*HZ, 600*HZ, 2047, 5};



< Spis treści


4.5. Scenariusze dostępu


Przydział buforów - funkcja getblk()


Parametry wejściowe: Zwracana wartość: Algorytm:
repeat: - poszukaj bufora w odpowiedniej liście tablicy haszującej - jeśli udało się odnaleść bufor o podanych numerach urządzenia i bloku oraz odpowiedniej wielkości i jeśli dodatkowo bufor zawiera aktualne dane, przypisz polu b_flashtime bufora wartość 0 i zwróć bufor jako wynik funkcji
get_free: - poszukaj bufora na odpowiedniej liście buforów wolnych - jeśli nie udało się odnaleść bufora na liście wolnych przejdź do refill - usuń bufor z listy wolnych - ustaw odpowiednio parametry nagłówka (numer bloku, urządzenia, status) - wstaw do odpowiednich kolejek (w tablicy mieszjącej i LRU) - zwróć bufor jako wynik funkcji
refill: - spróbuj zwolnić część buforów (funkcja refill_freelist()) - poszukaj bufora w odpwiedniej liście tablicy haszującej - jeśli poprzedni krok zakończył się niepowodzeniem przejdź do get_free - przejdź do repeat


Funkcja brelse()

Parametr wejściowy:
Zwracana wartość: Algorytm:


< Spis treści


4.6. Strategia odzyskiwania buforów


funkcja refill_freelist


Parametr wejściowy: Zwracana wartość: Algorytm:


< Spis treści



4.7. Zalety i wady podręcznej pamięci buforowej


Zalety:



  • zwiększenie przepustowości systemu poprzez zmiejszenie liczby operacji we/wy

  • jednorodny dostęp do dysku (jądro kopiuje dane z i do buforów , niezależnie od tego, czy są one częścią pliku, i-węzła czy bloku identyfikacyjnego)

  • ukrycie przed użytkownikiem faktu, że operacje dyskowe są operacjami blokowymi; dzieki temu programy są prostsze (użytkownik nie musi dbać o to, aby dane, które chce zapisać były odpowiednio rozmieszczone na dysku) i bardziej przenośne (nie zależą od wielkości bloku)

  • utrzymywanie bloku dyskowego w co najwyżej jednym buforze tablicy mieszającej pomaga w zapewnianu integralności systemu plików

    Wady:

  • możliwość utraty niezapisanych danych w przypadku awarii
  • podwójne kopiowanie danych (z dysku do pamięci jądra, a następnie do pamieci procesu)


    < Spis treści



    5. Katalogi w Linuksie




    Informacje ogólne

    Katalogi nadają systemowi plików strukturę hierarchiczną.
    Katalog
    • zwykły plik, ale o specjalnej strukturze
    • składa się z ciągu pozycji katalogowych
    • Opis pozycji katalogowej
      inode jednoznaczny numer i-węzła
      rec_len rozmiar pozycji
      name_len długość nazwy
      name nazwa

    • może zawierać pozycje z numerem i-węzla równym 0 (pozycja usunięta)
    • zawsze zawiera pliki o nazwach: . i ..
    • prawa dostępu
    • Prawa dostępu dla katalogów
      czytanie możliwość przeglądania zawartości katalogu
      pisanie tworzenie i usuwanie plików
      wykonywanie możliwość przeszukiwania katalogu

    • procesy czytają katalogi w taki sam sposób jak zwykłe pliki
    • jądro systemu posiada wyłączne prawo do zapisywania katalogu

    Bibliografia


    Opracował: Damian Wójtowicz

    < Spis treści


    5.1. Podręczna pamięć buforowa nazw katalogów


    Opis algorytmu

    W celu przyspieszenia dostępu do pozycji katalogowych część z nich jest przetrzymywana w pamięci w strukturze dentry.
     
     

    W pamięci znajduje się lista mieszająca dentry_hashtable , której elementami są dwukierunkowe listy. Elementami tych list są właśnie pozycje katalogowe.

    static struct list_head dentry_hashtable[D_HASHSIZE];
    D\_HASHSIZE = 1Kb

     

    Funkcja mieszająca zależy od:

    • struktury dentry ojca
    • nazwy katalogu
    • długości katalogu

     

    Pozycje aktualnie nieużywane (tzn. d\_count = 0) przechowywane są w kolejce LRU dentry_unused.
     
     


    Struktury danych i funkcje

    Struktury danych:
     
     
    struct dentry {
    int d_count; /*ilu jest użytkowników*/
    unsigned int d_flags;struct inode * d_inode; /*struktura inode naszego katalogu lub NULL*/
    struct dentry * d_parent; /*nadkatalog - ojciec katalogu*/
    struct dentry * d_mounts;
    struct dentry * d_covers;
    struct list_head d_hash; /*lista mieszająca, do której należy ten katalog*/
    struct list_head d_lru; /*lista pozycji katalogowych nieużywanych występuje tylko przy d_cout = 0*/
    struct list_head d_child; /*lista podkatalogów ojca naszego katalogu - dzieci naszego ojca*/
    struct list_head d_subdirs; /*lista dzieci naszego ojca*/
    struct list_head d_alias; /*aliasy d_inode*/
    struct qstr d_name; /*dane nazwy katalogu*/
    unsigned long d_time;
    struct dentry_operations *d_op;
    struct super_block * d_sb; /*korzeń drzewa, w którym znajduje się nasz katalog*/
    unsigned long d_reftime; /*czas ostatniego odwołania*/
    void * d_fsdata;
    unsigned char d_iname[DNAME_INLINE_LEN] /*występuje przy krótkich nazwach*/}
     
    struct sqstr{
    const unsigned char* name;
    unsigned int len;
    unsigned int hash;}
    /*zmienna hash w tej strukturze zależy od nazwy i jej długości*/
     
    Funkcje:
    • d_add - dodaje strukturę dentry naszego katalogu do listy mieszającej
    • d_delete - jeśli jest ostatnim użytkownikiem to usuwa nasz katalog, wpp. usuwa listę mieszającą d_hash
    • dget - dodaje użytkownika (tzn. zwiększa d_count)
    • dput - usuwa użytkownika, jeśli jesteśmy ostatni i mamy strukturę dentry naszego katalogu to sprawdzamy również naszych przodków
    • d_lookup szuka naszego katalogu mając dane: ojca i nazwę


    Opracowała: Ewelina Hernik

    < Spis treści



    5.2. Tłumaczenie nazwy ścieżkowej na numer i-węzła




    Wstęp

    Geneza algorytmu:

    • użytkownik systemu posługuje sie nazwami ścieżkowymi
    • jądro systemu wewnętrznie operuje na numerach i-węzłów
    • potrzebny mechanizm pozwalający przekształcić scieżkę opisującą dostęp do pliku na numer odpowiedniego i-węzła
    Zajmuje się tym wszystkim algorytm namei:
    • przegląda on nazwę ścieżkową składowa po składowej (oddziela je "/")
    • przekształca każdą składową na numer i-węzła zgodnie z nazwą składowej i przeszukiwanym katalogiem
    • jako wynik przekazuje i-wezeł związany z wejściową nazwą ścieżkową

    Schemat działania algorytmu


    Podział algorytmu na funkcje

    Warto zdawać sobie sprawę, że w pliku "fs/namei.c" znajduję sie kilka odmian algorytmu namei przydatnych w odmiennych sytuacjach. Można tam znaleść także wiele funkcji pomocniczych, z których korzystają funkcje implementujące algorytm.
    • Algorytm namei w standardowej postaci wystepuje w dwóch wersjach: namei() oraz lnamei(). Jedyną różnicą jest to, iż namei() dopuszcza dowiązania symboliczne, a lnamei() - nie. Są one używane przez proste polecenia np. chmod. Wywołania tych funkcji są przez makro (plik "include/linux/fs.h") zamieniane bezpośrednio na wywołania funkcji __namei().
      #define namei(pathname)        __namei(pathname, 1)
      #define lnamei(pathname)       __namei(pathname, 0)
    • struct dentry * __namei(const char *pathname,
      				unsigned int lookup_flags)

      Zwraca i-węzeł odpowiadający plikowi o podanej nazwie ścieżkowej. Używa funkcji lookup_dentry().

    • struct dentry * lookup_dentry(const char * name, 
      		struct dentry * base, unsigned int lookup_flags)

      Rzeczywista realizacja algorytmu namei w najprostszej postaci. Funkcja przekształca nazwę ścieżkową na końcowy i-węzeł.

    • struct dentry * open_namei(const char * pathname, int flag, int mode)

      Jest to bardziej skomplikowana wesja funkcji namei(), wykorzystywana przez funkcję open(). Różni się od poprzedniej sprawdzaniem większej liczby warunków.

    • static struct dentry * cached_lookup(struct dentry * parent, 
      	    	struct qstr *name, int flags)
      
    • static struct dentry * real_lookup(struct dentry * parent, struct qstr *name, int flags)
    • Powyższe funkcje znajdują i-węzeł odpowiadający pierwszemu elementowi podanej ścieżki, zaczynając od danego i-węzła. Różnią się tym, że pierwsza wykorzystuje podręczną pamięć buforową nazw katalogów, a druga - nie.


    Sposób wykorzystania algorytmu

    Sama funkcja namei() jest wykorzystywana tylko przez niektóre funkcje systemowe - te w najprostszy sposób korzystające z przekształcania nazw ścieżkowych na numery i-węzłów (np. chmod). Funkcje bardziej wymagające jak np. open korzystają z własnych wersji algorytmu (open_namei()). W nich własnie dokonują się dodatkowe sprawdzenia np. czy plik, o który chodzi może być czytany.

    Kilka uwag

    • Warto zauważyć, że realizacja algorytmu namei w najprostszej wersji (namei()) jest zasadniczo zgodna z opisem podanym przez Bacha w jego książce "Budowa systemu operacyjnego UNIX" (str. 85-88). Jedyne różnice wynikają stąd, że jądro Linuksa nie wie w jaki sposób system plików przechowuje informacje o plikach i katalogach, więc wywołuje odpowiednie funkcje.
    • Porównując implementację algorytmu namei zawartą w jądrze 2.2.12 z poprzednimi (w stabilnej wersji sprzed 2.2.0) warto zauważyć, że funkcje dir_namei(), _namei() oraz follow_link() zostały połączone w jedną lookup_dentry(), która obejmuje wszystkie szczególne przypadki poprzedniego kodu.

    Bibliografia


    Opracował: Damian Wójtowicz

    < Spis treści


    6. Montowanie i demontowanie systemów plików




    Spis rzeczy

    6.1. Wstęp
    6.2. Mount table
    6.3. Montowanie - funkcja mount()
    6.4. Odmontowywanie - funkcja umount()
    6.5. Przekraczanie punktów montowania podczas analizy ścieżek plików

    6.1. Wstęp

     

    W Linuksie systemy plików z punktu widzenia użytkownika są reprezentowane jako jedno wspólne hierarchiczne drzewo katalogów.

    Różne systemy plików są identyfikowane jako poddrzewa "zaczepione" w pewnych węzłach drzewa katalogów. Czynność takiego "zaczepiania" zwana jest montowaniem systemów plików.

    Węzeł będący korzeniem drzewa zamontowanego systemu nazywamy punktem zamontowania (mount point). Po inicjalizacji systemu istnieje zawsze co najmniej jeden system plików, tzw. "root file system" - katalog "/" jest jego punktem montowania.

    Zalety montowania:

    • prosty dostęp do katalogów montowanych systemów poleceniem cd

    • przezroczystość dla oprogramowania użytkowego (nie musi ono wiedzieć czy katalog należy do lokalnego dysku, czy może do odległego komputera via Network File System)

    Uwagi:

    • współistnienie bardzo wielu różnych typów systemów plików wewnątrz jednego drzewa jest możliwe dzięki stworzeniu abstrakcji systemu plików - VFS (Virtual File System)

    • reprezentacja zamontowanych systemów plików w jądrze za pomocą tablicy super bloków

    • informacje o zamontowanym systemie plików można pobrać także z i-węzła odpowiadającego punktowi zamontowania tego systemu plików

    • istnieje lista możliwych do zamontowania przez jądro systemów plików, rozszerzalna przez doładowywanie odpowiednich modułów (np. moduł obsługi NTFS-u)

    • poprzednia zawartość katalogu, w którym został zamontowany system plików przestaje być widoczna aż do jego odmontowania

    • montować można również systemy plików urządzeń wirtualnych, ciekawym przykładem jest system plików proc, NFS lub ramdysk

    • prawo do montowania i odmontowywania systemów plików ma użytkownik-root; inni użytkownicy mogą to robić jedynie w odniesieniu do systemów plików, dla których został ustawiony parametr users w pliku /etc/fstab

    • istnieje możliwość przemontowania systemu plików z innymi parametrami



    6.2. Mount table

     

    Informacje o zamontowanych systemach plików są przechowywane w tzw. tablicy montowania.
    W rzeczywistości jest ona zrealizowana jako lista jednokierunkowa, o początku w zmiennej vfsmntlist i elemencie typu vfsmount:


    vfsmntlist
    struct vfsmount  {
    kdev_t mnt_dev - nr urządzenia char *mnt_devname - nazwa urządzenia char *mnt_dirname - punkt montowania unsigned int mnt_flags - flagi zamontowania systemu plików; struct super_block *mnt_sb - wskaźnik do superbloku zaalokowanego przez system plików; struct vfsmount *mnt_next - następny system plików na liście
    }
    Istnieje również dostęp do informacji na temat:
    • ostatnio zamontowanego sys. plików - vfsmnttail,
    • ostatnio używanego sys. plików - mru_vfsmnt


    6.3. Montowanie - funkcja mount()

     

    Do montowania służy polecenie mount, które odpowiada funkcji systemowej mount().
    Jej argumenty są takie same jak pola struktury vfsmount.

    Typy systemów plików obsługiwanych przez Linuxa, to m.in:

    • minix - pierwotny system plików używany przez Linuxa, posiadający wiele ograniczeń,
    • ext - zaprojektowany specjalnie dla Linuxa,
    • ext2 - obecna, ulepszona wersja systemu ext,
    • fat, vfat - używane przez MS-DOS, MS Windows,
    • umsdos - rozszerzenie funkcjonalności zwykłych partycji DOSowych przez dodanie kontroli uprawnień dostępu do plików podobnej jak w ext2,
    • ntfs - (nie myl z nfs), używany przez Windows NT,
    • nfs - sieciowy, rozproszony system plików,
    • proc - wirtualny system plików udostępniający informacje o systemie,
    • iso9660 - system plików używany do zapisu dysków CD
    Dla niektórych systemów plików, np. proc, NFS nie istnieją urządzenia blokowe. Listę aktualnie możliwych do zamontowania typów systemów plików udostępnia plik /proc/filesystems.

    Algorytm montowania:

    • sprawdź, czy użytkownik ma prawo do montowania tego systemu plików,
    • pobierz i-węzły: urządzenia i punktu montowania,
    • sprawdź, czy punkt montowania jest istniejącym katalogiem (jeśli nie to błąd),
    • przeglądnij listę (file_system_type *file_systems) aktualnie możliwych do zamontowania (zarejestrowanych) typów sys. plików w poszukiwaniu żądanego typu
      • jeśli nie udało się, to jądro może się zwrócić do kernel demona - kdemon - aby załadował moduł do obsługi danego typu systemu plików (i zarejestrował ten typ); w przypadku porażki - błąd,
    • użyj numeru urządzenia do znalezienia tablicy funkcji obsługi danego urządzenia blokowego,
    • odczytaj (utwórz) super-blok systemu plików czerpiąc informację z urządzenia blokowego (jeśli istnieje),
    • dodaj nową pozycję do tablicy montowania,
    • wpisz dowiązanie i-węzła "przykrywanego" katalogu do super-bloku systemu plików,
    • wpisz do i-węzła katalogu montowania w dentry->d_mounts wskaźnik do i-węzła korzenia zamontowanego systemu plików


    Uwagi:
    • operacja montowania nie powiedzie się, gdy:
      • system plików jest już używany (zamontowany),
      • katalog będący punktem montowania systemu plików jest już użyty do zamontowania innego sys. lub jest bieżącym katalogiem dla jakiegoś procesu,
    • w przypadku systemu ext2 utworzenie super-bloku jest proste dzięki podobieństwu jego struktur do VFS-u; inaczej jest np. dla systemu NFS - wymagane są bardziej skomplikowane działania, m.in. połączenie się z serwerem NFS

    Przykład polecenia montowania - dla partycji MS-DOS:

    # mount -t vfat /dev/hda5 /mnt/dos

     


    6.4. Odmontowywanie - funkcja umount()

     

    Do odmontowywania używamy funkcji umount(). Po odmontowaniu systemu plików z danego katalogu będącego punktem montowania, katalog ten odzyskuje swą pierwotną zawartość.

    Parametrem funkcji umount() może być:
    • ścieżka do urządzenia blokowego, np. "/dev/hda2"
      lub
    • ścieżka do punktu montowania, np. "/mnt/dos"

    Algorytm funkcji umount():

    • pobierz i-węzeł pliku będącego parametrem i sprawdź, czy (1) należy on do urządzenia blokowego, czy (2) do katalogu (punktu montowania):
      • w przypadku (1) pobierz z tablicy super-bloków odpowiedni super-blok systemu plików odpowiadający urz. blokowemu, stamtąd zaś pobierz i-węzeł punktu montowania,
      • w przypadku (2) pobierz super-blok wskazywany przez jedno z pól i-węzła punktu montowania
      • jeśli i-węzeł nie odpowiada powyższym przypadkom, to błąd,
    • sprawdź, czy są jakieś otwarte pliki we wskazanym systemie plików lub czy jakiś proces nie ma aktualnego katalogu ustawionego na katalog tego systemu - jeśli tak jest, to nie można odmontować; sprawdzenie polega na wykryciu obecności w pamięci aktywnych i-węzłów systemu plików,
    • wyczyść pole d_mounts i-węzła punktu montowania,
    • usuń z pamięci i-węzeł punktu montowania oraz i-węzeł korzenia drzewa katalogów odmontowanego systemu,
    • w razie potrzeby zapisz super-blok odmontowanego systemu

     


    6.5. Przekraczanie punktów montowania podczas analizy ścieżek plików

     

    Umożliwienie montowania systemów plików wewnątrz drzewa katalogów spowodowało utrudnienia w analizie ścieżek plików (algorytm namei). Problemem jest tu przekraczanie katalogów będących punktami montowania.

    Przykład: dla partycji /dev/hda5 zamontowanej w katalogu "/mnt/dos" muszą być możliwe do wykonania m.in. następujące komendy:

    # cd /mnt/dos/programy
    # cd ../..
    
    Czynności wykonywane dla pierwszej komendy polegają głównie na wywołaniu funkcji namei("/mnt/dos"):
    • pobranie i-węzła drzewa katalogów,
    • znalezienie pliku katalogu opisanego przez "/" i znalezienie w nim nazwy "mnt",
    • wyznaczenie numeru i-węzła dla nazwy "/mnt" i znalezienie go w pamięci funkcją iget,
    • stwierdzenie, że pole d_mounts tego i-węzła jest <>NULL, zatem jest to punkt montowania jakiegoś sys. plików,
    • korzystając z d_mounts->d_inode->i_sb->s_root (w jądrze 2.0 było to jedno pole) znajdujemy wskaźnik do korzenia drzewa katalogów systemu plików, który jest podmontowany w "/mnt",
    • znalezienie (ponownie funkcją iget) i-węzła dla katalogu "/programy" w drzewie zamontowanego systemu plików, po czym ustalany jest ten i-węzeł jako bieżący katalog procesu

    Załóżmy, że wykonaliśmy już 1-szą komendę (jesteśmy w "/mnt/dos/programy"). Druga komenda opiera się o wywołanie funkcji namei("../..") i działa inaczej, gdyż przekraczamy punkt montowania w przeciwnym kierunku:
    • po dokonaniu analizy ścieżki ".." - tak jak dla poprzednio, znajdujemy i-węzeł odpowiadający korzeniowi zamontowanego systemu plików /dev/hda5
    • wyszukanie w ten sam sposób pozycji ".." dla znalezionego i-węzła nie mogło by się udać, gdyż jest to i-węzeł korzenia,
    • należy sprawdzić, czy i-węzeł korzenia odpowiada korzeniowi pewnego zamontowanego systemu plików (/dev/hda5) - wystarczy sprawdzić w super-bloku wskazywanym przez ten i-węzeł, czy katalog montowania nie jest korzeniem,
    • powyższe sprawdzenie daje pomyślny wynik, więc należy pobrać z d_inode->i_sb->s_root (przechodząc przez super-blok) i-węzeł punktu zamontowania ("/mnt/dos"), po czym wykonać dla niego operację "..", przechodząc do katalogu "/mnt"

    Tekst i rysunki: Jarosław Staniek, MIM UW INF3 - jaroslaw.staniek@students.mimuw.edu.pl

    < Spis treści


    7. Struktury danych procesów związane z systemem plików

       W systemie Linux związek procesu z systemem plików opisany jest przez pola fs i files struktury task_struct. Są one odpowiednio typów fs_struct oraz files_struct, które przedstawione są na rysunku 7.1:

    Struktury fs_struct i files_struct Rysunek 7.1: Proces a system plików - struktury danych

    Funkcje struktury fs_struct

    • definiuje domyślną maskę praw nadawaną plikom tworzonym przez proces
    • ustala korzeń systemu plików dla procesu
    • ustala aktualny katalog procesu

    Funkcje struktury files_struct

    • pamięta wszystkie deskryptory plików otwartych przez proces
    • pamięta zbiór deskryptorów, które mają zostać zamknięte po wywołaniu przez proces funkcji exec

    Definicje struktur z opisem pól

    struct fs_struct {
      atomic_t count; - licznik odwołań do struktury
      int umask; - domyślna maska praw
      struct dentry *root;   - wskaźnik do korzenia systemu plików
      struct dentry *pwd; - wskaźnik do aktualnego katalogu procesu
    }

    struct files_struct {
      atomic_t count; - licznik odwołań do struktury
      int max_fds; - aktualna wielkość tablicy fd
      int max_fdset; - aktualny rozmiar zbiorów close_on_exec i open_fds
      int next_fd; - potencjalny pierwszy wolny deskryptor pliku
      struct file ** fd; - tablica wskaźników do struktur file
      fd_set *close_on_exec; - wskaźnik do zbioru numerów deskryptorów zamykanych przy wywołaniu exec
      fd_set *open_fds; - wskaźnik do zbioru numerów wszystkich otwartych deskryptorów
      fd_set close_on_exec_init;   - inicjalny zbiór numerów deskryptorów na który wskazuje close_on_exec
      fd_set open_fds_init; - inicjalny zbiór numerów otwartych deskryptorów
      struct file * fd_array
      [NR_OPEN_DEFAULT];
    - początkowa tablica wskaźników do struktur file
    }

    Bibliografia

    • M. Beck, H. Boehme, ... , Linux kernel - Jądro systemu.
    • Źródła linuksa (ścieżki względne od /usr/src/linux):
      • fs/file.c
      • fs/open.c
      • include/linux/sched.h
      • include/linux/file.h


    < Spis treści


    8. Struktura pliku zwykłego



    Za sprawą systemu operacyjnego użytkownik postrzega plik jako ciągły strumień bajtów o określonej długości. W rzeczywistości plik nie jest trzymany na dysku w sposób ciągły. Gdyby bowiem tak było, tzn. plik zajmowałby następujące po sobie bloki, dochodziłoby do fragmentacji zewnętrznej oraz ciężko byłoby zwiększyć jego rozmiar.

     W systemie ext2 plik jest przechowywany na dysku w wielu nie występujących po sobie blokach. Opis jego rozłożenia na bloki dyskowe oraz dodatkowe informacje związane z nim (tj. właściciel, prawa dostępu, rozmiar itp.) są zapisane w i-węźle (ext2_inode).

    Strategia ta pozwala na dużą elastyczność systemu plików, lecz zarazem komplikuje sposób lokalizacji danych. Przechowywanie bowiem w i-węźle listy numerów bloków zajmowanych przez plik jest nieefektywne ze względu na obsługę tej listy i zmusza do narzucenia dużych ograniczeń na rozmiar pliku, badź do wprowadzenia i-węzłów o zmiennej wielkości.

    Przyjęte zostało więc inne rozwiązanie. W i-węźle znajduje się tablica o nazwie

           i_block (dla struktury i-węzła na dysku) albo

           i_data (w przypadku struktury i-węzła w pamięci).

    Jej rozmiar opisuje stała EXT2_N_BLOCKS standardowo równa 15.

    W tablicy tej przechowywane są cztery typy adresów:

    • bezpośredni - wskazuje na blok z danymi. Liczbę  odresów bezpośrednich  w tablicy określa stała EXT2_NDIR_BLOCK równa 12.
    • pojedynczy pośredni - jest to adres bloku, który zawiera listę adresów bezpośrednich.   Rozmiar bloku wynosi standardowo 1024 B, a rozmiar wskaźnika do bloku 4 B, zatem w bloku pośrednim pamiętanych jest 256 adresów bloków.
    • podwójny pośredni - adres bloku zawierającego listę adresów bloków pojedynczych pośrednich.
    • potrójny pośredni - jest to adres bloku zawierającego listę bloków podwójnych pośrednich.


    W tablicy znajduje się po jednym adresie pojedynczym, podwójnym i potrójnym pośrednim.



    (rysunek)




    W ten sposób można zapamiętać plik o rozmiarze 16 GB. Pliki jednak mogą mieć co najwyżej 4 GB, bo i-węźle pole rozmiaru pliku zajmuje 32 bity. Z tego też powodu nie ma bloków poczwórnych pośrednich. Niektóre numery bloków mogą być równe zero, co oznacza, że na pewnej przestrzeni pliku nie zostało nic zapisane - na takie bloki nie zużywa się miejsca na dysku. Do wspomnianej sytuacji może dojść w wyniku wywołania funkcji lseek.

    Stałe określające wielkość tablicy i_block i jej podział zdefiniowane są w pliku include/linux/ext2_fs.h.

    #define EXT2_NDIR_BLOCKS 12                    - liczba bloków bezpośrednich
    #define EXT2_IND_BLOCK   EXT2_NDIR_BLOCKS      - pozycja w tablicy bloku pośredniego
    #define EXT2_DIND_BLOCK  (EXT2_IND_BLOCK + 1)  - ... podwójnego pośredniego 
    #define EXT2_TIND_BLOCK  (EXT2_DIND_BLOCK + 1) - ... potrójnego pośredniego
    #define EXT2_N_BLOCKS    (EXT2_TIND_BLOCK + 1) - liczba wszystkich bloków

    Bibliografia

    1. Pliki zródłowe Linuxa:
      • include/linux/ext2_fs.h (stałe wymienione wyżej i struktura i-węzła na dysku)
      • include/linux/ext2_fs_i.h (struktura i-węzła w pamieci)

    Opracował: Michał Wiktor Żmijewski

    < Spis treści


    8.1. Struktura pliku zwykłego - struct file


          Jedną z funkcji każdego systemu operacyjnego jest ochrona zasobów przed niewłaściwym wykorzystaniem. W przypadku systemów wielozadaniowych problem komplikuje się ze wzgledu na konieczność synchronizacji dostępu do nich. Pliki są doskonałym tego przykładem - często zdarza się, że wiele procesów współubiega  się o prawo odczytu lub zapisu. Najprostszym rozwiązaniem wydaje się wprowadzenie dodatkowej struktury danych związanej z każdym plikiem. Tak też zrobiono - struktura file zawiera podstawowe informacje umożliwiające prawidłowa synchronizacje dostępu do pliku.

    STRUCT  FILE
     TYP  NAZWA KOMENTARZ
    struct file* f_next wskażnik na następny element na liście
    struct file** f_pprev wskażnik na element przedostatni (!)
    struct dentry* f_dentry
    struct file_operations* f_op dowiązanie do struktury danych zawierającej implementację dozwolonych operacji na pliku
    mode_t f_mode tryb dostępu do pliku
    loff_t f_pos pozycja pliku
    unsigned int f_count licznik odwolań
    unsigned int f_flags flagi funkcji open
    unsigned long f_reada,f_ra...max,end,len,win te dane pomocnicze wykorzystywane są przy realizacji algorytmu breada w celu odczytania bloku z wyprzedzeniem
    struct fown_struct f_owner struktura, w której przechowywane są dane o właścicielu
    unsigned int  f_uid,f_gid
    int f_error kod bledu
    unsigned long f_version licznik aktualizowany analogicznie jak pole i_version struktury inode
    void* private_data dane wykorzystywane przez sterownik tty oraz być może inne

    Struktury odpowiadające fizycznym plikom przechowywane są na dwukierunkowej liście cyklicznej. Proszę zwrócić uwagę, że wygodniej było pamietać element przedostatni zamiast bezpośrednio poprzedzającego. Dostęp do tablicy plików możliwy jest za pomocą globalnego wskaźnika first_file.


    Poniżej zamieszczamy opisy struktur oraz stalych wykorzystywanych przez strukture file:

    struct fown_struct { int pid; /* pid albo -pgrp do którego powinien zostać wyslany sygnal SIGIO */ uid_t uid, euid; /* uid/euid procesu ustawiającego nowego właściciela */ int signum; /* posix.1b rt sygnał, który ma zostać wyemitowany do IO */ };


    Flagi funkcji open():
    • O_CREATE - plik zostaje utworzony jeżeli jeszcze nie istnieje
    • O_EXCL- flaga ta jest zazwyczaj łączona z O_CREATE, zwraca błąd jeśli plik istnieje
    • O_RDONLY - plik zostaje otwarty jedynie do czytania
    • O_ WRONLY - plik zostaje otwarty jedynie do pisania
    • O_APPEND - dane będa dopisywane na koniec pliku nawet jeżeli już ma niezerowa długość
    • O_RDWR- plik zostaje otwarty zarówno do czytania jak i do pisania
    • O_SYNC - operacje będą blokowały proces aż do ich rzeczywistego wykonania
    • O_NOCTTY - jeżeli pathname odnosi się do terminala to nie stanie on się terminalem sterującym
    • O_NONBLOK - jeżeli trzeba czekać na dowolna operacje to zostanie zwrócony bład
    • O_NDELAY - jak wyżej
    • O_TRUNC - wskaźnik końca pliku zostanie ustawiony na bieżącą pozycje w pliku


    Prawa nadane procesowi, który otworzył plik:

    • FMODE_WRITE - prawo czytania
    • FMODE_READ - prawo pisania



    struct file_operations {
        loff_t (*llseek) (struct file *, loff_t, int);
        ssize_t (*read) (struct file *, char *, size_t, loff_t *);
        ssize_t (*write) (struct file *, const char *, size_t, loff_t *);
        int (*readdir) (struct file *, void *, filldir_t);
        unsigned int (*poll) (struct file *, struct poll_table_struct *);
        int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
        int (*mmap) (struct file *, struct vm_area_struct *);
        int (*open) (struct inode *, struct file *);
        int (*flush) (struct file *);
        int (*release) (struct inode *, struct file *);
        int (*fsync) (struct file *, struct dentry *);
        int (*fasync) (int, struct file *, int);
        int (*check_media_change) (kdev_t dev);
        int (*revalidate) (kdev_t dev);
        int (*lock) (struct file *, int, struct file_lock *);
    };
    Dalszych informacji o tej strukturze należy szukać w rozdziale poświęconym operacjom na pliku dostępnym dla procesu.

    Opracował: Bartosz Gogolewski

    < Spis treści


    9. Dostęp do danych z pliku - funkcja bmap()



  • Wstęp
  • Implementacja
  • Pseudo-kod algorytmu bmap
  • Przykład
  • Bibliografia

  • Wstęp

    Funkcja bmap() służy do przekształcania logicznego adresu bajtu w pliku na adres fizyczny na dysku. Deklaracja ( z include/linux/fs.h ):
     int bmap( struct inode *inode,int block)
    Argumenty:
    struct inode * inode
    wskaźnik do i-węzła pliku
    int block
    żądany logiczny number bloku z pliku: block=logiczny-number-bajtu/rozmiar-bloku
    Funkcja zwraca:
    • 0 gdy wystapił błąd
    • fizyczny numer bloku na dysku w przeciwnym wypadku

    Implementacja

    Funkcja bmap jest bardzo prosta: wywołuje funkcję bmap dla danego inode (o ile taka jest zdefiniowana).

    Dla przykładu omówię funkcję ext2_bmap( struct inode * inode, int block). Jest ona zadeklarowana w pliku include/linux/ext2_fs.h. Implementacja znajduje się w fs/ext2/inode.c. W tym pliku znajdują się również funkcje/makra pomocnicze:

    inode_bmap(inode, nr)
    makro, zwraca pozycję nr z tablicy bloków i-węzła. Parametr nr musi należeć do przedziału [0, EXT2_N_BLOCKS-1]
    int block_bmap(struct buffer_head * bh, int nr )
    funkcja, traktując zawarty w bh odczytany blok jako tablicę wartości 32-bitowych, zwraca jej pozycję o numerze nr. Zwalnia bufor przez wywołanie brelse(bh).

    W funkcji zdefiniowane są następujące zmienne pomocnicze:

    int addr_per_block
    określa, ile fizycznych adresów bloków mieści się w jednym bloku
    int addr_per_block_bits
    jej wartością jest log2(addr_per_block) (patrz fs/ext2/super.c linia 548)

    Bloki pliku należą do czterech kategorii. Zaczynając od zera, mamy:

    bloków bezpośrednich:
    EXT2_NDIR_BLOCKS (=12)
    bloków pośrednich pierwszego poziomu:
    2^(addr_per_block_bits) = addr_per_block
    bloków pośrednich drugiego poziomu:
    2^(2*addr_per_block_bits) = addr_per_block^2
    bloków pośrednich trzeciego poziomu:
    2^(3*addr_per_block_bits) = addr_per_block^3
    Tak więc w sumie jeden plik może zawierać:
    max= 12 + addr_per_block + addr_per_block^2 + addr_per_block^3 bloków.


    Pseudo-kod algorytmu bmap

    
    ext2_bmap( struct inode * inode, int block)
    {
      /*czy wartość block mieści się w odpowiednich granicach*/ 
       if ( block < 0 ) return 0;
       if ( block >= max) return 0;
    
      
       if ( block < EXT2_NDIR_BLOCKS )
         {  /*blok bezpośredni*/ 
           return inode( inode, block );
         }
    	
      /* pomijamy bloki bezpośrednie, wartością zmiennej block staje 
         sie numer bloku pośredniego poziomu I, II lub III*/ 
       block-= EXT2_NDIR_BLOCKS;    
    
       if ( block < addr_per_block )
         {       /* jest to blok pośredni pierwszego poziomu */ 
           i= inode_bmap( inode, EXT2_IND_BLOCK ); 
           if ( i == 0 ) return 0;
    	     /* w i jest teraz adres bloku pośredniego poziomu I*/ 
                 /* trzeba wczytac blok o adresie i (to robi bread(...)) */ 
    
           return block_bmap( bread(..., i), block );  /* szukany adres bloku */ 
         }
    
       /* pomijamy bloki pośrednie poziomu I, wartością zmiennej block staje 
          sie numer bloku pośredniego poziomu II lub III*/ 
       block-= addr_per_block;
    
       if ( block < (1 << (addr_per_block_bits*2) ))
         {       /* jest to blok pośredni drugiego poziomu */ 
           i= inode_bmap( inode, EXT2_DIND_BLOCK ); 
           if ( i == 0 ) return 0;
    	     /* w i jest teraz adres bloku pośredniego poziomu I*/ 
    
           bl= bread(..., i);   
                 /* w tym bloku znajduje się adres bloku pośredniego poziomu II */ 
           i= block_bmap( bl, block >> addr_per_block_bits );
           if ( i == 0 ) return 0;
                 /* szukany adres: */ 
           return block_bmap( bread(..., i), block & (addr_per_block-1) );
         }
       /* jest to blok pośredni poziomu III: block staje się numerem bloku
          pośredniego poziomu III */ 
       block-= (1 << (addr_per_block_bits*2) );
       /*  czytamy kolejne bloki pośrednie... */ 
       i= inode_bmap( inode, EXT2_TIND_BLOCK );
       if ( i == 0 ) return 0;
       i= block_bmap( bread(..., i), block >> (addr_per_block_bits*2));
       if ( i == 0 ) return 0;
       i= block_bmap( bread(..., i), (block >> addr_per_block_bits) &
    	(addr_per_block-1));
       if ( i == 0 ) return 0;
       /*  ... i mamy wynik  */
       return block_bmap( bread(..., i), block & (addr_per_block-1));
    }
        

    Jak widać, algorytm ten najszybciej działa dla krótkich plików. Im plik dłuższy, tym więcej trzeba dostępów do dysku, aby znaleźć żądany blok.



    Przykład

    Załóżmy, ze w bloku mieszczą się 4 adresy. Wtedy mamy: addr_per_block= 4 addr_per_block_bits= log2(4) = 2.

    Przypuśćmy, ze układ bloków pewnego pliku jest taki, jak na rysunku poniżej:

    (rysunek)
    Chcemy odczytać blok o numerze logicznym 22.

    block= 22

    Ponieważ 22 >= 12=EXT2_NDIR_BLOCK, to blok nie należy do bloków bezpośrednich.

    block= block-12 = 22-12 = 10

    10 >= addr_per_block=4, więc blok nie należy do bloków jednokrotnie pośrednich.

    block= block - addr_per_block = 10 - 4 = 6

    block = 6 < addr_per_block^2 = 16, więc blok należy do bloków podwójnie pośrednich.

    Pobieramy adres bloku podwójnego pośredniego w i-węźle (indeks 13): 185.

    Wczytujemy ten blok.

    Liczymy 6 >> addr_per_block_bits = 6 >> 2 = 1.

    Odczytujemy więc drugą pozycję w bloku 185 - jest to 114.

    Wczytujemy blok 114.

    Numer szukanego bloku znajduje się na pozycji 6 & (addr_per_block - 1) = 6 & 3 = 0110b & 0011B = 0010b = 2.

    Zatem numer fizyczny bloku o numerze logicznym 22 to 600.




    Bibliografia

    • include/linux/fs.h
      deklaracja funkcji bmap
    • fs/inode.c
      definicja funkcji bmap
    • include/linux/ext2_fs.h
      deklaracja funkcji ext2_bmap
    • fs/ext2/inode.c
      definicja funkcji ext2_bmap, makro inode_bmap, funkcja block_bmap


    Opracował: Karol Droste

    < Spis treści


    10. Interfejs systemu plików


    10.1. Tworzenie i otwieranie pliku


    • Wprowadzenie
    • Definicja
    • Struktury danych
    • Implementacja
    • Uwagi
    • Bibliografia

    Wprowadzenie

    Funkcja open() otwiera plik i zwraca deskryptor (nieujemna liczba calkowita uzywana przy czytaniu z pliku, pisaniu do pliku, innych operacjach na pliku) lub w przypadku niepowodzenia -1.



    Definicja

    int open(const char*pathname, int flags, int mode);
    
    Wynik - deskryptor pliku(>=0), lub -1 w przypadku bledu
    pathname - sciezka dostepu do pliku
    flags - okresla sposob otwierania pliku
    mode - uzywane z flaga OCREAT(wpp ignorowana). Okresla prawa dostepu do pliku.
    Jest modyfikowane przez atrybut umask procesu: prawa dostepu do tworzonego 
    pliku beda rowne (mode & ~umask).
    
    Uzywane jako parametr flags:
    
    O_RDONLY,O_WRONLY,O_RDWR (otwieranie pliku odpowiednio tylko do czytania,
    tylko do pisania, do czytania/pisania. Flagi te mozemy modyfikowac bitowa
    operacja OR flagami opisanymi ponizyej)
    
    O_CREAT (jesli plik nie istnieje istnieje bedzie stworzony)
    O_EXCL (uzywany z flaga O_CREAT, jesli plik juz istnieje zwroc blad)
    O_NOCTTY (jesli pathname odnosi sie do terminala, to nie stanie sie on
              terminalem sterujacym, nawet jesli takowego procesu nie posiada)
    O_TRUNC (jesli plik juz istnieje obetnij go)
    O_APPEND (poczatkowo oraz przed kazdym pisaniem wskaznik do pliku bedzie
               ustawiany na jego koncu)
    O_NBLOCK / O_NDELAY (open oraz kazda inna nastepna operacja na zwroconym
                         deskryptorze bedzie konczyla sie bledem w przypadku
                         koniecznosci czekania na jakies zdarzenie np. zdjecie 
                         blokady z pliku);
    O_SYNC (dowolne operacje pisania na deskryptorze beda blokowaly proces 
           az do momentu wykonania fizycznego zapisu na urzadzeniu docelowym)


    Kody bledow zwracane na zmiennej errno w przypadku bledu

    EEXIST (plik pathname juz istnieje - O_CREAT i O_EXECL ustawione)
    EISDIR (otwierano katalog do pisania)
    ETXTBSY (otwierano plik wykonaywalny do pisania)
    EFAULT (pathname wskazuje na obszar poza przestrzenia adresowa procesu)
    EACCES (dostep do pliku zabroniony, lub jeden z katalogow w pathname nie 
            zezwala na przeszukiwanie (wykonywanie))
    ENAMETOOLONG (nazwa pathname zbyt dluga)
    ENOENT (katalog skladowy uzyty w sciezce nie istnieje lub jest 
           nieprawidlowym dowiazaniem)
    ENFILE (prog na maksymalna liczbe otwartych plikow w systemie zostal 
           osiagniety)       
    ENOMEM (niewystarczajaca ilosc pamieci dostepnej jadru systemu)       
    EROFS (plik z systemu plikow typu read-only otwierano do pisania)
    ELOOP (pathname zawiera dowiazania cykliczne tj. takie, ze jego rozwiniecie
          nadal uzywa tego dowiazania)
    ENOSPC (brak miejsca na urzadzeniu na utworzenie nowego pliku)      
    


    Spotkane ograniczenia na zasoby systemowe

    • Proces moze miec maksymalnie min{NR_OPEN=256, rlim[RLIMIT_NOFILE].rlim_cur /*inicjalizowane na NR_OPEN*/} otwartych plikow.rlim jest lokalna tablica procesu.
    • lista("tablica") plikow nie moze miec wiecej niz NR_FILE= 1024 pozycji


    Implementacja

    int open(const char*pathname, int flags, int mode)
    {
      file= get empty filp(); /*file - wolna pozycja w tablicy plikow*/
      mode = mode & ~umask;/* uwzglednienie umask procesu*/
      Sprawdzenie praw, poprawnosci argumentow;
      if (flag&)_CREAT){
            inode= get_empty_inode();/*inode-wolna pozycja w tablicy i-wezlow*/
            zainicjalizowanie pozycji inode w tablicy i-wezlow; 
      }else
            inode=i-wezel pliku o nazwie pathname;
      if (flag&O_TRUNC)
          do truncate();
      if (plik otwarto do pisania )
         inode->i_writecount++;
      Zaincjalizowanie pozycji file w tablicy plikow;
      Poszukiwanie wolnej pozycji nr w tablicy deskryptorow fd procesu;
      fd[nr]=file;
      return (nr);
    }


    Uwagi

    1. Wywolanie funkcji systemowej creat jest rownowazne wywolaniu
       open z flaga rowna O_CREAT|O_WRONLY|O_TRUNC

    2. Gdy nie ma wolnej pozycji w tablicy plikow, wowczas alokowana
       jest nie jedna struktura file lecz cala strona pamieci na
       (PAGE_SIZE/sizeof(struct file)) nowych pozycji w tablicy plikow.

    3. Definicja funkcji jest zawarta w "./includ/asm-alpha/unistd.h".
       Widac tam, ze funkcja open kozysta z funkcji sys_open, ktorej
       implementacja jest w "./fs/open.c";

    4. W opisie funkcji open zostalo dokonane niewielkie przeklamanie,
       tzn. w przypadku bledu funkcja zwraca tak naprawde kod bledu a nie
       -1. Mialo to jednak sluzyc wiekszej przejzystosci tekstu, ktory moze
       byc czytany przez potencjalnych czytelnikow programistow.


    Bibliografia

       1. Pliki zrodlowe Linuxa: ./include/asm-alpha/unistd.h, ./fs/open.c,
          ./fs/namei.c
       2. Pomoc ("man") dostepna pod Linuxem



    Autorzy:
    • Tworzenie, otwieranie i zamykanie pliku - Krzysztof Juchimiuk
    • Funkcje : read() i write() - Jakub Lenart
    • Obsługa łączy nienazwanych i blokowanie plików - Michal Domagała

    < Spis treści


    10.2. Czytanie z pliku -funkcja read()



    Wprowadzenie

    Funkcje systemowe ogolnie mozna podzielic na :
     
    • funkcje obslugujace istniejace pliki,
    • sluzace do tworzenia nowych plikow,
    • obslugujace i-wezel,
    • pozwalaace przemieszczac sie w systemie plikow,
    • rozszerzajace drzewo systemu plikow i zmieniaja struktore hierarchii systemu plikow



    Opis funkcji read()

    • Sluzy do obslugi istniejacego pliku;
    • Wczytanie odpowiednia ilosc bajtow danych z otwartego pliku do odpowiedniej struktory danych uzytkownika;
    • Zwieksza wskaznik o liczbe przeczytanych bajtow;
    • Sekwencyjne wczytywanie kolejnych blokow dyskowych i w konsekwencji wczytuje dane z wyprzedzeniem.


    Opis funkcji read dla wszystkich systemow plikow



    Funkcja read:

    DEFINICJA:
    
    int read(unsigned int fd, char *buf, unsigned int count );
    
    WYNIK: liczba wczytanych bajtow, 
                     0 w przypadku napotkania na koniec pliku,
                     -1 w przypadku bledu; 
    
            errno = EDEADLOCK(jezeli na plik lub rekordy jest zalozona blokada)
                    EFAULT(w przestrzeni wirtualnej nie ma zaalokowanego
                                  bufora uzytkownika )
                     EINVAL(jezeli nie mozemy wywolac funkcji read dla konkreatnego                               
                     EBADF(niepoprawny deskryptor pliku)
    systemu plikow)
    fd jest deskryptorem czytanego pliku
    buf jest adresem struktory danych uzytkownika do ktorego wczytujemy dane
    count jest to liczba bajtow, ktore chce wczytac uzytkownik

    Dla wszystkich systemu plikow dana jest ta sama funkcja read:

    {
    • pobieramy i-wezel korzystajac z deskryptora pliku uzytkownika
    • sprawdzamy w tablicy plikow prawo do czytania dla pliku
    • sprawdzamy czy mozemy wywolac read dla konkretnego systemu plikow
    • upewniamy sie czy nie ma blokady na rekord do czytania
    • upewniamy sie czy struktura danych uzytkownika jest zaalokowana w jego przestrzeni
    • dla systemu plikow EXT2 wywolujemy funkcje generic_file_read

    Opis ważniejszych struktur danych



    struct file {
      loff_t f_pos; /* pozycja w pliku */

      f_rawin; /*rozmiar danych wczytanych w trakcie ostatnio wykonywanego czytania z wyprzedzeniem */

      f_ramax; /*aktualny maksymalny rozmiar danych, ktore mozna przeczytac z wyprzedzeniem */

    struct file_operations /* operacje na pliku w konkretnym file-systemie */

    Opis funkcji generic_file_read()



    Funkcja generic_file_read():

    DEFINICJA:

    int generic_file _read(struct inode *inode,struct file *filp, char *buf,int count)

    WYNIK:  liczba wczytanych bajtow
                        0 w przypadku napotkania na koniec pliku
                        -1 w przypadku bledu

            errno=          ENOMEM(blad przy tworzeniu ramki pamieci)
                                    EIO(blad wejscia-wyjscia)

    Parametry buf i count takie same jak w wywolaniu read.
    inode jest i-wezlem czytanego pliku
    filp pozycja w tablicy plikow odpowiadajaca danemu plikowi
    buf jest adresem struktory danych uzytkownika do ktorego wczytujemy dane
    count jest to liczba bajtow, ktore chce wczytac uzytkownik

    Funkcja generic_file_read() wykorzystuje czytanie z wyprzedzeniem, gdy stwierdzi ze odczyt jest sekwencyjny. W strukturze struct_file czytanego pliku znajduja sie pola :

    • f_ramax - wartosc ta zmienia sie dynamicznie w trakcie wykonywania sie funkcji read i dodatkowo musi byc wieksza niz MIN_READAHEAD=PAGE_SIZE*3, i wieksza niz: MAX_READAHEAD=PAGE_SIZE*18, gdzie PAGE_SIZE jest rozmiarem ramki w pamieci operacyjnej.
    • f_rawin


    Z tych pol korzysta funkcja generic_file _readahead(), ktora zwraca nam ramke pamieci czytajac z wyprzedzeniem lub 0 gdy nie udalo sie jej znalezc lub stworzyc nowej ramki.

    Algorytm funkcji generic_file_read():

    {
    • Jesli aktualna pozycja w pliku znajduje sie poza obszarem poprzednio wczytanym z wyprzedzeniem, to ustawiamy pola f_rawin, f_ramax na 0 W przeciwnym przypadku stwierdzamy, ze dostep do pliku jest sekwencyjny i kontynuujemy czytanie z wyprzedzeniem ustawiajac zmienna reada_ok na 1
    • Jesli ilosc danych do przeczytania z pliku jest mniejsza niz polowa rozmiaru ramki pamieci(PAGE_SIZE=4kb) to f_ramax=0 W przeciwnym przypadku jesli ilosc bajtow do przeczytania jest wieksza niz f_ramax to f_ramax=(ilosc danych do przeczytania)
    • for(;;) {
      •  
      • Probujemy znalezc ramke pamieci zawierajaca dane z pliku na aktualnej pozycji
      • Jesli udalo sie nam to idziemy do etykiety znaleziono_ramke Jesli nie to do etykiety nie_znaleziono_ramki znaleziono_ramke:
      • Jesli ramka jest aktualna lub zablokowana(np. z powodu zapelniania jej danymi) to probujemy dostac nastepna ramke wywolujac funkcje generic_file_read () W przeciwnym przypadku pole f_ramax ustawiamy na MIN_READAHEAD
      • Czekamy na ramke(nie na ta dodatkowa byc moze zwrocona przez generic_file_readahead)
      • Jesli ramka jest nieaktualna to idziemy do etykiety ramka_blad Jesli jest aktualna to do etykiety sukces sukces:
      • Obliczamy ilosc bajtow, ktore mamy wczytac z ramki
      • Kopjujemy dane z ramki do bufora uzytkownika
      • Nie zwalniamy ramki ale zmniejszamy licznik odwolan do niej
      • Zwiekszamy pozycje w pliku, pozycje w buforze, ilosc przeczytanych bajtow i ilosc bajtow, ktore mamy do przeczytania o ilosc bajtow przeczytanych z ramki
      • Jesli nie mamy nic do czytania to jesli trzeba to szeregujemy procesy (schedule()) a nastepnie wyskocz z petli for W przeciwnym przypadku idz na poczatek petli for(continue) nie_znaleziono_ramki:
      • Jesli nie mamy dodatkowej ramki przeczytanej z wyprzedzeniem to tworzymy nowa ramke(_get_free_page) i jesli funkcja nie zwrocila bledu to idziemy na poczatek petli for(continue) W przeciwnym przypadku jesli mamy dodatkowa ramke pamieci to dodajemy ja do kolejki haszujacej ramek (wywolujemy funkcje add_to_page_cache) a nastepnie wywolujemy funkcje readpage zglaszajac zadanie zapisania ramki danymi z dysku
      • Jesli operacja zakonczy sie pomyslnie to idziemy do etykiety znaleziono_ramke
      • Jesli blad to wyskakujemy z petli for ramka_blad:: /*znalezlismy ramke ale nie jest aktualna*/
      • Zglaszamy zadanie zapisu ramki (funkcja readpage)
      • Jesli wszystko poszlo dobrze to czekamy na ramke (byc moze jest w trakcie zapelniania) i idziemy do etykiety sukces W przeciwnym przypadku wyskakujemy z petli for }/*koniec petli for*/
    • Jesli mamy dodatkowa ramke to ja zwalniamy
    • Jesli i-wezel nie jest tylko do odczytu to aktualizujemy czas ostatniego dostepu do i-wezla na aktualny
    • Zaznaczamy ze i-wezel jest brudny
    • Zwracamy ilosc przeczytanych bajtow }/*koniec algorytmu funkcji generic_file_read



    Uwagi

    1. Funkcja systemowa read() nie moze opozniac wczytywania danych, tak jak to robi funkcja write() w przypadku zapisu. Jesli dane nie znajduja sie w wewnetrznym buforze jadra, to proces musi zaczekac na pobranie ich z dysku.
    2. W przypadku plików specjalnych oraz laczy komunikacyjnych powrot z funkcji read() jest natychmiastowy, jesli plik byl otworzony z flaga O_NDELAY i nie ma danych do przeslania.
    3. Jesli system nie jest zbyt przeciazony i dane moga przez pewien czas pozostawaæ w buforze, to w przypadku czytania sekwencyjnego, zastosowane wczytywanie z wyprzedzeniem jest skuteczne i w znaczny sposób poprawia wydajnosc systemu.
    4. Implementacja funkcji read() w Linux 2.2.12 jest taka sama jak w wersji 2.0.20. Natomiast troche zmodyfikowana zostala funkcja generic_file_read(). A mianowicie dodano wywolanie funkcji szeregujacej procesy schedule(), zapewne w celu zoptymalizowania pracy systemu. Dodano takze wywolanie funkcji UPDATE_ATIME(inode) do aktualizacji atrybutow i-wezla.



    < Spis treści


    10.3. Pisanie do pliku - funkcja write()



    Objasnienia


    Znaczenie bitow slowa dostepu do pliku, zapisanego w i-wezle na polu mode.
     


       

      bit setuid - bit nadawania efektywnego identyfikatora uzytkownika (fsuid) dla procesu wykonujacego plik,

      bit setgid - to samo dla fsgid,

      bit przyklejania - gdy 1, to w pamieci zostaje kopia pliku po jego wykonaniu,

      typ pliku (4 bity z lewej, ósemkowo):

      001 - FIFO

      002 - Character Device

      004 - Directory

      inne: Regular (zwykly), Block Device, Link, Socket.



    struktury file:

    struct file {
      mode_t f_mode; /* tryb otwarcia pliku (pisanie/czytanie) - to nie jest ten sam tryb, co w i-wezle (patrz wyzej) */

      loff_t f_pos; /* pozycja w pliku */

      struct file_operations *f_op; /* operacje na pliku w konkretnym file-systemie */

      unsigned short f_flags; /* czy plik ma szczególne wlasciwosci (tylko do nadpisywania, niezmienialny, do pisania synchronicznego lub inne) - dotyczy konkretnego otwarcia pliku, a nie wspolnego dla wszystkich otwarc i-wezla*/

      struct file *f_next, *f_prev; /* wskaznik do poprzedniego i nastepnego pliku w tablicy plikow, ktora wcale nie jest tablica, ale lista dwukierunkowa. Jadro trzyma wskaznik do pierwszego pliku. Maksymalny rozmiar tej listy to 1024 = NR_FILE = maksymalna liczba plikow otwartych w systemie */

      struct inode *f_inode; /* i-wezel odpowiadajacy plikowi */

      ... (i inne, dla mojego tematu nieistotne)

    }



    Opis funkcji write() dla wszystkich systemow plikow


    Funkcja write()

    • Sluzy do pisania do pliku (w tym rowniez gniazda, gdyz gniazdo jest typem pliku).
    • funkcja write() sklada sie z dwóch czesci: niezaleznej i zaleznej od systemu plików;
    • Opuszcza semafor na i-wezle;
    • Czesc zalezna od systemu plikow;
    • Podnosi semafor na i-wezle;
    • Sprawdzenie prawa tylko do odczytu;
    • Ustala miejsce odkad nastapi pisanie;
    • Warunek : biezaca pozycja w pliku <= 2GB;
    • Pobierany jest bufor odpowiadajacy i-wezlowi pliku i obliczonemu numerowi bloku;
    • Wczytanie porcji danych do pobranego przed chwila bufora;
    • Uaktualnienie wskaznikow biezacej pozycji w pliku i biezacego miejsca pobierania danych z miejsca wskazanego przy wywolaniu funkcji;
    • Jesli plik zostal otwarty do operacji synchronicznych , wykorzystywany jest algorytm "pisania z opoznieniem". Jesli nie, to zajety blok jest natychmiast zwalniany.
    • Gdy tablica z buforami zapelni sie jej zawartosc jest zapisywana w calosci, a zajmowane bufory - zwalniane.
    • Modyfikacja czasu ostatniej modyfikacji pliku i  i-wezla (ustawiane sa na czas biezacy).;
    • Modyfikacja dlugosci pliku zapisana w i-wezle.;
    • Zaznaczenie i-wezla jako "brudnego" i zwolnienia blokady nalozonej na i-wezel funkcja konczy dzialanie.

    DEFINICJA:
    int write(unsigned int fd, char *buf, unsigned int licznik)

    WYNIK:
    liczba zapisanych bajtow lub blad.

    DANE:
    deskryptor pliku (fd), wskaznik do danych do zapisania (buf), ilosc bajtow do zapisania (licznik).

    Algorytm:

    {

    • Sprawdz w tablicy plikow prawo do pisania (pole file->mode); Jezeli licznik=0, zwroc zero;

    • Upewnij sie, ze:

    • nie ma blokady na rekord do zapisania (locks_verify_area);

    • mozemy czytac z obszaru pamieci wskazywanego przez buf o dlugosci licznik (verify_area);

    • Jesli proces nie ma euid=0 (effective user id), czyli nie jest jednym z superuserow, to:

    • skasuj bit setuid;

    • jezeli bit setgid jest ustawiony, a grupa nie ma prawa wykonywania (odpowiedni bit w slowie dostepu do pliku w i-wezle jest zerem), to zostaw bit setgid, gdyz plik jest kandydatem do obowiazkowego blokowania (mandatory locking), w p.p. skasuj bit setgid (utrata nadawania efektywnego identyfikatora grupy dla procesu przy pisaniu do pliku);

    • Opusc semafor w i-wezle (zabezpieczenie przed jednoczesnym pisaniem);

    • Wywolaj write dla konkretnego file-systemu lub, jesli mamy do czynienia z gniazdem, to write dla gniazda - po prostu funkcje ze struktury file_operations;

    • Podnies semafor;
    }



    Kazdy filesystem ma swoja strukture file_operations, w której okreslone sa charakterystyczne dla niego operacje na plikach:

    struct file_operations {

      int (* lseek) (struct inode *, struct file *, off_t, int);

      int (* read) (struct inode *, struct file *, char *, int);

      int (* write) (struct inode *, struct file *, const char *, int);

      int (* open) (struct inode *, struct file *);

      i inne ... (plik fs.h)

    }

    Do struktury tej prowadzi wskaznik z kazdego pliku w tablicy plikow.



    Po opuszczeniu semafora, w file-systemie Ext2 wykonywana przez jadro jest:

    Funkcja ext2_file_write()


    DEFINICJA:
    int ext2_file_write(inode *inode, file *file, char *buf, int licznik)

    WYNIK:
    liczba zapisanych bajtow lub blad.

    DANE:
    plik oraz odpowiadajacy mu i-wezel, buf i licznik - to wskaznik i rozmiar obszaru w pamieci z danymi (te same, co w funkcji write).
    Algorytm pisania, c.d. :

    {

    Upewnij sie, ze:

        file-system nie jest zamontowany read-only;

        jestesmy plikiem regularnym, tzn. nie gniazdem, katalogiem, FIFO, etc.
         

        Jesli plik jest tylko do nadpisywania (mówi o tym pole f_flags w strukturze file), to zacznij pisac na koncu pliku, w p.p. zacznij od aktualnej pozycji pliku;

        Oblicz numer bloku logicznego, w którym znajduje sie pozycja w pliku, na której bedziemy pisali, oraz offset (to jest tak, ze pozycje mozna traktowac jako adres: bity mowiace o numerze bloku i o przesunieciu);

        while (licznik>0)

        {

          Jesli wyszlismy poza pozycje nr 2GB, to zakoncz petle, a na wartosc wynikowa przypisz blad;

          Pobierz bufor odpowiadajacy logicznemu blokowi i-wezla ( getblk() );

          Liczba bajtów do zapisania := min{liczba bajtów od offsetu do konca bloku, licznik}. Chodzi o to, ze piszemy albo do konca bloku, albo mniej. Urzadzenie blokowe potrafi zapisac tylko caly blok, a nie pojedynczy bajt.

          Odlicz od licznika liczbe bajtow pozostalych do konca bloku;

          Jezli bufor nie jest aktualny i nie mamy go calego zapisac (po to obliczalismy liczbe bajtow do zapisania), to wczytaj odpowiadajacy mu blok z dysku (funkcja ll_rw_block(READ,...));

          Skopiuj dane w pamieci z (argumentu funkcji) 'buf' do pobranego bufora;

          Zaktualizuj Virtual Memory Cache odnosnie danego bloku - pobierz strone, zapisz do niej dane z bloku i zwolnij ja ( funkcja update_vm_cache() );

          Przesun pozycje do pisania w pliku oraz miejsce pobierania danych w 'buf';

          Zaznacz, ze bufor jest aktualny i nie jest brudny (brudny oznacza, ze co innego jest na dysku, niz w pamieci, oraz dane w pamieci sa lepsze);

          Jesli plik jest do pisania synchronicznego, to zapisz bufor do tymczasowej tablicy i nie zapisuj go na dysk, dopoki jej nie zapelnisz (rozmiar tablicy: NBUF =16);

          w p.p. (zapis nie synchroniczny) zwolnij bufor;

          Jesli tablica jest juz pelna, to zapisz ja cala ( funkcja ll_rw_block(WRITE,...) ) i pozwalniaj bufory;

          Zajmij sie nastepnym blokiem w pliku i ustaw w nim offset na 0.

        }

        Jesli zostaly bufory w tymczasowej tablicy (liczba blokow nie byla wielokrotnoscia 16), to je pozapisuj i pozwalniaj;

        Jesli pozycja, w której skonczylismy pisac jest wieksza niz rozmiar pliku, to zwieksz jego rozmiar;

        Ustaw w i-wezle czas ostatniej modyfikacji pliku (mtime) i i-wezla (ctime) na obecny;

        Ustaw pozycje pliku w tablicy plików na ta, gdzie skonczylismy pisac;

        Zaznacz, ze i-wezel jest brudny.

    }

    Uwagi


  • Pisanie z opoznieniem po 16 blokow w funkcji ext2_file_write.
  • Liczba otwartych plikow nie moze byc wieksza niz 1024 (NR_FILE) oraz liczba plikow otwartych przez jeden proces nie moze byc wieksza niz 256 (NR_OPEN).



  • < Spis treści


    10.4. Obsługa łączy nienazwanych




    Wprowadzenie

    Łącza nienazwane są wygodnym mechanizmem komunikacji między procesami spokrewnionymi. Wygodnym dlatego, że system - zamiast programisty - martwi się o zapewnienie synchronizacji.


    Jak system przechowuje łącze?

    Łącza nienazwane przechowywane są w i-węźle z ustawionym polem i_pipe na 1. W polu u ustawione są parametry łącza oraz bufor na dane. Format parametrów jest nastepujący:

    
    struct pipe_inode_info {
    	struct wait_queue * wait; /* kolejka procesów zablokowanych na łączu */
    	char * base;              /* bufor danych */
    	unsigned int start;       /* indeks początku danych w buforze */
    	unsigned int lock;	  /* blokada */
    	unsigned int rd_openers;  /* nieużywane dla łączy nienazwanych */
    	unsigned int wr_openers;  /* nieużywane dla łączy nienazwanych */
    	unsigned int readers;	  /* liczba procesów które mogą czytać */
    	unsigned int writers;	  /* liczba procesów które mogą pisać  */
    };
    
    Bufor danych jest kolejką zaimplementowaną w tablicy. W starszych wersjach przechowywano jeszcze ilość danych w buforze. Obecnie jest to pamiętane w i-węźle. Bufor ma pojemność 4kilobajtów.


    Jak używamy łączy nienazwanych?

    Jak sama nazwa wskazuje łącza nie maja nazw. Odwołuje się do nich przez deskryptory. Ponieważ jedynym sposobem na przekazanie deskryptora do innego procesu jest wykonanie funkcji fork() i stworzenie potomka, który odziedziczy tablicę deskryptorów ojca, łącza nienazwane mogą byc używane jedynie przez procesy spokrewnione.
    Aby komunikować się poprzez łącza nienazwane należy:

    1. Utworzyć łącze
    2. Stworzyć potomka
    3. Zamknąć zbędne deskryptory (nie obligatoryjne, ale rozsądne)
      • Nadawca zamyka deskryptor do czytania
      • Odbiorca zamyka deskryptor do pisania
    4. Enjoy pipe!!!
    Jak widać z tego schematu, łącze jest jednokierunkowe (tzn. należy je używać "tylko w jedna strone"). Jeżeli potrzebujemy komunikacji dwustronnej potrzebujemy dwóch łączy.
    Przyklad:


    Tworzenie łącza - funkcja int pipe(int fd[2])

    Aby utworzyć łącze wywołujemy pipe(), jako parametr przekazujemy dwuelementową tablicę typu int.
    Jeżeli funkcja się powiedzie, zwrócone zostanie zero, a tablica wypełniona numerami deskryptorow.

    fd[0]
    deskryptor do czytania z łącza
    fd[1]
    deskryptor do pisania do łącza
    Jeżeli funkcja się nie powiedzie zwracane jest -1 a na zmiennej errno przekazywane są następujące kody błędu:
    EMFILE
    brak wolnych deskryptorów
    EFAULT
    niewłaściwy argument fd
    Łącza zamyka się zwyczajnie, robiąc close() na deskryptorze. Łącza zamyka się "na zawsze", do raz zamkniętego deskryptora nie ma już powrotu.


    Czytanie z łącza - funkcja int read(int fd, char* buf, size_t count)

    Czytamy z łącza zwykłą funkcją read() ze zwykłym znaczeniem parametrów. Informacja przeczytana opuszcza łącze. W zależności od wypełnienia łącza (ściślej bufora łącza) i parametru count zachowuje się ona w sposob nastepujący:

    proces chce czytać mniej niż jest w buforze
    proces przeczyta tyle bajtów ile zażądal
    proces chce czytać wiecej niż jest w buforze i bufor jest niepusty
    proces przeczyta tyle bajtów ile jest w buforze
    bufor jest pusty i read wywołano z flagą O_NONBLOCK
    read() zwroci -1, errno ustawione na EAGAIN
    bufor jest pusty i read wywołano bez flagi O_NONBLOCK
    proces jest usypiany do czasu gdy w buforze coś się znajdzie
    Wyjątek! Jeżeli nie ma już procesu, ktory miałby otwarty deskryptor pisania do łącza oraz bufor jest pusty, to bez względu na flagę O_NONBLOCK zwracane jest zero.


    Pisanie do łącza - funkcja int write(int fd, char* buf, size_t count)

    Piszemy do łącza zwykłą funkcją write() ze zwykłym znaczeniem parametrów. W zależności od wypełnienia bufora zachowuje się ona w sposob następujacy:
    proces chce zapisać mniej niż jest wolnego miejsca w buforze
    proces zapisze tyle bajtów ile chce
    proces chce zapisać wiecej niż jest wolnego miejsca w buforze oraz read wywołano z flagą O_NONBLOCK
    proces zapisze tyle bajtów ile może
    proces chce zapisać wiecej niż jest wolnego miejsca w buforze oraz read wywołano bez flagi O_NONBLOCK
    proces zapisze tyle ile może i zaśnie do czasu, gdy w łączu zwolni się miejsce. UWAGA! Jeżeli kilka procesów będzie w takiej sytuacji ( zapchane łącze) możliwa jest sytuacja że komunikaty się wymieszają
    Jeżeli nie ma procesu który mogłby czytać z łącza, próba zapisu nie powiedzie się. W takiej sytuacji do procesu zostanie wyslany sygnał SIGPIPE, zwrócona wartość -1 oraz errno ustawione na EPIPE.


    Autor:Michał Domagała


    < Spis treści


    10.5. Zamykanie pliku - funkcja close()



    Spis tresci

    • Wprowadzenie
    • Definicja
    • Struktury danych
    • Implementacja
    • Uwagi
    • Bibliografia


    Wprowadzenie

    • Funkcja zwalnia deskryptor pliku.
    • Daje mozliwosc przydzielenia deskryptora innemu plikowi.
    • Jesli deskryptor jest ostatnim ktory odnosi sie do danej pozycji w tablicy plikow, wowczas pozycja ta jest zwalniana.
    • Zdejmowane sa wszelkie blokady zalozone przez proces na pliku.


    Definicja

    int close(unsigned int fd) fd - deskryptor pliku

    Zwraca -0 jesli sie uda, albo -1 w przypadku niepowodzenia (gdy fd nie jest prawidlowym deskryptorem otwartego pliku)

    Implementacja

    int close(unsigned int fd);
    };
    
    asmlinkage int sys_close(unsigned int fd)
    {
            int error; 
            struct file * filp; /*wskaznik na strukture pliku*/
    
            lock_kernel();
            error = -EBADF;
            filp = fcheck(fd); /*jesli otwarto plik o deskryptorze fd
                                 zwracany jest wskaznik do struktury pliku,
                                 wpp. NULL*/
            if (filp) {
                    struct files_struct * files = current->files;
                    files->fd[fd] = NULL; /* zwolnienie pozycji w tablicy 
                                           deskryptorow*/
                    put_unused_fd(fd);    /*zwolnienie deskryptora*/
                    FD_CLR(fd, &files->close_on_exec);
                    error = filp_close(filp, files);
                    /*wartosc zwrocona przez filp_close
                    zadecyduje o wykonaniu sys_close*/
            }
            unlock_kernel();
            return error;
    }
    
    
    int filp_close(struct file *filp, fl_owner_t id)
    {
            int retval;
            struct dentry *dentry = filp->f_dentry;
    
            if (filp->f_count == 0) { 
             /*jesli nikt juz nie odwoluje sie do pliku*/
                    printk("VFS: Close: file count is 0\n");
                    return 0;
            }
            retval = 0;
            if (filp->f_op && filp->f_op->flush)
                    retval = filp->f_op->flush(filp);
            if (dentry->d_inode)
               /*zdjecie blokad na pliku zalozonych przez proces*/
               locks_remove_posix(filp, id); 
            fput(filp);
            return retval;
    }
    


    Uwagi

    1. Funkcja close zwraca w przypadku bledu kod bledu na zmiennej



    Bibliografia

    1. Pliki zrodlowe Linuxa: ./fs/open.c 2. Pomoc "man" dostepna pod linuxem.


    < Spis treści


    10.6. Blokowanie plików



    Czym jest blokowanie plików?

    Blokowanie pliku polega na ograniczeniu innym procesom dostępu do tego pliku. Na przykład, jeśli proces chce coś zapisać do pliku, to chciałby zabronić innym procesom dostępu do niego na czas zapisywania.


    Terminologia

    Wiele angielskich terminów można przetłumaczyć na słowo blokada. Aby uniknąć nieporozumień, przyjmijmy:

    • blokadę na pliku, czyli ograniczenie dostępu do pliku (ang. lock) będziemy nazywać blokadą
    • blokadę procesu, czyli jego uśpienie (ang. wait) będziemy nazywać wstrzymywaniem
    • blokadę systemu, czyli wzajemną blokadę procesów (ang. deadlock) będziemy nazywać zakleszczeniem lub zastojem.


    Ogólny schemat stosowania blokad

    1. Załóż blokadę na plik
    2. Pracuj z plikiem
    3. Zdejmij blokadę
    Aby dowiedzieć się o blokadach obowiązkowych, działających trochę inaczej, kliknij tu.


    Blokujemy na dwa sposoby


    Standard BSD

    Dwa rodzaje blokad:dzielona (ang. shared) i wyłączna (ang. exclusive)

    fd - deskryptor pliku który chcemy blokować

    Aby założyc blokadę dzieloną
    Napisz flock(fd, LOCK_SH)
    Aby założyc blokadę wyłączną
    Napisz flock(fd, LOCK_EX)
    Aby zdjąć blokadę
    Napisz flock(fd, LOCK_UN)
    Zakładanie blokady jest operacją wstrzymującą, to znaczy jeżeli proces nie może założyc blokady, to zaśnie aż do chwili, gdy będzie mógł. Można blokować niewstrzymująco, dodając flagę LOCK_NB.
    Przykład: flock(fd, LOCK_SH|LOCK_NB)
    Blokady BSD nie działają na NFS.


    Standard POSIX

    Sprawa bardziej skomplikowana.
    Blokujemy nie całe pliki, lecz wybrane kawałki.
    Aby założyć blokadę, nie wystarczy podać LOCK_*. Trzeba wypełnić następującą strukturę:

    
    struct flock {
            short l_type;         /* typ blokady */
            short l_whence;   /* tryb obliczania przesunięcia w rekordzie (jak w lseek)*/
            off_t l_start;         /* początek obszaru blokowanego */
            off_t l_len;           /* długość obszaru blokowanego */
            pid_t l_pid;          /* właściciel blokady  */
    };
    
    
    Typy blokady
    F_RDLCK
    Blokada odczytu
    F_WRLCK
    Blokada zapisu
    F_UNLCK
    Zdjęcie blokady
    fd - deskryptor pliku który chcemy blokować
    lock - zmienna typu struct flock
    Aby założyć blokadę należy:
    1. Wypełnić skrupulatnie strukture lock
    2. Napisać fcntl(fd, _polecenie_, &lock)
    Korzystamy z nastepujących poleceń funkcji fcntl():
    F_SETLK
    Niewstrzymująca próba założenia blokady
    F_SETLKW
    Wstrzymująca próba założenia blokady.
    UWAGA!!!System sprawdza czy nie wystąpi zakleszczenie (ang. deadlock).
    Jeśli tak to założenie blokady się nie powiedzie.
    F_GETLK
    Sprawdzenie czy założenie blokady doprowadzi do zakleszczenia. Jeśli nie, to zmodyfikuje pole l_type na F_UNLCK, jeśli tak to na lock zwróci kolidującą blokadę.
    Blokady POSIX działają na NFS.


    Blokady POSIX i BSD są względem siebie całkowicie niezależne!(ang. oblivious).


    Blokady zalecane i obowiązkowe

    Opisane wyżej blokady są zalecane (ang. advisory), co oznacza że ich stosowanie jest dobrowolne. Jeżeli jakiś proces nie chce stosować się do ceremoniału zakładania i zwalniania blokad, nikt go nie zmusi. Ponadto blokady zalecane funkcjonują jedynie pomiędzy procesami je stosującymi. Oznacza to, że proces-prostak może dostać się do zablokowanego pliku, a także uda się założyć blokadę na plik aktualnie używany przez prostaka.

    Kiedy używamy blokad obowiązkowych (ang. mandatory), wszystkie procesy muszą je stosować. Toteż jeżeli jeden proces założy blokadę obowiązkową, wszystkie inne muszą się podporządkować. Ponadto, każde wywołanie funkcji systemowych takich jak open, read,write itp. powoduje sprawdzenie istniejących blokad oraz założenie własnych. Na przykład próba odczytania pierwszych dziesięciu bajtów pliku f spowoduje blokadę odczytu na pierwszych 10 bajtach f.
    Blokady obowiązkowe są cechą systemu plików. Są włączane i wyłączane podczas montowania. Standardowo blokady obowiązkowe są wyłączone.
    Blokady obowiązkowe sa zawsze typu POSIX



    Autor:Michał Domagała

    < Spis treści


    11. System plików proc



    1. System plików proc

    • jest strukturą zaimplementowaną w jądrze linuxa,
    • nie jest związany z żadnym fizycznym urządzeniem,
    • nie odróżnia się zasadniczo od zwykłego systemu plikowego.



    2. Powody, dla których istnieje system plików proc

    • umożliwia łatwe śledzenie procesów (zawartość pamięci, deskryptory plików, parametry procesu),
    • w sposób prosty i przejrzysty pokazuje wiele parametrów systemu operacyjnego oraz sprzętu (z informacji tych korzysta wiele programów),
    • umożliwia zmianę niektórych zmiennych jądra w trakcie jego działania.



    3. Implementacja systemu plików proc

    • ...znajduje się w katalogu fs/proc/
    • Rejestracja systemu plików proc:
      • linux dowiaduje się o nim w procedurze sys_setup umieszczonej w pliku fs/filesystems.c, która standardowo jest wykonywana przy starcie systemu
      • procedura ta wywołuje procedurę init_proc_fs umieszczoną w pliku fs/proc/procfs_syms.c, która inicjuje strukturę proc
      • inicjacja, poza opisem modułu, w którym siedzi proc, rejestruje go w linuxie za pomocą procedury register_filesystem umieszczonej w pliku fs/super.c. Od tej pory system plików typu proc może być montowany.
      • jak wiadomo przy starcie linuxa montowane są systemy plików zapisane w pliku fstab. W większości dystrybucji dokonuje się to podczas uruchamiania skryptu /sbin/init.d/boot. Zrobiono jednak wyjątek dla proc. Nie jest ważne, czy znajduje się on w fstab, czy nie; nie jest brany pod uwagę. Montuje się go w tym samym skrypcie przed zamontowaniem pozostałych systemów plików z fstab.

    • Interfejs vfs dla systemu plików proc:
      • proc jak każdy inny system plików korzysta z interfejsu vfs
      • super_block dla proc jest zwracany przez funkcję proc_read_super (jest tam generowany). Proc posiada również własne operacje na i-węzłach (proc_sops). Wszystkie te funkcje są umieszczone w pliku proc/inode.c.
      • poszczególne pliki udostępniają zróżnicowane interfejsy. Standardowo zaimplementowane są tylko funkcje odczytu katalogu i oglądania pliku. Dla niektórych plików jest dodatkowo funkcja zapisu pliku. Dla linków są zaimplementowane funkcje czytania linka i podążania za linkiem. Interfejsy plików są umieszczone w strukturach typu file_operations i inode_operations w różnych plikach katalogu fs/proc/ .

    • Budowa wewnętrzna proc
      • drzewo plików zbudowane jest ze struktur proc_dir_entry (plik include/linux/proc_fs.h):
        
        struct proc_dir_entry {
          //Wirtualny numer i-węzła
          unsigned short low_ino;
          //Długość nazwy pliku
          unsigned short namelen;
          //Nazwa pliku
          const char *name;
          //Atrybuty pliku (czy jest katalogiem itp.)
          mode_t mode;
          //Liczba plików w katalogu (dla katalogów)
          nlink_t nlink;
          //Identyfikator właściciela pliku
          uid_t uid;
          //Identyfikator grupy właściciela pliku
          gid_t gid;
          //Długosc pliku
          unsigned long size;
          //Wskaźnik do struktury opisującej dostępne operacje na pliku
          struct inode_operations *ops;
          //Funkcja wywoływana przy odczycie pliku
          int (*get_info)(char*,char**,off_t,int,int);
          //Funkcja ustawiająca w i-węźle odpowiednie atrybuty dla pliku
          void (*fill_inode)(struct inode*);
          //Wskaźnik do następnego pliku w katalogu
          struct proc_dir_entry *next;
          //Wskaźnik do katalogu nadrzędnego pliku
          struct proc_dir_entry *parent;
          //Wskaźnik do pierwszego pliku w katalogu (dla katalogów)
          struct proc_dir_entry *subdir;
          //Dane
          void *data;
        };
        
      • każda taka struktura opisuje jakiś plik lub katalog. Wyjątkiem są katalogi procesów. Wszystkie one korzystają z tej samej gałęzi drzewa struktur proc_dir_entry. Rozróżniane są one na poziomie funkcji dostępu do plików. Jest to zaimplementowane w fukcjach proc_read_dir i proc_root_lookup
      • drzewo jest budowane za pomocą funkcji proc_register i proc_register_dynamic w funkcjach proc_root_init, proc_base_init i innych (plik fs/proc/root.c).

      Drzewo struktur proc_dir_entry


      • interfejs proc-a (patrz interfejs vfs) w swoich funkcjach korzysta właśnie z tych struktur (na ich podstawie generuje odpowiednie i-węzły)
      • podczas odczytu pliku w domyśle zawartość powinna być zwracana przez funkcję get_info. Często funkcja ta jest pusta. Wtedy wykonywana jest odpowiednia funkcja z tablicy (tak naprawde nie jest to tablica). Cały mechanizm wyszukiwania funkcji znajduje się w pliku fs/proc/array.c
      • w tym samym pliku znajdują się poszczególne funkcje zwracające dane dla różnych plików np. get_uptime, get_kstat, get_loadavg.



    4. Opis wybranych plików i katalogów

    • proc/cmdline - informacje o trybie uruchomienia systemu
    • proc/cpuinfo - informacje o procesorze
    • proc/devices - lista numerów urządzeń
    • proc/dma - lista zarejestrowanych kanałów DMA
    • proc/filesystems - lista dostępnych systemów plików
    • proc/interrupts - lista przerwań wraz z liczbami wywołań
    • proc/ioports - lista zarejestrowanych regionów portów I/O
    • proc/kcore - pamięć fizyczna systemu (format pliku core)
    • proc/kmsg - jeżeli nie pracuje syslog, to lista wiadomości jądra
    • proc/ksyms - lista definicji symboli modułów
    • proc/meminfo - informacje o pamięci
    • proc/modules - lista modułów załadowanych do systemu
    • proc/mounts - lista zamontowanych systemów plików
    • proc/net/ - katalog z różnymi plikami dotyczącymi sieci
    • proc/net/route - lista interfejsów sieciowych
    • proc/pci - lista znalezionych urządzeń PCI
    • proc/scsi/ - katalog z różnymi plikami dotyczącymi urządzeń SCSI
    • proc/self/ - link do katalogu procesu odwołującego się do proc
    • proc/stat - statystyki jądra systemu
    • proc/sys/ - katalog z różnymi plikami dotyczącymi systemu
    • proc/sys/fs/ - katalog z różnymi zmiennymi systemu plików
    • proc/sys/kernel/ - katalog z różnymi zmiennymi jądra
    • proc/sys/net/ - katalog z różnymi zmiennymi dotyczącymi sieci
    • proc/sys/vm/ - katalog z różnymi zmiennymi dotyczącymi pamięci wirtualnej
    • proc/uptime - czas działania systemu
    • proc/version - informacje o wersji jądra
    • proc/(pid)/ - katalog z informacjami o procesie o numerze (pid)
    • proc/(pid)/cmdline - linia wywołania (komendy) procesu
    • proc/(pid)/cwd - link do bieżącego katalogu procesu
    • proc/(pid)/environ - zmienne środowiskowe procesu
    • proc/(pid)/exe - link do wykonywanego kodu procesu
    • proc/(pid)/fd - katalog używanych deskryptorów plików
    • proc/(pid)/maps - lista obecnie zmapowanych regionów pamięci procesu
    • proc/(pid)/mem - pamięć procesu
    • proc/(pid)/root - link do katalogu głównego dla procesu



    Autor: Paweł Kwiatkowski - pawel.kwiatkowski@students.mimuw.edu.pl, rysunek: Jarosław Staniek

    < Spis treści