Tüm eğitimler
Rehber Linux / Sistem C IPC

Unix IPC —
Pipe, FIFO, Socket, Shared Memory.

süreçler arası iletişim mekanizmalarını syscall seviyesinde öğren — hangisini ne zaman seçersin?

00 IPC mekanizmaları

Her Linux process'i izole bir address space'te çalışır; IPC bu izolasyonu kontrollü biçimde aşmanın kernel destekli yoludur.

Neden IPC gerekli?

Linux'ta her process, kendi sanal address space'ine sahiptir. Bir process'in 0x7fff... adresindeki değişken, başka bir process'in aynı adresindeki değişkenden tamamen bağımsızdır. Bu izolasyon güvenlik ve kararlılık için kritiktir, ancak iş bölümü yapan process'lerin veri paylaşması gerektiğinde bir iletişim mekanizması şarttır.

  Process A          Process B
  [addr space A]     [addr space B]
       │                   │
       │   ← IPC →         │
       │  (kernel buffer    │
       │   veya shared map) │
    

Mekanizmalar karşılaştırma tablosu

Mekanizma Latency Throughput Kalıcılık Multi-process Karmaşıklık
pipe()DüşükOrta (64 KB buffer)GeçiciSadece parent-childÇok düşük
FIFODüşükOrtaDosya sistemiİlgisiz process'lerDüşük
Unix socketDüşük–ortaYüksekGeçici (abstract) / FSÇok process, çok istemciOrta
TCP loopbackOrtaYüksekGeçiciEvet, ağ üzeri deOrta
mmap (anonim)Çok düşükÇok yüksekGeçiciSadece fork sonrasıOrta
POSIX shmÇok düşükÇok yüksek/dev/shmİlgisiz process'lerOrta–yüksek
POSIX mqDüşükOrtaKernel nesneİlgisiz process'lerOrta

Seçim rehberi

  Hangi IPC?
       │
       ├─ Parent-child, tek yön → pipe()
       │
       ├─ İlgisiz process'ler, basit mesaj → FIFO
       │
       ├─ İlgisiz process'ler, bidirectional
       │   veya fd passing gerekiyor       → Unix domain socket
       │
       ├─ Yüksek throughput, düşük latency → POSIX shm + semaphore
       │
       └─ Öncelikli mesaj sıralaması       → POSIX message queue
    

Bu bölümde öğrendikleriniz

  • Process izolasyonu güvenlik için gereklidir; IPC bu izolasyonu kontrollü aşar
  • Latency açısından shm > Unix socket > pipe/FIFO > TCP loopback sıralaması geçerlidir
  • Doğru mekanizma seçimi gereksiz karmaşıklıktan ve performans kayıplarından korur

01 Pipe

pipe(), Linux'un en temel IPC mekanizmasıdır: kernel içinde tek yönlü bir byte akışı tamponu — fork ve exec ile birleşince shell pipe'ının temelini oluşturur.

pipe() syscall

pipe(int fds[2]) iki file descriptor döner: fds[0] okuma ucu, fds[1] yazma ucudur. Data her zaman fds[1]'e yazılır, fds[0]'dan okunur.

pipe-example.c
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <sys/wait.h>

int main(void) {
    int fds[2];
    char buf[128];

    if (pipe(fds) == -1) {   /* pipe oluştur */
        perror("pipe");
        return 1;
    }

    pid_t pid = fork();

    if (pid == 0) {
        /* child: okuyucu — yazma ucunu kapat */
        close(fds[1]);
        ssize_t n = read(fds[0], buf, sizeof(buf) - 1);
        buf[n] = '\0';
        printf("child okudu: %s\n", buf);
        close(fds[0]);
        _exit(0);
    }

    /* parent: yazıcı — okuma ucunu kapat */
    close(fds[0]);
    const char *msg = "merhaba child";
    write(fds[1], msg, strlen(msg));
    close(fds[1]);   /* kapat → child'da EOF görülür */

    wait(NULL);
    return 0;
}

Shell pipe'ı nasıl çalışır?

ls | grep txt yazdığınızda shell şunları yapar:

  shell
   │
   ├─ pipe(fds)          ← kernel buffer oluştur
   │
   ├─ fork() → child1    ← ls için
   │    └─ dup2(fds[1], STDOUT_FILENO)  ← stdout → pipe yazma ucu
   │       close(fds[0]); close(fds[1])
   │       exec("ls")
   │
   └─ fork() → child2    ← grep için
        └─ dup2(fds[0], STDIN_FILENO)   ← stdin → pipe okuma ucu
           close(fds[0]); close(fds[1])
           exec("grep", "txt")
    

Pipe kapasitesi ve blocking semantiği

DurumDavranış
Boş pipe'tan read()Yazma ucu açıksa bloklar; yazma ucu kapalıysa 0 (EOF) döner
Dolu pipe'a write()Okuma ucu açıksa bloklar; okuma ucu kapalıysa SIGPIPE gönderilir
Kapasite (Linux)Varsayılan 65536 byte; fcntl(fd, F_SETPIPE_SZ, size) ile değiştirilebilir
pipe-nonblock.c — SIGPIPE bastırma
#include <signal.h>
#include <unistd.h>
#include <errno.h>

/* Yöntem 1: SIGPIPE'ı yoksay */
signal(SIGPIPE, SIG_IGN);
ssize_t n = write(fds[1], buf, len);
if (n == -1 && errno == EPIPE) {
    /* okuyucu kapandı, yazma başarısız */
}

/* Yöntem 2: MSG_NOSIGNAL (send() için) */
send(sock_fd, buf, len, MSG_NOSIGNAL);

Bu bölümde öğrendikleriniz

  • pipe(fds): fds[0] okuma, fds[1] yazma — tek yönlü, kernel buffer
  • Shell | operatörü pipe + fork + dup2 + exec zinciridir
  • Dolu pipe'a yazma ve okuyucu yoksa SIGPIPE — SIG_IGN veya MSG_NOSIGNAL ile bastırılır

02 Named pipe (FIFO)

FIFO, pipe'ın dosya sistemi üzerinde kalıcı bir adrese sahip versiyonudur — ilgisiz process'ler arasında isimsiz pipe olmaksızın haberleşmeyi mümkün kılar.

mkfifo ile oluşturma

terminal
# Komut satırından FIFO oluştur
mkfifo /tmp/myfifo

# İzinleri kontrol et
ls -l /tmp/myfifo
# prw-r--r-- 1 user user 0 Apr 12 10:00 /tmp/myfifo
# "p" → named pipe
fifo-server.c — okuyucu process
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>

int main(void) {
    const char *path = "/tmp/myfifo";

    /* FIFO oluştur (zaten varsa EEXIST — sorun değil) */
    mkfifo(path, 0666);

    printf("Yazıcı bekleniyor...\n");

    /* O_RDONLY: yazıcı taraf açana kadar BLOKLAR */
    int fd = open(path, O_RDONLY);

    char buf[256];
    ssize_t n;
    while ((n = read(fd, buf, sizeof(buf))) > 0) {
        buf[n] = '\0';
        printf("Alındı: %s", buf);
    }

    close(fd);
    unlink(path);   /* FIFO'yu sil */
    return 0;
}
fifo-client.c — yazıcı process
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main(void) {
    /* O_WRONLY: okuyucu taraf açana kadar BLOKLAR */
    int fd = open("/tmp/myfifo", O_WRONLY);

    const char *msg = "merhaba fifo\n";
    write(fd, msg, strlen(msg));

    close(fd);
    return 0;
}

Blocking semantiği ve O_NONBLOCK

FIFO'nun kritik özelliği: open() çağrısı kendisi bloklar. Okuyucu O_RDONLY ile açarken yazıcı gelene kadar bekler; yazıcı O_WRONLY ile açarken okuyucu gelene kadar bekler. Bu senkronizasyonu O_NONBLOCK ile kırabilirsiniz:

fifo-nonblock.c
/* Non-blocking open: karşı taraf yoksa -1 / ENXIO döner */
int fd = open("/tmp/myfifo", O_RDONLY | O_NONBLOCK);
if (fd == -1) {
    perror("open");   /* ENXIO: yazıcı henüz bağlanmamış */
}

FIFO vs pipe farkları

Özellikpipe()FIFO
Dosya sistemi girişiYokVar (mkfifo)
Process ilişkisiSadece fork ile parent-childİlgisiz process'ler
KalıcılıkTüm fd'ler kapanınca yok olurDosya sisteminde kalır (unlink gerekir)
open() bloklamaYok (fork sonrası fd zaten var)Var — karşı taraf açana kadar bloklar
NOT

FIFO'lar syslog pipe gibi "log aggregator" senaryolarında kullanışlıdır: bir process sürekli FIFO'dan okurken birden fazla başka process aynı FIFO'ya yazar. Kernel, yazmaları sırayla kuyruğa alır.

Bu bölümde öğrendikleriniz

  • FIFO, dosya sisteminde görünür bir pipe — mkfifo(path, mode) ile oluşturulur
  • Her iki taraf da open() yapana kadar bloklar — randevu noktası gibi davranır
  • O_NONBLOCK ile blocking open davranışı kırılabilir

03 Unix domain socket

AF_UNIX socket'leri, TCP/IP stack'i olmadan dosya sistemi üzerinde tam çift yönlü haberleşme sağlar — nginx, systemd, Docker daemon'ı hepsi Unix socket kullanır.

AF_UNIX vs AF_INET

ÖzellikAF_UNIXAF_INET (TCP loopback)
YolDosya sistemi (/tmp/app.sock) veya abstractIP:port (127.0.0.1:8080)
TCP/IP overheadYokVar (checksum, seq number…)
LatencyDaha düşükDaha yüksek
Ağ üzeri erişimHayır (sadece aynı host)Evet
fd passingEvet (SCM_RIGHTS)Hayır

Tam server/client örneği

unix-server.c
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

#define SOCK_PATH "/tmp/myapp.sock"

int main(void) {
    int srv, cli;
    struct sockaddr_un addr;
    char buf[256];

    /* 1. Socket oluştur */
    srv = socket(AF_UNIX, SOCK_STREAM, 0);

    /* 2. Adres ayarla */
    memset(&addr, 0, sizeof(addr));
    addr.sun_family = AF_UNIX;
    strncpy(addr.sun_path, SOCK_PATH, sizeof(addr.sun_path) - 1);

    /* 3. Önceki socket dosyasını temizle */
    unlink(SOCK_PATH);

    /* 4. Bind + listen */
    bind(srv, (struct sockaddr *)&addr, sizeof(addr));
    listen(srv, 5);

    printf("İstemci bekleniyor: %s\n", SOCK_PATH);

    /* 5. Accept döngüsü */
    while (1) {
        cli = accept(srv, NULL, NULL);
        ssize_t n = read(cli, buf, sizeof(buf) - 1);
        buf[n] = '\0';
        printf("Alındı: %s\n", buf);

        const char *resp = "ACK";
        write(cli, resp, strlen(resp));
        close(cli);
    }

    close(srv);
    unlink(SOCK_PATH);
    return 0;
}
unix-client.c
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

int main(void) {
    int fd;
    struct sockaddr_un addr;
    char buf[64];

    fd = socket(AF_UNIX, SOCK_STREAM, 0);

    memset(&addr, 0, sizeof(addr));
    addr.sun_family = AF_UNIX;
    strncpy(addr.sun_path, "/tmp/myapp.sock", sizeof(addr.sun_path) - 1);

    if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
        perror("connect");
        return 1;
    }

    const char *msg = "merhaba server";
    write(fd, msg, strlen(msg));

    ssize_t n = read(fd, buf, sizeof(buf) - 1);
    buf[n] = '\0';
    printf("Yanıt: %s\n", buf);

    close(fd);
    return 0;
}

Abstract namespace

Abstract namespace socket'leri dosya sistemine yazılmaz; sun_path[0] = '\0' olduğunda kernel otomatik olarak abstract namespace'e bağlar. unlink() gerekmez, socket tüm referanslar kapandığında kaybolur.

abstract-socket.c
struct sockaddr_un addr;
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;

/* sun_path[0] = '\0' → abstract namespace */
/* gerçek isim sun_path[1]'den başlar        */
strncpy(addr.sun_path + 1, "myapp", sizeof(addr.sun_path) - 2);

/* addrlen: sun_family boyutu + 1 (null byte) + isim uzunluğu */
socklen_t len = sizeof(sa_family_t) + 1 + strlen("myapp");
bind(fd, (struct sockaddr *)&addr, len);

SCM_RIGHTS: fd passing

Unix socket'lerin benzersiz özelliği: bir process, açık bir file descriptor'ı başka bir process'e gönderebilir. Docker'ın containerd ile haberleşmesi, systemd'nin socket activation mekanizması bu yöntemi kullanır.

fd-pass-sender.c — fd gönderme
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>

void send_fd(int sock, int fd_to_send) {
    struct msghdr msg = {0};
    char cmsg_buf[CMSG_SPACE(sizeof(int))];
    char dummy = '!';
    struct iovec iov = { .iov_base = &dummy, .iov_len = 1 };

    msg.msg_iov     = &iov;
    msg.msg_iovlen  = 1;
    msg.msg_control = cmsg_buf;
    msg.msg_controllen = sizeof(cmsg_buf);

    struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
    cmsg->cmsg_level = SOL_SOCKET;
    cmsg->cmsg_type  = SCM_RIGHTS;     /* fd passing protokolü */
    cmsg->cmsg_len   = CMSG_LEN(sizeof(int));
    memcpy(CMSG_DATA(cmsg), &fd_to_send, sizeof(int));

    sendmsg(sock, &msg, 0);
}

Bu bölümde öğrendikleriniz

  • AF_UNIX, TCP/IP overhead'i olmadan tam çift yönlü haberleşme sağlar
  • Abstract namespace (sun_path[0]='\0') dosya sistemi bağımlılığını ortadan kaldırır
  • SCM_RIGHTS ile process'ler arası file descriptor transferi mümkündür

04 socketpair()

socketpair(), dosya sistemi kullanmadan iki bağlı socket oluşturur — fork tabanlı parent-child haberleşmede pipe()'a göre çift yönlü çalışması öne çıkan avantajıdır.

socketpair() nedir?

socketpair(AF_UNIX, SOCK_STREAM, 0, sv) çağrısı, birbirine bağlı iki socket descriptor döner. Normal pipe'tan farkı: her iki uç da hem okuyup hem yazabilir (full-duplex). Bir çift ile parent-child arasında çift yönlü kanal kurulur.

socketpair-example.c
#include <sys/socket.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <sys/wait.h>

int main(void) {
    int sv[2];
    char buf[128];

    /* İki bağlı socket oluştur */
    if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) == -1) {
        perror("socketpair");
        return 1;
    }

    pid_t pid = fork();

    if (pid == 0) {
        /* child: sv[1] kullanır, sv[0]'ı kapatır */
        close(sv[0]);

        const char *msg = "child'dan mesaj";
        write(sv[1], msg, strlen(msg));

        ssize_t n = read(sv[1], buf, sizeof(buf) - 1);
        buf[n] = '\0';
        printf("child yanıt aldı: %s\n", buf);

        close(sv[1]);
        _exit(0);
    }

    /* parent: sv[0] kullanır, sv[1]'i kapatır */
    close(sv[1]);

    ssize_t n = read(sv[0], buf, sizeof(buf) - 1);
    buf[n] = '\0';
    printf("parent aldı: %s\n", buf);

    const char *reply = "parent yanıtı";
    write(sv[0], reply, strlen(reply));

    wait(NULL);
    close(sv[0]);
    return 0;
}

pipe vs socketpair karşılaştırması

Özellikpipe()socketpair()
YönTek yönlü (fds[0] oku, fds[1] yaz)Çift yönlü (her iki uç da oku/yaz)
Çift yön içinİki pipe gerekirTek çift yeterli
fd sayısı2 (çift yön için 4)2 (çift yön yeterli)
SOCK_DGRAM desteğiYokVar (mesaj sınırları korunur)
NOT

SOCK_DGRAM ile kullanılan socketpair(), mesaj sınırlarını korur: write(sv[0], "abc", 3) ve write(sv[0], "xyz", 3) iki ayrı read() çağrısı ile alınır. SOCK_STREAM'de byte akışı birleşebilir.

Bu bölümde öğrendikleriniz

  • socketpair(), dosya sistemi olmadan iki bağlı socket üretir
  • Full-duplex olduğundan çift yönlü haberleşme için tek çift yeterlidir
  • SOCK_DGRAM ile mesaj sınırları korunur; SOCK_STREAM'de byte akışı birleşir

05 mmap ile shared memory

mmap ile anonim bellek eşlemesi, fork sonrası parent-child arasında sıfır kopya veri paylaşımı sağlar — en düşük latency IPC mekanizmasıdır.

MAP_SHARED | MAP_ANONYMOUS

mmap-shm.c
#include <sys/mman.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h>

int main(void) {
    /* MAP_SHARED: fork sonrası aynı fiziksel sayfayı paylaşırlar */
    /* MAP_ANONYMOUS: dosya değil, sıfır-başlatılmış bellek       */
    int *shared = mmap(
        NULL,
        sizeof(int),
        PROT_READ | PROT_WRITE,
        MAP_SHARED | MAP_ANONYMOUS,
        -1, 0
    );

    if (shared == MAP_FAILED) {
        perror("mmap");
        return 1;
    }

    *shared = 0;

    pid_t pid = fork();

    if (pid == 0) {
        /* child: değeri artır */
        *shared = 42;
        _exit(0);
    }

    wait(NULL);

    /* parent: child'ın yazdığı değeri görür */
    printf("shared değer: %d\n", *shared);   /* 42 */

    munmap(shared, sizeof(int));
    return 0;
}

MAP_SHARED vs MAP_PRIVATE

FlagYazma DavranışıKullanım
MAP_SHAREDTüm mapping'ler aynı fiziksel sayfayı görür; yazma diğerlerini etkilerIPC, dosyaya geri yazma
MAP_PRIVATECopy-on-write: yazma anında özel kopya oluşturulur; diğerleri etkilenmezProcess-özel çalışma kopyası

/dev/shm ile dosya tabanlı mmap

devshm-mmap.c
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main(void) {
    /* /dev/shm: RAM-backed tmpfs */
    int fd = open("/dev/shm/myapp-buf", O_CREAT | O_RDWR, 0600);
    ftruncate(fd, 4096);   /* boyutu ayarla */

    char *buf = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    close(fd);   /* fd'yi kapat, mapping hâlâ aktif */

    strcpy(buf, "paylaşılan veri");

    msync(buf, 4096, MS_SYNC);   /* değişiklikleri garantile */
    munmap(buf, 4096);
    unlink("/dev/shm/myapp-buf");
    return 0;
}
DİKKAT

Senkronizasyon olmadan paylaşılan belleğe birden fazla process eş zamanlı yazdığında race condition kaçınılmazdır. MAP_SHARED bellek kullanımında her zaman mutex (PTHREAD_PROCESS_SHARED) veya semaphore kullanın. Bu kural hem anonim mmap hem POSIX shm için geçerlidir.

Bu bölümde öğrendikleriniz

  • MAP_SHARED | MAP_ANONYMOUS ile fork sonrası aynı fiziksel sayfa paylaşılır
  • MAP_PRIVATE copy-on-write sağlar — yazma diğer mapping'leri etkilemez
  • Senkronizasyon olmadan race condition kaçınılmazdır; mutex veya semaphore şarttır

06 POSIX shm

POSIX shared memory, anonim mmap'ten farklı olarak ilgisiz process'lerin isim üzerinden aynı bellek bölgesine erişmesine olanak tanır.

shm_open + mmap akışı

posix-shm-producer.c
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>

#define SHM_NAME  "/myapp-shm"
#define SHM_SIZE  4096

int main(void) {
    /* 1. shm_open: /dev/shm/myapp-shm dosyası oluşturur */
    int fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0666);
    if (fd == -1) { perror("shm_open"); return 1; }

    /* 2. Boyutu ayarla */
    ftruncate(fd, SHM_SIZE);

    /* 3. mmap ile adres alanına eşle */
    char *ptr = mmap(NULL, SHM_SIZE,
                    PROT_READ | PROT_WRITE,
                    MAP_SHARED, fd, 0);
    close(fd);

    /* 4. Veri yaz */
    snprintf(ptr, SHM_SIZE, "producer verisi: %d", getpid());
    printf("Yazıldı, consumer bekleniyor...\n");

    sleep(10);   /* consumer okuyana kadar bekle */

    munmap(ptr, SHM_SIZE);
    shm_unlink(SHM_NAME);   /* temizlik */
    return 0;
}
posix-shm-consumer.c
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>

int main(void) {
    /* O_RDONLY: sadece oku */
    int fd = shm_open("/myapp-shm", O_RDONLY, 0);
    if (fd == -1) { perror("shm_open"); return 1; }

    char *ptr = mmap(NULL, 4096, PROT_READ, MAP_SHARED, fd, 0);
    close(fd);

    printf("Okunan: %s\n", ptr);

    munmap(ptr, 4096);
    return 0;
}

İsimlendirme kuralları

/myapp-shmİsim / ile başlamalı, sonrasında / içermemeli — /dev/shm/myapp-shm dosyasına karşılık gelir
ftruncate(fd, size)shm_open sonrası boyutu ayarlamak için şart; yoksa mmap SIGBUS üretir
shm_unlink(name)İsmi siler, ancak tüm mmap'ler kapanana kadar bellek kullanımda kalır
-lrtBazı eski sistemlerde gerekli; glibc 2.17+ ile artık gerekmez

Pipe vs shm hız karşılaştırması

1 GB veri transferi için kaba ölçüm (aynı host, Linux 5.15):

MekanizmaSüreThroughputKopya
POSIX shm + pointer~10 ms~100 GB/sSıfır kopya
Unix socket~1200 ms~850 MB/s2 kopya (kernel buffer)
pipe~1500 ms~680 MB/s2 kopya (kernel buffer)
TCP loopback~2100 ms~480 MB/s2 kopya + TCP overhead

Bu bölümde öğrendikleriniz

  • shm_open → ftruncate → mmap üçlüsü POSIX shm'nin temel akışıdır
  • İsim / ile başlamalı; shm_unlink ile temizlenmelidir
  • Shared memory, sıfır kopya ile pipe/socket'ten çok daha yüksek throughput sağlar

07 POSIX message queue

POSIX message queue, öncelik tabanlı mesaj sıralama ve async bildirim desteği ile pipe/socket'ten farklı bir IPC semantiği sunar.

mq_open ve mq_attr yapısı

mq-producer.c
#include <mqueue.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <stdio.h>
#include <string.h>

#define QUE_NAME  "/myqueue"

int main(void) {
    struct mq_attr attr = {
        .mq_flags   = 0,
        .mq_maxmsg  = 10,       /* en fazla 10 mesaj bekleyebilir */
        .mq_msgsize = 256,      /* her mesaj en fazla 256 byte   */
        .mq_curmsgs = 0
    };

    mqd_t mqd = mq_open(QUE_NAME, O_CREAT | O_WRONLY, 0666, &attr);
    if (mqd == (mqd_t)-1) { perror("mq_open"); return 1; }

    const char *msg1 = "normal mesaj";
    const char *msg2 = "acil mesaj";

    /* Priority 5: normal */
    mq_send(mqd, msg1, strlen(msg1) + 1, 5);

    /* Priority 10: daha yüksek → kuyrukta öne geçer */
    mq_send(mqd, msg2, strlen(msg2) + 1, 10);

    mq_close(mqd);
    return 0;
}
mq-consumer.c
#include <mqueue.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <stdio.h>
#include <string.h>

int main(void) {
    mqd_t mqd = mq_open("/myqueue", O_RDONLY);
    if (mqd == (mqd_t)-1) { perror("mq_open"); return 1; }

    struct mq_attr attr;
    mq_getattr(mqd, &attr);

    char buf[256];
    unsigned int prio;
    ssize_t n;

    /* Yüksek priority'li mesaj önce gelir */
    while ((n = mq_receive(mqd, buf, attr.mq_msgsize, &prio)) > 0) {
        printf("[prio=%u] %s\n", prio, buf);
    }

    mq_close(mqd);
    mq_unlink("/myqueue");   /* temizlik */
    return 0;
}

mq_notify: async bildirim

mq-notify.c — signal ile async bildirim
#include <mqueue.h>
#include <signal.h>
#include <fcntl.h>
#include <stdio.h>

static mqd_t g_mqd;

static void mq_handler(int sig) {
    char buf[256];
    unsigned int prio;
    ssize_t n = mq_receive(g_mqd, buf, sizeof(buf), &prio);
    if (n > 0) {
        buf[n] = '\0';
        printf("async alındı: %s\n", buf);
    }

    /* Her alımdan sonra notify yeniden kaydet */
    struct sigevent sev = { .sigev_notify = SIGEV_SIGNAL,
                           .sigev_signo  = SIGUSR1 };
    mq_notify(g_mqd, &sev);
}

int main(void) {
    g_mqd = mq_open("/myqueue", O_RDONLY);

    signal(SIGUSR1, mq_handler);

    struct sigevent sev = { .sigev_notify = SIGEV_SIGNAL,
                           .sigev_signo  = SIGUSR1 };
    mq_notify(g_mqd, &sev);

    /* Ana döngü: mesaj geldiğinde signal handler çalışır */
    while (1) pause();

    return 0;
}

Kernel parametreleri

terminal
# Maksimum kuyruk sayısı
cat /proc/sys/fs/mqueue/queues_max

# Maksimum mesaj sayısı per kuyruk
cat /proc/sys/fs/mqueue/msg_max

# Maksimum mesaj boyutu
cat /proc/sys/fs/mqueue/msgsize_max

# Geçici olarak artır
sudo sysctl fs.mqueue.msg_max=100

Bu bölümde öğrendikleriniz

  • mq_open → mq_send/mq_receive → mq_close → mq_unlink tam yaşam döngüsüdür
  • Yüksek priority mesajlar kuyrukta otomatik olarak öne geçer
  • mq_notify ile mesaj geldiğinde SIGEV_SIGNAL veya SIGEV_THREAD ile async bildirim alınır

08 Senkronizasyon: semaphore ve mutex

Shared memory ile birlikte senkronizasyon kullanmak zorunludur — sem_t ve PTHREAD_PROCESS_SHARED mutex bu ihtiyacı karşılar.

POSIX semaphore: sem_open (named)

named-sem.c
#include <semaphore.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <stdio.h>

int main(void) {
    /* Named semaphore: /my-sem → /dev/shm/sem.my-sem */
    sem_t *sem = sem_open("/my-sem", O_CREAT, 0666, 1);
    if (sem == SEM_FAILED) { perror("sem_open"); return 1; }

    sem_wait(sem);          /* değer > 0 ise azalt ve devam et; 0 ise blokla */
    /* kritik bölge */
    printf("kritik bölgedeyim\n");
    sem_post(sem);          /* değeri artır, bekleyenleri uyandır */

    sem_close(sem);
    sem_unlink("/my-sem");
    return 0;
}

Unnamed semaphore: fork ile kullanım

unnamed-sem.c
#include <semaphore.h>
#include <sys/mman.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h>

typedef struct {
    sem_t sem;
    int   counter;
} SharedData;

int main(void) {
    SharedData *data = mmap(NULL, sizeof(SharedData),
                            PROT_READ | PROT_WRITE,
                            MAP_SHARED | MAP_ANONYMOUS, -1, 0);

    /* pshared=1: fork sonrası process'ler arasında paylaşılabilir */
    sem_init(&data->sem, 1, 1);
    data->counter = 0;

    for (int i = 0; i < 4; i++) {
        if (fork() == 0) {
            for (int j = 0; j < 10000; j++) {
                sem_wait(&data->sem);
                data->counter++;        /* race condition yok */
                sem_post(&data->sem);
            }
            _exit(0);
        }
    }

    for (int i = 0; i < 4; i++) wait(NULL);

    printf("Sonuç: %d (beklenen: 40000)\n", data->counter);

    sem_destroy(&data->sem);
    munmap(data, sizeof(SharedData));
    return 0;
}

PTHREAD_PROCESS_SHARED mutex

process-shared-mutex.c
#include <pthread.h>
#include <sys/mman.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h>

typedef struct {
    pthread_mutex_t mtx;
    int             value;
} ShmRegion;

int main(void) {
    ShmRegion *reg = mmap(NULL, sizeof(ShmRegion),
                          PROT_READ | PROT_WRITE,
                          MAP_SHARED | MAP_ANONYMOUS, -1, 0);

    /* Mutex'i process-shared olarak başlat */
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);
    pthread_mutex_init(&reg->mtx, &attr);
    pthread_mutexattr_destroy(&attr);

    reg->value = 0;

    if (fork() == 0) {
        pthread_mutex_lock(&reg->mtx);
        reg->value = 99;
        pthread_mutex_unlock(&reg->mtx);
        _exit(0);
    }

    wait(NULL);

    pthread_mutex_lock(&reg->mtx);
    printf("value: %d\n", reg->value);   /* 99 */
    pthread_mutex_unlock(&reg->mtx);

    pthread_mutex_destroy(&reg->mtx);
    munmap(reg, sizeof(ShmRegion));
    return 0;
}

Producer-consumer: shm + semaphore

prod-cons.c — tam örnek
#include <semaphore.h>
#include <sys/mman.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h>
#include <string.h>

#define BUF_SIZE 256

typedef struct {
    sem_t  empty;   /* başlangıç: 1 (boş slot var) */
    sem_t  full;    /* başlangıç: 0 (dolu slot yok) */
    char   buf[BUF_SIZE];
} Channel;

int main(void) {
    Channel *ch = mmap(NULL, sizeof(Channel),
                       PROT_READ | PROT_WRITE,
                       MAP_SHARED | MAP_ANONYMOUS, -1, 0);

    sem_init(&ch->empty, 1, 1);
    sem_init(&ch->full,  1, 0);

    if (fork() == 0) {
        /* consumer */
        sem_wait(&ch->full);             /* veri gelene kadar bekle */
        printf("consumer: %s\n", ch->buf);
        sem_post(&ch->empty);            /* slot boşaldı */
        _exit(0);
    }

    /* producer */
    sem_wait(&ch->empty);               /* slot boşalana kadar bekle */
    strncpy(ch->buf, "merhaba consumer", BUF_SIZE);
    sem_post(&ch->full);                /* veri hazır */

    wait(NULL);
    sem_destroy(&ch->empty);
    sem_destroy(&ch->full);
    munmap(ch, sizeof(Channel));
    return 0;
}

Bu bölümde öğrendikleriniz

  • Named semaphore (sem_open) ilgisiz process'ler arasında kullanılır; unnamed (sem_init pshared=1) fork sonrası çalışır
  • PTHREAD_PROCESS_SHARED ile pthread mutex shared memory üzerine yerleştirilir
  • Producer-consumer pattern: empty/full semaphore çifti ile senkronize edilir

09 Hangisini seçmeli: karar matrisi

Doğru IPC mekanizması seçimi; kullanım senaryosu, performans gereksinimi ve geliştirme maliyeti dengesinde yapılır.

Büyük karar tablosu

Koşul Önerilen Mekanizma Neden
Parent-child, tek yön, basitpipe()En az API, fork ile doğal entegrasyon
Parent-child, çift yönsocketpair()Tek fd çifti, full-duplex
İlgisiz process'ler, tek yön, kalıcı kanalFIFOİsim tabanlı bağlantı, düşük karmaşıklık
İlgisiz process'ler, bidirectional / fd passingUnix domain socketSCM_RIGHTS, tam çift yön, çok istemci
Yüksek throughput, büyük veri bloklarıPOSIX shm + mutex/semSıfır kopya, en düşük latency
Öncelikli mesaj sıralama gerekiyorPOSIX mqPriority-based kuyruk, mq_notify
Farklı host'lar arasıTCP socket (AF_INET)Sadece TCP ağ üzeri çalışır

Latency sıralaması

  En hızlı ←──────────────────────────────────────────→ En yavaş

  POSIX shm    Unix socket    FIFO / pipe    TCP loopback
  (0 kopya)    (2 kopya)      (2 kopya)      (2 kopya + TCP)
  ~10 ns/op    ~1 µs/op       ~2 µs/op       ~5–20 µs/op
    

Bandwidth sıralaması

  En yüksek ←──────────────────────────────────────────→ En düşük

  POSIX shm / mmap    Unix socket    pipe    POSIX mq    TCP loopback
  RAM bant genişliği   ~4–8 GB/s    ~2 GB/s  msgsize     ~1–3 GB/s
    

Pratik tavsiyeler

Parent-child, tek yönpipe() kullan — en minimal API, kernel buffer ücretsiz, fork ile doğal
İlgisiz process'ler, basit mesajFIFO yeterli — mkfifo + open + read/write, overhead düşük
İlgisiz process'ler, bidirectional veya fd-passingUnix domain socket — nginx, systemd, DBus'ın tercihi
Yüksek throughput, düşük latencyPOSIX shm + semaphore — video frame, sensör verisi, büyük buffer paylaşımı
Öncelikli mesajlarPOSIX mq — gerçek zamanlı sistemlerde kontrol/veri ayırımı için
Farklı makine veya containerTCP/Unix socket — Unix socket container içi haberleşmede de tercih edilir

Yaygın hatalar ve kaçınma yolları

HataSonuçÇözüm
Shared memory'de senkronizasyon yokRace condition, veri bozulmasısem_t veya PTHREAD_PROCESS_SHARED mutex
FIFO'yu unlink etmemekSonraki başlatmada stale dosya kalırBaşlangıçta unlink + sonunda tekrar
shm_open'dan sonra ftruncate yapmamakmmap başarısız veya SIGBUSHer zaman ftruncate(fd, size) uygula
SIGPIPE'ı ignore etmemekOkuyucu kapandığında process crashsignal(SIGPIPE, SIG_IGN) veya MSG_NOSIGNAL
pipe() ile çift yönlü iletişimDeadlock riskisocketpair() veya iki pipe kullan

Bu bölümde öğrendikleriniz

  • Latency: shm > Unix socket > pipe/FIFO > TCP loopback
  • Bandwidth: shm/mmap > Unix socket > pipe > TCP
  • Senaryo eşleştirmesi: parent-child → pipe; ilgisiz → FIFO veya Unix socket; hız kritik → shm; öncelik → mq
  • Shared memory her zaman senkronizasyonla birlikte kullanılmalıdır