Projekt Linux 2.4.7
Systemy Operacyjne 2001/2002
Szeregowanie zleceń do urządzeń blokowych
Urządzenie mem
Spis treści
1. Szeregowanie zleceń do urządzeń blokowych
1.1. Struktury danych
1.2. Przesyłanie buforów do kolejki urządzenia
1.3. Tworzenie żądań
1.4. Algorytm C-LOOK
2. Urządzenie mem
2.1. Wprowadzenie
2.2. Podurządzenia mem
2.3. Funkcje obsługi urządzeń
2.4. Podurządzenia /dev/random oraz /dev/urandom
3. Zadania
1. Szeregowanie zleceń do urządzeń blokowych
Procesy działające w systemie mogą żądać wykonania
pewnych operacji zapisu lub odczytu danych na różnych
urządzeniach wejścia-wyjścia. Struktura request zdefiniowana w pliku
include/linux/blkdev.h ,
dokładnie opisuje zlecenie każdej operacji do urządzenia blokowego.
struct request {
struct list_head queue;
/*
Dopuszczalna liczba ominięć żądania. Jest zmieniana
po każdym ominięciu tak aby nie wystąpiło zagłodzenie.
(Żądania się starzeją)
*/
int elevator_sequence;
struct list_head table;
/*
Określa stan żądania.
RQ_ACTIVE - zwykłe żądanie; RQ_DONE - żądanie wykonane; RQ_SCSI_BUSY,
RQ_SCSI_DONE, RQ_SCSI_DISCONETCING - żądanie dotyczące urządzeń SCSI.
*/
volatile int rq_status;
/*
Określa numer główny i podrzędny urządzenia (do wyłuskiwania
oddzielnych numerów służą makra MAJOR oraz MINOR).
*/
kdev_t rq_dev;
/*
Rodzaj zadania READ - odczyt; WRITE - zapis; READA - odczyt z wyprzedzeniem.
*/
int cmd;
/*
Licznik błędów otrzymanych podczas realizowania zadania.
*/
int errors;
/*
Początkowy sektor oraz liczba sektorów urządzenia na których ma być
wykonane żądanie.
*/
unsigned long sector;
unsigned long nr_sectors;
unsigned long hard_sector, hard_nr_sectors;
unsigned int nr_segments;
unsigned int nr_hw_segments;
unsigned long current_nr_sectors;
void * special;
char * buffer;
struct completion * waiting;
/*
Pierwszy i ostatni bufor na liście żądań.
*/
struct buffer_head * bh;
struct buffer_head * bhtail;
/*
Kolejka, na której znajduję się żądanie.
*/
request_queue_t *q;
};
Jak widać wyżej z każdym żądaniem związana jest lista buforów.
Bufor może być rozumiany jako najmniejsza jednostka danych przenoszona
jedną transakcją między urządzeniem a sterownikiem. Struktura opisująca
bufor znajduję się w pliku źródłowym /include/linux/fs.h
struct buffer_head {
/*
Wskaźnik do następnego bufora na liście.
*/
struct buffer_head *b_next;
unsigned long b_blocknr;
unsigned short b_size;
unsigned short b_list;
/*
Wirtualne urządzenie z jakim jest aktualnie związany bufor.
(B_FREE - nie związany z żadnym urządzeniem).
*/
kdev_t b_dev;
/*
Ilość procesów korzystających z bufora.
*/
atomic_t b_count;
/*
Rzeczywiste urządzenie z jakim jest aktualnie związany bufor.
*/
kdev_t b_rdev;
/*
Stan bufora:
BH_Lock - zablokowany
BH_Protected - chroniony
BH_Uptodate - zawiera aktualne dane
BH_Dirty - informacja, że należy zapisać bufor
BH_Req - zażądany
BH_Mapped - odwzorowywany na dysku
*/
unsigned long b_state;
unsigned long b_flushtime;
/*
Dwulkierunkowa cykliczna lista buforów.
*/
struct buffer_head *b_next_free;
struct buffer_head *b_prev_free;
struct buffer_head *b_this_page;
/*
Następny bufor w żądaniu.
*/
struct buffer_head *b_reqnext;
struct buffer_head **b_pprev;
/*
Wskaźnik do danych bufora.
*/
char * b_data;
struct page *b_page;
/*
Procedura obsługi zakończenia operacji we-wy dla tego bufora.
*/
void (*b_end_io)(struct buffer_head *bh, int uptodate);
/*
Zarezerwowany dla procedury kończącej we-wy.
*/
void *b_private;
/*
Rzeczywiste położenie bufora na dysku.
*/
unsigned long b_rsector;
wait_queue_head_t b_wait;
struct inode * b_inode;
struct list_head b_inode_buffers;
#ifdef CONFIG_BUFFER_DEBUG
struct buffer_history b_history;
#endif
};
Aby zlecenie mogło być wykonane musi zostać wstawione do kolejki zleceń
konkretnego urządzenia.
Z każdym urządzeniem blokowym związane jest taka kolejka żądań zdefiniowana
w pliku /include/linux/blkdev.h
struct request_queue
{
/*
Z każdym urządzeniem związana jest pula wolnych żądań.
Te pola to listy wolnych żądań urządzenia
(połowa do oczytu, połowa do zapisu).
*/
struct list_head request_freelist[2];
/*
Listy żądań oddane z puli wolnych żądań
*/
struct list_head pending_freelist[2];
int pending_free[2];
/*
Kolejka żądań związana z danym urządzeniem.
*/
struct list_head queue_head;
elevator_t elevator;
/* Procedury służące do operacji wykonywanych na kolejce (wstawianie zlecenia, aktywowanie
kolejki, wykonywanie żądania) */
request_fn_proc * request_fn;
merge_request_fn * back_merge_fn;
merge_request_fn * front_merge_fn;
merge_requests_fn * merge_requests_fn;
make_request_fn * make_request_fn;
plug_device_fn * plug_device_fn;
void * queuedata;
struct tq_struct plug_tq;
/* określa czy kolejka jest uruchomiona */
char plugged;
/* określa czy aktualne żądanie jest aktywne */
char head_active;
/* dla bezpieczeństwa danych w kolejce */
spinlock_t queue_lock;
/* procesy czekają tutaj na wolne żądania w puli */
wait_queue_head_t wait_for_request;
};
Rys. Struktury danych używane do szeregowania zleceń we-wy.
|
Podstawowym mechanizmem służącym do szeregowania żądań dostępu do urządzeń
jest funkcja ll_rw_block (low level access to block devices)
zdefiniowana w pliku /drivers/block/ll_rw_block.c .
Funkcja ta jest wspólna dla wszystkich urządzeń blokowych. Jest wywoływana
przez funkcję block_read oraz
block_write . Jej głównym zadaniem jest
wysłanie listy buforów do kolejki żądań konkretnego urządzenia ze zleceniem
odpowiedniej operacji (zapisu lub odczytu).
void ll_rw_block(int rw, int nr, struct buffer_head * bhs[])
/* Gdzie rw to rodzaj operacji (READ, WRITE, READA); nr to ilosc buforow
dla operacji odczytu lub zapisu; bhs - tablica wskaźników do tych buforów. */
{
Ustal odpowiednią wielkość bloku dla tego urządzenia;
Sprawdź czy wszystkie bufory mają odpowiednią wielkość;
Jeśli nie to błąd;
Sprawdź czy nie wystąpiła sytuacja,
w której żadany jest zapis na urządzeniu tylko do odczytu;
Jeśli tak to błąd;
Dla każdego wskaźnika z tablicy bhs wywołuj {
Sprawdź czy bufor jest zablokowany.
Jeśli tak pomiń ten bufor,
Jeśli nie ustaw b_state na BH_Lock
Zwiększ ilość odwołań do bufora
i ustaw procedurę kończącą we-wy.
Jeśli żądany jest odczyt
a bufor, do którego odnosi się wskaźnik,
jest już odpowiednio wypełniony pomiń ten bufor.
Jeśli żądany jest zapis
a bufor, do którego odnosi się wskaźnik,
jest czysty pomiń ten bufor.
Wywołaj submit_bh (rw, rozpatrywany bufor)
}
}
Funkcja submit_bh jest bardzo podobna do funkcji
generic_make_reuest i wywołuję tą funkję do wykonania
większej części roboty. Dodatkowymi czynnościami wykonywanymi przez
funkcję są:
sprawdzenie czy b_state równa się BH_Lock jeśli nie to błąd,
ustawienie b_state na BH_Req,
ustalenie b_rdev na podstawie b_dev,
ustalenie b_rsectors na podstawie b_blocknr i b_size.
Głównym zadaniem generic_make_request , podobnie
jak poprzednio opisanych funkcji, jest przesłanie nagłówka bufora do kolejki
odpowiedniego urządzenia. Definicja znajduje się w pliku
/drivers/block/ll_rw_block.c , oto skrócony opis:
void generic_make_request(int rw, struct buffer_head * bh)
/* Gdzie rw to rodzaj operacji (READ, WRITE, READA); bh - wskaźnik
bufora do przesłania. */
{
Sprawdz czy ustawiona jest procedura
obsłgi końca operacji we-wy. Jeśli nie to błąd.
Sprawdz czy wielkość bloku dla tego urządzenia.
zgadza się z wielkością buforą. Jeśli nie to
wypisz komunikat o błędzie i wywołaj procedurę
końca operacji we-wy. (To się może wydarzyć
np. przy montowaniu urządzenia)
W pętli wykonuj {
q := znajdź kolejkę dla tego urządzenia
Jeśli q równa się NULL to błąd (urządzenie przestało działać)
} do momentu aż powiedzie się wywołanie procedury zlecającej operację
dla tego urządzenia (q->make_request_fn(q, rw, bh) )
}
Rys. Schemat wołania funkcji w celu wstawienia żądania.
|
Wiemy już w jaki sposób odbywa się przesyłanie każdego bufora do kolejki
żądań urządzenia. Robią to wyżej opisane funkcję. Oczywiście
zaszeregowanie żądania na właściwe miejsce w kolejce i obsługa żądania zależy
już od konkretnego urządzenia. W większości przypadków wykorzystywany
jest standardowa
funkcja szeregująca __make_request , aczkolwiek możliwe jest zdefiniowanie
innej funkcji (trzeba potem jej nazwę umieścić w polu make_request_fn przy
inicjacji kolejki). Funkcja __make_request używa struktury elevator_s .
Struktura ta udostępnia wiele funkcji, za pośrednictwem
których realizowany jest algorytm szeregowania windowego C-LOOK.
Oprócz szeregowania żądań funkcja __make_request zajmuję się synchronizacją.
Może się zdarzyć, że w kolejce brakuje wolnych żądań do pobrania.
Do realizacji czekania służy kolejka wait_for_request . Do samej realizacji
żądania służy funkcja request_fn .
W algorytmie C-LOOK ramię dysku rozpoczyna od jednej krawędzi dysku i przemieszcza się
w kierunku krawędzi przeciwległej, obsługując zamówienia po osiągnięciu każdego kolejnego cylindra,
aż dotrze do zamówienia znajdującego się na skraju dysku (zamówienie dotyczące cylindra o
najwyższym numerze). Po obsłużeniu ostatniego zamówienia ramię dysku przesuwa się do cylindra
z którym związane jest pierwsze zamówienie z kolejki zleceń (zamówienie związane z cylindrem
o najmniejszym numerze). C-LOOK należy do klasy algorytmów windy zwanych tak ze względu na
analogię w działaniu. Nazwa C-LOOK wzięła się stąd, że przed kontynuowaniem ruchu głowicy
,,patrzy się'' w nich, czy w danym kierunku znajduję się jeszcze jakieś zlecenie.
Rozważmy przykład. Zlecenia dotyczące cylindrów przychodzą w następującej kolejności: 98, 183,
37, 122, 14, 124, 65, 67. Głowica znajduje się nad 53 cylindrem.
Rys. Planowanie zleceń metodą C-LOOK.
|
2. Urządzenie mem
Pamięć jest widziana w systemie jako urządzenie znakowe o numerze głównym 1.
Pewne programy obsługi urządzeń znakowych mogą wykonywać abstrakcyjne funkje
nie związane z żadnym fizycznym urządzeniem. Te wirtualne urządzenia,
zwane podurządzeniami mem, są identyfikowane numerami podrzędnymi od 1 do 9.
Realizują one różne, pożyteczne z punktu widzenia użytkownia, funkcje.
Lokalizacja w systemie plików
|
Numer podrzędny
|
Realizowana funkcja
|
/dev/mem
|
1
|
Dostęp do pamięci fizycznej
|
/dev/kmem
|
2
|
Dostęp do pamięci widzianej przez jądro
|
/dev/null
|
3
|
Wysyłane dane giną bezpowrotnie
|
/dev/port
|
4
|
Dostęp do portów we-wy
|
/dev/zero
|
5
|
Generuje nieskończony ciąg zer
|
/dev/full
|
7
|
Generuje błąd przepełnienia przy próbie zapisu
|
/dev/random
|
8
|
Generuje liczby pseudolosowe
|
/dev/urandom
|
9
|
Generuje liczby pseudolosowe w trybie nieblokującym
|
Funkcje obsługi pierwszych sześciu urządzeń są zaimplementowane w
pliku drivers/mem.c . Funkcję obsługi /dev/random
oraz /dev/urandom są zdefiniowane w pliku drivers/random.c .
Oto kilka przykładowych funkcji oraz skrócony opis ich działania:
static ssize_t read_mem(struct file * file, char * buf,
size_t count, loff_t *ppos)
/* Fukcja służąca do oczytu z pamięci */
{
jeśli (ppos >= koniec pamięci)
zwróć 0;
jeśli (count > koniec pamieci - ppos)
count = koniec pamieci - ppos;
wywołaj copy_to_user (buf, __va (ppos), count);
/* funkcja pobiera wskaźnik bufora do którego ma skopiować dane, adres
w pamięci od którego ma się zacząc kopiowanie oraz ilość bajtów
do skopiowania. Makro __va dokonuje translacji adresu uwzględniając rozmiar
pamięci zajmowany przez jądro. */
w przypadku powodzenie zwraca count, wpp. BŁĄD;
}
static ssize_t write_kmem(struct file * file, const char * buf,
size_t count, loff_t *ppos)
/* Funkcja służąca do zapisu w pamięci jądra */
{
jeśli (ppos >= koniec pamięci)
zwróć 0;
jeśli (count > koniec pamieci - ppos)
count = koniec pamieci - ppos;
Wywołaj do_write_mem, która po sprawdzeniu czy nie mamy do czynienia
z architekturą sparc lub mc68000 wywołoju funkcję
get_from_user (buf, ppos, count)
w przypadku powodzenie zwraca count, wpp. BŁĄD;
}
W wielu sytuacjach istnieje potrzeba generowania liczb losowych
(np. przy przesyłaniu danych protokołem TCP, w kryptografii).
,,Jakość liczb losowych'' (łatwość odgadnięcia algorytmu
służącego do ich generowania) jest kluczowa z punktu widzenia bezpieczeństwa
wielu aplikacji.
Wygodnym interfejsem do generatora ,,dobrych'' liczb
losowych są specjalne urządzenia znakowe /dev/random oraz /dev/urandom .
Podstawą działania generatora dla tych urządzeń
jest pojemnik entropii. Stan pojemnika jest zmieniany na skutek różnych
czynników zależnych od środowiska (ang. enivronmental noise).
Czynniki te można podzielić ze względu na ich pochodzenie:
- Pochodzące z klawiatury - czas między kolejnymi naciśnięciami klawiszy
i kod ostatnio wprowadzonego znaku,
- Pochodzące od myszy - czas od ostatniego przesunięcia myszy
oraz ostatnia pozycja,
- Przerwania - czas między przerwaniami (Nie wszystkie przerwania są dobrym
źródłem losowości. Przerwania zegarowe są zbyt regularne. Lepsze w tym
przypadku są przerwania od dysku twardego. Ich występowanie jest mniej
przewidywalne),
- Urządzenia blokowe - czas od ostatniej pomyślnie zakończonej operacji
na urządzeniu blokowym.
Oprócz tego do generowania liczb losowych używana jest standardowa funkcja
mieszająca SHA i MD5.
Uwaga! Poza interfejsem dostępnym za pośrednictwem urządzeń /dev/random oraz
/dev/urandom możliwy jest interfejs zaprojektowany do użycia z poza jądra:
void get_random_bytes (void *buf, int nbytes);
Funkcja ta umieszcza żądaną ilość nbytes bajtów w buforze buf.
Zadanie 1.
Jaka struktura danych jest używana do przechowywania zleceń do urządzenia?
Odpowiedź: Kolejka.
Zadanie 2.
Jaka struktura danych jest używana do przechowywania nagłówków buforów (buffer_head)
wewnątrz struktury opisującej żądanie?
Odpowiedź: Dwukierunkowa lista cykliczna.
Zadanie 3.
Żądania do dysku dotyczące cylindrów przychodzą w następującej kolejności: 35, 1, 77, 19, 120,
189, 15, 165, 55. Głowica dysku znajduje się nad cylindrem 50. W jakiej kolejności zostaną obsłużone
te zlecenia zgodnie z algorytmem C-LOOK?
Odpowiedź: 55, 77, 120, 165, 189, 1, 15, 35.
Góra strony
|