SOE Systemy Operacyjne Wykład 6 Wątki, sygnały i szeregowanie w systemach UNIX, Linux dr inż. Andrzej Wielgus Instytut Mikroelektroniki i Optoelektroniki WEiTI PW
Wątki w systemie Solaris Modele implementacji wątków model mieszany -do Solaris 8 włącznie model 1-1 -Solaris 8, 9 i 10
Wątki w systemie Solaris Wątki jądra podstawowe obiekty jądra niezależnie szeregowane i wykonywane równolegle na wielu procesorach realizują wewnętrzne funkcje jądra (np. usługi systemowe) wspierają wykonywanie procesów lekkich atrybuty wątku zawartość rejestrów z poziomu jądra informacje o priorytecie i algorytmie szeregowania wskaźniki do sąsiednich wątków w kolejce wskaźnik do stosu wskaźniki do związanych struktur lwp i proc
Wątki w systemie Solaris Procesy lekkie (lightweight process -LWP) wspierany przez jądro wątek z poziomu użytkownika interfejs pomiędzy wątkami użytkownika i wątkami jądra każdy proces lekki może obsługiwać jeden lub kilka wątków użytkownika w ramach jednego procesu każdy proces lekki jest związany z własnym wątkiem jądra atrybuty procesu lekkiego identyfikator zawartość rejestrów informacje o sposobie obsługi sygnałów informacje o zużyciu zasobów budziki czasu wirtualnego wskaźnik do struktury związanego wątku jądra wskaźnik do struktury procesu proc
Wątki w systemie Solaris Wątki użytkownika zarządzane przez bibliotekę wątków, bez udziału jądra tworzenie i usuwanie, szeregowanie, synchronizacja obsługiwane przez procesy lekkie wątek związany - związany na stałe z procesem lekkim wątek niezwiązany - współdzieli pulę procesów lekkich w procesie atrybuty wątku identyfikator zawartość rejestrów stos użytkownika, przydzielony przez bibliotekę priorytet maska blokowanych sygnałów dane prywatne wątku, zarządzane przez bibliotekę
Wątki w systemie Solaris Proces 1 Proces 2 Proces 3 L L L L L jądro CPU CPU CPU
Szeregowanie w systemie Solaris Koncepcja wielopoziomowego planowania kolejek Szeregowaniu podlegają wątki jądra a nie procesy Klasy szeregowania lokalne priorytety w klasie algorytm szeregowania w klasie Globalne szeregowanie priorytetowe globalne priorytety 0-169 wybór wątku o najwyższym priorytecie wywłaszczanie
Klasy szeregowania Klasa szeregowania Priorytety globalne Kategoria wątków sprzętowe 160-169 wątki przerwań RT 100-159 wątki czasu rzeczywistego SYS 60-99 wątki systemowe jądra IA 0-59 wątki interaktywne TS 0-59 wątki w klasie z podziałem czasu FX 0-59 FSS 0-59
Klasy szeregowania Klasa RT (wątki czasu rzeczywistego) algorytmy szeregowania: FCFS lub RR wywłaszczanie Klasa SYS (wątki systemowe) szeregowanie priorytetowe Klasa TS (wątki z podziałem czasu) szeregowanie priorytetowe z kwantem czasu kwant czasu odwrotnie proporcjonalny do priorytetu postarzanie - obniżanie priorytetu po wyczerpaniu kwantu czasu
Informacje o parametrach szeregowania ps -c[opcje] dispadmin -l dispadmin -c klasa -g [-r rozdzielczosc] priocntl -l priocntl -d [-i typ_id] [lista]
Modyfikacja parametrów szeregowania dispadmin -c klasa -s plik priocntl -s [-c klasa] [opcje] [-i typ_id ] [ lista] priocntl -e [-c klasa] [opcje] cmd [arg ]
Wątki w systemie Linux Model 1-1 Procesy i wątki traktowane identycznie przez jądro Funkcja clone() tworzy nowy proces tradycyjny lub wątek int clone(int (*fn)(void *), void *child_stack, int flags, void *arg); argumenty wywołania określają zakres współdzielenia zasobów
Szeregowanie w systemie Linux (jądro 2.4) Koncepcja wielopoziomowego planowania kolejek 2 kategorie procesów procesy zwykłe procesy czasu rzeczywistego Atrybuty planowania policy -algorytm szeregowania rt_priority -priorytet statyczny priority -początkowy priorytet dynamiczny counter -czas procesora przyznany procesowi, używany jako priorytet dynamiczny procesu
Szeregowanie w systemie Linux (jądro 2.4) Szeregowanie priorytetowe z wywłaszczaniem priorytet efektywny -waga procesu waga = 1000 + rt_priority -procesy RT waga = counter -procesy zwykłe Procesy czasu rzeczywistego rt_priority > 0 algorytmy szeregowania: FCFS lub RR
Szeregowanie w systemie Linux (jądro 2.4) Procesy zwykłe rt_priority = 0 planowanie priorytetowe z przyznawanym czasem procesora wartość początkowa counter = priority w każdym takcie zegara counter = counter -1 ponowne przeliczenie priorytetów, gdy counter = 0 dla wszystkich procesów gotowych counter = counter/2 + priority mechanizm postarzania procesów
Szeregowanie w systemie Linux (jądro 2.6.8) Atrybuty planowania procesu static_prio -priorytet statyczny prio -priorytet dynamiczny sleep_avg -miara czasu uśpienia timeslice -przydział czasu procesora Ustawianie priorytetu statycznego domyślna wartość początkowa: static_prio = 0 ustawiona przez użytkownika: static_prio = nice zakres wartości nice: <-20, 19> ustawianie wartości nice funkcja systemowa nice() polecenia nice i renice
Szeregowanie w systemie Linux (jądro 2.6.8) Ustawianie priorytetu dynamicznego bonus lub kara za korzystanie z procesora tylko procesy zwykłe (nie RT) CURRENT_BONUS = sleep_avg * MAX_BONUS / MAX_SLEEP_AVG bonus = CURRENT_BONUS MAX_BONUS / 2 prio = static_prio bonus Obliczanie przydziału czasu procesora na podstawie priorytetu statycznego timeslice = MIN_TIMESLICE + (MAX_TIMESLICE MIN_TIMESLICE) * (MAX_PRIO 1 static_prio) / MAX_USER_PRIO -1
Szeregowanie w systemie Linux (jądro 2.6.8) Kolejki procesów gotowych przydzielonych do poszczególnych procesorów oddzielna kolejka na każdego CPU wskaźniki na dwie tablice priorytetów prio_array_t *active procesy aktywne, które nie wyczerpały jeszcze swoich przydziałów czasu procesora prio_array_t *expired procesy, które wyczerpały już swoje przydziały czasu procesora
Szeregowanie w systemie Linux (jądro 2.6.8) Tablica priorytetów unsigned int nr_active liczba aktywnych procesów unsigned long bitmap[bitmap_size] bitmapa priorytetów struct list_head queue[max_prio] tablica list procesów dla poszczególnych wartości priorytetu
Szeregowanie w systemie Linux (jądro 2.6.8) Funkcja scheduler_tick() uruchamiana po każdym przerwaniu zegarowym sprawdza, czy proces wyczerpał swój przydział czasu procesora Funkcja schedule() główna funkcja szeregowania procesów uruchamiana, gdy: proces zwalnia CPU w wyniku wywołania funkcji systemowej proces wyczerpał przydział czasu procesora - stwierdzone przez scheduler_tick() Złożoność obliczeniowa O(1)
Standardy implementacji biblioteki wątków POSIX - standard POSIX IEEE Std 1003.1:1996 i IEEE Std 1003.1:2001 opisuje interfejs programowania wielowątkowego w systemach uniksowych biblioteka funkcji: libpthread funkcje pthread_xxx Solaris - standard UI (UNIX International) opracowany dla systemu Solaris z uwzględnieniem specyfiki jego implementacji modelu m-n biblioteka funkcji: libthread funkcje thr_xxx
Podstawowe operacje na wątkach (POSIX) Tworzenie wątku Pobieranie i modyfikacja atrybutów Kończenie wątku Oczekiwanie na zakończenie wątku Odłączanie wątku
Tworzenie wątku int pthread_create(pthread_t *thread, pthread_attr_t *attr, void *(*start_func)(void *), void *arg); tworzy nowy wątek z domyślnym zestawem atrybutów zwraca identyfikator przez thread nowy wątek działa współbieżnie z wątkiem bieżącym (wołającym funkcję) nowy wątek wykonuje podaną funkcję start_func() z argumentem arg
Atrybuty wątku Atrybut Wartości Znaczenie scope detachstate PTHREAD_SCOPE_PROCESS PTHREAD_SCOPE_SYSTEM PTHREAD_CREATE_JOINABLE (domyślnie) wątek szeregowany na poziomie procesu, nie związany na stałe z procesem lekkim LWP (unbound) wątek szeregowany na poziomie jądra, związany na stałe z procesem lekkim LWP (bound) wątek dołączalny, inne wątki mogą oczekiwać na jego zakończenie i odebrać status (synchronizować się) PTHREAD_CREATE_DETACHED wątek odłączony, inne wątki nie mogą oczekiwać na jego zakończenie (synchronizować się)
Atrybuty wątku c.d. Atrybut Wartości Znaczenie stackaddr NULL (domyślnie) adres początkowy stosu (domyślnie: stos alokowany przez system) stacksize NULL (domyślnie) rozmiar stosu (domyślnie: 1 MB) schedpolicy SCHED_OTHER (domyślnie) planowanie priorytetowe SCHED_FIFO SCHED_RR planowanie FCFS planowanie rotacyjne RR schedparam inheritsched parametry szeregowania wątku PTHREAD_EXPLICIT_SCHED parametry szeregowania ustawiane na podstawie argumentów wywołania funkcji pthread_create() PTHREAD_INHERIT_SCHED parametry szeregowania dziedziczone po procesie macierzystym
Kończenie wątku Powrót z wykonywanej funkcji start_func() Zakończenie procesu poprzez _exit() Wywołanie funkcji pthread_exit() void pthread_exit(void *retval); wątki dołączalne - zasoby pamięci zwalniane po odebraniu statusu przez inny wątek wątki odłączone - zasoby pamięci są zwalniane natychmiast
Oczekiwanie na zakończenie wątku int pthread_join(pthread_t thread, void **thread_return); wstrzymuje wołający wątek do momentu zakończenia wątku wskazanego przez thread zwraca status zakończenia int pthread_detach(pthread_t thread); odłącza wskazany wątek
Przykład 1 1 wątek roboczy /* Silberschatz, Galvin and Gagne: Operating System Concepts, 2003 */ /* modyfikacje: A. Wielgus */ #include <pthread.h> int sum; /* zmienna współdzielona przez wątki */ void *runner(void *param); /* funkcja wątku */ main(int argc, char *argv[]) { pthread_t tid; /* identyfikator wątku */ pthread_attr_t attr; /* zbiór atrybutów */
Przykład 1 1 wątek roboczy /* pobranie domyślnych atrybutów */ pthread_attr_init(&attr); if (argc!= 2){ printf("niewłaściwa liczba argumentów\n"); exit(1); } /* tworzenie wątku */ if (pthread_create(&tid,&attr,runner,argv[1])) perror("pthread_create"); /* oczekiwanie na zakończenie wątku */ if (pthread_join(tid,null)) perror("pthread_join"); printf("sum = %d\n",sum); }
Przykład 1 1 wątek roboczy /* funkcja wątku */ void* runner(void*param) { int upper = atoi(param); int i; sum = 0; if (upper > 0) { for (i = 1; i <= upper; i++) sum += i; } pthread_exit(0); }
Przykład 2 wiele wątków roboczych #include <pthread.h> #define N 10 void *runner(void *param); /* funkcja watku roboczego */ main(int argc, char *argv[]) { pthread_t tid[n]; /* tablica identyfikatorow watkow */ pthread_attr_t attr; /* zbior atrybutow watkow */ int i, *stat[n]; if (argc > N+1) { printf("nieprawidlowa liczba argumentow\n"); exit(1); }
Przykład 2 wiele wątków roboczych /* get the default attributes */ pthread_attr_init(&attr); /* tworzenie watkow roboczych - jeden watek na kazdy argument wywolania */ for (i=1; i<argc; i++) { if (pthread_create(&tid[i-1],&attr,runner,argv[i])) perror("watek"); } /* oczekiwanie na zakonczenie watkow roboczych */ for (i=1; i<argc; i++) { pthread_join(tid[i-1],(void**)&(stat[i-1])); printf("pid=%d tid=%d ",getpid(), pthread_self()); printf("sum(%d) = %d\n", atoi(argv[i]), *stat[i-1]); } }
Przykład 2 wiele wątków roboczych void *runner(void *param) { int upper = atoi(param); int i; int sum; } sum = 0; if (upper > 0) { for (i = 1; i <= upper; i++) sum += i; } printf("pid=%d tid=%d sum = %d\n",getpid(), pthread_self(), sum); pthread_exit(&sum);
Proces a wątek Tradycyjny proces proces jednowątkowy Rozwidlanie procesu wielowątkowego: powielanie wszystkich wątków funkcja fork() (Solaris) powielanie tylko wątku wołającego funkcja fork() (POSIX) funkcja fork1()
Tworzenie programów wielowątkowych Wątki POSIX Plik nagłówkowy #include <pthread.h> Makrodefinicja #define _REENTRANT -D _REENTRANT Wywołanie kompilatora gcc [opcje... ] plik... lpthread...
Przesyłanie i obsługa sygnałów Sygnały Obsługa sygnałów w procesach Obsługa sygnałów w wątkach
Sygnały Sygnał asynchroniczna informacja dla procesu o zajściu zdarzenia Generacja sygnałów wyjątki (przerwania programowe) przerwania z terminala (ctrl c, ctrl \, ctrl z) inne procesy powiadomienia (zakończenie operacji we-wy) budziki
Przegląd wybranych sygnałów Nazwa sygnału Numer sygnału Znaczenie Domyślna reakcja procesu SIGHUP 1 Zerwanie połączenia z terminalem sterującym Zakończenie procesu SIGINT 2 Przerwanie Zakończenie procesu SIGQUIT 3 Zakończenie procesu Zakończenie procesu i zrzut obrazu pamięci do pliku core SIGILL 4 Nielegalna instrukcja w kodzie Zakończenie procesu SIGKILL 9 Zabicie procesu Bezwzględne zakończenie procesu SIGSEGV 11 Przekroczenie dopuszczalnego adresu pamięci Zakończenie procesu SIGALRM 14 Alarm programowy Ignorowanie SIGTERM 15 Przerwanie programowe Zakończenie procesu
Przegląd wybranych sygnałów Nazwa sygnału Numer sygnału Znaczenie Domyślna reakcja procesu SIGCONT Wznowienie wykonywania procesu Wznowienie wykonywania procesu SIGSTOP Bezwzględne zatrzymanie procesu Bezwzględne wstrzymanie wykonywania procesu SIGTSTP SIGCHLD zależny od implementacji Zatrzymanie procesu w wyniku wprowadzenie z terminala sekwencji Ctrl-z Informacja o zakończeniu (śmierci) procesu potomnego Wstrzymanie wykonywania procesu Ignorowanie SIGUSR1 Sygnał definiowany przez użytkownika Niezdefiniowana SIGUSR2 Sygnał definiowany przez użytkownika Niezdefiniowana
Obsługa sygnałów w procesach Sprawdzanie nadesłanych sygnałów maska nadesłanych sygnałów (w strukturze proc) tylko proces wykonywany może obsłużyć sygnały przed powrotem do trybu użytkownika z funkcji systemowej lub obsługi przerwania tuż przed uśpieniem lub tuż po obudzeniu procesu Sposoby obsługi domyślna ignorowanie własna funkcja obsługi wyjątki: SIGKILL i SIGSTOP
Obsługa sygnałów w procesach Blokowanie sygnałów maska blokowanych sygnałów (w strukturze proc) jądro przechowuje informacje o nadesłanych sygnałach wyjątki: SIGKILL i SIGSTOP
Obsługa sygnałów w wątkach Wspólne funkcje obsługi sygnałów (ustawione dla wszystkich wątków w procesie) Odrębne maski blokowanych sygnałów Sygnały spowodowane przez pułapki (SIGILL, SIGSEGV itp.) obsługiwane przez wątek, który spowodował pułapkę Sygnały spowodowane przez przerwania czyli zdarzenia zewnętrzne (SIGIO, SIGINT itp.) obsługiwane przez jeden dowolny wątek, którego maska na to pozwala następny sygnał, w trakcie obsługi poprzedniego, obsługiwany przez następny wątek
Wysyłanie sygnałów do procesu int kill(pid_t pid, int sig); Wysyła sygnał sig do procesu lub grupy procesów: pid > 0 pid = 0 pid = -1 pid < -1 -do procesu o identyfikatorze pid -do wszystkich procesów w grupie procesu wysyłającego -do wszystkich procesów w systemie z wyjątkiem procesu init (PID=1) -do wszystkich procesów w grupie o identyfikatorze pgid = -pid
Wysyłanie sygnałów do wątków int pthread_kill(pthread_t thread, int sig); Wysyła sygnał do wskazanego wątku w tym samym procesie
Ustawianie obsługi int sigaction(int sig, const struct sigaction *act, struct sigaction *oact); struct sigaction { } void (*sa_handler)(int); sigset_t sa_mask; int sa_flags; -sposób obsługi sygnału -maska sygnałów blokowanych -flagi Ustawia sposób obsługi sygnału sig przez sa_handler: SIG_DFL obsługa domyślna SIG_IGN ignorowanie wskaźnik do własnej funkcji obsługi (nazwa funkcji) Blokuje sygnał sig oraz inne sygnały sa_mask na czas obsługi
Ustawianie obsługi Ustawienie obsługi jednokrotne void (*signal(int sig, void (*handler)(int)))(int); Ustawienie obsługi na stałe (do odwołania) void (*sigset(int sig, void (*handler)(int)))(int);
Ustawianie maski blokowanych sygnałów int sigprocmask(int how, const sigset_t *set, sigset_t *oset); int pthread_sigmask(int how, const sigset_t *set, sigset_t *oset); Ustawia lub pobiera maskę blokowanych sygnałów w wątku w zależności od wartości argumentu how: SIG_BLOCK SIG_UNBLOCK SIG_SETMASK -dodaje zbiór sygnałów set do maski -usuwa zbiór sygnałów set z maski -ustawia zbiór sygnałów set jako maskę jeśli set==null, funkcja tylko zapisuje aktualną maskę w oset
Ustawianie maski blokowanych sygnałów Operacje na masce int sigemptyset(sigset_t *set); int sigfillset(sigset_t *set); int sigaddset(sigset_t *set, int signo); int sigdelset(sigset_t *set, int signo); int sigismember(sigset_t *set, int signo);
Uzyskanie informacji o niezałatwionych sygnałach int sigpending(sigset_t *set); Pobiera zbiór niezałatwionych sygnałów set, które zostały wysłane do procesu, ale ich dostarczenie było zablokowane Funkcja sigismember() pozwala zbadać obecność konkretnego sygnału w zbiorze set
Wstrzymywanie procesu w oczekiwaniu na sygnał int pause(void); Funkcja wstrzymuje proces w oczekiwaniu na sygnał int sigsuspend(const sigset_t *mask); Funkcja podmienia maskę blokowanych sygnałów na mask i wstrzymuje proces w oczekiwaniu na sygnał int sigwait(sigset_t *set); int sigwait(const sigset_t *set, int *sig); Funkcja pobiera jeden niezałatwiony sygnał ze zbioru set lub wstrzymuje wątek w oczekiwaniu na sygnał ze zbioru. Pobrany sygnał jest usuwany ze zbioru sygnałów niezałatwionych, a numer sygnału jest zwracany jako wartość funkcji lub zapisywany w sig. Działanie funkcji jest niezależne od maski blokowanych sygnałów, czyli wątek może czekać synchronicznie na sygnał.
Przykład sygnały w procesie jednowątkowym (1) #include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <signal.h> #include <errno.h> void Obsluga(int); int licznik; int main(void) { struct sigaction nowaakcja; sigset_t maska; sigfillset(&maska); nowaakcja.sa_handler = Obsluga; nowaakcja.sa_mask = maska; if (sigaction(sigint, &nowaakcja, NULL)) perror("blad sigaction"); sigdelset(&maska, SIGINT);
Przykład sygnały w procesie jednowątkowym (2) } while(1) sigsuspend(&maska); return(0); void Obsluga(int numersygnalu) { } licznik++; printf("odebralem %d sygnal SIGINT (%d).\n", licznik, numersygnalu);
Obsługa sygnałów Obsługa asynchroniczna ustawienie funkcji obsługi sygnału w procesie sigaction() wykonuje dowolny wątek (zwykle główny) dotyczy wszystkich wątków można ustawić blokowanie sygnałów poza jednym wybranym wątkiem Obsługa synchroniczna zablokowanie sygnałów we wszystkich wątkach w wybranym wątku: sigwait() obsługa sygnału bezpośrednio w kodzie
Funkcje obsługi sygnałów - zalecenia Stosować wywołania funkcji bezpiecznych przy obsłudze sygnałów (ang. Async-Signal-Safe) atrybut MT-level w man Stosować bezpieczne typy zmiennych globalnych (o dostępie atomowym) volatile sig_atomic_t typ integer oraz wskaźniki można uznać za bezpieczne
Odmierzanie czasu Budziki systemowe dla procesu ITIMER_REAL -budzik czasu rzeczywistego odmierza czas rzeczywisty sygnał SIGALRM ITIMER_VIRTUAL -budzik czasu wirtualnego odmierza czas wykonywania procesu w trybie użytkownika sygnał SIGVTALRM ITIMER_PROF -budzik profilowania odmierza czas wykonywania procesu w trybie użytkownika i w trybie jądra sygnał SIGPROF ITIMER_REALPROF -budzik profilowania czasu rzeczywistego
Odmierzanie czasu int getitimer(int which, struct itimerval *value); int setitimer(int which, const struct itimerval *value, struct itimerval *ovalue); Odczytuje lub ustawia jeden z budzików unsigned int alarm(unsigned int sec); int usleep(useconds_t useconds); W procesach jednowątkowych ustawia budzik czasu rzeczywistego procesu W procesach wielowątkowych -> nanosleep() int nanosleep(const struct timespec *rqtp, struct timespec *rmtp); Usypia bieżący wątek
Odmierzanie czasu struct itimerval { struct timeval it_interval; /* timer interval */ struct timeval it_value; /* current value */ }; struct timeval { time_t tv_sec; /* seconds */ suseconds_t tv_usec; /* microseconds */ }; struct timespec { time_t tv_sec; /* seconds */ long tv_nsec; /* nanoseconds */ } timespec_t;