Toto je první článek ze série o meziprocesové komunikaci (IPC) v Linuxu. Série používá příklady kódu v C k objasnění následujících mechanismů IPC:
- Sdílené soubory
- Sdílená paměť (se semafory)
- Trubky (pojmenované i nepojmenované)
- Fronty zpráv
- Zásuvky
- Signály
Tento článek shrnuje některé základní koncepty, než přejdeme k prvním dvěma z těchto mechanismů:sdílené soubory a sdílená paměť.
Základní koncepty
Proces je spuštěný program a každý proces má svůj vlastní adresní prostor, který obsahuje paměťová místa, ke kterým má proces povolen přístup. Proces má jedno nebo více vláknů provádění, což jsou sekvence spustitelných instrukcí:a jednovláknové proces má pouze jedno vlákno, zatímco vícevláknový proces má více než jedno vlákno. Vlákna v rámci procesu sdílejí různé zdroje, zejména adresní prostor. Vlákna v rámci procesu tedy mohou komunikovat přímočaře prostřednictvím sdílené paměti, ačkoli některé moderní jazyky (např. Go) podporují disciplinovanější přístup, jako je použití kanálů bezpečných pro vlákna. Zde je zajímavé, že různé procesy ve výchozím nastavení nedělají sdílet paměť.
Existují různé způsoby, jak spustit procesy, které pak komunikují, a v následujících příkladech dominují dva způsoby:
- Ke spuštění jednoho procesu se používá terminál a ke spuštění dalšího se možná používá jiný terminál.
- rozvětvení systémových funkcí je volán v rámci jednoho procesu (rodič), aby vytvořil další proces (podřízený).
První příklady používají terminálový přístup. Příklady kódu jsou k dispozici v souboru ZIP na mém webu.
Sdílené soubory
Programátoři jsou až příliš obeznámeni s přístupem k souborům, včetně mnoha úskalí (neexistující soubory, špatná oprávnění k souborům atd.), které sužují používání souborů v programech. Nicméně sdílené soubory mohou být nejzákladnějším mechanismem IPC. Zvažte relativně jednoduchý případ, kdy jeden proces (výrobce ) vytváří a zapisuje do souboru a další proces (spotřebitel ) čte ze stejného souboru:
writes +-----------+ reads
producer-------->| disk file |<-------consumer
+-----------+
Zjevnou výzvou při používání tohoto mechanismu IPC je závodní podmínka může nastat:výrobce a spotřebitel by mohli přistupovat k souboru přesně ve stejnou dobu, čímž by byl výsledek neurčitý. Aby se předešlo sporu, musí být soubor uzamčen způsobem, který zabrání konfliktu mezi zápisem operace a jakákoli další operace, ať už jde o čtení nebo zápis . Uzamykací API ve standardní systémové knihovně lze shrnout následovně:
- Producent by měl před zápisem do souboru získat exkluzivní zámek na soubor. Exkluzivní zámek může být držen maximálně jedním procesem, což vylučuje spor, protože žádný jiný proces nemá k souboru přístup, dokud není zámek uvolněn.
- Spotřebitel by měl před čtením ze souboru získat alespoň sdílený zámek. Více čtenářů může obsahovat sdílené zámek současně, ale žádný zapisovatel může přistupovat k souboru, když i jeden čtenář má sdílený zámek.
Sdílený zámek zvyšuje efektivitu. Pokud jeden proces pouze čte soubor a nemění jeho obsah, není důvod bránit ostatním procesům, aby dělaly totéž. Psaní však jasně vyžaduje výhradní přístup k souboru.
Standardní I/O knihovna obsahuje pomocnou funkci s názvem fcntl které lze použít ke kontrole a manipulaci s výhradními i sdílenými zámky na souboru. Funkce funguje prostřednictvím deskriptoru souboru , nezáporná celočíselná hodnota, která v rámci procesu identifikuje soubor. (Různé deskriptory souborů v různých procesech mohou identifikovat stejný fyzický soubor.) Pro zamykání souborů poskytuje Linux funkci knihovny flock , což je tenký obal kolem fcntl . První příklad používá fcntl funkce k odhalení podrobností API.
Příklad 1. producent program
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#define FileName "data.dat"
#define DataString "Now is the winter of our discontent\nMade glorious summer by this sun of York\n"
void report_and_exit(const char* msg) {
perror(msg);
exit(-1); /* EXIT_FAILURE */
}
int main() {
struct flock lock;
lock.l_type = F_WRLCK; /* read/write (exclusive versus shared) lock */
lock.l_whence = SEEK_SET; /* base for seek offsets */
lock.l_start = 0; /* 1st byte in file */
lock.l_len = 0; /* 0 here means 'until EOF' */
lock.l_pid = getpid(); /* process id */
int fd; /* file descriptor to identify a file within a process */
if ((fd = open(FileName, O_RDWR | O_CREAT, 0666)) < 0) /* -1 signals an error */
report_and_exit("open failed...");
if (fcntl(fd, F_SETLK, &lock) < 0) /** F_SETLK doesn't block, F_SETLKW does **/
report_and_exit("fcntl failed to get lock...");
else {
write(fd, DataString, strlen(DataString)); /* populate data file */
fprintf(stderr, "Process %d has written to data file...\n", lock.l_pid);
}
/* Now release the lock explicitly. */
lock.l_type = F_UNLCK;
if (fcntl(fd, F_SETLK, &lock) < 0)
report_and_exit("explicit unlocking failed...");
close(fd); /* close the file: would unlock if needed */
return 0; /* terminating the process would unlock as well */
}
Hlavní kroky v producentu výše uvedený program lze shrnout následovně:
- Program deklaruje proměnnou typu struct flock , který představuje zámek a inicializuje pět polí struktury. První inicializace:
lock.l_type = F_WRLCK; /* exclusive lock */
dělá ze zámku exkluzivní (čtení-zápis ) spíše než sdílené (pouze pro čtení). ) zámek. Pokud výrobce získá zámek, pak žádný jiný proces nebude schopen zapisovat nebo číst soubor, dokud producent uvolní zámek, buď explicitně pomocí příslušného volání fcntl nebo implicitně uzavřením souboru. (Když se proces ukončí, všechny otevřené soubory budou automaticky uzavřeny, čímž se uvolní zámek.)
- Program poté inicializuje zbývající pole. Hlavním efektem je, že celý soubor má být uzamčen. Uzamykací rozhraní API však umožňuje uzamknout pouze určené bajty. Pokud například soubor obsahuje více textových záznamů, pak lze jeden záznam (nebo dokonce část záznamu) uzamknout a zbytek ponechat odemčený.
- První volání do fcntl :
if (fcntl(fd, F_SETLK, &lock) < 0)
pokusí se zamknout výhradně soubor a zkontroluje, zda bylo volání úspěšné. Obecně platí, že fcntl funkce vrací -1 (tedy méně než nula) k označení selhání. Druhý argument F_SETLK znamená, že volání fcntl není blok:funkce se okamžitě vrátí, buď udělí zámek, nebo signalizuje selhání. Pokud je příznak F_SETLKW (W na konci je na čekání ), místo toho bylo použito volání fcntl bude blokovat, dokud nebude možné získat zámek. Ve voláních fcntl , první argument fd je deskriptor souboru, druhý argument určuje akci, která se má provést (v tomto případě F_SETLK pro nastavení zámku) a třetím argumentem je adresa struktury zámku (v tomto případě &lock ).
- Pokud je producent získá zámek, program zapíše do souboru dva textové záznamy.
- Po zapsání do souboru producent změní l_type struktury zámku do pole odemknout hodnota:
lock.l_type = F_UNLCK;
a volá fcntl k provedení operace odblokování. Program skončí zavřením souboru a ukončením.
Příklad 2. spotřebitel program
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#define FileName "data.dat"
void report_and_exit(const char* msg) {
perror(msg);
exit(-1); /* EXIT_FAILURE */
}
int main() {
struct flock lock;
lock.l_type = F_WRLCK; /* read/write (exclusive) lock */
lock.l_whence = SEEK_SET; /* base for seek offsets */
lock.l_start = 0; /* 1st byte in file */
lock.l_len = 0; /* 0 here means 'until EOF' */
lock.l_pid = getpid(); /* process id */
int fd; /* file descriptor to identify a file within a process */
if ((fd = open(FileName, O_RDONLY)) < 0) /* -1 signals an error */
report_and_exit("open to read failed...");
/* If the file is write-locked, we can't continue. */
fcntl(fd, F_GETLK, &lock); /* sets lock.l_type to F_UNLCK if no write lock */
if (lock.l_type != F_UNLCK)
report_and_exit("file is still write locked...");
lock.l_type = F_RDLCK; /* prevents any writing during the reading */
if (fcntl(fd, F_SETLK, &lock) < 0)
report_and_exit("can't get a read-only lock...");
/* Read the bytes (they happen to be ASCII codes) one at a time. */
int c; /* buffer for read bytes */
while (read(fd, &c, 1) > 0) /* 0 signals EOF */
write(STDOUT_FILENO, &c, 1); /* write one byte to the standard output */
/* Release the lock explicitly. */
lock.l_type = F_UNLCK;
if (fcntl(fd, F_SETLK, &lock) < 0)
report_and_exit("explicit unlocking failed...");
close(fd);
return 0;
}
Spotřebitel program je komplikovanější, než je nutné pro zvýraznění funkcí uzamykacího API. Zejména spotřebitel program nejprve zkontroluje, zda je soubor výhradně uzamčen a teprve poté se pokusí získat sdílený zámek. Příslušný kód je:
lock.l_type = F_WRLCK;
...
fcntl(fd, F_GETLK, &lock); /* sets lock.l_type to F_UNLCK if no write lock */
if (lock.l_type != F_UNLCK)
report_and_exit("file is still write locked...");
F_GETLK operace specifikovaná v fcntl volání zkontroluje zámek, v tomto případě exkluzivní zámek daný jako F_WRLCK v prvním prohlášení výše. Pokud zadaný zámek neexistuje, pak fcntl call automaticky změní pole typu zámku na F_UNLCK na tuto skutečnost upozornit. Pokud je soubor výhradně uzamčen, spotřebitel končí. (Robustnější verze programu může mít spotřebitel spát trochu a zkuste to znovu několikrát.)
Pokud soubor není aktuálně uzamčen, pak spotřebitel pokusí získat sdílené (pouze pro čtení ) zámek (F_RDLCK ). Pro zkrácení programu F_GETLK zavolejte na fcntl mohl být zrušen, protože F_RDLCK volání by selhalo v případě čtení i zápisu zámek již byl držen nějakým jiným procesem. Připomeňme, že pouze pro čtení lock zabraňuje jakémukoli jinému procesu v zápisu do souboru, ale umožňuje jiným procesům číst ze souboru. Stručně řečeno, sdílený zámek může být držen více procesy. Po získání sdíleného zámku spotřebitel program čte bajty jeden po druhém ze souboru, vytiskne bajty na standardní výstup, uvolní zámek, zavře soubor a ukončí se.
Zde je výstup ze dvou programů spuštěných ze stejného terminálu s % jako příkazový řádek:
% ./producer
Process 29255 has written to data file...
% ./consumer
Now is the winter of our discontent
Made glorious summer by this sun of York
V tomto prvním příkladu kódu jsou data sdílená prostřednictvím IPC text:dva řádky ze Shakespearovy hry Richard III . Obsah sdíleného souboru však může být objemný, libovolné bajty (např. digitalizovaný film), díky čemuž je sdílení souborů působivě flexibilním mechanismem IPC. Nevýhodou je, že přístup k souboru je relativně pomalý, ať už zahrnuje čtení nebo zápis. Jako vždy přichází programování s kompromisy. Další příklad má výhodu IPC prostřednictvím sdílené paměti, spíše než sdílených souborů, s odpovídajícím zvýšením výkonu.
Sdílená paměť
Linuxové systémy poskytují dvě samostatná API pro sdílenou paměť:starší System V API a novější POSIX. Tato rozhraní API by však nikdy neměla být smíchána v jedné aplikaci. Nevýhodou přístupu POSIX je, že funkce jsou stále ve vývoji a závisí na nainstalované verzi jádra, což má vliv na přenositelnost kódu. Například rozhraní POSIX API ve výchozím nastavení implementuje sdílenou paměť jako soubor mapovaný v paměti :pro segment sdílené paměti systém udržuje záložní soubor s odpovídajícím obsahem. Sdílenou paměť pod POSIX lze konfigurovat bez záložního souboru, ale to může ovlivnit přenositelnost. Můj příklad používá POSIX API se záložním souborem, který kombinuje výhody přístupu do paměti (rychlost) a ukládání souborů (perzistence).
Příklad sdílené paměti má dva programy s názvem memwriter a memreader a používá semafor koordinovat jejich přístup ke sdílené paměti. Kdykoli se objeví sdílená paměť s spisovatelem ať už jde o multiprocessing nebo multi-threading, tak i riziko sporu založeného na paměti; semafor se tedy používá ke koordinaci (synchronizaci) přístupu ke sdílené paměti.
memwriter program by měl být spuštěn nejprve ve vlastním terminálu. čtečka paměti program pak může být spuštěn (během tuctu sekund) v jeho vlastním terminálu. Výstup z memreaderu je:
This is the way the world ends...
Další zdroje pro Linux
- Cheat pro příkazy Linuxu
- Cheat sheet pro pokročilé příkazy systému Linux
- Bezplatný online kurz:Technický přehled RHEL
- Síťový cheat pro Linux
- Cheat sheet SELinux
- Cheat pro běžné příkazy pro Linux
- Co jsou kontejnery systému Linux?
- Naše nejnovější články o Linuxu
Každý zdrojový soubor má nahoře dokumentaci vysvětlující příznaky odkazů, které mají být zahrnuty během kompilace.
Začněme přehledem toho, jak semafory fungují jako synchronizační mechanismus. Obecný semafor se také nazývá počítací semafor , protože má hodnotu (obvykle inicializovanou na nulu), kterou lze zvýšit. Vezměme si obchod, který půjčuje kola, se stovkou z nich na skladě, s programem, který úředníci používají k půjčování. Při každém zapůjčení kola se semafor zvýší o jedničku; když se kolo vrátí, semafor se sníží o jedničku. Výpůjčky mohou pokračovat, dokud hodnota nedosáhne 100, ale pak se musí zastavit, dokud nebude vráceno alespoň jedno kolo, čímž se semafor sníží na 99.
binární semafor je speciální případ vyžadující pouze dvě hodnoty:0 a 1. V této situaci semafor funguje jako mutex :konstrukt vzájemného vyloučení. Příklad sdílené paměti používá jako mutex semafor. Když je hodnota semaforu 0, memwriter sám má přístup ke sdílené paměti. Po zápisu tento proces zvýší hodnotu semaforu, čímž umožní čtečce paměti ke čtení sdílené paměti.
Příklad 3. Zdrojový kód pro memwriter proces
/** Compilation: gcc -o memwriter memwriter.c -lrt -lpthread **/
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <semaphore.h>
#include <string.h>
#include "shmem.h"
void report_and_exit(const char* msg) {
perror(msg);
exit(-1);
}
int main() {
int fd = shm_open(BackingFile, /* name from smem.h */
O_RDWR | O_CREAT, /* read/write, create if needed */
AccessPerms); /* access permissions (0644) */
if (fd < 0) report_and_exit("Can't open shared mem segment...");
ftruncate(fd, ByteSize); /* get the bytes */
caddr_t memptr = mmap(NULL, /* let system pick where to put segment */
ByteSize, /* how many bytes */
PROT_READ | PROT_WRITE, /* access protections */
MAP_SHARED, /* mapping visible to other processes */
fd, /* file descriptor */
0); /* offset: start at 1st byte */
if ((caddr_t) -1 == memptr) report_and_exit("Can't get segment...");
fprintf(stderr, "shared mem address: %p [0..%d]\n", memptr, ByteSize - 1);
fprintf(stderr, "backing file: /dev/shm%s\n", BackingFile );
/* semaphore code to lock the shared mem */
sem_t* semptr = sem_open(SemaphoreName, /* name */
O_CREAT, /* create the semaphore */
AccessPerms, /* protection perms */
0); /* initial value */
if (semptr == (void*) -1) report_and_exit("sem_open");
strcpy(memptr, MemContents); /* copy some ASCII bytes to the segment */
/* increment the semaphore so that memreader can read */
if (sem_post(semptr) < 0) report_and_exit("sem_post");
sleep(12); /* give reader a chance */
/* clean up */
munmap(memptr, ByteSize); /* unmap the storage */
close(fd);
sem_close(semptr);
shm_unlink(BackingFile); /* unlink from the backing file */
return 0;
}
Zde je přehled toho, jak memwriter a memreader programy komunikují prostřednictvím sdílené paměti:
- memwriter výše zobrazený program volá shm_open k získání deskriptoru souboru pro záložní soubor, který systém koordinuje se sdílenou pamětí. V tomto okamžiku nebyla přidělena žádná paměť. Následné volání klamavě pojmenované funkce ftruncate :
ftruncate(fd, ByteSize); /* get the bytes */
přiděluje ByteSize bytů, v tomto případě skromných 512 bytů. memwriter a memreader programy přistupují pouze ke sdílené paměti, nikoli k záložnímu souboru. Systém je zodpovědný za synchronizaci sdílené paměti a záložního souboru.
- memwriter poté zavolá mmap function:
caddr_t memptr = mmap(NULL, /* let system pick where to put segment */
ByteSize, /* how many bytes */
PROT_READ | PROT_WRITE, /* access protections */
MAP_SHARED, /* mapping visible to other processes */
fd, /* file descriptor */
0); /* offset: start at 1st byte */získat ukazatel na sdílenou paměť. (čtečka paměti provede podobné volání.) Typ ukazatele caddr_t začíná na c pro calloc , systémová funkce, která inicializuje dynamicky alokované úložiště na nuly. memwriter používá memptr pro pozdější zápis operace pomocí knihovny strcpy (kopírování řetězce).
- V tuto chvíli memwriter je připraven k zápisu, ale nejprve vytvoří semafor, který zajistí výhradní přístup ke sdílené paměti. Spor by nastal, pokud by memwriter psali při čtečce paměti četl. Pokud zavoláte na sem_open úspěšná:
sem_t* semptr = sem_open(SemaphoreName, /* name */
O_CREAT, /* create the semaphore */
AccessPerms, /* protection perms */
0); /* initial value */pak může zápis pokračovat. Název semaforu (postačí jakýkoli jedinečný neprázdný název) identifikuje semafor v memwriter a čtečka paměti . Počáteční hodnota nula udává tvůrce semaforu, v tomto případě memwriter , právo přejít v tomto případě k zápisu operace.
- Po napsání memwriter zvýší hodnotu semaforu na 1:
if (sem_post(semptr) < 0) ..
zavoláním na sem_post funkce. Zvýšení semaforu uvolní zámek mutexu a povolí memreader k provedení jeho čtení úkon. Pro dobrou míru, memwriter také odmapuje sdílenou paměť z memwriter adresní prostor:
munmap(memptr, ByteSize); /* unmap the storage *
Tím se zablokuje memwriter z dalšího přístupu ke sdílené paměti.
Příklad 4. Zdrojový kód pro memreader proces
/** Compilation: gcc -o memreader memreader.c -lrt -lpthread **/
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <semaphore.h>
#include <string.h>
#include "shmem.h"
void report_and_exit(const char* msg) {
perror(msg);
exit(-1);
}
int main() {
int fd = shm_open(BackingFile, O_RDWR, AccessPerms); /* empty to begin */
if (fd < 0) report_and_exit("Can't get file descriptor...");
/* get a pointer to memory */
caddr_t memptr = mmap(NULL, /* let system pick where to put segment */
ByteSize, /* how many bytes */
PROT_READ | PROT_WRITE, /* access protections */
MAP_SHARED, /* mapping visible to other processes */
fd, /* file descriptor */
0); /* offset: start at 1st byte */
if ((caddr_t) -1 == memptr) report_and_exit("Can't access segment...");
/* create a semaphore for mutual exclusion */
sem_t* semptr = sem_open(SemaphoreName, /* name */
O_CREAT, /* create the semaphore */
AccessPerms, /* protection perms */
0); /* initial value */
if (semptr == (void*) -1) report_and_exit("sem_open");
/* use semaphore as a mutex (lock) by waiting for writer to increment it */
if (!sem_wait(semptr)) { /* wait until semaphore != 0 */
int i;
for (i = 0; i < strlen(MemContents); i++)
write(STDOUT_FILENO, memptr + i, 1); /* one byte at a time */
sem_post(semptr);
}
/* cleanup */
munmap(memptr, ByteSize);
close(fd);
sem_close(semptr);
unlink(BackingFile);
return 0;
}
V obou memwriter a memreader programů, hlavní funkce sdílené paměti jsou shm_open a mmap :po úspěchu vrátí první volání deskriptor souboru pro záložní soubor, který pak druhé volání použije k získání ukazatele na segment sdílené paměti. Volání shm_open jsou v těchto dvou programech podobné kromě toho, že memwriter program vytváří sdílenou paměť, zatímco memreader přistupuje pouze k této již vytvořené paměti:
int fd = shm_open(BackingFile, O_RDWR | O_CREAT, AccessPerms); /* memwriter */
int fd = shm_open(BackingFile, O_RDWR, AccessPerms); /* memreader */
S deskriptorem souboru v ruce volání mmap jsou stejné:
caddr_t memptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
První argument pro mmap je NULL , což znamená, že systém určí, kam alokovat paměť ve virtuálním adresovém prostoru. Místo toho je možné (ale složité) zadat adresu. MAP_SHARED příznak označuje, že alokovanou paměť lze sdílet mezi procesy, a poslední argument (v tomto případě nula) znamená, že offset pro sdílenou paměť by měl být první bajt. velikost argument určuje počet bajtů, které mají být alokovány (v tomto případě 512), a argument ochrany udává, že sdílenou paměť lze zapisovat a číst.
Když memwriter program se úspěšně spustí, systém vytvoří a udržuje záložní soubor; v mém systému je soubor /dev/shm/shMemEx , s shMemEx jako moje jméno (uvedené v záhlaví souboru shmem.h ) pro sdílené úložiště. V aktuální verzi memwriter a memreader programy, prohlášení:
shm_unlink(BackingFile); /* removes backing file */
odstraní podpůrný soubor. Pokud odpojíte Pokud je příkaz vynechán, záložní soubor přetrvává i po ukončení programu.
čtečka paměti , jako je memwriter , přistupuje k semaforu prostřednictvím jeho názvu ve volání sem_open . Ale čtečka paměti poté přejde do stavu čekání na memwriter inkrementuje semafor, jehož počáteční hodnota je 0:
if (!sem_wait(semptr)) { /* wait until semaphore != 0 */
Jakmile čekání skončí, memreader přečte ASCII bajty ze sdílené paměti, vyčistí a ukončí.
Rozhraní API sdílené paměti zahrnuje operace explicitně pro synchronizaci segmentu sdílené paměti a záložního souboru. Tyto operace byly z příkladu vynechány, aby se omezilo nepořádek a udrželo se zaměření na sdílení paměti a kód semaforu.
memwriter a memreader programy se pravděpodobně spustí bez vyvolání sporu, i když je odstraněn kód semaforu:memwriter vytvoří segment sdílené paměti a okamžitě do něj zapíše; čtečka paměti nemůže ani přistupovat ke sdílené paměti, dokud nebude tato vytvořena. Osvědčený postup však vyžaduje, aby byl přístup ke sdílené paměti při každém zápisu synchronizován operace je v mixu a semaforové API je natolik důležité, aby bylo zvýrazněno v příkladu kódu.
Zabalení
Příklady sdílených souborů a sdílené paměti ukazují, jak mohou procesy komunikovat prostřednictvím sdíleného úložiště , soubory v jednom případě a segmenty paměti ve druhém. API pro oba přístupy jsou relativně přímočaré. Mají tyto přístupy společnou nevýhodu? Moderní aplikace se často zabývají streamováním dat, skutečně s masivně velkými datovými proudy. Přístupy se sdíleným souborem ani sdílenou pamětí nejsou vhodné pro masivní datové toky. Kanály jednoho nebo druhého typu jsou vhodnější. Část 2 tak představuje kanály a fronty zpráv, opět s příklady kódu v C.
[Stáhněte si kompletního průvodce meziprocesovou komunikací v Linuxu]