encyklopedia.space

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

SystemMechanizmy 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;
}

Patrz także