Klasyczny algorytm Uniksa zaimplementowano m. in. w Wersji 3 Systemu V (System V Release 3) oraz w 4.3BSD Unix.
Algorytm ten wykorzystuje kolejki priorytetowe ze sprzężeniem zwrotnym. Dynamiczny priorytet procesu zmienia się w zależności od długości kwantów czasu, które zużywa proces. W przypadku, gdy budzony jest proces o wyższym priorytecie, proces o niższym priorytecie jest wywłaszczany.
Klasyczne jądro uniksowe jest niewywłaszczalne. Oznacza to, że procesowi działającemu w trybie jądra nie może być odebrany procesor. Proces może dobrowolnie oddać procesor, np. w sytuacji gdy zasypia w oczekiwaniu na dostęp do jakiegoś zasobu lub w oczekiwaniu na jakieś zdarzenie. Tylko proces działający w trybie użytkownika może zostać wywłaszczony.
Z każdym procesem są związane następujące wartości:
Niższa wartość oznacza niższy priorytet.5.1 Priorytety 0-69 przeznaczone są dla procesów działających w trybie użytkownika, a 70-119 dla procesów w trybie jądra.
W trybie jądra wartość priorytetu może zostać chwilowo podwyższona, aby przyspieszyć zwolnienie przez proces zajętych zasobów. Na przykład procesy oczekujące na odczyt z terminala otrzymują przy budzeniu priorytet 90, natomiast oczekujące na dyskowe operacje wejścia/wyjścia -- 99:
unix_wakeup(Process p, int reason) { p.pri = system_priority(reason); // 70-119 if ( p.pri > current.pri ) ... // przeszereguj procesy }
Powrót do trybu użytkownika powoduje przywrócenie priorytetowi dynamicznemu wartości z zakresu 0-69, jeśli proces dostał priorytet jądra z zakresu 70-119:
unix_return_to_user_mode(Process p) { p.pri = p.umdpri; }
Każdemu procesowi, który oddaje procesor, czy to w wyniku wykorzystania kwantu czasu, czy zaśnięcia, zwiększa się pole cpu o długość wykorzystanego kwantu czasu do wartości co najwyżej 127:
unix_lostcpu(Process p) { if ( quantum_expired(p) || sleeping(p) ) p.cpu = min ( 127, p.cpu + last_quantum(p) ); }
Co sekundę wartość pola cpu ulega zmniejszeniu -- jest mnożona przez tzw. czynnik rozpadu (ang. decay factor), zależny od implementacji. W SVR3 przyjęto stały czynnik rozpadu równy 1/2:
unix_tick() { for_each_process(p) { p.cpu = p.cpu / 2; unix_recalc_umdpri(p); // zob. niżej } }W 4.3BSD czynnik rozpadu jest zależny od liczby
act
procesów aktywnych w systemie w ciągu ostatniej sekundy: bsd_tick() { for_each_process(p) { p.cpu = p.cpu * 2 * act / ( 2 * act + 1 ); unix_recalc_umdpri(p); } }
Przy stałym czynniku rozpadu, jak zaimplementowano to w SVR3, w systemie obciążonym procesy zorientowane na obliczenia znacznie podwyższają swój priorytet (tj. zmniejszają wartość średniej wykładniczej cpu). Przyczyną jest fakt, iż w systemie obciążonym procesy dostają procesor stosunkowo rzadko. W tej sytuacji priorytety procesów zorientowanych na wejście-wyjście i procesów zorientowanych na obliczenia są do siebie bardzo zbliżone. W 4.3BSD algorytm tym wolniej podwyższa priorytety, im bardziej obciążony jest system. Pozwala to zachować odpowiednie zróżnicowanie priorytetów dla tych dwóch rodzajów procesów.
Każda zmiana wartości pola cpu powoduje odświeżenie priorytetu trybu użytkownika:
unix_recalc_umdpri(Process p) { p.umdpri = min ( 69, 29-(p.cpu / 4)+(2 * p.upri) ); }
Procesy silnie korzystające z procesora mają dużą wartość cpu i tym samym niższy priorytet. Procesy długo śpiące mają, dzięki postarzaniu, coraz mniejszą wartość cpu i wyższy priorytet. Wagi 1/4 dla cpu oraz 2 dla upri są przykładowe, lecz ilustrują proporcje przyjęte w większości implementacji.
Implementacja powyższego algorytmu korzysta z dwukierunkowych list procesów o tej samej wartości pri. Niektóre architektury, jak np. VAX-11 wspierają sprzętowo operacje na tak zorganizowanych kolejkach priorytetowych.
Do wykonania wybierany jest pierwszy proces z niepustej kolejki o najwyższym priorytecie:
Process unix_select() { Queue queue = find_highest_queue(); // szukanie niepustej kolejki o największym numerze if ( queue == NULL ) return NULL; // wszystkie kolejki puste else return first(queue); // pierwszy proces z kolejki }
Proces, który zużył kwant czasu dostaje nowy kwant i wędruje na koniec kolejki priorytetowej, w której się znajduje:
unix_tqexpired(Process p) { p.counter = new_quantum(); move_last(p); // // + kod doliczający kwant czasu, jak // w unix_lostcpu() // }
Dokładny opis algorytmu Uniksa można znaleźć m. in. w [2] i [5].
Klasyczny algorytm Uniksa posiada następujące cechy: