sched(7)

NAZWA
sched - przegląd szeregowania zadań

OPIS
Od Linuksa 2.6.23, domyślnym planistą jest CFS, „Completely Fair Scheduler” (zupełnie sprawiedliwy dyspozytor). Planista CFS zastąpił wcześniejszego dyspozytora „O(1)”.

  Podsumowanie API
Linux udostępnia poniższe wywołania systemowe, służące do kontrolowania zachowania planisty procesora, algorytmu oraz priorytetów procesów (lub precyzyjniej: wątków).

nice(2)
    Ustawia nową wartość nice wątku wywołującego i zwraca nową wartość nice.

getpriority(2)
    Zwraca wartość nice wątku, grupy procesów lub zbioru wątków podanego użytkownika.

setpriority(2)
    Ustawia wartość wątku, grupy procesów lub zbioru wątków podanego użytkownika.

sched_setscheduler(2)
    Ustawia algorytm i parametry szeregowania podanego wątku.

sched_getscheduler(2)
    Zwraca algorytm szeregowania podanego wątku.

sched_setparam(2)
    Ustawia parametry szeregowania podanego wątku.

sched_getparam(2)
    Pobiera parametry szeregowania podanego wątku.

sched_get_priority_max(2)
    Zwraca maksymalny dostępny priorytet w danym algorytmie szeregowania.

sched_get_priority_min(2)
    Zwraca minimalny dostępny priorytet w danym algorytmie szeregowania.

sched_rr_get_interval(2)
    Pobiera kwant używany wobec wątków szeregowanych przez algorytm karuzelowy („round-robin”).

sched_yield(2)
    Powoduje zwolnienie procesora przez wywołującego, dzięki czemu może być wykonany jakiś inny wątek.

sched_setaffinity(2)
    (typowo linuksowe) Ustawia koligację procesora podanego wątku.

sched_getaffinity(2)
    (typowo linuksowe) Pobiera koligację procesora podanego wątku.

sched_setattr(2)
    Ustawia algorytm i parametry szeregowania podanego wątku. To (typowo linuksowe) wywołanie systemowe zapewnia nadzbiór funkcjonalności sched_setscheduler(2) oraz sched_setparam(2).

sched_getattr(2)
    Pobiera algorytm i parametry szeregowania podanego wątku. To (typowo linuksowe) wywołanie systemowe zapewnia nadzbiór funkcjonalności sched_getscheduler(2) oraz sched_getparam(2).

  Algorytmy szeregowania
Planista (dyspozytor) to część jądra decydująca o tym, który gotowy do działania wątek zostanie wykonany przez procesor jako następny. Każdy wątek ma przypisany algorytm szeregowania zadań oraz statyczny priorytet szeregowania, sched_priority. Dyspozytor podejmuje swe decyzje w oparciu o wiedzę o algorytmie szeregowania oraz statycznych priorytetach wszystkich wątków w systemie.

W przypadku wątków korzystających z jednego ze zwykłych algorytmów szeregowania (SCHED_OTHER, SCHED_IDLE, SCHED_BATCH), sched_priority nie jest stosowane przy podejmowaniu decyzji o szeregowaniu (musi być podane jako 0).

Procesy korzystające z jednego z algorytmów szeregowania czasu rzeczywistego (SCHED_FIFO, SCHED_RR) mają wartość sched_priority z przedziału od 1 (niski) do 99 (wysoki). Jak wskazują wartości, wątki czasu rzeczywistego mają zawsze wyższy priorytet od zwykłych wątków. Proszę zauważyć, że POSIX.1 wymaga obsługi przez implementację co najmniej 32 oddzielnych poziomów priorytetów w przypadku algorytmów szeregowania czasu rzeczywistego i niektóre systemy obsługują tylko to minimum. Przenośne programy powinny stosować sched_get_priority_min(2) i sched_get_priority_max(2), aby poznać przedział priorytetów obsługiwanych dla danego algorytmu.

Idea jest taka, że dyspozytor zarządza listą działających wątków dla każdej możliwej wartości sched_priority. Aby określić następny uruchomiony wątek, planista szuka niepustej listy o najwyższym statycznym priorytecie i wybiera wątek na jej początku.

Algorytm szeregowania wątku określa miejsce, w którym zostanie on umieszczony na liście wątków o takim samym statycznym priorytecie oraz sposób przemieszczenia się wewnątrz tej listy.

Wszelkie szeregowanie następuje z wywłaszczaniem: jeśli pojawi się wątek o wyższym priorytecie gotowy do uruchomienia, aktualnie uruchomiony wątek zostanie wywłaszczony i powróci na listę oczekujących swego statycznego poziomu priorytetu. Algorytm szeregowania określa kolejność jedynie na liście działających wątków o takim samym statycznym priorytecie.

  SCHED_FIFO: Szeregowanie typu pierwszy na wejściu, pierwszy na wyjściu
Algorytm SCHED_FIFO (z ang. first in-first out) można stosować tylko ze statycznymi priorytetami wyższymi niż 0, co oznacza, że gdy wątek SCHED_FIFO stanie się gotowy do uruchomienia, zawsze natychmiast wywłaszczy każdy działający wątek SCHED_OTHER, SCHED_BATCH lub SCHED_IDLE. SCHED_FIFO jest algorytmem prostego planowania bez podziału na wycinki czasowe. Do wątków szeregowanych według algorytmu SCHED_FIFO stosują się następujące reguły:

•   Działający wątek SCHED_FIFO wywłaszczony przez inny wątek o wyższym priorytecie pozostanie na czele listy swojego priorytetu i wznowi wykonanie, gdy tylko wszystkie wątki o wyższym priorytecie zostaną ponownie zablokowane.

•   Gdy zablokowany wątek SCHED_FIFO stanie się gotowy do uruchomienia, zostanie umieszczony na końcu listy swojego priorytetu.

•   Gdy wywołanie do sched_setscheduler(2), sched_setparam(2), sched_setattr(2), pthread_setschedparam(3) lub pthread_setschedprio(3) zmieni priorytet działającego lub gotowego do działania wątku SCHED_FIFO, identyfikowanego przez pid, efekt pozycji wątku na liście zależy od kierunku zmiany priorytetu wątku:

    (a) Jeśli podnosi się priorytet wątku, jest on umieszczany na końcu listy jego nowego priorytetu. Może zatem wywłaszczyć aktualnie działający wątek o tym samym priorytecie.

    (b) Jeśli priorytet wątku nie ulega zmianie, jego pozycja na liście nie ulega zmianie.

    (c) Jeśli obniża się priorytet wątku, jest on umieszczany na czele listy jego nowego priorytetu.

    Zgodnie z POSIX.1-2008, zmiany priorytetu (lub algorytmu szeregowania) wątku, dokonane mechanizmem innym niż pthread_setschedprio(3), powinny spowodować umieszczenie wątku na końcu listy jego priorytetu.

•   Wątek wywołujący sched_yield(2) zostanie umieszczony na końcu listy.

Nie występują inne zdarzenia mogące przesunąć wątek szeregowany według algorytmu SCHED_FIFO na liście oczekujących wątków gotowych do działania o tym samym priorytecie statycznym.

Wątek SCHED_FIFO działa do momentu zablokowania przez żądanie wejścia/wyjścia, wywłaszczenia przez wątek o wyższym priorytecie lub wywołania przez niego sched_yield(2).

  SCHED_RR: Szeregowanie rotacyjne (karuzelowe)
Algorytm SCHED_RR (z ang. round-robin) jest prostym rozszerzeniem SCHED_FIFO. Wszystko, co opisano powyżej wobec SCHED_FIFO, stosuje się również wobec SCHED_RR z tym wyjątkiem, że każdy wątek może działać jedynie przez maksymalny kwant czasu. Jeśli wątek SCHED_RR działał przez czas równy lub dłuższy kwantowi czasu, zostanie umieszczony na końcu listy dla jego priorytetu. Wątek SCHED_RR wywłaszczony przez wątek o wyższym priorytecie, który następnie wznowił działanie jako wątek działający, ukończy niewygasły fragment swojego kwantu czasu szeregowania rotacyjnego. Długość kwantu czasu można pozyskać za pomocą sched_rr_get_interval(2).

  SCHED_DEADLINE: Szeregowanie sporadyczne z budżetem czasu
Od Linuksa 3.14, Linux udostępnia szeregowanie sporadyczne z budżetem czasu (SCHED_DEADLINE). Ten algorytm zaimplementowano obecnie za pomocą GEDF (Global Earliest Deadline First) w połączeniu z CBS (Constant Bandwidth Server). Aby ustawić i pobrać ten algorytm wraz z powiązanymi atrybutami, konieczne jest użycie typowo linuksowych wywołań systemowych sched_setattr(2) i sched_getattr(2).

Zadaniem sporadycznym jest takie, które ma sekwencje zadań, z których każde jest aktywowane co najwyżej raz na okres. Każde zadanie ma też limit względny, przed którym powinno ukończyć wykonanie oraz czas obliczeniowy, który jest czasem procesora potrzebnym na wykonanie zadania. Moment, w którym zadanie wybudza się, ze względu na nową pracę do wykonania, nazywa się momentem nadejścia (również momentem żądania lub momentem zwolnienia). Moment uruchomienia to czas, w którym zadanie rozpoczyna swe wykonywanie. Limit absolutny powstaje zatem przez dodanie limitu względnego do czasu nadejścia.

Poniższy diagram ukazuje powyższe pojęcia:

    nadejście/wybudzenie              limit absolutny
         | moment uruchomienia              |
         |        |                         |
         v        v                         v
    -----x--------xooooooooooooooooo--------x--------x---
                  |<- czas obl. -->|
         |<-------- limit względny -------->|
         |<-------------- okres -------------------->|

Przy ustawianiu algorytmu SCHED_DEADLINE wobec wątku za pomocą sched_setattr(2), można podać trzy parametry: Czas Działania (Runtime), Limit (Deadline) oraz Okres (Period). Parametry te niekoniecznie odpowiadają wcześniej wymienionym pojęciom: praktyką jest zwykle ustawienie Czasu Działania na wartość większą niż przeciętny czas obliczeniowy (lub czas wykonania w najgorszym wariancie, w przypadku trudnych zadań czasu rzeczywistego), Limit na limit względny a Okres na okres zadania. Zatem w szeregowaniu SCHED_DEADLINE mamy:

    nadejście/wybudzenie              limit absolutny
         | moment uruchomienia              |
         |        |                         |
         v        v                         v
    -----x--------xooooooooooooooooo--------x--------x---
                  |<- Czas Działania ->|
         |<------------- Limit ------------>|
         |<-------------- Okres -------------------->|

Trzy parametry niniejszego szeregowania odpowiadają polom sched_runtime, sched_deadline oraz sched_period struktury sched_attr; zob. sched_setattr(2). Pola te wyrażają wartości w nanosekundach. Jeśli poda się sched_period jako 0, przyjmowana jest wartość taka sama jak w sched_deadline.

Jądro wymaga aby:

    sched_runtime <= sched_deadline <= sched_period

Dodatkowo, w bieżącej implementacji, wszystkie wartości parametrów muszą wynosić przynajmniej 1024 (tj. odrobinę ponad jedną mikrosekundę, stanowiącą rozdzielczość implementacji) i mniej niż 2^63. Jeśli któryś z tych warunków nie będzie spełniony, sched_setattr(2) zawiedzie z błędem EINVAL.

CBS gwarantuje brak interferencji pomiędzy zadaniami, poprzez dławienie wątków próbujących przekroczyć ich określony Czas Działania.

Aby zagwarantować szeregowanie z budżetem czasu, jądro musi zapobiegać sytuacjom, gdy uszeregowanie zbioru wątków SCHED_DEADLINE jest nierealistyczne w podanych ograniczeniach czasowych. Jądro dokonuje zatem testu przyjęcia przy ustawianiu lub zmienianiu algorytmu i atrybutów SCHED_DEADLINE. Test przyjęcia polega na obliczeniu, czy zmiana jest realistyczna: jeśli nie jest, sched_setattr(2) zawiedzie z błędem EBUSY.

Na przykład, jest wymagane (lecz niekoniecznie wystarczające), aby całkowite użycie było mniejsze lub równe całkowitej liczbie dostępnych procesorów, ponieważ każdy wątek może działać maksymalnie przez Czas Działania na Okres, czyli jego użycie wynosi Czas Działania dzielony przez jego Okres.

Aby wypełnić gwarancje uczynione przy przyjęciu wątku przez algorytm SCHED_DEADLINE, wątki SCHED_DEADLINE są wątkami o najwyższym (kontrolowanym przez użytkownika) priorytecie w systemie; jeśli działa jakiś wątek SCHED_DEADLINE, wywłaszczy on wszystkie wątki szeregowane według innych algorytmów.

Wywołanie fork(2) przez wątek szeregowany według algorytmu SCHED_DEADLINE zawiedzie z błędem EAGAIN, chyba że wątek ma ustawiony swój znacznik reset-on-fork (zob. niżej).

Wątek SCHED_DEADLINE po wywołaniu sched_yield(2) zwolni bieżącą pracę i zaczeka na rozpoczęcie nowego okresu.

  SCHED_OTHER: Domyślny sposób szeregowania zadań w Linuksie
Algorytm SCHED_OTHER można stosować tylko ze statycznym priorytetem 0 (tj. wątki szeregowane przez algorytmy czasu rzeczywistego są zawsze priorytetowe wobec procesów SCHED_OTHER). SCHED_OTHER jest standardowym algorytmem szeregowania w Linuksie, przeznaczonym do wszystkich wątków niewymagających specjalnych mechanizmów czasu rzeczywistego.

Wątek do uruchomienia jest wybierany z listy o statycznym priorytecie 0, w oparciu o priorytet dynamiczny, określany jedynie wewnątrz tej listy. Priorytet dynamiczny bazuje na wartości nice (zob. niżej) i jest zwiększany przy każdym kwancie czasu, w którym wątek jest gotowy do uruchomienia, ale odmówił mu tego dyspozytor (tzw. postarzanie wątku). W ten sposób uzyskuje się sprawiedliwy postęp dla wszystkich wątków SCHED_OTHER.

W kodzie źródłowym jądra Linux, algorytm SCHED_OTHER nazywa się tak naprawdę SCHED_NORMAL.

  Wartość nice
Wartość nice jest atrybutem, który można wpłynąć na dyspozytora, aby faworyzował (lub przeciwnie) proces w decyzjach szeregowania zadań. Wpływa na szeregowanie procesów SCHED_OTHER i SCHED_BATCH (zob. niżej). Wartość nice można modyfikować za pomocą nice(2), setpriority(2) lub sched_setattr(2).

Zgodnie z POSIX.1, wartość nice jest atrybutem przynależnym procesowi; tj. wątki procesu powinny dzielić wartość nice. Jednak w Linuksie wartość nice jest atrybutem przynależnym wątkowi: różne wątki tego samego procesu mogą mieć różne wartości nice.

Przedział wartości nice różni się między systemami UNIX. We współczesnym Linuksie przedział wynosi od -20 (wysoki priorytet) do +19 (niski priorytet). W niektórych innych systemach wynosi on -20..20. Bardzo wczesne jądra Linux (przed Linuksem 2.0) korzystały z przedziału -nieskończoność..15.

Poziom wpływu wartości nice na relatywne szeregowanie procesów SCHED_OTHER również różni się między systemami UNIX oraz pomiędzy wersjami jądra Linux.

Wraz z wprowadzeniem dyspozytora CFS w Linuksie 2.6.23, Linux przyjął algorytm przewidujący znacznie większy wpływ relatywnych różnic wartości nice. W bieżącej implementacji, różnica każdej jedności w wartości nice pomiędzy dwoma procesami stanowi mnożnik 1,25, o jaki planista faworyzuje proces o wyższym priorytecie. Powoduje to zapewnienie rzeczywiście niewielkiego czasu procesora procesom o bardzo niskim priorytecie (wartość nice +19), gdy w systemie występują istotniejsze zadania oraz udostępnia większość procesora przy wysokim priorytecie (wartość nice -20) aplikacjom, które tego wymagają (np. niektóre programy związane z dźwiękiem).

W Linuksie, za pomocą limitu zasobów RLIMIT_NICE, można zdefiniować limit wartość nice, jaki może uzyskać nieuprzywilejowany proces; więcej szczegółów w podręczniku setrlimit(2).

Więcej szczegółów na temat wartości nice opisano poniżej, w podrozdziałach o funkcji autogrupowania i szeregowania grup.

  SCHED_BATCH: Szeregowanie procesów wsadowych
(Od Linuksa 2.6.16). Algorytm SCHED_BATCH można stosować wyłącznie ze statycznym priorytetem 0. Algorytm podobny do SCHED_OTHER w tym, że szereguje wątki zgodnie z ich priorytetem dynamicznym (bazującym na wartości nice). Różnica jest taka, że tutaj dyspozytor zawsze zakłada, że wątek jest obciążający dla procesora. W konsekwencji, planista zastosuje niewielką karę przy wybudzeniu, co powoduje umiarkowanie rzadsze przydzielanie mu zasobów.

Algorytm przydatny w przypadku obciążeń nieinteraktywnych, które jednak nie chcą zmniejszać swojego priorytetu poprzez wartość nice oraz w przypadku obciążeń wymagających deterministycznego algorytmu szeregowania, bez dodatkowego wywłaszczania wywoływanego przez interaktywność (pomiędzy ich zadaniami).

  SCHED_IDLE: Szeregowanie zadań o bardzo niskim priorytecie
(Od Linuksa 2.6.23). Algorytm SCHED_IDLE można stosować tylko ze statycznym priorytetem 0; wartość nice procesu nie ma na niego wpływu.

Przeznaczony do uruchamiania zadań o ekstremalnie niskim priorytecie (niższym nawet niż wartość nice +19 w algorytmach SCHED_OTHER lub SCHED_BATCH).

  Resetowanie algorytmu szeregowania procesów potomnych
Każdy wątek ma znacznik szeregowania reset-on-fork (zresetuj przy rozwidleniu). Gdy jest ustawiony, potomkowie utworzeni przy fork(2) nie dziedziczą uprzywilejowanych algorytmów szeregowania. Znacznik reset-on-fork może być ustawiony przez:

•   zsumowanie (logiczne OR) znacznika SCHED_RESET_ON_FORK do argumentu policy przy wywołaniu sched_setscheduler(2) (od Linuksa 2.6.32); albo

•   podanie znacznika SCHED_FLAG_RESET_ON_FORK w attr.sched_flags przy wywołaniu sched_setattr(2).

Proszę zauważyć, że stałe używane przez oba interfejsy mają różne nazwy. Stan znacznika reset-on-fork można analogicznie pobrać za pomocą sched_getscheduler(2) i sched_getattr(2).

Funkcja reset-on-fork jest przeznaczona do aplikacji odtwarzających media i może posłużyć do uniknięcia możliwości ominięcia przez aplikacje limitu zasobu RLIMIT_RTTIME (zob. getrlimit(2)) przez tworzenie wielu procesów potomnych.

Precyzyjniej: jeśli ustawiono znacznik reset-on-fork, do nowo tworzonych potomków stosują się następujące zasady:

•   Jeśli wątek wywołujący ma ustawiony algorytm szeregowania SCHED_FIFO lub SCHED_RR, algorytm jest resetowany na SCHED_OTHER w procesach potomnych.

•   Jeśli proces wywołujący ma ujemną wartość nice, wartość nice jest resetowana na zero w procesach potomnych.

Po włączeniu znacznika reset-on-fork, można go zresetować tylko, jeśli wątek ma przywilej CAP_SYS_NICE. Znacznik jest wyłączony w procesach potomnych tworzonych przez fork(2).

  Przywileje i limity zasobów
Przed Linuksem 2.6.12, tylko wątki uprzywilejowane (CAP_SYS_NICE) mogą mieć ustawiony niezerowy priorytet statyczny (tj. ustawiony algorytm szeregowania czasu rzeczywistego). Jedyną zmianę, jaką może dokonać nieuprzywilejowany wątek, jest ustawienie algorytmu SCHED_OTHER, a to można zrobić tylko, jeśli efektywny identyfikator użytkownika wywołującego jest równy rzeczywistemu lub efektywnemu identyfikatorowi użytkownika wątku docelowego (tj. wątku określonemu przez pid), którego algorytm jest zmieniany.

Wątek musi być uprzywilejowany (CAP_SYS_NICE), aby ustawić lub zmodyfikować algorytm SCHED_DEADLINE.

Od Linuksa 2.6.12, limit zasobów RLIMIT_RTPRIO definiuje górny limit statycznego priorytetu wątku nieuprzywilejowanego w algorytmach SCHED_RR i SCHED_FIFO. Reguły zmiany algorytmów szeregowania i priorytetów są następujące:

•   Jeśli nieuprzywilejowany wątek ma niezerowy miękki limit RLIMIT_RTPRIO, to może zmienić swój algorytm szeregowania zadań i priorytet, z takim ograniczeniem, że priorytet nie może być ustawiony na wyższy niż maksymalna z wartości: jego bieżącego priorytetu i jego limitu miękkiego RLIMIT_RTPRIO.

•   Jeśli miękki limit RLIMIT_RTPRIO wynosi 0, to jedynymi dozwolonymi zmianami są: obniżenie priorytetu lub przełączenie z algorytmu czasu rzeczywistego na niebędący takowym.

•   Wedle tych samych zasad, inny nieuprzywilejowany wątek może również dokonać tych zmian, o ile efektywny identyfikator użytkownika wątku dokonującego zmiany jest taki sam, jak rzeczywisty lub efektywny identyfikator użytkownika wątku docelowego.

•   Specjalne zasady tyczą się algorytmu SCHED_IDLE. Przed Linuksem 2.6.39, nieuprzywilejowany wątek działający według tego algorytmu nie mógł go zmienić, niezależnie od wartości jego limitu zasobów RLIMIT_RTPRIO. Od Linuksa 2.6.39, nieuprzywilejowany wątek może przełączyć się na algorytm SCHED_BATCH lub SCHED_OTHER, o ile jego wartość nice znajduje się w przedziale dozwolonym przez jego limit zasobów RLIMIT_NICE (zob. getrlimit(2)).

Wątki uprzywilejowane (CAP_SYS_NICE) ignorują limit RLIMIT_RTPRIO; podobnie jak w starszych jądrach, mogą dokonywać dowolnych zmian dotyczących algorytmu szeregowania zadań i priorytetów. Więcej informacji na temat RLIMIT_RTPRIO znajduje się w podręczniku getrlimit(2).

  Ograniczanie użycia procesora przez procesy czasu rzeczywistego i z budżetem czasu
Nieblokująca, nieskończona pętla w wątku zaszeregowanym według algorytmu SCHED_FIFO, SCHED_RR lub SCHED_DEADLINE potencjalnie może na zawsze zablokować dostęp do procesora wszystkim innym wątkom. Przed Linuksem 2.6.25, jedynym sposobem zapobiegnięcia zamrożenia systemu przez zbiegły proces czasu rzeczywistego było uruchomienie (na konsoli) powłoki zaszeregowanej z wyższym priorytetem statycznym niż testowana aplikacja. Można było w ten sposób awaryjnie zabić testowaną aplikację czasu rzeczywistego, która nie zablokowała lub nie zakończyła się zgodnie z oczekiwaniami.

Od Linuksa 2.6.25, istnieją inne techniki radzenia sobie ze zbiegłymi procesami czasu rzeczywistego i z budżetem czasu. Jedną z nich jest użycie limitu zasobów RLIMIT_RTTIME do nałożenia ograniczenia na czas procesora, jaki może skonsumować proces czasu rzeczywistego. Więcej szczegółów w podręczniku getrlimit(2).

Od Linuksa 2.6.25, Linux zapewnia również dwa pliki /proc, które mogą posłużyć do zarezerwowania określonej ilości czasu procesora dla procesów innych niż czasu rzeczywistego. Zarezerwowanie czasu procesora w ten sposób, pozwala również na przydzielenie pewnej ilości czasu procesora dla (przykładowo) powłoki roota, z której można zabić zbiegły proces. Oba pliki określają wartości czasu w mikrosekundach:

/proc/sys/kernel/sched_rt_period_us
    Plik określa „okres szeregowania zadań” równoważny 100% przepustowości procesora. Wartość w pliku może być z zakresu od 1 do INT_MAX, czyli od 1 mikrosekundy do około 35 minut. Domyślna wartość w pliku wynosi 1 000 000 (1 sekunda).

/proc/sys/kernel/sched_rt_runtime_us
    Wartość w tym pliku określa, jak dużo tego „okresu szeregowania zadań” w systemie może być przeznaczone na wszystkie procesy czasu rzeczywistego oraz procesy szeregowane z budżetem czasu. Wartość w pliku może być z zakresu od -1 do INT_MAX-1. Podanie -1 uczyni ten czas takim samym jak ten okres; tj. nie odkłada się czasu procesora na procesy inne niż czasu rzeczywistego (takie zachowanie występowało przed Linuksem 2.6.25). Wartość domyślna w pliku to 950 000 (0,95 sekundy), co oznacza że 5% czasu procesora jest zarezerwowane na procesy szeregowane wedle algorytmów innych niż czasu rzeczywistego i z budżetem czasu.

  Czas odpowiedzi
Zablokowany wątek o wysokim priorytecie oczekujący na wejście/wyjście ma określony czas odpowiedzi, zanim zostanie ponownie zaszeregowany. Sterownik zapisujący urządzenia może znacznie ograniczyć ten czas odpowiedzi poprzez korzystanie z procedury obsługi przerwań zwanej „slow interrupt”.

  Różne
Procesy potomne dziedziczą algorytm i parametry szeregowania poprzez fork(2). Algorytm i parametry szeregowania są zachowywane poprzez execve(2).

W przypadku procesów czasu rzeczywistego, aby uniknąć opóźnień związanych ze stronicowaniem, potrzebne jest zwykle blokowanie pamięci; można go dokonywać za pomocą mlock(2) lub mlockall(2).

  Funkcja autogrupowania
Od Linuksa 2.6.38, jądro zapewnia funkcję zwaną autogrupowaniem, która ma na celu poprawienie wydajności interaktywnych środowisk graficznych, podczas wykonywania wieloprocesowych, mocno obciążających procesor prac, takich jak budowanie jądra Linux z wieloma równoległymi procesami budowania (tj. z opcją -j make(1)).

Funkcja działa razem z planistą CFS i wymaga, aby jądro było skonfigurowane z opcją CONFIG_SCHED_AUTOGROUP. W działającym systemie, funkcję można włączyć lub wyłączyć poprzez plik /proc/sys/kernel/sched_autogroup_enabled; wartość 0 wyłącza tę funkcję, a wartość 1 ją włącza. Domyślną wartością w pliku jest 1, chyba że podczas rozruchu jądra ustawiono parametr noautogroup.

Nowa autogrupa jest tworzona przy tworzeniu nowej sesji za pomocą setsid(2); dzieje się tak np. przy uruchamianiu nowego okna terminala. Nowy proces tworzony przez fork(2) dziedziczy członkostwo w autogrupie swojego rodzica. Wszystkie procesy w sesji są zatem członkami tej samej autogrupy. Autogrupa jest automatycznie niszczona, gdy zakończy się ostatni proces grupy.

Gdy autogrupowanie jest włączone, wszyscy członkowie autogrupy są umieszczani w tej samej „grupie zadań” dyspozytora jądra. Gdy jest wyłączone, tworzenie grup odbywa się tak samo, członkostwo w autogrupie jest wciąż widoczne w /proc, lecz autogrupy nie są używane. Planista CFS używa algorytmu wyrównującego rozłożenie cykli procesora pomiędzy grupami zadań. Korzyści, jakie z tego wynikają dla wydajności interaktywnego środowiska graficznego może przybliżyć następujący przykład.

Załóżmy, że są dwie autogrupy rywalizujące o ten sam procesor (tj. może być to system jednoprocesorowy albo wykorzystano taskset(1) do ograniczenia wszystkich procesów do tego samego procesora w wieloprocesorowym systemie SMP). Pierwsza grupa zawiera dziesięć procesów ograniczonych wydajnością procesora, pochodzących od polecenia budującego jądro: make -j10. Druga zawiera jeden proces ograniczony wydajnością procesora: odtwarzacz wideo. Efekt autogrupowania jest taki, że obie grupy otrzymają po połowie cykli procesora. Odtwarzacz wideo otrzyma zatem 50% cykli procesora, zamiast 9%, które najprawdopodobniej odbiły by się negatywnie na ciągłości odtwarzania wideo. Sytuacja w wieloprocesorowym systemie SMP jest bardziej złożona, ale ogólny efekt jest taki sam: dyspozytor rozkłada cykle procesora pomiędzy poszczególne grupy zadaniowe w ten sposób, że autogrupy zawierające dużą liczbę procesów ograniczonych wydajnością procesora nie zmonopolizują cykli procesora kosztem innych zadań systemu.

Członkostwo procesu w autogrupie (grupie zadań) można sprawdzić w pliku /proc/pid/autogroup:

    $ cat /proc/1/autogroup
    /autogroup-1 nice 0

Za pomocą tego pliku można też zmodyfikować przydział procesora przypisany autogrupie. Robi się to zapisując liczbę z przedziału wartości „nice” do pliku, co powoduje przypisanie wartości nice autogrupie. Dozwolony zakres to od +19 (niski priorytet) do -20 (wysoki priorytet). Zapisanie wartości spoza tego przedziału spowoduje zgłoszenie przez write(2) błędu EINVAL.

Ustawienie nice autogrupy ma takie samo znaczenie jak wartość nice procesu, jednak odnosi się do rozłożenia cykli procesora do autogrupy jako całości, w oparciu o relatywne wartości nice innych autogrup. W przypadku procesu wewnątrz autogrupy, cykle procesora jakie on otrzyma będą skutkiem łącznego oddziaływania wartości nice autogrupy (w porównaniu z innymi autogrupami) oraz wartości nice procesu (w porównaniu z innymi procesami tej samej autogrupy).

Korzystanie z kontrolera procesora cgroups(7) do umieszczania procesów w grupach kontrolnych (cgroup) innych niż główna grupa kontrolna procesora przesłania efekt działania autogrupowania.

Funkcja autogrupowania grupuje jedynie procesy szeregowane według algorytmów innych niż czasu rzeczywistego (tj. dotyczy algorytmów SCHED_OTHER, SCHED_BATCH i SCHED_IDLE). Nie grupuje procesów szeregowanych według algorytmów czasu rzeczywistego oraz z budżetem czasu. Takie procesy są szeregowane zgodnie z zasadami opisanymi wcześniej.

  Wartość nice oraz szeregowanie grup
Przy szeregowaniu procesów innych niż czasu rzeczywistego (tj. szeregowanych według algorytmów SCHED_OTHER, SCHED_BATCH i SCHED_IDLE), dyspozytor CFS używa techniki znanej jako „szeregowanie grup”, jeśli tylko jądro skonfigurowano z opcją CONFIG_FAIR_GROUP_SCHED (jest to opcja typowo włączona).

W szeregowaniu grup, wątki są szeregowane w „grupach zadań”. Grupy zadań mają relację hierarchiczną, zakorzenioną w pierwotnej grupie zadaniowej systemu, zwanej „główną grupą zadań”. Grupy zadań są tworzone w następujących okolicznościach:

•   Wszystkie wątki grupy kontrolnej (cgroup) procesora tworzą grupę zadań. Rodzicem tej grupy zadań jest grupa zadań odpowiadającej rodzicielskiej grupy kontrolnej.

•   Jeśli autogrupowanie jest włączone, wszystkie wątki, które są (w sposób dorozumiany) umieszczane w autogrupie (tj. z tej samej sesji, tworzonej przez setsid(2)) tworzą grupę zadań. Każda nowa autogrupa jest zatem oddzielną grupą zadań. Główna grupa zadań jest rodzicem wszystkich takich autogrup.

•   Jeśli autogrupowanie jest włączone, główna grupa zadań składa się ze wszystkich procesów w głównej grupie kontrolnej procesora, które nie zostały w sposób dorozumiany umieszczone w nowej autogrupie.

•   Jeśli autogrupowanie jest wyłączone, główna grupa zadań składa się ze wszystkich procesów w głównej grupie kontrolnej procesora.

•   Jeśli wyłączono szeregowanie grup (tj. jądro skonfigurowano bez opcji CONFIG_FAIR_GROUP_SCHED), wszystkie procesy w systemie są jak gdyby umieszczane w jednej grupie zadań.

Przy korzystaniu z szeregowaniu grup, wartość nice wątku ma znaczenie przy decyzjach szeregowania tylko względem innych wątków w tej samej grupie zadań. Ma to pewne zaskakujące konsekwencje w kontekście tradycyjnego zachowania wartości nice w systemach uniksowych. W szczególności, jeśli autogrupowanie jest włączone (a tak domyślnie jest w wielu dystrybucjach), to użycie setpriority(2) lub nice(1) wobec procesu odnosi efekt na szeregowaniu tylko względem innych procesów wykonywanych w tej samej sesji (zwykle: tym samym oknie terminala).

Co za tym idzie, w przypadku dwóch procesów (przykładowo), które są jedynymi procesami ograniczonymi wydajnością procesora, występujących w dwóch sesjach (np. różnych oknach terminala, zatem każde z zadań jest przypisanych do innej autogrupy), modyfikacja wartość nice procesu w jednej z sesji nie odnosi skutku wobec decyzji dyspozytora odnoszącej się do procesu w drugiej sesji. Przydatnym obejściem tego nieintuicyjnego zachowania może być polecenie, takie jak poniższe, modyfikujące wartość nice autogrupy wszystkich procesów w sesji terminala:

    $ echo 10 > /proc/self/autogroup

  Funkcje czasu rzeczywistego w jądrze Linux z głównej serii
Od Linuksa 2.6.18, Linux jest stopniowo wzbogacany o zdolności czasu rzeczywistego; większość pochodzi z uprzedniego zestawu łatek realtime-preempt. Łatki są nazywane następująco:

    patch-wersjajądra-rtwersjałatki

i można je pobrać ze strony http://www.kernel.org/pub/linux/kernel/projects/rt/ lub klonując drzewo git https://git.kernel.org/pub/scm/linux/kernel/git/rt/linux-stable-rt.git. Poszczególne wydania są utrzymywane tak długo, jak dane jądro LTS.

Od Linuksa 6.12 można włączyć wywłaszczanie czasu rzeczywistego bez konieczności instalowania łatek. realtime-patch wciąż będzie istniał, zapewniając obsługę jeszcze niezintegrowanych architektur, sterowników oraz funkcji w trakcie rozwoju.

Od Linux 6.13 możliwości czasu rzeczywistego są zapewniane jako opcja, a nie model wywłaszczania. Po tej zmianie, dostępne są następujące modele wywłaszczania: CONFIG_PREEMPT_NONE, CONFIG_PREEMPT_VOLUNTARY, CONFIG_PREEMPT oraz CONFIG_PREEMPT_LAZY. Możliwości czasu rzeczywistego można włączyć opcją CONFIG_PREEMPT_RT, a model wywłaszczania można ustawić na CONFIG_PREEMPT lub CONFIG_PREEMPT_LAZY. Ten drugi rzadziej wywłaszcza zadania SCHED_NORMAL, próbując ograniczyć wywłaszczanie posiadaczy blokad. Nie ma to wpływu na zadania czasu rzeczywistego.

Po włączeniu CONFIG_PREEMPT_RT, Linux staje się standardowym systemem operacyjnym czasu rzeczywistego. Algorytmy szeregowania SCHED_FIFO, SCHED_RR i SCHED_DEADLINE są wówczas stosowane do uruchamiania wątku z prawdziwym priorytetem czasu rzeczywistego oraz zapewnionym minimalnym opóźnieniem szeregowania najgorszego przypadku.

UWAGI
Do ograniczania użycia procesora przez grupy procesów można stosować kontroler procesora cgroups(7).

Pierwotnie, standardowy Linux był pomyślany jako uniwersalny system operacyjny, zdolny do obsługi procesów tła, aplikacji interaktywnych oraz mniej wymagających aplikacji czasu rzeczywistego (aplikacji, które zwykle muszą mieścić się w ograniczeniach czasowych). Choć Linux 2.6 umożliwił wywłaszczanie przez jądro, a nowo wprowadzony dyspozytor O(1) zapewnia, że czas potrzebny na uszeregowanie jest stały i deterministyczny, niezależnie od liczby aktywnych zadań, prawdziwe zachowanie czasu rzeczywistego nie było dostępne do Linuksa 2.6.17.

ZOBACZ TAKŻE
chcpu(1), chrt(1), lscpu(1), ps(1), taskset(1), top(1), getpriority(2), mlock(2), mlockall(2), munlock(2), munlockall(2), nice(2), sched_get_priority_max(2), sched_get_priority_min(2), sched_getaffinity(2), sched_getparam(2), sched_getscheduler(2), sched_rr_get_interval(2), sched_setaffinity(2), sched_setparam(2), sched_setscheduler(2), sched_yield(2), setpriority(2), pthread_getaffinity_np(3), pthread_getschedparam(3), pthread_setaffinity_np(3), sched_getcpu(3), capabilities(7), cpuset(7)

Programming for the real world - POSIX.4 autorstwa Billa O. Gallmeistera, O'Reilly & Associates, Inc., ISBN 1-56592-074-0.

Dokumentacja dyspozytora w jądrze Linux (https://docs.kernel.org/scheduler).

Warte przeglądnięcia na stronie: https://wiki.linuxfoundation.org/realtime/start

TŁUMACZENIE
Tłumaczenie niniejszej strony podręcznika: Michał Kułach <michal.kulach@gmail.com>

Niniejsze tłumaczenie jest wolną dokumentacją. Bliższe informacje o warunkach licencji można uzyskać zapoznając się z GNU General Public License w wersji 3 (https://www.gnu.org/licenses/gpl-3.0.html) lub nowszej. Nie przyjmuje się ŻADNEJ ODPOWIEDZIALNOŚCI.

Błędy w tłumaczeniu strony podręcznika prosimy zgłaszać na adres listy dyskusyjnej manpages-pl-list@lists.sourceforge.net.