IPC – komunikacja międzyprocesowa
IPC (ang. Inter‑Process Communication) to zbiór mechanizmów i metod pozwalających na wymianę danych oraz synchronizację działań pomiędzy procesami działającymi w ramach jednego lub kilku systemów operacyjnych. Dzięki IPC procesy mogą współdzielić zasoby, przekazywać komunikaty, a także koordynować swoje działanie w sposób bezpieczny i wydajny.
Historia
Pierwsze rozwiązania IPC pojawiły się już w systemach Unix opracowywanych na początku lat 70. XX wieku – w postaci potoków (pipes) i sygnałów. Z biegiem czasu, wraz z rozwojem architektury wieloprocesorowej i rozproszonych aplikacji, wprowadzono bardziej zaawansowane mechanizmy, takie jak kolejki komunikatów, pamięć współdzielona i gniazda socket. Standard POSIX ujednolicił interfejsy IPC, co umożliwiło ich przenośność pomiędzy różnymi systemami, w tym Linux i Windows.
Podstawowe modele IPC
- Potoki (pipes) i potoki nazwane (named pipes, FIFO) – proste, jednokierunkowe kanały danych wykorzystywane głównie w Unix i Linux. Więcej o FIFO można przeczytać w artykule FIFO.
- Kolejki komunikatów (message queues) – umożliwiają asynchroniczną wymianę komunikatów o określonych priorytetach.
- Pamięć współdzielona (shared memory) – najwydajniejszy sposób wymiany dużych bloków danych, wymagający dodatkowej synchronizacji przy pomocy semaforów lub muteksów.
- Semafory i muteksy – mechanizmy synchronizacyjne zapobiegające wyścigom i zapewniające wzajemne wykluczanie.
- Sygnały (signals) – proste powiadomienia o zdarzeniach (np. sygnał
SIGTERM), wykorzystywane głównie do sterowania procesem. - Gniazda (sockets) – najuniwersalniejszy model, pozwalający na komunikację zarówno w obrębie jednego hosta, jak i pomiędzy różnymi maszynami w sieci (TCP/IP, Unix domain sockets).
- Remote Procedure Call (RPC) – abstrakcja umożliwiająca wywoływanie procedur znajdujących się w innym procesie lub na innym komputerze, często oparta na gniazdach.
IPC a wątki
Choć wątki (ang. threads) są częścią tego samego procesu i współdzielą jego pamięć, w praktyce wiele technik IPC jest wykorzystywanych także do komunikacji między wątkami w ramach programowania wielowątkowego. Przykładowo, mutexy i semafory służą zarówno do synchronizacji procesów, jak i wątków.
Implementacje w popularnych systemach operacyjnych
| System | Mechanizmy IPC |
|---|---|
| Linux | pipes, FIFO, message queues (System V i POSIX), shared memory, semaphores, sockets, eventfd, signalfd |
| Windows | Named pipes, mailslots, mailslot files, memory‑mapped files, mailslot messages, sockets, mailslot‑based RPC, Windows Messages |
| Unix | pipes, FIFO, System V IPC (queues, semaphores, shared memory), POSIX IPC, sockets, signals |
Bezpieczeństwo i ograniczenia
Wykorzystanie IPC wiąże się z koniecznością zapewnienia bezpieczeństwa komputerowego. Należy kontrolować:
- uprawnienia dostępu (np. prawa do odczytu/zapisu w FIFO lub pamięci współdzielonej),
- ryzyko ataków typu race condition przy nieodpowiedniej synchronizacji,
- ewidentualizację i izolację procesów, aby uniknąć nieautoryzowanego podglądu danych.
Zastosowania
IPC znajduje zastosowanie w wielu dziedzinach informatyki, m.in.:
- systemy operacyjne – zarządzanie zasobami i usługami systemowymi,
- bazy danych – wymiana zapytań i wyników pomiędzy procesami serwera,
- aplikacje rozproszone – komunikacja między węzłami klastrów,
- oprogramowanie multimedialne – synchronizacja strumieni audio i wideo,
- systemy wbudowane – wymiana danych pomiędzy procesami sterującymi sprzętem.
Przykład użycia w języku C (POSIX)
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(void) {
int pipefd[2];
pid_t cpid;
char buf;
if (pipe(pipefd) == -1) {
perror("pipe");
return 1;
}
cpid = fork();
if (cpid == -1) {
perror("fork");
return 1;
}
if (cpid == 0) { // proces potomny – odbiera
close(pipefd[1]); // zamykamy koniec zapisu
read(pipefd[0], &buf, 1);
printf("Otrzymano znak: %c\n", buf);
close(pipefd[0]);
} else { // proces macierzysty – wysyła
close(pipefd[0]); // zamykamy koniec odczytu
write(pipefd[1], "X", 1);
close(pipefd[1]);
wait(NULL); // czekamy na zakończenie potomka
}
return 0;
}