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.
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.
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:
static struct file_system_type *file_systems
.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.
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).
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.
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.
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:
Funkcją i_put() można zwolnić i-węzeł znajdujżcy się w pamięci:
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()
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.
Diagram algorytmu ext2_new_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
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
Autor: Bartosz Kapturkiewicz , bk174730@students.mimuw.edu.pl
Rodzaje kolejek LRU |
Stała | Wartość | Opis kolejki |
BUF_CLEAN | Kolejka buforów, których bloki mają zawartość zgodną z zawartością odpowiednich bloków na dysku | |
BUF_LOCKED | Kolejka buforów zablokowanych | |
BUF_DIRTY | Kolejka buforów, wktórych bloki danych mają zawartość niezgodną z odpowiadającymi im blokami dyskowymi |
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 */ };
Flaga | Wartość | Znaczenie |
BH_Uptodate | 1 jeśli zawartość bufora jest zgodna z zawartością odpowiedniego bloków na dysku | |
BH_Dirty | 1 jeśli bufor zawiera modyfikowane dane | |
BH_Lock | 1 jeśli dostęp do bufora jest zablokowany (procesy oczekują w kolejce b_wait) | |
BH_Req | 0 jeśli dane w buforze zastały unieważnione | |
BH_Protected | 1 jeśli bufor jest chroniony | |
BH_LowPrio | 1 jeśli bufor ma niski priorytet |
Parametry wątku bdflush |
Parametr | Domyślna wartość | Znaczenie |
nfract | Procentowa część buforów z zawartością niezgodną z odpowiednim blokiem na dysku (po jej przekroczeniu uaktywniany jest proces bdflush) | |
ndirty | Maksymalna liczba bloków, które demon dbflush może zapisać przy jednokrotnym uaktywnienu | |
nrefill | Liczba buforów zawierających dane zgodne odpowiednimi blokami na dysku generowana przy jednokrotnym wywołaniu refill_freelist() | |
nref_dirt | Liczba buforów zawierających dane niezgodne z danymi na dysku, po przekroczeniu której uaktywniany jest demon dbflush w funkcji refill_freelist | |
interval | opóźnienie zapisu w procesie kupdate | |
age_buffer | czas, o który opóźniony jest zapis bloku zawierającego nieuaktualnione dane | |
age_super | czas, o który opóźniony jest zapis bloku specjalnego zawierającego nieuaktualnione dane | |
dummy2 | parametr nie jest używany | |
dummy3 | parametr nie jest używany |
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};
inode | jednoznaczny numer i-węzła |
rec_len | rozmiar pozycji |
name_len | długość nazwy |
name | nazwa |
czytanie | możliwość przeglądania zawartości katalogu |
pisanie | tworzenie i usuwanie plików |
wykonywanie | możliwość przeszukiwania katalogu |
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:
Pozycje aktualnie nieużywane (tzn. d\_count
= 0) przechowywane są w kolejce LRU dentry_unused.
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:
Geneza algorytmu:
#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)
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.
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
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:
Uwagi:
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:
Istnieje również dostęp do informacji na temat: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}
Do montowania służy polecenie mount, które odpowiada funkcji systemowej mount().
Typy systemów plików obsługiwanych przez Linuxa, to m.in:
Algorytm montowania:
Przykład polecenia montowania - dla partycji MS-DOS:
# mount -t vfat /dev/hda5 /mnt/dos
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ść.
Algorytm funkcji umount():
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.
Czynności wykonywane dla pierwszej komendy polegają głównie na wywołaniu funkcji namei("/mnt/dos"):# cd /mnt/dos/programy # cd ../..
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:
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 |
} |
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) alboW tablicy tej przechowywane są cztery typy adresów:
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
|
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 */ };
Prawa nadane procesowi, który otworzył plik:
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.
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
int block
block=logiczny-number-bajtu/rozmiar-bloku
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)
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 )
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
int addr_per_block_bits
Bloki pliku należą do czterech kategorii. Zaczynając od zera, mamy:
EXT2_NDIR_BLOCKS
(=12)2^(addr_per_block_bits) = addr_per_block
2^(2*addr_per_block_bits) = addr_per_block^2
2^(3*addr_per_block_bits) = addr_per_block^3
max= 12 + addr_per_block + addr_per_block^2 + addr_per_block^3
bloków.
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.
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:
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.
bmap
bmap
ext2_bmap
ext2_bmap
,
makro inode_bmap
,
funkcja block_bmap
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)
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)
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); }
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.
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
{
f_rawin; /*rozmiar danych wczytanych w trakcie ostatnio wykonywanego czytania z wyprzedzeniem */
f_ramax; /*aktualny maksymalny rozmiar danych, ktore mozna przeczytac z wyprzedzeniem */
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 :
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.
{
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):
002 - Character Device
004 - Directory
inne: Regular (zwykly), Block Device, Link, Socket.
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)
WYNIK:
liczba zapisanych bajtow lub blad.
DANE:
deskryptor pliku (fd), wskaznik do danych do zapisania (buf),
ilosc bajtow do zapisania (licznik).
Algorytm:
{
struct file_operations {
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.
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:
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)
{
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.
}
Łącza nienazwane są wygodnym mechanizmem komunikacji między procesami spokrewnionymi. Wygodnym dlatego, że system - zamiast programisty - martwi się o zapewnienie synchronizacji.
Łą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 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:
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.
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:
Zwraca -0 jesli sie uda, albo -1 w przypadku niepowodzenia (gdy fd nie
jest prawidlowym deskryptorem otwartego pliku)
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; }
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.
Wiele angielskich terminów można przetłumaczyć na słowo blokada. Aby uniknąć nieporozumień, przyjmijmy:
Dwa rodzaje blokad:dzielona (ang. shared) i wyłączna (ang. exclusive)
fd - deskryptor pliku który chcemy blokować
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
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
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; };