Nejjednodušší forma IPC je, že rodič může zjistit, jak skončil jeho potomek. Rodič může potomkovi sdělit informace také přes jeho argumenty a proměnné prostředí. Žádný z těchto mechanizmů ale rodiči neumožňuje s potomkem komunikovat za jeho běhu a už vůbec neumožňuje komunikaci dvou procesů, které nejsou ve vztahu rodič-potomek. Za určitý primitivní druh komunikace se dají považovat signály, ale "skutečná" komunikace znamená výměna informací čili dat. Např. klasické balení souboru dvojicí tar+gzip, tar cf - * | gzip - > soubor.tar.gz, využívá mechanizmu roury (pipes).
Druhy IPC se liší těmito kritérii:
Jednou z nejjednodušších metod IPC je sdílená paměť, která
umožňuje dvěma a
více procesům přistupovat ke stejné oblasti paměti.
Sdílená paměť je nejrychlejší formou IPC, protože procesy sdílí stejnou
paměť a pro přístup do ní není potřeba žádné systémové volání. Protože ale
systém neposkytuje žádnou formu synchronizace, musíme si ji zajistit sami.
Např. dva procesy by neměly zapisovat zároveň na stejné místo v paměti.
Tradičním nástrojem pro synchronizaci jsou semafory.
Pro použití sdílené paměti ji jeden proces musí alokovat. Všichni si ji pak "připojí". Po používání ji "odpojí" a ten proces, který ji alokoval, ji dealokuje. Sdílená paměť se alokuje po celočíselných násobcích velikosti stránky paměti.
getpagesize(2) |
#include <unistd.h> |
size_t getpagesize(void); |
|
shmget(2) |
#include <sys/ipc.h> #include <sys/shm.h>
|
int shmget(key_t key, int size, int shmflg); |
|
Př. Napište program, který vytvoří sdílenou paměť o velikosti stránky paměti s právy zápisu pro sebe a čtení pro ostatní.
Potomek po fork
zdědí připojené úseky, po
exec
a exit
jsou všechny připojené
úseky odpojeny, ale ne dealokovány!
shmat(2) |
#include <sys/types.h> #include <sys/shm.h>
|
void *shmat ( int shmid, const void *shmaddr, int shmflg ); |
|
Př. Uložte něco (např. text) do sdílené paměti.
shmdt(2) |
#include <sys/types.h> #include <sys/shm.h>
|
int shmdt ( const void *shmaddr); |
|
Př. Před ukončením programu sdílenou paměť odpojte.
shmctl(2) |
#include <sys/ipc.h> #include <sys/shm.h>
|
int shmctl(int shmid, int cmd, struct shmid_ds *buf); |
|
Př. Vytvořte potomka, který si zjistí informace o této paměti a z nich vypíše její velikost, pak přečte, co tam rodič zapsal a vypíše to.
Každý úsek sdílené paměti musí být explicitně zrušen, protože systém ji nezruší ani po ukončení procesů!
Př. Před ukončením programu sdílenou paměť zrušte.
ipcs(8) |
ipcs -m |
|
Př. Podívejte se, zda v systému nezůstaly úseky nepoužívané sdílené paměti.
ipcrm(8) |
ipcrm shm id... |
|
Př. Zrušte úseky nepoužívané sdílené paměti.
Mapovaná paměť umožňuje různým procesům komunikovat přes sdílený soubor. Může se zdát, že je to stejné jako sdílená paměť, ale jsou zde technické rozdíly. Mapovaná paměť se dá použít jak pro komunikaci, tak pro jednoduchý přístup do souboru. Linux rozdělí soubor na paměťové stránky a ty zkopíruje do paměti, takže proces k nim může přistupovat jako do paměti, číst i zapisovat.
mmap(2) |
#include <unistd.h> #include <sys/mman.h>
|
caddr_t mmap(void *start, size_t length, int prot , int flags, int fd,
off_t offset);
|
|
munmap(2) |
#include <unistd.h> #include <sys/mman.h>
|
int munmap(void *start, size_t length); |
|
Různé procesy mohou komunikovat použitím mapované paměti téhož souboru a
použití MAP_SHARED.
Stejně jako u sdílené paměti je potřeba explicitně zajistit synchronizaci,
např. semafory nebo zamykáním souboru.
Časté použití mapované paměti je také pro rychlé čtení a zápis do souboru
nebo ukládání datových struktur do souboru.
OTÁZKA: Když ukládaná datová struktura obsahuje ukazatele, po načtení této struktury budou tyto ukazatele neplatné. Proč? Jaké shody okolností by musely nastat, aby byly platné?
Další funkce týkající se sdílené nebo mapované paměti jsou:
Roura umožňuje jednosměrnou komunikaci. Data zapsaná do
"zapisovacího konce" jsou čtena z "čtecího konce". Roury jsou sériová
zařízení, data
jsou čtena ve stejném pořadí, v jakém byla zapsána. Používají se ke
komunikaci dvou vláken jednoho procesu nebo mezi rodičovským procesem
a potomky.
V shellu symbol | vytvoří rouru. Např. ls | less
vytvoří
dva potomky
shellu, ls a less. Shell také vytvoří rouru spojující standardní
výstup ls se standardním vstupem less.
Kapacita roury je omezená. Pokud zapisující proces zapisuje rychleji
než čtecí proces čte a roura už nemůže uchovat data, zapisovací proces
je blokován, dokud se neuvolní místo. Pokud se proces pokouší číst z
roury, ale není co číst, je blokován, dokud nebude co číst. Roura tedy
automaticky synchronizuje dva procesy.
pipe(2) |
#include <unistd.h> |
int pipe(int filedes[2]); |
|
Deskriptory souboru, které vytvoří pipe
, jsou platné jen
v procesu a
jeho potomcích. Při volání fork
jsou deskriptory kopírovány do
potomka, proto roura může spojovat pouze příbuzné procesy. Po volání
fork
mají jak rodič tak i potomek oba konce roury. Roura
jako komunikační zařízení má však jen dva konce, proto se musí v rodiči i v
potomkovi nepoužívané konce ihned uzavřít. Zvláště existence dvou
zapisovacích konců roury způsobuje podivné chování.
Při čtení z roury, která má zapisovací konec uzavřený, vrací
read
hodnotu 0. A při zápisu do roury, která má uzavřený čtecí
konec, obdrží proces signál SIGPIPE.
Př.
Napište program, který vytvoří rouru a potomka. Jeden proces pak do roury něco
zapíše, druhý to přečte a vypíše. K otevření konce roury ve formě streamu
(FILE *
) použijte funkci fdopen
, k uzavření zase
close
.
Často je potřeba vytvořit potomka tak, aby jeden konec roury byl jeho standardní vstup nebo výstup.
dup(2) dup2(2) |
#include <unistd.h> |
int dup(int oldfd); int dup2(int oldfd, int newfd);
|
|
Př.
Přesměrujte std. vstup potomka na čtecí konec roury a změňte program potomka
na sort
. V rodiči do roury zapište několik vět. Roura má buffer
určité velikosti, proto po zápisu vět zavolejte fflush
.
Deskriptor std. vstupu je STDIN_FILENO.
Běžné použítí rour je zasílání nebo příjímání dat od programu, který
běží jako potomek. K vytvoření tohoto stavu je potřeba volat
postupně funkce pipe
, fork
,
dup2
, exec
a fdopen
. Volání
všech těchto
funkcí nahrazují funkce popen
a pclose
.
popen(3) |
#include <stdio.h> |
FILE *popen(const char *command, const char *type); |
|
pclose(3) |
#include <stdio.h> |
int pclose(FILE *stream); |
|
Př.
Poslední úkol (sort) proveďte pomocí popen
a pclose
.
FIFO (First In, First Out) je roura, která je souborem. Jakýkoliv
proces může číst nebo zapisovat do FIFO, procesy nemusí být
příbuzné. FIFO musí být otevřená pro čtení i zápis dřív, než se z ní
může něco číst nebo do ní zapisovat. Otevření pro čtení proces
blokuje, dokud ji neotevře jiný proces pro zápis a opačně.
FIFO se někdy nazývá pojmenovaná roura.
mkfifo(1) |
mkfifo [OPTION] NAME... |
|
Př.
Vytvořte FIFO, např. /tmp/fifo. V jednom terminálu z ní čtěte pomocí
cat < /tmp/fifo
. V jiném do ní zapisujte pomocí cat >
/tmp/fifo
a zadávejte věty (ukončené ENTER). Sledujte, jak se věty
vypisují v prvním terminálu. Zadávání vět ukončete pomocí C-D. Smažte FIFO.
mkfifo(3) |
#include <sys/types.h> #include <sys/stat.h>
|
int mkfifo ( const char *pathname, mode_t mode ); |
|
Z FIFO může číst nebo do ní zapisovat více procesů. Data z každého zapisujícího procesu jsou zapisována atomicky.
Další funkce týkající se rour nebo FIFO jsou: