Główna pętla programu - Pierwszą czynnością w głównej pętli programu jest sprawdzenie
czy nie próbujemy pisać poza urządzenie. Jeśli tak to odpowiednio obsługujemy ten błąd (patrz kod źródłowy
poniżej). Jeśli nie to próbujemy odczytać blok numer block
wołając funkcję getblk
jeśli otrzymamy NULL
to odpowiednio radzimy sobie z tym problemem zwracająć błąd zwolniwszy uprzednio
wszystkie ewentualnie trzymane bufory. W przeciwnym wypadku sprawdzamy czy otrzymany bufor zawiera
aktualne dane. Jeśli nie to jeżeli mamy teraz zapisać cały blok po prostu czekamy na odblokowanie bufora.
Jeśli jednak porcja danych którą zapisujemy jest mniejsza od rozmiaru bloku to jesteśmy zmuszeni do
odczytania bufora z dysku. Wynika to z faktu iż jesteśmy w stanie operować jedynie całymi blokami i
jeśli byśmy zapisali naszą część bloku to pozostała część bloku zostałaby utracona. Odczyt może odbywać
się z wyprzedzeniem lub bez w zależności od wartości odpowiednich flag (szczegóły w kodzie poniżej).
Po tym wszystkim wykonywane są różne robocze operacje takie jak aktualizacja pozycji w pliku itp.
Teraz następuje właściwe kopiowanie danych z buf
do bufora. Następnie, jeśli jest podniesiona
flaga O_NSYNC
czyli proces wywołujący chce zaczekać do momentu fizycznego zapisania danych,
dopisujemy bufor do naszej listy pomocniczej. W przeciwnym wypadku zaznaczamy (wołając funkcję
brelse
że nie będziemy już korzystali z tego bufora. Teraz sprawdzamy czy przypadkiem nasza
lista pomocnicza z nagłówkami buforów (która jest wykorzystywana tylko w przypadku gdy podniesiona jest
flaga O_NSYNC
) nie jest już pełna. Jeśli jest to zlecamy i czekamy na wykonanie zapisu dla wszystkich
buforów z tej listy. To w zasadzie koniec pętli jedyne czynności które jeszcze wykonujemy to
wywołanie funkcji balance_dirty
dla naszego urządzenia, która to powoduje sprawdzenie czy nie zachodzą warunki
przy których powinno być wołane zapisywanie brudnych buforów na urządzenie. A oto jak wygłąda kod tej pętli :
while (count>0) {
// chcemy czytać poza urządzeniem tak więc błąd
if (block >= size)
{
retval = -ENOSPC;
// najpierw jednak musimy "posprzątać"
goto cleanup;
}
// obliczamy ile będziemy zapisywać do bloku w tej pętli
chars = blocksize - offset;
if (chars > count)
chars=count;
// odczytujemy blok do którego będziemy zapisywać
bh = getblk(dev, block, blocksize);
// jeśli otrzymamy NULL'a to błąd
if (!bh)
{
retval = -EIO;
goto cleanup;
}
// jeżeli bufor zawiera nieaktualne dane
if (!buffer_uptodate(bh))
{
// jeśli będziemy zapisywać pełny blok
if (chars == blocksize)
// czekamy na zdjęcie blokady z bufora
wait_on_buffer(bh);
else
{
// jeżeli będziemy zapisywać tylko część bloku to :
bhlist[0] = bh;
// jeżeli nie możemy odczytywać z wyprzedzeniem
if (!filp->f_reada || !read_ahead[MAJOR(dev)])
{
// zaznaczamy że będzeimy odcztytywać jeden blok
blocks = 1;
}
else
{
// w przeciwnym wypadku czytamy z wyprzedzeniem
blocks = read_ahead[MAJOR(dev)] / (blocksize >> 9) / 2;
if (block + blocks > size) blocks = size - block;
if (blocks > NBUF) blocks=NBUF;
if (!blocks) blocks = 1;
for(i=1; i<blocks; i++)
{
bhlist[i] = getblk (dev, block+i, blocksize);
// jeżeli dla któregokolwiek bufora dostaniemy NULL'a to konczymy
if (!bhlist[i])
{
while(i >= 0) brelse(bhlist[i--]);
retval = -EIO;
goto cleanup;
}
}
}
// zlecamy uaktualnienie buforów
ll_rw_block(READ, blocks, bhlist);
for(i=1; i<blocks; i++) brelse(bhlist[i]);
wait_on_buffer(bh);
// jeżeli tuż po odczycie juz mamy nieaktualne dane to od razu błąd
if (!buffer_uptodate(bh))
{
brelse(bh);
retval = -EIO;
goto cleanup;
}
};
};
// teraz będziemy już się zajmować następnym blokiem
block++;
p = offset + bh->b_data;
offset = 0;
// przesuwamy się w pliku
*ppos += chars;
written += chars;
count -= chars;
// kopiujemy dane z buf do bufora
copy_from_user(p,buf,chars);
p += chars;
buf += chars;
// zaznaczamy że nasz bufor zawiera aktualne dane
mark_buffer_uptodate(bh, 1);
// zaznaczamy również że jest brudny
mark_buffer_dirty(bh);
// jeżeli podniesiona jest flaga O_NSYNC
if (filp->f_flags & O_SYNC)
// dopisujemy ostatnio przeczytany bufor do naszej pomocniczej tabeli
bufferlist[buffercount++] = bh;
else
// w przeciwnym wypadku nie będziemy juz do niczego naszego bufora potrzebowali
brelse(bh);
// jeżeli pomocnicza tabela na bufory jest pełna to :
if (buffercount == NBUF)
{
// oznacza to że podniesiona była flaga O_NSYNC bo w przeciwnym wypadku
// w ogóle nie operujemy zmienną buffercount ani tablicą
// tak więc zgodnie ze znaczeniem tej flagi
// zlecamy zapis wszystkich bloków na urządzenie
ll_rw_block(WRITE, buffercount, bufferlist);
// i w tej pętli czekamy aż wszystkie zostaną zapisane
for(i=0; i<buffercount; i++)
{
wait_on_buffer(bufferlist[i]);
if (!buffer_uptodate(bufferlist[i]))
write_error=1;
brelse(bufferlist[i]);
}
buffercount=0;
}
// funkcja balance_dirty sprawdza czy zachodzą warunki w których trzeba
// zapisać brudne bufory
// na urządzenie
balance_dirty(dev);
// jeśli gdzieś wcześniej wystąpiły jakieś błędy to wychodzimy z pętli
if (write_error)
break;
} // while