23 KiB
Pytanie 9: Procesy i wątki w systemie operacyjnym
Pytanie
"Procesy i wątki w systemie operacyjnym. Omówić budowę, szybkość działania i zakres zastosowania. Przedstawić problemy i możliwości komunikacji i synchronizacji."
Przedmiot: SOI (Systemy Operacyjne)
📚 Odpowiedź główna
Wprowadzenie
Proces i wątek to podstawowe jednostki wykonania w systemach operacyjnych. Różnią się poziomem izolacji i kosztami przełączania.
1. Proces (Process)
Definicja
Proces = program w trakcie wykonania + jego kontekst (zasoby, stan).
Budowa procesu
┌─────────────────────────────────────────────────────────────────┐
│ PRZESTRZEŃ ADRESOWA PROCESU │
├─────────────────────────────────────────────────────────────────┤
│ ┌─────────────────┐ │
│ │ STOS │ ← zmienne lokalne, adresy powrotu │
│ │ (Stack) │ rośnie w dół ↓ │
│ ├─────────────────┤ │
│ │ ↓ │ │
│ │ │ ← wolna przestrzeń │
│ │ ↑ │ │
│ ├─────────────────┤ │
│ │ STERTA │ ← pamięć dynamiczna (malloc/new) │
│ │ (Heap) │ rośnie w górę ↑ │
│ ├─────────────────┤ │
│ │ BSS │ ← zmienne globalne niezainicjowane │
│ ├─────────────────┤ │
│ │ DATA │ ← zmienne globalne zainicjowane │
│ ├─────────────────┤ │
│ │ TEXT │ ← kod programu (read-only) │
│ │ (Code) │ │
│ └─────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
PCB (Process Control Block)
Struktura w jądrze przechowująca informacje o procesie:
| Pole | Opis |
|---|---|
| PID | Identyfikator procesu |
| Stan | Running, Ready, Blocked, etc. |
| Rejestry CPU | PC, SP, flagi, rejestry ogólne |
| Informacje o pamięci | Tablice stron, limity |
| Informacje I/O | Otwarte pliki, urządzenia |
| Informacje rozliczeniowe | Czas CPU, limity |
| Priorytet | Szeregowanie |
| Wskaźniki | Rodzic, dzieci, kolejki |
Stany procesu
┌──────────────────┐
(utworzenie) │ │ (zakończenie)
↓ │ │ ↓
┌─────────┐ │ ┌──────────┐ │ ┌──────────┐
│ NEW │───┼──→│ READY │←──┼──│TERMINATED│
└─────────┘ │ └──────────┘ │ └──────────┘
│ ↑↓ │
│ (scheduler) │
│ ↓↑ │
│ ┌──────────┐ │
│ │ RUNNING │ │
│ └──────────┘ │
│ │ │
│ (I/O, wait) │
│ ↓ │
│ ┌──────────┐ │
└───│ BLOCKED │───┘
└──────────┘
2. Wątek (Thread)
Definicja
Wątek = lekka jednostka wykonania współdzieląca przestrzeń adresową procesu.
Budowa wątku
┌─────────────────────────────────────────────────────────────────┐
│ PROCES │
├─────────────────────────────────────────────────────────────────┤
│ │
│ WSPÓŁDZIELONE: PRYWATNE (per wątek): │
│ ┌─────────────────┐ ┌───────┐ ┌───────┐ ┌───────┐ │
│ │ Przestrzeń │ │ Stos │ │ Stos │ │ Stos │ │
│ │ adresowa │ │ W1 │ │ W2 │ │ W3 │ │
│ ├─────────────────┤ ├───────┤ ├───────┤ ├───────┤ │
│ │ Kod (TEXT) │ │Rejestry│ │Rejestry│ │Rejestry│ │
│ ├─────────────────┤ │ CPU │ │ CPU │ │ CPU │ │
│ │ Dane globalne │ ├───────┤ ├───────┤ ├───────┤ │
│ ├─────────────────┤ │ PC │ │ PC │ │ PC │ │
│ │ Sterta (Heap) │ ├───────┤ ├───────┤ ├───────┤ │
│ ├─────────────────┤ │ TID │ │ TID │ │ TID │ │
│ │ Otwarte pliki │ └───────┘ └───────┘ └───────┘ │
│ │ Sygnały │ Wątek 1 Wątek 2 Wątek 3 │
│ └─────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
TCB (Thread Control Block)
| Pole | Opis |
|---|---|
| TID | Identyfikator wątku |
| Stan | Running, Ready, Blocked |
| Rejestry | PC, SP, rejestry ogólne |
| Stos | Wskaźnik do prywatnego stosu |
| Priorytet | Szeregowanie |
| Wskaźnik do PCB | Proces macierzysty |
3. Porównanie: Proces vs Wątek
Tabela porównawcza
| Cecha | Proces | Wątek |
|---|---|---|
| Przestrzeń adresowa | Własna, izolowana | Współdzielona z procesem |
| Tworzenie | Wolne (~ms) | Szybkie (~μs) |
| Przełączanie kontekstu | Wolne (TLB flush) | Szybkie (tylko rejestry) |
| Komunikacja | IPC (pipe, socket, shm) | Bezpośrednia (współdzielona pamięć) |
| Izolacja | Pełna | Brak (awaria = awaria procesu) |
| Zasoby | Własne | Współdzielone |
| Wieloprocesorowość | Naturalna | Wymaga synchronizacji |
Koszty czasowe (typowe)
| Operacja | Czas |
|---|---|
| Tworzenie procesu | 1-10 ms |
| Tworzenie wątku | 10-100 μs |
| Przełączanie procesu | 1-10 μs |
| Przełączanie wątku | 0.1-1 μs |
| Komunikacja IPC | 1-100 μs |
| Współdzielona pamięć | 10-100 ns |
4. Typy wątków
Wątki użytkownika (User-level Threads)
┌─────────────────────────────────────────┐
│ PRZESTRZEŃ UŻYTKOWNIKA │
│ ┌─────────────────────────────────┐ │
│ │ Biblioteka wątków (pthread) │ │
│ │ ┌─────┐ ┌─────┐ ┌─────┐ │ │
│ │ │ W1 │ │ W2 │ │ W3 │ │ │
│ │ └─────┘ └─────┘ └─────┘ │ │
│ └─────────────────────────────────┘ │
├─────────────────────────────────────────┤
│ JĄDRO │
│ Widzi tylko JEDEN wątek (proces) │
└─────────────────────────────────────────┘
Zalety: Szybkie przełączanie, przenośność Wady: Blokujące wywołanie blokuje wszystkie wątki, brak prawdziwej równoległości
Wątki jądra (Kernel-level Threads)
┌─────────────────────────────────────────┐
│ PRZESTRZEŃ UŻYTKOWNIKA │
│ ┌─────┐ ┌─────┐ ┌─────┐ │
│ │ W1 │ │ W2 │ │ W3 │ │
│ └──┬──┘ └──┬──┘ └──┬──┘ │
├─────────┼───────┼───────┼───────────────┤
│ ↓ ↓ ↓ │
│ ┌─────────────────────────────┐ │
│ │ JĄDRO (scheduler) │ │
│ │ Zarządza wszystkimi wątkami│ │
│ └─────────────────────────────┘ │
└─────────────────────────────────────────┘
Zalety: Prawdziwa równoległość, blokada jednego nie blokuje innych Wady: Wolniejsze operacje (wywołanie systemowe)
Modele mapowania
| Model | Opis | Przykłady |
|---|---|---|
| 1:1 | 1 wątek user = 1 wątek kernel | Linux, Windows |
| N:1 | N wątków user = 1 wątek kernel | Green threads |
| M:N | M wątków user = N wątków kernel | Solaris, Go goroutines |
5. Komunikacja między procesami (IPC)
Mechanizmy IPC
┌─────────────────────────────────────────────────────────────────┐
│ MECHANIZMY IPC │
├─────────────────┬─────────────────┬─────────────────────────────┤
│ SYGNAŁY │ POTOKI │ PAMIĘĆ WSPÓŁDZIELONA │
│ (Signals) │ (Pipes) │ (Shared Memory) │
├─────────────────┼─────────────────┼─────────────────────────────┤
│ KOLEJKI │ GNIAZDA │ PLIKI MAPOWANE │
│ KOMUNIKATÓW │ (Sockets) │ (Memory-mapped) │
│ (Msg Queues) │ │ │
└─────────────────┴─────────────────┴─────────────────────────────┘
Szczegóły mechanizmów
Potoki (Pipes)
// Potok nienazwany (anonimowy)
int fd[2];
pipe(fd);
// fd[0] - odczyt, fd[1] - zapis
// Potok nazwany (FIFO)
mkfifo("/tmp/myfifo", 0666);
Cechy: Jednokierunkowe, FIFO, między powiązanymi procesami (anonimowe)
Pamięć współdzielona (Shared Memory)
// POSIX shared memory
int fd = shm_open("/my_shm", O_CREAT | O_RDWR, 0666);
ftruncate(fd, SIZE);
void* ptr = mmap(NULL, SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
// Użycie
strcpy(ptr, "Hello from process A");
// Proces B może odczytać przez ten sam shm_open
Cechy: Najszybszy IPC, wymaga synchronizacji!
Gniazda (Sockets)
// Unix domain socket (lokalne)
int sock = socket(AF_UNIX, SOCK_STREAM, 0);
// Network socket (sieciowe)
int sock = socket(AF_INET, SOCK_STREAM, 0);
Cechy: Uniwersalne, lokalne i sieciowe, dwukierunkowe
Kolejki komunikatów (Message Queues)
// POSIX message queue
mqd_t mq = mq_open("/my_queue", O_CREAT | O_RDWR, 0666, &attr);
mq_send(mq, message, strlen(message), priority);
mq_receive(mq, buffer, MAX_SIZE, &priority);
Cechy: Asynchroniczne, z priorytetami, zachowują granice wiadomości
6. Synchronizacja
Problemy współbieżności
1. Wyścig (Race Condition)
// Dwa wątki wykonują:
counter++; // NIE ATOMOWE!
// W asemblerze:
// LOAD counter → reg
// ADD 1 → reg
// STORE reg → counter
// Możliwy przebieg:
// Wątek A: LOAD (counter=5)
// Wątek B: LOAD (counter=5)
// Wątek A: ADD (reg=6)
// Wątek B: ADD (reg=6)
// Wątek A: STORE (counter=6)
// Wątek B: STORE (counter=6)
// Wynik: 6 zamiast 7!
2. Zakleszczenie (Deadlock)
Wątek A: lock(mutex1) → czeka na mutex2
Wątek B: lock(mutex2) → czeka na mutex1
→ DEADLOCK! (wzajemne oczekiwanie)
Warunki Coffmana (wszystkie muszą być spełnione):
- Mutual exclusion - zasób może mieć tylko jeden właściciel
- Hold and wait - trzymaj i czekaj na więcej
- No preemption - nie można odebrać zasobu
- Circular wait - cykliczne oczekiwanie
3. Głodzenie (Starvation)
Proces nigdy nie dostaje zasobu, bo inni mają wyższy priorytet.
4. Inwersja priorytetów (Priority Inversion)
Proces o niskim priorytecie blokuje proces o wysokim priorytecie (przez mutex).
Mechanizmy synchronizacji
Mutex (Mutual Exclusion)
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_lock(&mutex);
// Sekcja krytyczna
counter++;
pthread_mutex_unlock(&mutex);
Semafor (Semaphore)
sem_t sem;
sem_init(&sem, 0, 3); // Wartość początkowa 3
sem_wait(&sem); // P() - dekrementuj, blokuj jeśli 0
// Sekcja krytyczna
sem_post(&sem); // V() - inkrementuj
Typy:
- Binarny (0/1) - jak mutex
- Licznikowy - ogranicza liczbę wątków (np. pula połączeń)
Zmienna warunkowa (Condition Variable)
pthread_mutex_t mutex;
pthread_cond_t cond;
// Producent
pthread_mutex_lock(&mutex);
buffer[in] = item;
pthread_cond_signal(&cond); // Obudź czekającego
pthread_mutex_unlock(&mutex);
// Konsument
pthread_mutex_lock(&mutex);
while (buffer_empty) {
pthread_cond_wait(&cond, &mutex); // Zwalnia mutex i czeka
}
item = buffer[out];
pthread_mutex_unlock(&mutex);
Bariera (Barrier)
pthread_barrier_t barrier;
pthread_barrier_init(&barrier, NULL, NUM_THREADS);
// W każdym wątku:
// ... obliczenia ...
pthread_barrier_wait(&barrier); // Czekaj na wszystkich
// ... dalsze obliczenia ...
Read-Write Lock
pthread_rwlock_t rwlock;
// Czytelnik
pthread_rwlock_rdlock(&rwlock); // Wielu może czytać
// ... czytanie ...
pthread_rwlock_unlock(&rwlock);
// Pisarz
pthread_rwlock_wrlock(&rwlock); // Wyłączny dostęp
// ... pisanie ...
pthread_rwlock_unlock(&rwlock);
📊 Porównanie mechanizmów synchronizacji
| Mechanizm | Blokujący | Użycie | Koszt |
|---|---|---|---|
| Mutex | Tak | Sekcja krytyczna | Niski |
| Spinlock | Tak (aktywne) | Krótkie sekcje, SMP | Bardzo niski* |
| Semafor | Tak | Ograniczanie zasobów | Niski |
| Cond. var. | Tak | Oczekiwanie na warunek | Niski |
| Bariera | Tak | Synchronizacja fazowa | Średni |
| RW Lock | Tak | Wielu czytelników | Średni |
| Atomics | Nie | Proste operacje | Najniższy |
*jeśli sekcja krytyczna krótka
7. Zastosowania
Kiedy procesy?
- Izolacja - awaria jednego nie wpływa na inne
- Bezpieczeństwo - różne uprawnienia
- Różne języki/technologie - mikrousługi
- Niezawodność - restart bez wpływu na system
Przykłady: Serwery WWW (fork), przeglądarki (proces per tab), bazy danych
Kiedy wątki?
- Współdzielenie danych - bez kopiowania
- Responsywność - UI thread + worker threads
- Równoległość CPU - obliczenia na wielu rdzeniach
- I/O asynchroniczne - czekanie nie blokuje wszystkiego
Przykłady: Gry, serwery aplikacji, przetwarzanie obrazu/wideo
🧠 Mnemoniki
"PEST" dla różnic Proces-wątek:
- Pamięć - proces ma własną, wątek współdzieli
- Efektywność - wątek szybszy
- Synchronizacja - wątki wymagają więcej
- Tworzenie - proces wolniejsze
"SPIN WAIT SLEEP" dla oczekiwania:
- Spinlock - aktywne czekanie (pętla)
- Mutex - uśpienie, wybudzenie przez scheduler
"COFFMAN" dla warunków deadlocka:
- Circular wait - cykliczne oczekiwanie
- Only one - mutual exclusion
- Forever hold - hold and wait
- Forced release - no preemption (brak)
"PV" dla semafora:
- P = Proberen (testuj) = wait = down = dekrementuj
- V = Verhogen (zwiększ) = signal = up = inkrementuj
❓ Możliwe pytania dodatkowe (follow-up)
Q1: "Jak unikać deadlocka?"
Odpowiedź:
-
Zapobieganie - złam jeden z warunków Coffmana:
- Zamawianie zasobów (zawsze lock A przed B)
- Żądaj wszystkich zasobów naraz
- Timeout na blokady
-
Unikanie - algorytm bankiera (sprawdź czy bezpieczne)
-
Wykrywanie i naprawianie - graf oczekiwania, zabij proces
// Zapobieganie przez porządek
// Zawsze: mutex1 przed mutex2
pthread_mutex_lock(&mutex1);
pthread_mutex_lock(&mutex2);
// ...
pthread_mutex_unlock(&mutex2);
pthread_mutex_unlock(&mutex1);
Q2: "Czym różni się fork() od pthread_create()?"
Odpowiedź:
| fork() | pthread_create() |
|---|---|
| Tworzy nowy proces | Tworzy nowy wątek |
| Kopiuje przestrzeń adresową (COW) | Współdzieli przestrzeń |
| Nowy PID | Ten sam PID, nowy TID |
| Komunikacja przez IPC | Komunikacja przez pamięć |
| Wolne (~ms) | Szybkie (~μs) |
// fork()
pid_t pid = fork();
if (pid == 0) {
// Proces potomny
} else {
// Proces rodzic
}
// pthread_create()
pthread_t thread;
pthread_create(&thread, NULL, thread_function, arg);
pthread_join(thread, NULL);
Q3: "Co to jest Copy-on-Write (COW)?"
Odpowiedź:
COW = optymalizacja fork() - strony pamięci są współdzielone dopóki nie zostaną zmodyfikowane.
Przed fork():
Proces A: [strona 1] [strona 2] [strona 3]
Po fork() (bez COW):
Proces A: [strona 1] [strona 2] [strona 3] ← kopia
Proces B: [strona 1] [strona 2] [strona 3] ← kopia
(kopiowanie całej pamięci - WOLNE!)
Po fork() (z COW):
Proces A: [strona 1 (shared, RO)]
↑
Proces B: [strona 1 (shared, RO)]
(współdzielenie - SZYBKIE!)
Po modyfikacji przez A:
Proces A: [strona 1 (kopia, RW)] ← kopia dopiero teraz!
Proces B: [strona 1 (shared, RO)]
Q4: "Wyjaśnij problem producent-konsument"
Odpowiedź:
Problem: Producent wytwarza dane, konsument je pobiera. Bufor ograniczony.
#define BUFFER_SIZE 10
int buffer[BUFFER_SIZE];
int count = 0;
sem_t empty, full;
pthread_mutex_t mutex;
// Inicjalizacja
sem_init(&empty, 0, BUFFER_SIZE); // Wolne miejsca
sem_init(&full, 0, 0); // Zajęte miejsca
void* producer(void* arg) {
while (1) {
int item = produce();
sem_wait(&empty); // Czekaj na wolne miejsce
pthread_mutex_lock(&mutex);
buffer[in] = item;
in = (in + 1) % BUFFER_SIZE;
pthread_mutex_unlock(&mutex);
sem_post(&full); // Sygnalizuj dane
}
}
void* consumer(void* arg) {
while (1) {
sem_wait(&full); // Czekaj na dane
pthread_mutex_lock(&mutex);
int item = buffer[out];
out = (out + 1) % BUFFER_SIZE;
pthread_mutex_unlock(&mutex);
sem_post(&empty); // Sygnalizuj wolne miejsce
consume(item);
}
}
Q5: "Co to są coroutines/goroutines?"
Odpowiedź:
Coroutines = współprogramy - lekkie "wątki" zarządzane w przestrzeni użytkownika.
| Cecha | Wątki | Coroutines |
|---|---|---|
| Scheduling | Preemptive (OS) | Cooperative (yield) |
| Stos | ~1-8 MB | ~2-8 KB |
| Tworzenie | ~10-100 μs | ~100 ns |
| Liczba | Setki-tysiące | Miliony |
Goroutines (Go):
go func() {
fmt.Println("Hello from goroutine")
}()
Async/await (Python, C#, JavaScript):
async def fetch_data():
data = await http_client.get(url)
return data
Q6: "Jak działa scheduler wątków?"
Odpowiedź:
Algorytmy schedulingu:
| Algorytm | Opis | Użycie |
|---|---|---|
| FIFO | Pierwszy przyszedł, pierwszy obsłużony | Prosty, batch |
| Round Robin | Kwant czasu, rotacja | Interaktywne |
| Priority | Wyższy priorytet pierwszy | Real-time |
| CFS | Completely Fair Scheduler | Linux |
| Multi-level Feedback | Priorytety + promocja/degradacja | Windows |
CFS (Linux):
- Wirtualny czas wykonania (vruntime)
- Czerwono-czarne drzewo
- Sprawiedliwy podział CPU
🎯 Kluczowe punkty do zapamiętania
- Proces = izolacja, własna pamięć, wolne tworzenie
- Wątek = współdzielenie, szybkie, wymaga synchronizacji
- IPC: Pipes, shared memory, sockets, message queues
- Synchronizacja: Mutex, semafor, cond var, bariera
- Deadlock: 4 warunki Coffmana, zapobiegaj przez porządek
- Race condition: Atomowe operacje lub blokady
📖 Źródła do pogłębienia
- Silberschatz, Galvin - "Operating System Concepts"
- Tanenbaum - "Modern Operating Systems"
- Love, R. - "Linux Kernel Development"
- Butenhof - "Programming with POSIX Threads"