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üşük | Orta (64 KB buffer) | Geçici | Sadece parent-child | Çok düşük |
| FIFO | Düşük | Orta | Dosya sistemi | İlgisiz process'ler | Düşük |
| Unix socket | Düşük–orta | Yüksek | Geçici (abstract) / FS | Çok process, çok istemci | Orta |
| TCP loopback | Orta | Yüksek | Geçici | Evet, ağ üzeri de | Orta |
| mmap (anonim) | Çok düşük | Çok yüksek | Geçici | Sadece fork sonrası | Orta |
| POSIX shm | Çok düşük | Çok yüksek | /dev/shm | İlgisiz process'ler | Orta–yüksek |
| POSIX mq | Düşük | Orta | Kernel nesne | İlgisiz process'ler | Orta |
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.
#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
| Durum | Davranış |
|---|---|
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 |
#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_IGNveyaMSG_NOSIGNALile 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
# 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
#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;
}
#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:
/* 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ı
| Özellik | pipe() | FIFO |
|---|---|---|
| Dosya sistemi girişi | Yok | Var (mkfifo) |
| Process ilişkisi | Sadece fork ile parent-child | İlgisiz process'ler |
| Kalıcılık | Tüm fd'ler kapanınca yok olur | Dosya sisteminde kalır (unlink gerekir) |
| open() bloklama | Yok (fork sonrası fd zaten var) | Var — karşı taraf açana kadar bloklar |
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_NONBLOCKile 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
| Özellik | AF_UNIX | AF_INET (TCP loopback) |
|---|---|---|
| Yol | Dosya sistemi (/tmp/app.sock) veya abstract | IP:port (127.0.0.1:8080) |
| TCP/IP overhead | Yok | Var (checksum, seq number…) |
| Latency | Daha düşük | Daha yüksek |
| Ağ üzeri erişim | Hayır (sadece aynı host) | Evet |
| fd passing | Evet (SCM_RIGHTS) | Hayır |
Tam server/client örneği
#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;
}
#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.
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.
#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.
#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ı
| Özellik | pipe() | socketpair() |
|---|---|---|
| Yön | Tek yönlü (fds[0] oku, fds[1] yaz) | Çift yönlü (her iki uç da oku/yaz) |
| Çift yön için | İki pipe gerekir | Tek çift yeterli |
| fd sayısı | 2 (çift yön için 4) | 2 (çift yön yeterli) |
| SOCK_DGRAM desteği | Yok | Var (mesaj sınırları korunur) |
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_DGRAMile 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
#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
| Flag | Yazma Davranışı | Kullanım |
|---|---|---|
MAP_SHARED | Tüm mapping'ler aynı fiziksel sayfayı görür; yazma diğerlerini etkiler | IPC, dosyaya geri yazma |
MAP_PRIVATE | Copy-on-write: yazma anında özel kopya oluşturulur; diğerleri etkilenmez | Process-özel çalışma kopyası |
/dev/shm ile dosya tabanlı mmap
#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;
}
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_ANONYMOUSile fork sonrası aynı fiziksel sayfa paylaşılırMAP_PRIVATEcopy-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ışı
#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;
}
#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ı
Pipe vs shm hız karşılaştırması
1 GB veri transferi için kaba ölçüm (aynı host, Linux 5.15):
| Mekanizma | Süre | Throughput | Kopya |
|---|---|---|---|
| POSIX shm + pointer | ~10 ms | ~100 GB/s | Sıfır kopya |
| Unix socket | ~1200 ms | ~850 MB/s | 2 kopya (kernel buffer) |
| pipe | ~1500 ms | ~680 MB/s | 2 kopya (kernel buffer) |
| TCP loopback | ~2100 ms | ~480 MB/s | 2 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_unlinkile 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ı
#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;
}
#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
#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
# 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_unlinktam yaşam döngüsüdür- Yüksek priority mesajlar kuyrukta otomatik olarak öne geçer
mq_notifyile 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)
#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
#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
#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(®->mtx, &attr);
pthread_mutexattr_destroy(&attr);
reg->value = 0;
if (fork() == 0) {
pthread_mutex_lock(®->mtx);
reg->value = 99;
pthread_mutex_unlock(®->mtx);
_exit(0);
}
wait(NULL);
pthread_mutex_lock(®->mtx);
printf("value: %d\n", reg->value); /* 99 */
pthread_mutex_unlock(®->mtx);
pthread_mutex_destroy(®->mtx);
munmap(reg, sizeof(ShmRegion));
return 0;
}
Producer-consumer: shm + semaphore
#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_initpshared=1) fork sonrası çalışır PTHREAD_PROCESS_SHAREDile 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, basit | pipe() | En az API, fork ile doğal entegrasyon |
| Parent-child, çift yön | socketpair() | Tek fd çifti, full-duplex |
| İlgisiz process'ler, tek yön, kalıcı kanal | FIFO | İsim tabanlı bağlantı, düşük karmaşıklık |
| İlgisiz process'ler, bidirectional / fd passing | Unix domain socket | SCM_RIGHTS, tam çift yön, çok istemci |
| Yüksek throughput, büyük veri blokları | POSIX shm + mutex/sem | Sıfır kopya, en düşük latency |
| Öncelikli mesaj sıralama gerekiyor | POSIX mq | Priority-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
Yaygın hatalar ve kaçınma yolları
| Hata | Sonuç | Çözüm |
|---|---|---|
| Shared memory'de senkronizasyon yok | Race condition, veri bozulması | sem_t veya PTHREAD_PROCESS_SHARED mutex |
| FIFO'yu unlink etmemek | Sonraki başlatmada stale dosya kalır | Başlangıçta unlink + sonunda tekrar |
| shm_open'dan sonra ftruncate yapmamak | mmap başarısız veya SIGBUS | Her zaman ftruncate(fd, size) uygula |
| SIGPIPE'ı ignore etmemek | Okuyucu kapandığında process crash | signal(SIGPIPE, SIG_IGN) veya MSG_NOSIGNAL |
| pipe() ile çift yönlü iletişim | Deadlock riski | socketpair() 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