Linux - Synchronizace (Synchronization)

Programování vláken není jednoduché. Není způsob, jak předem zjistit, kdy systém spustí jedno vlákno a kdy druhé. Debugování vícevláknového programu je těžké, protože nelze vždy reprodukovat chybné chování.
Většina problémů je způsobena tím, že vlákna přistupují ke stejným datům. Na jednu stranu je to velká výhoda vláken, ale na druhou stranu to může být nebezpečné. Když je jedno vlákno v půli cesty při zapisování (čtení) dat a druhé je čte (zapisuje), pravděpodobně vznikne chyba. Často program funguje jen pokud vlákno běží dříve nebo později než jiné vlákno. Chyby, které vznikají v důsledku nesprávného pořadí vykonávání vláken, se nazývají race conditions (chyby souběhu), vlákna soutěží o to, které poběží dřív. K eliminování race conditions je potřeba zajistit, aby se vlákna vykonávala ve správném pořadí nebo aby se některé operace prováděly atomicky, tzn. je potřeba vlákna synchronizovat. Atomická operace je nerozdělitelná a nepřerušitelná, když začne, nebude zastavena nebo přerušena, dokud se neukončí.

Semafory pro vlákna (Semaphores for threads)

Semafor je "počítadlo" umožňující synchronizaci více vláken. Systém zaručuje, že zjištění nebo modifikování hodnoty semaforu je atomická operace. Hodnota semaforu je nezáporné číslo, umožňuje dvě operace:

Semafory pro vlákna jsou implementovány podle standardu POSIX, hlavičkový soubor je <semaphore.h>.

sem_init(3)
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
  • inicializuje semafor, na který ukazuje sem, na hodnotu value
  • pshared specifikuje, zda je semafor lokální pro aktuální proces (0), nebo zda se má sdílet s ostatními procesy (!0), na Linuxu tohle ale není implementováno, musí být vždy 0
  • vrací 0, při chybě -1

sem_destroy(3)
#include <semaphore.h>
int sem_destroy(sem_t * sem);
  • zruší semafor, na který ukazuje sem
  • žádné vlákno by na semafor nemělo čekat, jinak chyba
  • vrací 0, při chybě -1

sem_wait(3)
#include <semaphore.h>
int sem_wait(sem_t * sem);
  • operace wait
  • bod zrušení vlákna
  • vrací vždy 0

sem_trywait(3)
#include <semaphore.h>
int sem_trywait(sem_t * sem);
  • neblokující operace wait, pokud má semafor hodnotu 0, vrací se s chybou
  • vrací 0, při chybě -1

sem_post(3)
#include <semaphore.h>
int sem_post(sem_t * sem);
  • operace post
  • nikdy neblokuje a může se bezpečně používat v asynchronních signal-handlerech
  • vrací 0, při chybě -1

sem_getvalue(3)
#include <semaphore.h>
int sem_getvalue(sem_t * sem, int * sval);
  • uloží aktuální hodnotu semaforu na místo, kam ukazuje sval
  • neměla by se používat k rozhodnutí operace se semaforem, může to vést k race condition: jiné vlákno může změnit hodnotu semaforu mezi touto funkcí a operací
  • vrací vždy 0

Př. Napište program, ve kterém vytvoříte vlákno. Jedno vlákno bude něco zapisovat do společného bufferu (naplní ho), druhé z něho číst (vyprázdní ho). Synchronizujte tyto dvě vlákna semafory tak, aby se zapisovalo, jen když je buffer prázdný, a četlo, jen když je plný, a do bufferu v danou chvíli přistupovalo jen jedno vlákno.

Semafory pro procesy (Semaphores for processes)

Procesy musí koordinovat přístup např. do společné sdílené a mapované paměti.
Semafory jsou alokovány, používány a dealokovány stejně jako úseky sdílené paměti, jsou také součástí systémových volání původního unixového systému System V IPC (poslední jsou fronty zpráv). I když ve většině případů stačí jeden semafor, semafory pro procesy existují po množinách.

semget(2)
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget ( key_t key, int nsems, int semflg )
  • vytvoří množinu nsems semaforů ke klíči key, pokud v key je IPC_PRIVATE, nebo pokud ke klíči key již neexistuje množina semaforů a v semflg je IPC_CREAT
  • v semflg může být:
    • IPC_CREAT - vytvoří novou množinu semaforů, pokud toto není, najde se množina asociovaná ke klíči key
    • IPC_EXCL - používá se s IPC_CREAT, chyba pokud množina semaforů s klíčem key existuje
    • práva přístupu - definovaná v <sys/stat.h>, může být:
      • S_IRUSR, S_IWUSR - čtení, změna pro vlastníka množiny semaforů
      • S_IROTH, S_IWOTH - pro ostatní
  • nsems může být 0, pak se nic nevytvoří, nebo kladné číslo udávající počet semaforů v množině
  • vrací identifikátor množiny semaforů, jinak -1
  • existuje limit počtu semaforů v množině, viz man

Semafory existují i když skončí všechny procesy, které je používají. Proces, který semafory vytvořil, je také musí dealokovat, jinak systému dojdou. Kromě alokace je potřeba semafory také inicializovat.

semctl(2)
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semctl (int semid, int semnum, int cmd, union semun arg)
  • zjistí informace o množině semaforů (identifikované pomocí semid) nebo o jednom semaforu, mění její práva, dealokuje ji a inicializuje ji
  • strukturu semun si většinou musíme deklarovat sami, viz man
  • cmd může být:
    • IPC_STAT - vrací informace do semid_ds struktury arg.buf, viz man, potřebuje právo čtení
    • IPC_SET - nastavuje práva
    • IPC_RMID - ihned zruší množinu semaforů (a odblokuje všechny čekající procesy), může jen ten (uživatel), který ji vytvořil nebo vlastník (nebo root)
    • GETALL - vrátí hodnotu všech semaforů do arg.array
    • GETVAL - vrátí hodnotu semnum-tého semaforu (první je 0-tý)
    • SETALL - nastaví hodnoty všech semaforů podle arg.array, čekající procesy jsou odblokovány
    • SETVAL - nastaví hodnotu semnum-tého semaforu na arg.val, čekající procesy jsou odblokovány
  • při chybě vrací -1, jinak záleží na cmd, při GETVAL vrací hodnotu semaforu

Každý semafor má nezápornou hodnotu a podporuje operace wait a post. Tyto operace se provádějí pomocí funkce semop.

semop(2)
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semop ( int semid, struct sembuf *sops, unsigned nsops )
  • provede operaci na vybraných semaforech (počtu nsops) z množiny semid, operace se provedou jen pokud se všechny provedou úspěšně
  • prvky pole sops specifikují operaci pomocí struktury sembuf, která má prvky:
    • sem_num - číslo ovlivňovaného semaforu, pro první je 0
    • sem_flg - když je zde SEM_UNDO, při ukončení procesu je semafor vrácen na svou předchozí hodnotu, platí pro všechna volání této funkce během existence procesu, sem_undo struktury procesu se nedědí při fork, ale při exec ano
    • sem_op - operace, může být:
      • kladné číslo - přidá se k hodnotě semaforu, při 1 je to operace post
      • nula - blokuje proces, dokud hodnota semaforu není 0 nebo množina semaforů není zrušena nebo proces nedostane signál, který se obslouží
      • záporné číslo - pokud je hodnota semaforu větší nebo rovna absolutní hodnotě tohoto čísla, je tato absolutní hodnota odečtena od hodnoty semaforu, jinak je proces blokován, dokud hodnota semaforu nebude větší nebo rovna absolutní hodnotě nebo nebo množina semaforů není zrušena nebo proces nedostane signál, který se obslouží, při -1 je to operace wait
  • vrací 0, při chybě -1

Př. Synchronizujte rodiče a potomka v programu ze cvičení 'Linux - Meziprocesní komunikace', část 'Sdílená paměť'. Použijte jeden binární semafor tak, aby proces četl, až tam druhý něco zapíše.

ipcs(8)
ipcs -s
  • vypíše pole semaforů a informace o nich

Př. Podívejte se, zda v systému nezůstaly nepoužívané množiny semaforů.

ipcrm(8)
ipcrm sem id...
  • zruší množinu semaforů specifikovanou pomocí id

Př. Zrušte nepoužívané množiny semaforů.

PŘÍKLAD

Další funkce týkající se semaforů jsou:



Jan Outrata
outrata@phoenix.inf.upol.cz