blk_dev_struct wskaznik na
posortowana liste zadan (nie mylic z zadaniami). Kazde zadanie opisane jest
przez dluga strukture request zdefiniowana w pliku
include/linux/blkdev.h.
struct request {
volatile int rq_status;
kdev_t rq_dev;
int cmd;
int errors;
unsigned long sector;
unsigned long nr_sectors;
unsigned long current_nr_sectors;
char * buffer;
struct semaphore * sem;
struct buffer_head * bh;
struct buffer_head * bhtail;
struct request * next;
}
rq_status
rq_dev
cmd
errors
sector, nr_sectors
current_nr_sectors
buffer
bh, bhtail
sem
Zadania dla wszystkich urzadzen blokowych znajduja sie w tablicy
all_requests o wielkosci 64 elementow, z czego 2/3 moze byc
zajete przez zadania zapisu. Proces, ktory chce zlecic urzadzeniu operacje
wejscia-wyjscia, a nie znajdzie wolnej pozycji w tablicy all_requests
zawiesza sie na kolejce wait_for_request.
Po wstawieniu do tablicy, kazde nowe zadanie wstawiane jest na liste zadan dla
konkretnego programu obslugi urzadzenia. Tak zwany Algorytm windy
(
Elevator algorithm) wstawia zadanie na wlasciwe miejsce utrzymujac liste
posortowana leksykograficznie po numerach drugorzednych urzadzenia, do ktorego
zadanie jest skierowane (numery glowne urzadzen w obrebie kolejki sa takie same),
i po pierwszym sektorze, ktorego zadanie dotyczy. W starszych wersjach Linuxa
zadania czytania umieszczane byly przed zadaniami zapisu, stwierdzono jednak
lepsze dzialanie systemu przy rownorzednym traktowaniu operacji.
ll_rw_block zdefiniowana w pliku
drivers/block/ll_rw_blk.c jest uzywana przez wszystkie urzadzenia
blokowe, a sluzy do spelniania skierowanych do nich zadan. Zanim opisze ja
dokladniej, przedstawie pomocnicze funkcje uzywane do utworzenia nowego zadania
i wstawienia go do kolejki.
add_requestall_requests na wlasciwe miejsce w kolejce zadan urzadzenia.
DEFINICJA:
void add_request( struct blk_dev_struct * dev,
/* wskaznik na strukture opisujaca urzadzenie */
struct request * req )
/* wskaznik na strukture opisujaca zadanie */
{
wylacz przerwania (cli);
zaznacz bufor zadania jako czysty (mark_buffer_clean);
if( w kolejce urzadzenia nie ma zadan )
{
wstaw zadanie na poczatek kolejki;
wywolaj procedure strategii urzadzenia;
przywroc przerwania (sti);
return;
}
wstaw zadanie na wlasciwe miejsce;
if( urzadzenie jest typu SCSI )
wywolaj procedure strategii urzadzenia;
przywroc przerwania;
}
make_request
all_requests
DEFINICJA:
static void make_request( int major, /* glowny numer urzadzenia */
int rw, /* rodzaj zadania */
struct buffer_head * bh ) /* bufor
ktorego zapisania lub odczytania zadamy */
{
if( bufor zajety )
return
zajmij bufor (lock_buffer);
if ( rw == READ i bufor zawiera aktualne dane ) {
zwolnij bufor;
return;
}
if ( rw == WRITE i bufor nie jest "brudny" (dirty) )
zwolnij bufor;
return;
}
wylacz przerwania (cli);
przejdz kolejke zadan urzadzenia, dla kazdego zadania:
sprawdz czy bufor bh mozna dolaczyc na poczatku lub na koncu
listy buforow zadania tak, zeby otrzymac uporzadkowana liste
kolejnych blokow urzadzenia, jesli tak, to
{
dolacz bufor do tej kolejki;
uaktualnij opis zadania;
przywroc przerwania;
return;
}
sprobuj znalezc wolne miejsce w tablicy all_requests;
if( nie ma miejsca i zadanie dotyczy operacji z wyprzedzeniem ) {
zwolnij bufor;
return;
}
czekaj na wolne miejsce w tablicy all_request (add_wait_queue);
wypelnij strukture request;
wstaw strukture do kolejki zadan urzadzenia (add_request);
}
Sama funkcja ll_rw_block dostaje jako parametr liste buforow
do ktorych ma wczytac dane, nastepnie:
make_request
ll_rw_block. Oczywiscie obsluga zadan nalezy juz do konkretnego
sterownika, zajmuje sie tym jego funkcja strategii (Strategy routine)
uzywajac niskopoziomowych funkcji do zapisywania i odczytywania danych.
DEFINICJA:
static void end_request( byte uptodate ) /* 1 - gdy zadanie zakonczylo
sie pomyslnie, 0 w p.p */
{
zadanie - aktualnie obslugiwane zadanie;
aktualny bufor - bufor, ktorego dotyczyla ostatnia operacja;
if( uptodate == 0 ) {
wypisz komunikat o bledzie (printk);
}
if( uptodate == 1)
zaznacz aktualny bufor jako zawierajacy aktualne dane;
zwolnij aktualny bufor;
if( lista zadania zawiera wiecej buforow )
nastepny bufor z listy staje sie aktualnym buforem;
return;
}
zaznacz zadanie jako wykonane (ustaw RQ_INACTIVE);
przesun wskaznik zadan na nastepne zadanie na liscie urzadzenia;
obudz procesy czekajace na miejsce w tablicy zadan;
}
Teraz ogolny algorytm dzialania procedury strategii - wlasciwe wszytkie urzadzenia
blokowe uzywaja podobnych algorytmow - zobacz
opis sterownika dysku twardego.
void procedura_strategii( void )
{
if( lista zadan pusta )
wylacz obsluge przerwan od urzadzenia;
return;
if( ustawiona obsluga przerwania (co znaczy ze czekamy na
zakonczenie obslugi jakiegos zadania) )
return;
zadanie - pierwsze zadanie z listy;
if( zadanie dotyczy zapisu )
ustaw procedure obslugi przerwania;
wywolaj niskopoziomowa procedure zapisu;
return;
if( zadanie dotyczy odczytu )
ustaw procedure obslugi przerwania - inna
niz dla zapisu;
wywolaj niksopoziomowa procedure odczytu;
return;
nieznane polecenie - panikuj (panic);
}
Uwagi:
Dokladne omowienie procedury strategii i precedur obslugi przerwan znajduje sie w nastepnym rozdziale, poswieconym programowi obslugi dysku.