Procesy o statusie TASK_INTERRUPTIBLE
i TASK_UNINTERRUPTIBLE
są podzielone na wiele klas, z których każda odpowiada zdarzeniu na jakie oczekują i jest reprezentowana przez kolejkę procesów oczekujących (ang. wait_queue
).
W pliku wait.h
jest zdefiniowana struktura i operacje na kolejkach procesów czekających. Jest tam dużo opcji używanych podczas testowania poprawności, jednak przy normalnym funkcjonowaniu systemu nie są one wykorzystywane, nie będziemy więc o tym wspominać.
Element kolejki wait_queue jest postaci:
struct wait_queue_t {
unsigned int flags;
struct task_struct * task;
struct list_head task_list;
}
Jedynymi wykorzystywanymi wartościami pola flags
są WQ_FLAG_EXCLUSIVE
i jej zaprzeczenie. Gdy zajdzie określone zdarzenie, przeglądana jest odpowiednia kolejka wait_queue i budzone są kolejne procesy, aż do napotkania określonej ilości elementów z flagą WQ_FLAG_EXCLUSIVE
. Wtedy kończy się seria procesów budzonych. Oznacza to, że jeśli wszystkie elementy w kolejce mają ustawioną flagę WQ_FLAG_EXCLUSIVE
, to procesy mogą być budzone pojedynczo.
Widać, że również ta kolejka jest oparta na list.h . Tym razem potrzebujemy wskaźnika do struktury procesu, bo istnieje dowolnie wiele różnych kolejek, więc dowiązanie do kolejki oczekujących nie może być elementem struktury opisującej proces.
Początek listy jest elementem wyróżnionym o trochę innej strukturze:
struct wait_queue_head_t{
wq_lock_t lock;
struct list_head task_list;
}
Początek wait_queue nie jest związany z żadnym procesem, za to posiada pole używane do zapewnienia niepodzielności operacji na jego kolejce. Kolejki procesów oczekujących są obsługiwane zarówno przez funkcje jądra jak i przez przerwania, więc operacje na nich (wstawianie i usuwanie) muszą być wykonywane z wyłączonymi przerwaniami.
DECLARE_WAITQUEUE(nowy_element, proces)
Tworzy element kolejki o nazwie nowy_element związany z podanym procesem i listą ze wskaźnikami zainicjowanymi na NULL.
DECLARE_WAIT_QUEUE_HEAD(nowa_kolejka)
Tworzy nowy początek kolejki o nazwie nowa_kolejka, z otwartym zabezpieczeniem i listą ze wskaźnikami wskazującymi na nową_kolejkę, czyli pustej listy.
Zainicjowanie wskaźnika do kolejki polega na stworzenie pustej listy z otwartym zabezpieczeniem:
void init_waitqueue_head(wait_queue_head_t *kolejka)
Stworzenie ze wskaźnika na wait_queue_t wskaźnika na element kolejki polega na wyzerowaniu flag i połączeniu go z procesem:
void init_waitqueue_entry(wait_queue_t *kolejka, struct task_struct *proces)
Sprawdzenie, czy kolejka wait_queue jest używana polega na sprawdzeniu, czy związana z nią lista nie jest pusta.
int waitqueue_active(wait_queue_head_t *kolejka)
Dostępne są funkcje umożliwiające dokonywanie prostych operacji na kolejkach:
void __add_wait_queue(wait_queue_head_t *poczatek,
wait_queue_t *nowy_element)
void __add_wait_queue_tail(wait_queue_head_t *poczatek,
wait_queue_t *nowy_element)
void __remove_wait_queue(wait_queue_head_t *poczatek,
wait_queue_t *usuwany_element)
Są one zaimplementowane przy użyciu standardowych funkcji z list.h Funkcje te nie zapewniają jednak atomowości, dlatego bardziej użyteczne są funkcje wykorzystujące przewidziane dla kolejki zabezpieczenia i ustawiające odpowiednio flagi:
void add_wait_queue(wait_queue_head_t *kolejka,
wait_queue_t *nowy_element)
void add_wait_queue_exclusive(wait_queue_head_t *kolejka,
wait_queue_t * nowy_element)
void remove_wait_queue(wait_queue_head_t *kolejka,
wait_queue_t * element)
Te funkcje już zapewniają atomowość operacji.
Na koniec omówmy jeszcze różnice między obecną strukturą kolejki, a tą z poprzedniej wersji jądra:
Przede wszystkim wprowadzono rozróżnienie pomiędzy zwykłym elementem kolejki a jej początkiem.
Zabezpieczenie jest teraz związane z konkretną kolejką i umieszczone w jej początku.
Poza tym wprowadzono flagę umożliwiającą budzenie procesów pojedynczo.