W systemie jest jedna kolejka procesów gotowych (ang. runqueue), grupująca procesy, które do tego, by móc się wykonywać, potrzebują co najwyżej procesora.
Wszystkie procesy w runqueue powinny mieć status TASK_RUNNING
(co oznacza, że są właśnie działające lub gotowe - oczywiście w systemie jednoprocesorowym może być tylko jeden proces działający). Może się co prawda zdarzyć, że w tej kolejce znajdzie się proces o statusie TASK_INTERRUPTIBLE
, ale tylko jeśli jest to proces właśnie działający, który zgłasza chęć oczekiwania na przerwanie. Sytuacja taka będzie trwała bardzo krótko - do czasu zakończenia przeszeregowania procesów. Wtedy proces ten zostanie usunięty z kolejki. Jedynym dla niej uzasadnieniem jest oszczędność operacji - być może proces zostanie obudzony przerwaniem nim zdążymy go usunąć z kolejki.
Ilość procesów w kolejce procesów gotowych przechowywana na zmiennej nr_running
.
Struktura kolejki runqueue jest tworzona poprzez pole run_list
znajdujące się w strukturze task_struct
. Jest ono typu list_head
, a więc tyczą się jej te same przepisy, które zostały omówione w rozdziale o kolejkach Linuksowych (list.h).
runqueue_head
. Nie jest on związany z żadnym procesem, ani nie jest elementem żadnej struktury. Jest to możliwe dzięki nowej strukturze kolejki (list.h).
Porównajmy, ze starą strukturą:
Do operowania na kolejce runqueue służą następujące procedury:
Dodawanie procesu do kolejki:
add_to_runqueue(struct task_struct * proces)
Bardzo istotne jest, że dodawanie do listy powoduje dodanie na sam początek listy. Funkcja scheduler()
korzysta z faktu, że procesy ostatnio dołączone do kolejki są na jej początku.
Usuwanie z procesu z kolejki:
del_from_runqueue(struct task_struct * proces)
Usuwanie z listy odbywa się w schedulerze
, po sprawdzeniu, że proces który go wywołał nadal nie jest w stanie TASK_RUNNING
.
Podczas usuwania pole run_list.next
jest ustawiane na NULL
. Dzięki temu można później rozpoznać, czy dany proces znajduje się na runqueue.
Aby sprawdzić, czy proces jest w kolejce runqueue wywołujemy:
int task_on_runqueue(struct task_struct *p)
Ta funkcja sprawdza, czy run_list.next
w danym procesie wskazuje na NULL
(co oznacza, że proces został usunięty z kolejki).
Warto zaznaczyć, że proces (procesy, jeśli mamy do czynienia z systemem wieloprocesorowym)idle ma pole run_list
zainicjowane na pustą kolejkę (to znaczy next
wskazuje na nią samą). Z tego powodu funkcja task_on_runqueue()
zwróci prawdę dla idle, pomimo że nie znajduje się on w runqueue.
Przesunięcie procesu na początek kolejki:
move_first_runqueue(struct task_struct * proces)
Przesunięcie procesu na koniec kolejki:
move_last_ranqueue(struct task_struct * proces)
Wszystkie powyższe operacje korzystają z funkcji zdefiniowanych w list.h a także odpowiednio zmieniają wartość nr_running
. Żadne (oprócz funkcji usuwającej) nie korzystają bezpośrednio ze wskaźników na element następny ani poprzedni.
Pomimo że procesy gotowe są podzielone na wiele logicznych kolejek w zależności od sposobu szeregowania (inny np. dla zwykłych procesów i procesów czasu rzeczywistego) i od priorytetu (dla procesów czasu rzeczywistego), to fizycznie są one umieszczone na jednej liście, a rozpoznanie jak należy postąpić przy kolejnym przeszeregowaniu jest dokonywane przez funkcję schedule()
.
Podczas żadnej z tych operacji nie jest sprawdzana logiczna poprawność działań: operacje są niezależne od statusu procesu i w żaden sposób nie jest chroniona ich niepodzielność. O te sprawy należy zadbać pisząc kod funkcji je wykorzystującej.
Kolejka runqueue jest obsługiwana zarówno przez funkcje jądra jak i przez przerwania, więc operacje na niej muszą być wykonywane z wyłączonymi przerwaniami. Do zapewnienia niepodzielności kolejki runqueue służy runqueue_lock
. Z zasady jest on używany do ochrony pojedynczej operacji, a nie ciągu operacji.