3 e 3 und Explizite Synchronisation Daniel Prokesch Institut für Technische Informatik Technische Universität Wien 27. April 2015 1
2 3 e Bisher betrachtet... Implizite Synchronisation Blockierende Lese- und Schreiboperationen Nicht-verwandte Prozesse mittels Sockets Verwandte Prozesse mittels Unnamed Pipes Heute... Datenaustasch über gemeinsamen Speicher Explizite Synchronisation von mehreren Prozessen
3 e Gemeinsamer Speicher: mehrere (verwandte sowie nicht-verwandte) Prozesse können auf selbe Region physikalischen Speichers zugreifen Gemeinsamer Speicher liegt im Adressraum der Prozesse, die den Speicher nutzen wollen Wird mit normalen Speicherzugriffsoperationen ausgelesen und verändert Schnelle Interprozesskommunikation: keine Intervention des Betriebssystemkernels 1 Explizite Synchronisation notwendig (gleichzeitiger Zugriff) 1 zero-copy, siehe http://www.linuxjournal.com/article/6345 3
4 3 e Erinnerung: mmap(2) Mapped eine Datei (file descriptor) in den Adressraum des Programms Mehrere Prozesse können auf den zugrunde liegenden Speicher zugreifen Gemeinsamer Speicher basierend auf gemeinsamer Ressource (einer Datei) shared file mapping
3 e Mapping erzeugen Erzeugen eines : mmap(2) #include <sys/mman.h> void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset); addr Vorschlag für Startadresse, sollte NULL sein length Größe des in Bytes, oft Dateigröße (siehe fstat(2)) prot Bitmaske für Speicherschutz: PROT_NONE (kein Zugriff erlaubt), PROT_READ, PROT_WRITE flags Bitmaske, z.b. MAP_SHARED, MAP_ANONYMOUS fd Der zu mappende File Descriptor offset Offset im File (Vielfaches der Page-Größe), 0 Rückgabewert: Startadresse des (an Seitengrenze ausgerichtet), MAP_FAILED im Fehlerfall ( errno) 5
3 im virtuellen Adressraum e in unterschiedlichen Prozessen an verschiedenen Adressen erzeugt Vorsicht beim Speichern von Zeigern! (z.b. Verkettete Listen,... ) 6
7 3 Mapping Hinweise e Granularität sind Speicherseiten des Adressraums Nach Erzeugen des kann der File Descriptor geschlossen werden sind in Linux unter /proc/pid/maps gelistet Nachteil von File für gemeinsamen Speicher: Persistent Kosten für Disk I/O Für verwandte Prozesse: Gemeinsame, anonyme (MAP_SHARED MAP_ANONYMOUS) Keine zugrunde liegende Datei Mapping vor fork() erzeugt: für Kindprozess(e) im selben Adressbereich verfügbar
3 Mapping entfernen e Entfernen eines : munmap() #include <sys/mman.h> int munmap(void *addr, size_t length); Entfernt ganze Speicherseiten aus angegebenen Bereich, Startadresse muss page-aligned sein Rückgabewert: 0 bei Erfolg, sonst -1 ( errno) 8
9 3 Mapping e... char *addr = mmap(null, length, PROT_READ PROT_WRITE, MAP_SHARED, fd, 0); if (addr == MAP_FAILED) error_exit("mmap"); if (close(fd) == -1) error_exit("close"); /* Code for working with mapped region */... if (munmap(addr, length) == -1) error_exit("munmap");
3 e Ermöglicht gemeinsamen Speicher zwischen nicht-verwandten Prozessen, ohne Datei auf Festplatte zu erzeugen memory objects über Namen identifizierbar Auf dediziertem Filesystem für flüchtigen Speicher erzeugt: tmpfs Wird wie gewöhnliches File ge-mmap-ed Vorteil: Es verhält sich wie ein richtiges Filesystem (z.b. swapping, Zugriffsrechte) Lebensdauer auf Systemlaufzeit beschränkt Teil der.1b realtime extensions Linken mit -lrt man 7 shm_overview 10
3 e API Öffnen/Erzeugen Erzeugen und Öffnen eines neuen Objekts oder Öffnen eines existierenden Objekts: shm_open(3) #include <sys/mman.h> #include <fcntl.h> /* For O_* constants */ int shm_open(const char *name, int oflag, mode_t mode); name Name der Form "/somename" oflag Bitmaske: O_RDONLY oder O_RDWR und eventuell... O_CREAT: legt Objekt an falls es nicht existiert zusätzlich O_EXCL: Fehler falls schon existiert mode Zugriffsrechte beim Erzeugen, sonst 0 Rückgabewert: File descriptor bei Erfolg, -1 im Fehlerfall ( errno) Linux: Objekt unter /dev/shm/somename erzeugt 11
12 3 e API Größe festlegen Der erzeugende Prozess legt üblicherweise die Größe (in Bytes) anhand des File Descriptors fest: ftruncate(2) #include <unistd.h> #include <sys/types.h> int ftruncate(int fd, off_t length); Rückgabewert: 0 bei Erfolg, -1 im Fehlerfall ( errno) Danach kann über den File Descriptor ein gemeinsames Mapping erzeugt (mmap(2)) und der File Descriptor geschlossen (close(2)) werden
3 e API Entfernen Einen Objektnamen entfernen: shm_unlink(3) int shm_unlink(const char *name); Name, der beim Erzeugen angegeben wurde Rückgabewert: 0 bei Erfolg, -1 im Fehlerfall ( errno) Darauffolgende shm_open() mit diesem Namen schlagen fehl (oder erzeugen neues Objekt) Der Speicher wird freigegeben, sobald der letzte Prozess das Mapping mit munmap() entfernt hat Übliche Befehle (ls, rm) zum Auflisten und Aufräumen in /dev/shm/ (z.b. bei Programmabsturz) 13
14 3 e #include <fcntl.h> #include <stdio.h> #include <sys/mman.h> #include <sys/types.h> #include <unistd.h> #define SHM_NAME "/myshm" #define MAX_DATA (50) #define PERMISSION (0600) struct myshm { unsigned int state; unsigned int data[max_data]; };
3 e int main(int argc, char **argv) { struct myshm *shared; /* create and/or open shared memory object */ int shmfd = shm_open(shm_name, O_RDWR O_CREAT, PERMISSION); if (shmfd == -1)... /* error */ /* extend (set size) */ if (ftrunctate(shmfd, sizeof *shared) == -1)... /* error */ /* map shared memory object */ shared = mmap(null, sizeof *shared, PROT_READ PROT_WRITE, MAP_SHARED, shmfd, 0); if (shared == MAP_FAILED)... /* error */ if (close(shmfd)) == -1)... /* error */ 15
3 e /* critical section entry... */ shared->data[0] = 23; printf("%d\n", shared->data[0]); /* critical section exit... */ /* unmap shared memory */ if (munmap(shared, sizeof *shared) == -1)... /* error */ /* remove shared memory object */ if (shm_unlink(shm_name) == -1)... /* error */ return 0; } /* end of main */ 16
17 3 e Synchronisation Sicherstellung von Einschränkungen nebenläufiger Prozesse In welcher Reihenfolge wird ein kritischer Abschnitt betreten: A vor B? B vor A? (Bedingungssynchronisation) Sicherstellen, dass nur genau ein Prozess auf verteilte Ressource zugreift (Wechselseitiger Ausschluss, mutual exclusion). Nicht notwendigerweise fair/abwechselnd.
3 e (1) e Thread A: a1: print "yes" Thread B: b1: print "no" Keine deterministische Abfolge von yes und no. Abhängig z.b. vom Scheduler. Mehrere Aufrufe produzieren möglicherweise Unterschiedliche Ausgaben. Weitere Ausgaben möglich? 18
19 3 e (2) e Thread A: a1: x = 5 a2: print x Thread B: b1: x = 7 Pfad zu Ausgabe 5 und am Ende x = 5? Pfad zu Ausgabe 7 und am Ende x = 7? Pfad zu Ausgabe 5 und am Ende x = 7? Pfad zu Ausgabe 7 und am Ende x = 5?
3 e (3) e Thread A: Thread B: a1: x = x + 1 b1: x = x + 1 Annahme: x ist mit 1 initialisiert. Mögliche Werte für x nach der Ausführung? Ist x++ atomic (atomar)? 20
21 3 e Grundidee Gemeinsame Variable zur Synchronisation 3 grundlegende Operationen: S = Init(N) Semaphor S mit Wert N erzeugen P(S), Wait(S), Down(S) S dekrementieren und blockieren wenn S negativ wird V(S), Post(S), Signal(S), Up(S) S inkrementieren und eventuell wartenden Prozess aufwecken
22 3 e - Serialisierung Thread A: statement a1 Thread B: statement b1 e Wie garantiert man a1 < b1 (a1 vor b1)?
3 e - Serialisierung e Initialisierung: S = Init(0) Thread A: statement a1 V(S) Thread B: P(S) statement b1 23
24 3 e - Mutex Thread A: x = x + 1 Thread B: x = x + 1 e Wie garantiert man, dass nur ein Thread in den kritischen Abschnitt eintritt?
3 e - Mutex e Initialisierung: mutex = Init(1) Thread A: P(mutex) x = x + 1 V(mutex) Thread B: P(mutex) x = x + 1 V(mutex) Kritischer Abschnitt erscheint atomar 25
3 e - Abwechselndes Arbeiten Thread A: for(;;) { x = x + 1 } Thread B: for(;;) { x = x + 1 } e Wie erreicht man, dass A und B abwechselnd arbeiten? 26
27 3 e e - Abwechselndes Arbeiten Initialisierung: S1 = Init(1) S2 = Init(0) Thread A: for(;;) { P(S1) x = x + 1 V(S2) } Thread B: for(;;) { P(S2) x = x + 1 V(S1) } 2 notwendig! Wie sieht die Synchronisation aus, wenn sich 3 Threads abwechseln sollen? N Threads?
3 e Synchronisation von Prozessen Nicht-verwandte Prozesse: Named s (Verwandte Prozesse oder Threads innerhalb eines Prozesses: Unnamed s) Ähnlich wie... Über Namen identifizierbar Auf dediziertem Filesystem für flüchtigen Speicher erzeugt: tmpfs Lebensdauer auf Systemlaufzeit beschränkt Teil der.1b realtime extensions Linken mit -pthread man 7 sem_overview Linux: Objekt unter /dev/shm/sem.somename erzeugt 28
3 e API Öffnen/Erzeugen Erzeugen und Öffnen eines neuen Semaphors oder Öffnen eines existierenden Semaphors: sem_open(3) #include <semaphore.h> #include <fcntl.h> /* For O_* constants */ /* create a new named semaphore */ sem_t *sem_open(const char *name, int oflag, mode_t mode, unsigned int value); /* open an existing named semaphore */ sem_t *sem_open(const char *name, int oflag); name Name der Form "/somename" oflag Bitmaske: O_CREAT, O_EXCL mode Zugriffsrechte (nur beim Erzeugen) value Initialwert (nur beim Erzeugen) Rückgabewert: Semaphoradresse bei Erfolg, im Fehlerfall SEM_FAILED ( errno) 29
3 e API Schließen/Entfernen Schließen eines Semaphors: sem_close(3) int sem_close(sem_t *sem); Entfernen eines Semaphors: sem_unlink(3) int sem_unlink(const char *name); Wird freigegeben, sobald ihn alle Prozesse geschlossen haben. Rückgabewert: 0 bei Erfolg, -1 im Fehlerfall ( errno) 30
3 e API Warten, P() Dekrementieren eines Semaphors: sem_wait(3) int sem_wait(sem_t *sem); Bei Semaphorwert > 0 kehrt die Funktion sofort zurück Sonst blockiert die Funktion bis der Wert positiv wird Rückgabewert: 0 bei Erfolg, -1 im Fehlerfall ( errno) und der Wert des Semaphors wird nicht verändert Wichtig Funktion sem_wait() kann durch ein Signal unterbrochen werden (errno == EINTR)! siehe Signalbehandlung 31
3 e API Signalisieren, V() Inkrementieren eines Semaphors: sem_post(3) int sem_post(sem_t *sem); Wenn Semaphorwert positiv wird, wird ein blockierter Prozess fortgesetzt Falls mehrere Prozesse warten: Reihenfolge ist unbestimmt (= weak semaphore) Rückgabewert: 0 bei Erfolg, -1 im Fehlerfall ( errno) und der Wert des Semaphors wird nicht verändert 32
3 e Prozess A (Code ohne Error-Handling) #include <stdio.h> #include <unistd.h> #include <semaphore.h> #include <fcntl.h> #define SEM_1 #define SEM_2 "/sem_1" "/sem_2" int main(int argc, char **argv) { sem_t *s1 = sem_open(sem_1, O_CREAT O_EXCL, 0600, 1); sem_t *s2 = sem_open(sem_2, O_CREAT O_EXCL, 0600, 0); for(int i = 0; i < 3; ++i) { sem_wait(s1); printf("critical: %s: i = %d\n", argv[0], i); sleep(1); sem_post(s2); } sem_close(s1); sem_close(s2); } return 0; 33
3 e Prozess B (Code ohne Error-Handling) #include <stdio.h> #include <unistd.h> #include <semaphore.h> #include <fcntl.h> #define SEM_1 #define SEM_2 "/sem_1" "/sem_2" int main(int argc, char **argv) { sem_t *s1 = sem_open(sem_1, 0); sem_t *s2 = sem_open(sem_2, 0); } for(int i = 0; i < 3; ++i) { sem_wait(s2); printf("critical: %s: i = %d\n", argv[0], i); sleep(1); sem_post(s1); } sem_close(s1); sem_close(s2); sem_unlink(sem_1); sem_unlink(sem_2); return 0; 34
3 e Unterbrechung durch Signale volatile sig_atomic_t want_quit = 0; int main(void) { sem_t *sem; // signal handler setup (no transparent restart), // semaphore setup... while (!want_quit) { if (sem_wait(sem) == -1) { // interrupted by system call? if (errno == EINTR) continue; error_exit(); }... if (sem_post(sem) == -1) error_exit(); } }... 35
3 e Wer legt Ressourcen an? Aufrufreihenfolge der Prozesse: fixe Reihenfolge (z.b.: Client-Server Systeme) beliebig Wer löscht Ressourcen? Fehlerfreier Programmverlauf Fehlerfall Unsynchronisiertes Aufräumen: Fehlerhafter Prozess löscht Ressourcen Synchronisiertes Aufräumen: Eigener Kommunikationskanal nötig (aufwändig) 36
3 e Ressourcenallokation... bei beliebiger Aufrufreihenfolge Anlegen, falls noch nicht existiert O_CREAT Flag ohne O_EXCL Flag z.b. : shmfd = shm_open(shm_name, O_CREAT O_RDWR, PERM); if (shmfd == -1)... /* error */ 37
3 e Ressourcenfreigabe... bei fixer Zugriffsabfolge Beim Löschen soll die Synchronisation zwischen den Prozessen sicherstellen, dass kein anderer Prozess als der Löschende mehr auf gemeinsame Ressourcen zugreift! Freigabe der selbst angelegten Ressourcen Freigabe insb. Kernel-persistenter Ressourcen bei normaler Prozessbeendigung (auch im Fehlerfall) Hilfreich: atexit(3) Registriert eine Funktion, die bei normaler Prozessbeendigung aufgerufen wird Mehrere Funktionen: in umgekehrter Reihenfolge der Registrierung Nicht bei _exit() 38
3 e Ressourcenfreigabe static int shmfd = -1; void allocate_resources(void) { shmfd = shm_open(shm_name, O_CREAT...);... } void free_resources(void) {... if (shmfd!= -1) { if (shm_unlink(shm_name) == -1) /* print error message, DON T CALL EXIT */; } } void main(void) { if (atexit(free_resources)!= 0) /* error */ } allocate_resources();... 39
3 e als schnelle Methode der IPC Explizite Synchronisation mit n Strategien zur Ressourcenallokation und -freigabe 40
41 3 Material e Linux Implementierung von /tmpfs: http://www.technovelty.org/linux/shared-memory.html Richard W. Stevens, UNIX Network Programming, Vol. 2: Interprocess Communications