praca_magisterska/pytania/odpowiedzi/09-procesy-watki.md

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):

  1. Mutual exclusion - zasób może mieć tylko jeden właściciel
  2. Hold and wait - trzymaj i czekaj na więcej
  3. No preemption - nie można odebrać zasobu
  4. 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ź:

  1. Zapobieganie - złam jeden z warunków Coffmana:

    • Zamawianie zasobów (zawsze lock A przed B)
    • Żądaj wszystkich zasobów naraz
    • Timeout na blokady
  2. Unikanie - algorytm bankiera (sprawdź czy bezpieczne)

  3. 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

  1. Proces = izolacja, własna pamięć, wolne tworzenie
  2. Wątek = współdzielenie, szybkie, wymaga synchronizacji
  3. IPC: Pipes, shared memory, sockets, message queues
  4. Synchronizacja: Mutex, semafor, cond var, bariera
  5. Deadlock: 4 warunki Coffmana, zapobiegaj przez porządek
  6. Race condition: Atomowe operacje lub blokady

📖 Źródła do pogłębienia

  1. Silberschatz, Galvin - "Operating System Concepts"
  2. Tanenbaum - "Modern Operating Systems"
  3. Love, R. - "Linux Kernel Development"
  4. Butenhof - "Programming with POSIX Threads"