Do spisu treści systemu plików

Skomentowany kod funkcji systemowych dotyczących realizacji operacji odczytu i zapisu w systemie plików FAT


Na tej stronie znajduje się skomentowany kod następujących funkcji:

fat_file_read()
fat_file_write()
fat_prefetch()
fat_truncate()
fat_bread()
fat_getblk()
fat_brelse()
fat_mark_buffer_dirty()
fat_set_uptodate()
fat_is_uptodate()
fat_ll_rw_block()

fat_prefetch()

Definicja tej funkcji znajduje się w pliku "fs/fat/file.c". Parametr "nb" podaje liczbę bloków, które należy wczytać naraz, zaś struktura "pre" opisuje listę buforów, która zostanie wczytana przez tę funkcję. Funkcja nadrzędna powinna była ustawić pole (*pre).file_sector na numer sektora, od którego zacznie się wczytywanie.
static void fat_prefetch (struct inode *inode, struct fat_pre *pre, int nb)
{

Super-blok jest ten sam, co super-blok i-węzła.

	struct super_block *sb = inode->i_sb;

Pomocnicza lista wczytywanych buforów. Inicjalnie ich liczba jest zerowa.

	struct buffer_head *bhreq[MSDOS_PREFETCH];
	int nbreq = 0;
	int i;
	for (i=0; i < nb; i++)
	{

Mapujemy numer pierwszego sektora do pobrania na fizyczny numer bloku na dysku

		int sector = fat_smap(inode,pre->file_sector);
		if (sector != 0){

Jeżeli mapowanie się powiodło, to wczytujemy blok do bufora. Gdy wczytywanie bloku się powiedzie, nagłówek bufora dopisujemy do listy w strukturze "pre". Następnie sprawdzamy, czy bufor, który mamy, jest aktualny. Jeżeli nie jest, to dopisujemy go do pomocniczej listy "bhreq". Ustawiamy numer kolejnego bloku do wczytywania na następny po właśnie wczytanym.

			struct buffer_head *bh;
			pre->file_sector++;
			bh = fat_getblk(sb, sector);
			if (bh == NULL)	break;
			pre->bhlist[pre->nblist++] = bh;
			if (!fat_is_uptodate(sb,bh))
				bhreq[nbreq++] = bh;
		}else{

Jeżeli mapowanie się nie udało, to kończymy.

			break;
		}
	}

Możliwe, że niektóre bufory są nieaktualne, musimy dla tych buforów wywołać "fat_ll_rw_block", aby wymusić pobranie z dysku ich aktualnej wersji. Nieaktualne bufory zostały zapisane na liście "bhreq" podczas pobierania (patrz wyżej).
 

	if (nbreq > 0) fat_ll_rw_block (sb,READ,nbreq,bhreq);

Jeżeli wczytanych buforów jest mniej niż wynosi długość listy buforów do zapełnienia, to pozostałe miejsca wypełniamy wskaźnikiem pustym.

	for (i=pre->nblist; ibhlist[i] = NULL;
}

fat_file_read()

Definicja tej funkcji znajduje się w pliku "fs/fat/file.c".
Funkcja ta realizuje operację wczytywania danych. Jest wywoływana bezpośrednio przez funkcję "read", struktura "file_operations" dla systemu plików FAT ma wskaźnik standardowej funkcji odczytu ustawiony na tę właśnie funkcję, zaś funkcja "read" odwołuje się do niskopoziomowej funkcji odczytu za pośrednictwem tego właśnie wskaźnika, dlatego operacja czytania danych z pliku znajdującego się w filesystemie typu FAT sprowadza się w zasadzie do wywołania tej właśnie funkcji.
Parametr "inode" wskazuje na i-węzeł pliku, z którego chcemy czytać. Parametr "filp" wskazuje na strukturę typu "file" tego pliku
int fat_file_read(struct inode *inode, struct file *filp, char *buf, int count)
{
	struct super_block *sb = inode->i_sb;
	char *start = buf;
	char *end   = buf + count;
	int i;
	int left_in_file;
	struct fat_pre pre;
		
Sprawdzamy, czy parametr wskazujący na i-węzeł jest poprawny

	if (!inode) {
		printk("fat_file_read: inode = NULL\n");
		return -EINVAL;
	}

Sprawdzamy, czy ten i-węzeł wskazuje na normalny plik lub na link do pliku. Jeśli nie, to wychodzimy z błędem. Linki występują tu w przypadku systemu UMSDOS, przy normalnym MSDOS nie powinno ich być.

	if (!S_ISREG(inode->i_mode) && !S_ISLNK(inode->i_mode)) {
		printk("fat_file_read: mode = %07o\n",inode->i_mode);
		return -EINVAL;
	}

Jeżeli pozycja pliku przekracza jego rozmiar zapisany w i-węźle albo liczba bajtów do przeczytania nie jest większa niż zero, to wyjdź z błędem.

	if (filp->f_pos >= inode->i_size || count <= 0) return 0;

Teraz powiemy cache'owi buforów, który blok zamierzamy wczytać z wyprzedzeniem. Czytamy z wyprzedzeniem co najwyżej MSDOS_PREFETCH buforów, ponieważ jesteśmy ograniczeni pojemnością stosu, rezultat tego wczytywania (listę buforów) musimy trzymać w lokalnych tablicach. Za każdym razem, gdy dostajemy bufor, uaktualniamy jego zawartość z dysku, jeśli to potrzebne.

	{

Musimy wczytywać całe bloki, więc do liczby bajtów, które chcemy pobrać z dysku (zmienna count_max) musimy dodać liczbę bajtów, która dzieli bieżącą pozycję w pliku od początku bloku. Zmienna to_reada jest liczbą bloków, które chcemy wczytać, obliczamy ją biorąc sufit z ilorazu liczby bajtów do wczytania i rozmiaru bloku. Inicjalizujemy numer bieżącego sektora, dzieląc pozycję w pliku przez rozmiar sektora.

		int count_max = (filp->f_pos & (SECTOR_SIZE-1)) + count;
		int to_reada;		
		pre.file_sector = filp->f_pos >> SECTOR_BITS;
		to_reada = count_max / SECTOR_SIZE;
		if (count_max & (SECTOR_SIZE-1)) to_reada++;

Jeżeli ustawiony jest znacznik czytania z wyprzedzeniem, a plik, który czytamy nie jest plikiem binarnym, to będziemy czytać z wyprzedzeniem, bo przy plikach tekstowych nie wiemy z góry ile będzie trzeba przeczytać. Plików binarnych nie czytamy domyślnie z wyprzedzeniem.

		if (filp->f_reada || !MSDOS_I(inode)->i_binary){

Zwiększ ilość bloków do przeczytania o ilość bloków, które należy przeczytać z wyprzedzeniem zapisaną w globalnej zmiennej read_ahead[] dla tego urządzenia, ale co najmniej o osiem.

			int ahead = read_ahead[MAJOR(inode->i_dev)];
			if (ahead == 0) ahead = 8;
			to_reada += ahead;
		}

Całkowita liczba bloków, które chcemy wczytać nie może być jednak większa niż pewna zadana z góry, maksymalna liczba MSDOS_PREFETCH.
 
		if (to_reada > MSDOS_PREFETCH) to_reada = MSDOS_PREFETCH;

Inicjalizujemy listę buforów ustawiając ilość buforów na liście na zero, następnie wczytujemy do buforów.

		pre.nblist = 0;
		fat_prefetch (inode,&pre,to_reada);
	}
Inicjalizujemy na zero indeks na liście buforów do wczytania.

	pre.nolist = 0;

Dopóki nie przekroczyliśmy końca pliku ani końca bufora w pamięci użytkownika, w pętli kopiujemy zawartość buforów do pamięci i doczytujemy nowe bufory z dysku, jeśli potrzeba. Wygląda to tak, że przechodzimy przez listę buforów wczytanych uprzednio, zapamiętujemy sobie wskaźnik na bieżący bufor, zaś w odpowiadającym miejscu na liście wstawiamy wskaźnik pusty. W ten sposób zostawiamy za sobą wskaźniki puste aż dojdziemy do połowy listy buforów. Jeśli dojdziemy do połowy, to tę drugą część listy przesuwamy na początek listy i dogrywamy do drugiej połówki listy buforów kolejną porcję, po czym przesuwamy się na początek listy i cały algorytm się powtarza.

	while ((left_in_file = inode->i_size - filp->f_pos) > 0
		&& buf < end){

Bieżącym buforem jest teraz kolejny do zapisania na liście. Jeżeli on nie istnieje (wskaźnik na niego jest pusty), kończymy pętlę. W przeciwnym razie zapamiętujemy sobie wskaźnik na niego w zmiennej "bh" i ustawiamy wskaźnik odpowiadający mu na liście na pusty oraz przesuwamy znacznik bieżącego bufora na liście.

		struct buffer_head *bh = pre.bhlist[pre.nolist];
		char *data;
		int size,offset;
		if (bh == NULL) break;

		pre.bhlist[pre.nolist] = NULL;
		pre.nolist++;

Jeżeli doszliśmy do połowy listy, to będzie trzeba dograć bufory z dysku. Przesuwamy zawartość drugiej połowy listy na początek, aktualizujemy ilość buforów (odejmując długość połowy listy, czyli tego, co już przetworzyliśmy od ostatniego razu) i dla tak uaktualnionej struktury wywołujemy "fat_prefetch", żądając wypełnienia drugiej połowy listy. Struktura "pre" ma pole wskazujące na numer sektora, które "fat_prefetch" uaktualnia po każdym dograniu kolejnego bufora, dlatego nie musimy dodatkowo wskazywać, które bloki wczytywać. Po wczytaniu przesuwamy się na początek listy, ponieważ adres kolejnego bufora do przetworzenia teraz, po skopiowaniu, jest na początku listy.

		if (pre.nolist == MSDOS_PREFETCH/2){
			memcpy (pre.bhlist,pre.bhlist+MSDOS_PREFETCH/2
				,(MSDOS_PREFETCH/2)*sizeof(pre.bhlist[0]));
			pre.nblist -= MSDOS_PREFETCH/2;
			fat_prefetch (inode,&pre,MSDOS_PREFETCH/2);
			pre.nolist = 0;
		}

Aktualny bufor, którego adres wcześniej zapamiętaliśmy, może jeszcze nie być dostępny (może być w trakcie wczytywania), więc czekamy, aż się pojawi.

		wait_on_buffer(bh);

Jeżeli ten bufor nie jest świeży, to prawdopodobnie mamy jakiś błąd odczytu, więc zwalniamy bufor i wychodzimy z pętli.
 

		if (!fat_is_uptodate(sb,bh)){
			fat_brelse (sb, bh);
			break;
		}

Dane, które chcemy wczytać z pliku nie muszą koniecznie znajdować się na początku bufora. Ustawiamy zmienne "data" i "size" odpowiednio tak, aby wskazywały na początek i na długość obszaru w buforze, który nas interesuje.

		offset = filp->f_pos & (SECTOR_SIZE-1);
		data = bh->b_data + offset;
		size = MIN(SECTOR_SIZE-offset,left_in_file);

Jeżeli plik jest binarny, to po prostu kopiujemy zawartość bufora do przestrzeni użytkownika bez żadnego filtrowania (które potrzebne jest przy plikach tekstowych), ale nie więcej niż użytkownik wyspecyfikował miejsca w buforze. Używamy do tego funkcji "memcpy_tofs" (Nazwę należy rozumieć jako "memory copy to far segment", a nie jako "memory copy to filesystem"), której definicja (inline) jest w pliku "/include/asm-i386/segment.h".

		if (MSDOS_I(inode)->i_binary) {
			size = MIN(size,end-buf);
			memcpy_tofs(buf,data,size);
			buf += size;
			filp->f_pos += size;
		}else{

Jeżeli wczytywany plik jest plikiem tekstowym, to opiujemy znaki z bufora do pamięci użytkownika, pomijając znak carriage_return i interpretując ctrl-Z jako znak końca pliku (przesuwa się znacznik bieżącej pozycji odczytywanej na koniec pliku). Funkcja "put_user" zapisuje jeden znak do obszaru użytkownika pod wskazany adres. Jej definicja (inline) znajduje się w pliku "/include/asm-i386/segment.h".

			for (; size && buf < end; size--) {
				char ch = *data++;
				filp->f_pos++;
				if (ch == 26){
					filp->f_pos = inode->i_size;
					break;
				}else if (ch != '\r'){
					put_user(ch,buf++);
				}
			}
		}

Po wczytaniu zawartości bufora do przestrzeni użytkownika zwlaniamy bufor, nie będzie już nam potrzebny.

		fat_brelse(sb, bh);
	}

Jeżeli zostały jeszcze wśród wczytanych jakieś bufory, których nie przetworzyliśmy (np. dlatego, że napotkano znak końca pliku (ctrl-Z) w pliku tekstowym), to zwalniamy tę resztę buforów.

	for (i=0; i < pre.nblist; i++)
		fat_brelse (sb, pre.bhlist[i]);

Jeżeli po tym wszystkim znacznik bieżącej pozycji w pamięci użytkownika się nie zmienił (tzn. że nie przesunęliśmy go kopiując z pliku jakieś znaki), to zwaracamy kod błędu operacji wejścia-wyjścia.

	if (start == buf)
		return -EIO;

Jeżeli ten plik nie był tylko do odczytu, to w jego i-węźle uaktulaniamy czas ostatniego dostępu na bieżący.

	if (!IS_RDONLY(inode))
		inode->i_atime = CURRENT_TIME;

Ustawiamy w deskryptorze pliku znacznik informujący, że dane tego pliku są wczytywane z wyprzedzeniem.

	filp->f_reada = 1;

Zwracamy liczbę wczytanych bajtów równą różnicy między aktualną a (zapamiętaną uprzednio) pierwotną pozycją wskaźnika do bufora w pamięci użytkownika.

	return buf-start;
}

fat_file_write()

Definicja tej funkcji znajduje się w pliku "fs/fat/file.c" Parametr "inode" wskazuje na i-węzeł pliku, do którego zapisujemy, parametr "filp" na jego deskryptor (struktura "file"), "buf" na adres w obszarze użytkownika zaś "count" jest liczbą zapisywanych bajtów.

int fat_file_write(
	struct inode *inode,
	struct file *filp,
	const char *buf,
	int count)
{
	struct super_block *sb = inode->i_sb;
	int sector,offset,size,left,written;
	int error,carry;
	const char *start;
	char *to,ch;
	struct buffer_head *bh;
	int binary_mode = MSDOS_I(inode)->i_binary;

Najpierw sprawdzamy poprawność argumentów, "inode" musi być niepusty zaś plik musi być albo linkiem, albo zwykłym plikiem. (Linki dotyczą systemu UMSDOS).

	if (!inode) {
		printk("fat_file_write: inode = NULL\n");
		return -EINVAL;
	}
	if (!S_ISREG(inode->i_mode) && !S_ISLNK(inode->i_mode)) {
		printk("fat_file_write: mode = %07o\n",inode->i_mode);
		return -EINVAL;
	}

Niektóre pliki (np. systemowe) mogą mieć zablokowane prawo do modyfikacji. Sprawdzamy to.

	if (IS_IMMUTABLE(inode))
		return -EPERM;

Jeżeli plik jest otwarty w trybie dopisywania, ustawiamy wskaźnik bieżącej pozycji na koniec pliku. Ale uwaga: taka obsługa operacji "append" może nie działać poprawnie, jeżeli kilka procesów próbuje dopisywać naraz.

	if (filp->f_flags & O_APPEND)
		filp->f_pos = inode->i_size;

Znów sprawdzamy argumenty.

	if (count <= 0)
		return 0;

Zapisujemy w pętli porcjami, zawsze na koniec przebiegu "size" podaje rozmiar zapisanej porcji. To dopóki nie dojdziemy do końca bufora użytkownika. Przy plikach tekstowych na ogół trzeba dopisywać znak carriage-return za użytkownika (ze względu na inny format plików tekstowych w DOSie i Linuxie, w Linuxie koniec wiersza zaznacza się jedynie przez linefeed, a w DOSie przez sekwencję carriage-return i linefeed, dlatego zapisując do pliku DOSowego przed każdym linefeed trzeba dodawać carriage-return), a te znaki wstawiane są pomiędzy dane użytkownika, więc może się zdarzyć, że w jednym przebiegu nie zapiszemy całej porcji (bo część zapisanych znaków to dodatkowo wstawione znaki końca wiersza), więc po zakończeniu jednego przebiegu może zostać trochę znaków do zapisania, wówczas może być potrzebny dodatkowy przebieg pętli. Generalnie nie jest to problemem, jeżeli są jeszcze dane do zapisania, bo wtedy wskaźnik bieżącego znaku w buforze użytkownika po prostu przesunie się naprzód mniej, niż zaplanowano i zostanie to nadrobione przy następnym przebiegu pętli. Problem jest wtedy, gdy już pobrano wszystkie znaki z przestrzeni użytkownika, a jednak nie wszystko udało się zapisać na dysk. Łatwo wydedukować, że taka sytuacja może wystąpić jedynie wtedy, gdy tym znakiem, którego nie udało się zapisać jest znak linefeed, będący ostatnim znakiem w buforze użytkownika, który poprzedzono przy zapisywaniu znakiem carriage-return. Tę właśnie sytuację obsługujemy dodatkowo. Pozwala na to zmienna "carry" w warunku końca pętli. Niżej, wewnątrz pętli, znajduje się warunek, który dopisuje linefeed, kiedy znacznik "carry" jest ustawiony i kasuje ten znacznik.

	error = carry = 0;
	for (start = buf; count || carry; count -= size) {

Mapujemy numer sektora a obrębie pliku na fizyczny numer sektora na dysku. Jeżeli się to nie uda, to możliwe, że jesteśmy na końcu sektora i trzeba doalokować do pliku nowy. Wtedy wywołujemy funkcję "fat_add_cluster", która robi to dla nas. Jej definicja znajduje się w pliku "/fs/fat/misc.c". Jeżeli i to się nie uda, to kończymy, obcinamy plik na bieżącej pozycji i wyskakujemy z błędem. Jeżeli się uda, to ten nowo zaalokowany sektor mapujemy na fizyczny numer.

		while (!(sector = fat_smap(inode,filp->f_pos >> SECTOR_BITS)))
			if ((error = fat_add_cluster(inode)) < 0) break;
		if (error) {
			fat_truncate(inode);
			break;
		}

Potrzebne jest jeszcze przesunięcie początku interesującego nas obszaru względem początku sektora oraz rozmiar porcji danych do zapisania. Porcja danych nie może być dłuższa niż odległość od końca sektora, bo jednorazowo zapisujemy po jednym sektorze. W ramach tego ograniczenia sztucznie ustawiamy rozmiar porcji na jeden, jeśli w buforze użytkownika nie ma już danych (count = 0), ale my zapisujemy jeszcze dodatkowy znak linefeed (carry = 1).

		offset = filp->f_pos & (SECTOR_SIZE-1);
		size = MIN(SECTOR_SIZE-offset,MAX(carry,count));

Jeżeli zapisywany plik jest binarny, jesteśmy na początku sektora oraz rozmiar danych do zapisania jest taki, jak rozmiar sektora lub zapisywane dane przekraczają aktualny koniec pliku, to pobieramy bufor bloku funkcją "fat_getblk", która nie próbuje wypełniać bufora aktualnymi danymi, bo w tej sytuacji i tak cała bieżąca zawartość bloku zostanie zamazana przez nowe dane.

		if (binary_mode
			&& offset == 0
			&& (size == SECTOR_SIZE
				|| filp->f_pos + size >= inode->i_size)){
			if (!(bh = fat_getblk(sb,sector))){
				error = -EIO;
				break;
			}

W przeciwnym razie korzystamy z funkcji "fat_bread", która dodatkowo ściąga aktualną kopię bloku z dysku.

		} else if (!(bh = fat_bread(sb,sector))) {
			error = -EIO;
			break;
		}

Jeżeli plik jest binarny, to po prostu kopiujemy porcję danych z obszaru użytkownika do bufora i przesuwamy znacznik.

		if (binary_mode) {
			memcpy_fromfs(bh->b_data+offset,buf,written = size);
			buf += size;
		} else {

Jeżeli plik nie jest binarny, to, niestety, trzeba kopiować bajt po bajcie, bo dokonujemy konwersji pomiędzy Linuxowym a DOSowym formatem zapisu plików tekstowych (znaki końca wiersza etc.). Funkcja "get_user()" zdefiniowana jest (jako inline) w pliku "/include/asm-i386/segmeng.h". Jej zadaniem jest pobranie znaku z obszaru użytkownika spod wskazanego adresu.

			written = left = SECTOR_SIZE-offset;
			to = (char *) bh->b_data+(filp->f_pos & (SECTOR_SIZE-1));
			if (carry) {
				*to++ = '\n';
				left--;
				carry = 0;
			}
			for (size = 0; size < count && left; size++) {
				if ((ch = get_user(buf++)) == '\n') {
					*to++ = '\r';
					left--;
				}
				if (!left) carry = 1;
				else {
					*to++ = ch;
					left--;
				}
			}
			written -= left;
		}

Kiedy już uaktualnimy zawartość bufora danymi z pamięci użytkownika, informujemy cache mapujący pliki do pamięci o zmianie zawartości bufora. Funkcja "update_vm_cache" zdefiniowana jest w pliku "mm/filemap.c".

		update_vm_cache(inode, filp->f_pos, bh->b_data + (filp->f_pos & (SECTOR_SIZE-1)), written);

Przesuwamy wskaźnik bieżącej pozycji w pliku o ilość zapisanych znaków. Jeżeli przekroczyliśmy koniec pliku, to rozmiar pliku się zwiększył, co odnotowujemy w i-węźle. Ten i-węzeł się teraz zmienił, oznaczamy go jako brudny, do zapisania na dysk.

		filp->f_pos += written;
		if (filp->f_pos > inode->i_size) {
			inode->i_size = filp->f_pos;
			inode->i_dirt = 1;
		}

Natomiast bufor oznaczamy jako posiadający aktualne dane i, jednocześnie, jako brudny, czyli do natychmiastowego zapisania na dysk, po czym zwalniamy ten bufor. Podsystem obsługi buforów zapisze go na dysk.
 

		fat_set_uptodate(sb, bh, 1);
		fat_mark_buffer_dirty(sb, bh, 0);
		fat_brelse(sb, bh);
	}

Jeżeli po tym wszystkim okazuje się, że wskaźnik aktualnej pozyji w buforze użytkownika się nie zmienił, to znaczy, że nie udało się zapisać nic. Zwracamy błąd.

	if (start == buf)
		return error;

Uaktualniamy datę w i-węźle. Nie musimy tym razem sprawdzać, czy i-węzeł ma prawo do zapisu, bo jeśli można zmieniać zawartość pliku, to można też zmieniać zawartość i-węzła. Ustawiamy dla tego i-węzła atrybut "archived" i zonaczamy jako brudny, ale nie zwalniamy, bo te modyfikacje nie mają znaczenia dla innych procesów korzystających z tego pliku. Zwracamy ostatecznie ilość zapisanych bajtów.

	inode->i_mtime = inode->i_ctime = CURRENT_TIME;
	MSDOS_I(inode)->i_attrs |= ATTR_ARCH;
	inode->i_dirt = 1;
	return buf-start;
}

fat_truncate()

Definicja tej funkcji znajduje się w pliku "fs/fat/file.c".
void fat_truncate(struct inode *inode)
{
	int cluster;

	if (IS_IMMUTABLE(inode))
		return;

Obliczamy ilość bloków do zwolnienia dzieląc rozmiar pliku przez rozmiar sektora (w bajtach) i zaokrąglając w górę - w ten sposób otrzymujemy całkowitą liczbę bloków zajętych przez plik. Używamy jej jako parametru dla funkcji "fat_free". I-węzeł oznaczamy jako brudny, bo jego zawartość się zmieniła.

	cluster = SECTOR_SIZE*MSDOS_SB(inode->i_sb)->cluster_size;
	(void) fat_free(inode,(inode->i_size+(cluster-1))/cluster);
	MSDOS_I(inode)->i_attrs |= ATTR_ARCH;
	inode->i_dirt = 1;
}

fat_bread()

Definicja tej funkcji znajduje się w pliku "fs/fat/buffer.c". Realizuje ona operację wczytywania bloków z dysku dla systemu FAT.
Wiele urządzeń specjalnych ma sztywno ustalone większe sprzętowe sektory (1k zamiast 512b). Pozwala to na zwiększenie pojemności. Na ogół system plików MSDOS, który znajduje się na takim urządzeniu nie dostosowuje parametrów swojej partycji do parametrów dysku. Używa klastrów (logicznych sektorów) o rozmiarze 512 bajtów, z co drugim sektorem logicznym startującym w środku sektora sprzętowego. Wówczas występuje bardzo niepożądana sytuacja, gdy klastry są mniejsze niż sektory. Operacje zapisu i odczytu na dysku odwołują się do całych sektorów. Sprzętowy sektor może trzymać dane należące do dwóch różnych plików. To oznacza, że sektor sprzętowy musi być często niepotrzebnie odczytywany i zapisywany w całości. Oczywiście to wpływa fatalnie na prędkość przy każdym systemie operacyjnym. Wewnętrznie Linux obsługuje system plików MsDOS jako system plików z 512-bajtowymi sektorami logicznymi. Przy dostępie do urządzeń, które mają większe bloki, alokowane są dodatkowe bufory-zaślepki, które wypełnia się informacją z całego sektora (1k). To pozwala na schowanie problemu, występującego w takich nietypowych sytuacjach, gdy mamy w MsDOS większe sektory, przed kodem jądra obsługującym DOSowy filesystem. Oczywiście nie likwiduje to problemu zmniejszenia wydajności operacji odczytu i zapisu.
Ze względu na nietypową organizację na dysku przy niektórych wersjach używanego przez MsDOS systemu plików FAT operacji zapisu musi towarzyszyć dodatkowa poprzedzająca operacja odczytu.
struct buffer_head *fat_bread (
	struct super_block *sb,
	int block)
{
	struct buffer_head *ret = NULL;

Rozmiar bloku może wynosić 512 bajtów lub 1k. Dla standardowego rozmiaru bloku (512 bajtów) wywołujemy standardową funkcję "bread" (plik "/fs/buffer.c").

	if (sb->s_blocksize == 512) {
		ret = bread (sb->s_dev,block,512);
	} else {

Wczytujemy zatem 1k blok, dane są w pierwszej lub drugiej połówce tego bloku, ale odczytać i tak musimy cały.

		struct buffer_head *real = bread (sb->s_dev,block>>1,1024);

		if (real != NULL){

Nagłówek bufora, który został stworzony przy okazji wczytywania, nie nadaje się do zwrócenia bezpośrednio, ponieważ dotyczy większego, jednokilobajtowego bufora. Musimy stworzyć nowy. Używamy funkcji "kmalloc" (kernel malloc) z pliku "/mm/kmalloc.c".

			ret = (struct buffer_head *)
			  kmalloc (sizeof(struct buffer_head), GFP_KERNEL);
			if (ret != NULL) {

Jeżeli właściwe dane zaczynają się w połowie sektora, to musimy przesunąć dodatkowo wskaźnik. Poniżej widać, jak połowa zaalokowanego bufora pozostaje niewykorzystana.

				memset (ret,0,sizeof(*ret));
				ret->b_data = real->b_data;
				if (block & 1) ret->b_data += 512;

Zwracać będziemy nowo stworzony nagłówek bufora, jednak stary musimy zapamiętać, żeby go potem móc skasować.

				ret->b_next = real;
			}else{
				brelse (real);
			}
		}
	}
	return ret;
}

fat_getblk()

Definicja tej funkcji znajduje się w pliku "fs/fat/buffer.c". Funkcja ta pobiera z dysku blok o numerze "block". Ponieważ bloki w systemie plików FAT mogą być większe niż standardowe 512 bajtów, ta funkcja wykorzystuje też ogólniejszą funkcję "fat_bread".
struct buffer_head *fat_getblk (
	struct super_block *sb,
	int block)
{
	struct buffer_head *ret = NULL;

Gdy urządzenie ma bloki o długości 512 bajtów, takie, jake ext2 (patrz też niżej, do opisu funkcji fat_ll_rw_block()), to po prostu wywołujemy standardową funkcję obsługi urządzenia (zdefniowaną w "/fs/buffer.c").

	if (sb->s_blocksize == 512){
		ret = getblk (sb->s_dev,block,512);
	}else{

Gdy urządzenie ma większe bloki, wywołujemy funkcję dedykowaną dla systemu plików FAT.

		ret = fat_bread (sb,block);
	}
	return ret;
}

fat_brelse()

Definicja tej funkcji znajduje się w pliku "fs/fat/buffer.c". Funkcja ta zwalnia bloki.
void fat_brelse (
	struct super_block *sb,
	struct buffer_head *bh)
{
	if (bh != NULL){

Standardowe bloki obsługujemy w standardowy sposób. Standardowa funkcja "brelse" zdefiniowana jest w "/fs/buffer.c".

		if (sb->s_blocksize == 512){
			brelse (bh);
		}else{

Zwalniamy niepotrzebny już bufor stworzony przy czytaniu z dysku. Nie próbujemy odzyskiwać jego drugiej połowy. Funkcja "kfree" zdefiniowana jest w "/mm/kmalloc.c".

			brelse (bh->b_next);
			kfree (bh);
		}
	}
}

fat_mark_buffer_dirty()

Definicja tej funkcji znajduje się w pliku "fs/fat/buffer.c".
void fat_mark_buffer_dirty (
	struct super_block *sb,
	struct buffer_head *bh,
	int dirty_val)
{

Jeżeli mamy do czynienia z blokiem należącym do systemu plików, w którym sektory są 1-kilobajtowe, to tym, co zapisujemy jest jednokilobajtowy bufor wczytany z dysku, a nie jego połówka. Standardowa fukcja "mark_buffer_dirty" zdefiniowana jest (inline) w pliku "/include/linux/fs.h".

	if (sb->s_blocksize != 512){
		bh = bh->b_next;
	}
	mark_buffer_dirty (bh,dirty_val);
}

fat_set_uptodate()

Definicja tej funkcji znajduje się w pliku "fs/fat/buffer.c".
void fat_set_uptodate (
	struct super_block *sb,
	struct buffer_head *bh,
	int val)
{

Podobnie jak wyżej, jeżeli mamy do czynienia z blokiem należącym do systemu plików, w którym sektory są 1-kilobajtowe, to tym, co zapisujemy jest jednokilobajtowy bufor wczytany z dysku, a nie jego połówka. Standardowa funkcja "mark_buffer_uptodate" zdefiniowana jest w "/fs/buffer.c".

	if (sb->s_blocksize != 512){
		bh = bh->b_next;
	}
	mark_buffer_uptodate(bh, val);
}

fat_is_uptodate()

Definicja tej funkcji znajduje się w pliku "fs/fat/buffer.c".
int fat_is_uptodate (
	struct super_block *sb,
	struct buffer_head *bh)
{

Analogicznie jak wyżej, z punktu widzenia podsystemu obsługi buforów buforem jest całe 1k wczytane z dysku, a nie połówka, z której korzystamy.

	if (sb->s_blocksize != 512){
		bh = bh->b_next;
	}

Standardowa funkcja buffer_uptodate zdefiniowana jako inline w "fs.h" zwraca po prostu wartość bitu "BH_Uptodate" z pola "b_state" nagłówka bufora.

	return buffer_uptodate(bh);
}

fat_ll_rw_block()

Definicja tej funkcji znajduje się w pliku "fs/fat/buffer.c".
Funkcja ta obsługuje wszelkie żądania dotyczące zapisu i odczytu bloków dyskowych w systemie plików FAT. Jest ona właściwie interfejsem do funkcji "ll_rw_block" (plik "/drivers/block/ll_rw_blk.c"). Parametr "sb" wskazuje na kopię super-bloku urządzenia w pamięci, "opr" to operacja do wykonania, "bh" to lista nagłówków buforów zaś "nbreq" to ilość buforów na tej liście.
void fat_ll_rw_block (
	struct super_block *sb,
	int opr,
	int nbreq,
	struct buffer_head *bh[32])
{

Odczytujemy rozmiar bloku dyskowego z super-bloku. Jeżeli ten rozmiar jest taki sam jak rozmiar bloku w systemie plików ext2, czyli 512 bajtów (chodzi tu o rozmiar fizycznego bloku na dysku, a nie logicznego, który może mieć 1024, 2048 lub 4096 bajtów), to po prostu wywołujemy funkcję "ll_rw_block".

	if (sb->s_blocksize == 512){
		ll_rw_block(opr,nbreq,bh);
	}else{

Jeżeli rozmiar bloku jest większy, to wielokrotnie wywołujemy funkcję "ll_rw_block", zaś kolejne pozycje listy nagłówków buforów wskazują kolejne bufory, do których należy czytać lub z których należy pisać na dysk.

		struct buffer_head *tmp[32];
		int i;
		for (i=0; i < nbreq; i++){
			tmp[i] = bh[i]->b_next;
		}
		ll_rw_block(opr,nbreq,tmp);
	}
}

Autor: Krzysztof Ostrowski