Procesy


Semafory Systemowe

Autor: Paweł Piechocki



Semafory systemowe służą do synchronizacji procesów na poziomie jądra.


1.0 Definicja



semaphore.h:

struct semaphore {
};

1.1 Uwagi



atomic_t jest typem liczb całkowitych, na którym zdefiniowane są operacje arytmetyczne wykonywane atomowo.
Proces śpiący na semaforze może zostać obudzony (przejść przez semafor) tylko gdy sleepers == 1 i count == 0.

Znaczenia pola count:
  • >0 semafor podniesiony, jeszcze count procesów może pod nim przejść
  • 0 semafor opuszczony, ale żaden proces na nim nie czeka
  • -1 semafor opuszczony, czeka na nim jeden bądź więcej procesów


  • Znaczenia pola sleepers:
  • 0 żaden proces nie czeka na semaforze - jeżeli jakiś proces śpiący na semaforze zastanie sleepers == 0 musi poprawić tą wartość na 1 i zasnąć ponownie
  • 1 na semaforze czeka co najmniej jeden proces
  • 2 wartość tymczasowa - jest przyjmowana wyłącznie wewnątrz procedury down przez nowo przybyłe procesy mające właśnie zasnąć na semaforze


  • wartości pola sleepers służą do modyfikacji i poprawiania pola count


    2.0 Deklaracja



    Do definiowania semaforów służą następujące makra:

    #define __SEMAPHORE_INITIALIZER(name,count) \
    { ATOMIC_INIT(count), 0, __WAIT_QUEUE_HEAD_INITIALIZER((name).wait) ... }

    #define __DECLARE_SEMAPHORE_GENERIC(name,count) \
    struct semaphore name = __SEMAPHORE_INITIALIZER(name,count)

    #define DECLARE_MUTEX(name) __DECLARE_SEMAPHORE_GENERIC(name,1)
    #define DECLARE_MUTEX_LOCKED(name) __DECLARE_SEMAPHORE_GENERIC(name,0)

    2.1 Uwagi



    Semafor jest inicjowany na wartość podaną jako parametr count, a pole sleepers jest zerowane.
    Istnieje też procedura sema_init() robiąca dokladnie, ponieważ niektóre wersje gcc zgłaszają ostrzeżenia przy próbie użycia tych makr.



    3.0 Operacje na semaforach



    3.1 Wywołania


    semaphore.h-->semaphore.c
    up-->__up
    down-->__down
    down_interruptible-->_down_interruptible
    down_trylock-->__down_trylock


    Procedury zawarte w semaphore.c są wywoływane tylko kiedy trzeba. Sprawdzaniem aktualnej wartości pola count, oraz jej inkrementacją i dekrementacją (atomowo) zajmują się procedury z semaphore.h. Procedury down* zawsze zmniejszają count a jeden, a up zawsze zwiększa count o 1.

    3.2 up



    Standardowa operacja podniesienia semafora (zwiększenie wartości o pola count o jeden, oraz obudzenie dokładnie jednego procesu czekającego na semaforze (jeśli taki istnieje)

    static inline void up(struct semaphore * sem)



    Atomowo zwiększa count o 1 i wywołuje __up, jeśli count <= 0.

    oid __up(struct semaphore *sem)


    {
    wake_up(&sem->wait); # Obudzić jeden z procesów czekających na kolejce wait, cała dalsza obsługa zawarta jest w algorytmach down*
    }

    3.3 down



    Standardowa operacja opuszczenia semafora (zmniejszenie pola count o jeden, oraz zaśnięcie na semaforze, jeśli count < 0). Zawiera także dalszą obsługę semafora już po obudzeniu procesu po podniesieniu semafora. Procedura __down (tak jak __down_itrrruptible) zostawia przy wyjściu (dla obudzonych procesów) wartości count (== 0) i sleepers (== 0) poprawne dla przypadku, gdy w kolejce związanej z semaforem nie czeka juz żaden inny proces. Jeśli tak jednak nie jest, poprawieniem tych wartości zajmuje się jeden obudzony proces, który zastając sleepers == 0 nie przechodzi pod semaforem, tylko wykonuje obrót pętli, modyfikuje pola sleepers oraz count i zasypia ponownie. Obie funkcję są tak skonstruowane, że mogą poprawiać siebie, a także poprawiać błędne wartości pozostawiane przez __down_trylock.

    Dodatkowo wszystkie funkcje __down* chronione są przez spinlocki przed odebraniem im sterowania przez np. przerwanie, więc wartości tymczasowe zmiennych semaforowych są bezpieczne. Z poniższego kodu wycięto mniej istotne dla samego algorytmu operacje.

    static inline void down(struct semaphore * sem)



    Atomowo zmniejsza count o 1 i wywołuje __down jeśli count < 0.

    void __down(struct semaphore * sem)



    Uwagi do algorytmu



    Kluczowym punktem tego algorytmu jest operacja:
    atomic_add_negative(sleepers - 1, &sem->count)

    Poniższe uwagi mogą pomóc w jej zrozumieniu:
    - ponieważ sleepers przyjmuje wartości 0,1,2, operacja ta może modyfikować pole count o 1, -1 lub pozostawiać je bez zmian
    - nowo przybyły proces zastający pustą kolejkę (count == -1 - zmniejszone w procedurze down, sleepers == 0) zwiększył sleepers do 1, zatem nie modyfikuje wartości count
    - nowo przybyły proces zastający niepustą kolejkę (count == -2 - wartość niepoprawna, zmniejszona w procedurze down, sleepers == 1) zwiększył sleepers do 2, zatem dodaje on jeden do count, tym samym przywracając poprawną wartość -1
    - proces który został obudzony i ma przejść pod semaforem (count == 0, sleepers == 1) nie modyfikuje count (tj. dodaje 0)
    - proces "poprawiający" (który zastał count == 0, sleepers == 0) odejmuje jeden od count i zasypia, tym samym przywracając poprawną wartość -1
    - we wszytkich tych przypadkach polu sleepers nadawana jest odpowiednia wartość poprzez zwykłe przypisanie

    3.4 down_interruptible



    Prawie identyczna do down. Poniżej zaznaczono tylko różnice. Zwraca 0 gdy udało się przejść pod semaforem lub -EINTR gdy wyjście nastąpiło na skutek otrzymania sygnału.

    static inline int down_interruptible(struct semaphore * sem)



    Atomowo zmniejsza count, ustawia domyślnie resultat na 0 i wywołuje __down_interruptible jeśli count < 0.

    int __down_interruptible(struct semaphore * sem)



    Uwagi do algorytmu



    Jedyną różnicą od algorytmu down jest zachowanie w przypadku obudzenia procesu przez sygnał. W tej sytuacji mamy count == -1, sleepers == 1, bo wiadomo że przynajmniej nasz proces spał na semaforze. Ustawienie sleepers na 0 zapewni, że kolejny obudzony proces poprawi po nas wartość count. Jednakże najpierw sami skorygujemy wartość count na 0, co jest wartością poprawną, jeśli nie ma więcej oczekujących procesów.


    3.5 down_trylock



    Procedura ta powiedzie się tylko gdy semafor jest podniesiony (zwraca wtedy 0).

    static inline int down_trylock(struct semaphore * sem)



    Atomowo zmniejsza count o 1, ustawia zwracaną wartość na 0 i wywołuje __down_trylock jeśli count < 0.

    int __down_trylock(struct semaphore * sem)



    Uwagi do algorytmu



    __down_trylock wywoływane jest w przypadku niepowodzenia, musi skorygować wartość count zmniejszoną o jeden przez down_trylock. Możliwe są tu następujące 2 sytucje:
    - nikt nie czeka na semaforze, zatem count = -1 (zmniejszone przez down_trylock), sleepers == 0 - zwiększamy do 1, dodajemy do count i otrzymujemy poprawną wartość
    - na semaforze czeka inny proces, zatem sleepers = -2 (zła wartość ustawiona przez down_try_lock), sleepers == 1 - zwiększamy do 2, dodajemy do count (teraz count == 0) i budzimy śpiący proces, aby poprawił (zmniejszył o jeden) wartość count (nie ma znaczenia czy obudzony proces zasnął wewnątrz down, czy down_interruptible)