15-bo‘lim
Jarayonlararo aloqa (IPC)
Quvurlar, nomlangan quvurlar, dup2 va qobiq konveyeri, bo'lishilgan xotira, Unix soketlari va IPC usullarini taqqoslash.
Ushbu bo‘lim mundarijasi
Jarayonlar bir-birining xotirasini ko'rmaydi - bu himoya. Lekin ularga baribir gaplashish kerak.
IPC usullari #
Oddiy quvur #
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
int main(void) {
int quvur[2]; /* [0] - o'qish, [1] - yozish */
if (pipe(quvur) < 0) {
return 1;
}
fflush(stdout);
pid_t bola = fork();
if (bola < 0) {
return 1;
}
if (bola == 0) {
close(quvur[0]); /* bola o'qimaydi */
const char *xabar = "Salom, ota jarayon!";
write(quvur[1], xabar, strlen(xabar));
close(quvur[1]);
_exit(0);
}
close(quvur[1]); /* ota yozmaydi */
char bufer[64];
ssize_t n = read(quvur[0], bufer, sizeof bufer - 1);
bufer[n > 0 ? n : 0] = '\0';
close(quvur[0]);
wait(NULL);
printf("Otaga kelgan xabar: %s\n", bufer);
printf("Uzunligi : %zd bayt\n", n);
return 0;
}
Otaga kelgan xabar: Salom, ota jarayon!
Uzunligi : 19 bayt
Quvurning ishlatilmaydigan uchini yopmaslik - eng ko'p uchraydigan xato.
Agar ota quvur[1] ni yopmasa, read hech qachon 0
qaytarmaydi - chunki tizimda yozish uchi hali ochiq turibdi.
Dastur abadiy kutib qoladi.
Qoida:
| Kim | Nimani yopadi |
|---|---|
| O'qiyotgan tomon | Yozish uchini (quvur[1]) |
| Yozayotgan tomon | O'qish uchini (quvur[0]) |
Teskari holat ham xavfli: hamma o'qish uchini yopib, keyin yozsangiz
SIGPIPE olasiz va dastur o'ladi.
Tarmoq serverlarida shuning uchun SIGPIPE ni o'chirib qo'yishadi:
signal(SIGPIPE, SIG_IGN);
Keyin write xato o'rniga EPIPE qaytaradi - bu boshqarish
osonroq.
Qobiq konveyerini qurish #
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
int main(void) {
int quvur[2];
if (pipe(quvur) < 0) {
return 1;
}
fflush(stdout);
/* Birinchi bola: matn chiqaradi */
pid_t birinchi = fork();
if (birinchi == 0) {
close(quvur[0]);
dup2(quvur[1], STDOUT_FILENO); /* stdout -> quvur */
close(quvur[1]);
printf("olma\nanor\nbanan\nolcha\n");
fflush(stdout);
_exit(0);
}
/* Ikkinchi bola: "ol" bilan boshlanganini sanaydi */
pid_t ikkinchi = fork();
if (ikkinchi == 0) {
close(quvur[1]);
dup2(quvur[0], STDIN_FILENO); /* stdin <- quvur */
close(quvur[0]);
char qator[64];
int sanoq = 0;
while (fgets(qator, sizeof qator, stdin) != NULL) {
if (strncmp(qator, "ol", 2) == 0) {
sanoq++;
}
}
fprintf(stderr, "\"ol\" bilan boshlanadi: %d ta\n", sanoq);
fflush(stderr);
_exit(0);
}
close(quvur[0]);
close(quvur[1]);
waitpid(birinchi, NULL, 0);
waitpid(ikkinchi, NULL, 0);
printf("Konveyer tugadi\n");
return 0;
}
"ol" bilan boshlanadi: 2 ta
Konveyer tugadi
dup2 - qobiqning sirils | grep txt yozganingizda bash aynan shuni qiladi:
1. pipe(quvur)
2. fork() -> birinchi bola
dup2(quvur[1], 1) stdout ni quvurga ulash
execvp("ls", ...)
3. fork() -> ikkinchi bola
dup2(quvur[0], 0) stdin ni quvurdan olish
execvp("grep", ...)
4. Ikkala uchni yopish
5. wait
dup2(eski, yangi) - yangi deskriptorni yopib, uni eski ning
nusxasiga aylantiradi.
Muhim: dup2 dan keyin asl deskriptorni yopish kerak, aks
holda quvur ochiq qolaveradi.
Fayl yo'naltirish (ls > fayl.txt) ham xuddi shunday:
int fd = open("fayl.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
dup2(fd, STDOUT_FILENO);
close(fd);
execvp("ls", argv);
Nomlangan quvur (FIFO) #
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
int main(void) {
const char *nom = "/tmp/sinov_fifo";
unlink(nom);
if (mkfifo(nom, 0600) < 0) {
perror("mkfifo");
return 1;
}
struct stat h;
lstat(nom, &h);
printf("FIFO yaratildi, turi FIFO mi? %s\n",
S_ISFIFO(h.st_mode) ? "ha" : "yo'q");
fflush(stdout);
pid_t bola = fork();
if (bola < 0) {
return 1;
}
if (bola == 0) {
/* Yozuvchi */
int fd = open(nom, O_WRONLY);
if (fd >= 0) {
write(fd, "FIFO orqali xabar", 17);
close(fd);
}
_exit(0);
}
/* O'quvchi */
int fd = open(nom, O_RDONLY);
char bufer[64];
ssize_t n = read(fd, bufer, sizeof bufer - 1);
bufer[n > 0 ? n : 0] = '\0';
close(fd);
wait(NULL);
unlink(nom);
printf("O'qilgan xabar: %s\n", bufer);
return 0;
}
FIFO yaratildi, turi FIFO mi? ha
O'qilgan xabar: FIFO orqali xabar
mkfifo /tmp/quvur
cat /tmp/quvur &
echo "salom" > /tmp/quvur
salom
open(nom, O_RDONLY) ikkinchi tomon ochmaguncha kutadi. Bu
xossa ataylab shunday - u ikki jarayonni sinxronlaydi.
Kutmasdan ochish uchun:
int fd = open(nom, O_RDONLY | O_NONBLOCK);
Lekin ehtiyot bo'ling: O_WRONLY | O_NONBLOCK bilan ochishga
urinsangiz va o'quvchi yo'q bo'lsa, ENXIO xatosi qaytadi.
FIFO fayl tizimida ko'rinadi, lekin diskda joy olmaydi - ma'lumot faqat yadro buferida yashaydi (odatda 64 KB).
Bo'lishilgan xotira #
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <semaphore.h>
typedef struct {
sem_t tayyor;
int sanoq;
char xabar[64];
} Umumiy;
int main(void) {
Umumiy *u = mmap(NULL, sizeof(Umumiy),
PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS, -1, 0);
if (u == MAP_FAILED) {
return 1;
}
sem_init(&u->tayyor, 1, 0); /* 1 = jarayonlar orasida */
u->sanoq = 0;
u->xabar[0] = '\0';
fflush(stdout);
pid_t bola = fork();
if (bola < 0) {
return 1;
}
if (bola == 0) {
for (int i = 0; i < 1000; i++) {
u->sanoq++;
}
strcpy(u->xabar, "bola tugatdi");
sem_post(&u->tayyor); /* otaga signal */
_exit(0);
}
sem_wait(&u->tayyor); /* bolani kutamiz */
printf("Bo'lishilgan sanoq : %d\n", u->sanoq);
printf("Bo'lishilgan xabar : %s\n", u->xabar);
printf("Nusxalash bo'ldimi?: yo'q - bir xil xotira\n");
wait(NULL);
sem_destroy(&u->tayyor);
munmap(u, sizeof(Umumiy));
return 0;
}
Bo'lishilgan sanoq : 1000
Bo'lishilgan xabar : bola tugatdi
Nusxalash bo'ldimi?: yo'q - bir xil xotira
Bu eng tez IPC usuli, chunki nusxalash yo'q - ikkala jarayon bir xil fizik sahifalarga yozadi.
Lekin aynan shu sababdan barcha poyga muammolari qaytadi:
| Muammo | Yechim |
|---|---|
| Bir vaqtda yozish | Mutex yoki semafor |
| "Tayyor" ni bilish | Semafor yoki shart o'zgaruvchisi |
| Yarim yozilgan struktura | Sinxronizatsiya + volatile emas, atomiklar |
Muhim nuqta: sem_init(&sem, 1, 0) dagi ikkinchi argument 1
bo'lishi shart - u semaforni jarayonlar orasida ishlatish mumkin
qiladi. 0 bo'lsa faqat oqimlar orasida ishlaydi.
Semafor ham bo'lishilgan xotirada yashashi kerak - stekda emas.
Nomlangan bo'lishilgan xotira #
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/stat.h>
int main(void) {
const char *nom = "/sinov_shm";
size_t hajm = 4096;
shm_unlink(nom);
int fd = shm_open(nom, O_CREAT | O_RDWR, 0600);
if (fd < 0) {
perror("shm_open");
return 1;
}
if (ftruncate(fd, (off_t) hajm) < 0) {
return 1;
}
char *xotira = mmap(NULL, hajm, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0);
close(fd);
if (xotira == MAP_FAILED) {
return 1;
}
strcpy(xotira, "nomlangan bo'lishilgan xotira");
/* Ikkinchi marta ochib, o'sha ma'lumotni ko'ramiz */
int ikkinchi = shm_open(nom, O_RDONLY, 0600);
char *oqish = mmap(NULL, hajm, PROT_READ, MAP_SHARED, ikkinchi, 0);
close(ikkinchi);
printf("Yozilgan : nomlangan bo'lishilgan xotira\n");
printf("O'qilgan : %s\n", oqish);
printf("Bir xilmi? %s\n",
strcmp(oqish, "nomlangan bo'lishilgan xotira") == 0 ? "ha" : "yo'q");
munmap(oqish, hajm);
munmap(xotira, hajm);
shm_unlink(nom);
return 0;
}
Yozilgan : nomlangan bo'lishilgan xotira
O'qilgan : nomlangan bo'lishilgan xotira
Bir xilmi? ha
ls -l /dev/shm/
-rw------- 1 husanboy husanboy 4096 sen 9 11:42 sinov_shm
Unix domen soketi #
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/wait.h>
int main(void) {
const char *yol = "/tmp/sinov_soket";
unlink(yol);
int server = socket(AF_UNIX, SOCK_STREAM, 0);
if (server < 0) {
return 1;
}
struct sockaddr_un manzil;
memset(&manzil, 0, sizeof manzil);
manzil.sun_family = AF_UNIX;
strncpy(manzil.sun_path, yol, sizeof manzil.sun_path - 1);
if (bind(server, (struct sockaddr *) &manzil, sizeof manzil) < 0) {
perror("bind");
return 1;
}
listen(server, 1);
fflush(stdout);
pid_t bola = fork();
if (bola < 0) {
return 1;
}
if (bola == 0) {
/* Mijoz */
int mijoz = socket(AF_UNIX, SOCK_STREAM, 0);
if (connect(mijoz, (struct sockaddr *) &manzil, sizeof manzil) == 0) {
write(mijoz, "so'rov", 6);
char javob[32];
ssize_t n = read(mijoz, javob, sizeof javob - 1);
javob[n > 0 ? n : 0] = '\0';
printf("Mijoz javob oldi : %s\n", javob);
fflush(stdout);
}
close(mijoz);
_exit(0);
}
/* Server */
int ulanish = accept(server, NULL, NULL);
char bufer[32];
ssize_t n = read(ulanish, bufer, sizeof bufer - 1);
bufer[n > 0 ? n : 0] = '\0';
printf("Server so'rov oldi: %s\n", bufer);
fflush(stdout);
write(ulanish, "javob", 5);
close(ulanish);
wait(NULL);
close(server);
unlink(yol);
return 0;
}
Server so'rov oldi: so'rov
Mijoz javob oldi : javob
Bir mashina ichidagi aloqada AF_UNIX soketi 127.0.0.1 orqali
TCP dan 2-3 barobar tez ishlaydi.
Sabab: TCP/IP steki (paket sarlavhalari, nazorat yig'indisi, oyna boshqaruvi) butunlay chetlab o'tiladi.
Qo'shimcha imkoniyat - Unix soketlari fayl deskriptorlarini uzatishi mumkin:
/* SCM_RIGHTS orqali ochiq faylni boshqa jarayonga berish */
Bu Docker, systemd, X11 kabi tizimlarda ishlatiladi: bitta jarayon portni ochadi va uni boshqasiga uzatadi.
PostgreSQL, MySQL, Redis - hammasi lokal ulanish uchun Unix soketini taklif qiladi:
psql -h /var/run/postgresql
redis-cli -s /var/run/redis/redis.sock
Usullarni taqqoslash #
| Usul | Tezlik | Yo'nalish | Qarindoshlik | Sinxronizatsiya |
|---|---|---|---|---|
| Quvur | O'rta | Bir tomonlama | Kerak | Avtomatik |
| FIFO | O'rta | Bir tomonlama | Kerak emas | Avtomatik |
| Bo'lishilgan xotira | Eng tez | Ikki tomonlama | Kerak emas | O'zingiz |
| Unix soketi | Yaxshi | Ikki tomonlama | Kerak emas | Avtomatik |
| TCP soketi | Sekinroq | Ikki tomonlama | Kerak emas | Avtomatik |
| Signal | Tez | Bir tomonlama | Kerak emas | Yo'q (16-bo'lim) |
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <sys/mman.h>
#include <sys/wait.h>
#define TAKROR 20000
#define HAJM 256
static double vaqt(void) {
struct timespec t;
clock_gettime(CLOCK_MONOTONIC, &t);
return t.tv_sec + t.tv_nsec / 1e9;
}
int main(void) {
char *umumiy = mmap(NULL, HAJM, PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS, -1, 0);
if (umumiy == MAP_FAILED) {
return 1;
}
char manba[HAJM];
memset(manba, 'A', HAJM);
/* Bo'lishilgan xotiraga yozish */
double b = vaqt();
for (int i = 0; i < TAKROR; i++) {
memcpy(umumiy, manba, HAJM);
}
double xotira_vaqti = vaqt() - b;
/* Quvur orqali uzatish */
int quvur[2];
pipe(quvur);
b = vaqt();
for (int i = 0; i < TAKROR; i++) {
write(quvur[1], manba, HAJM);
char qabul[HAJM];
read(quvur[0], qabul, HAJM);
}
double quvur_vaqti = vaqt() - b;
close(quvur[0]);
close(quvur[1]);
munmap(umumiy, HAJM);
printf("%d marta %d bayt uzatildi\n\n", TAKROR, HAJM);
printf("Bo'lishilgan xotira quvurdan tezmi? %s\n",
xotira_vaqti < quvur_vaqti ? "ha" : "yo'q");
printf("Kamida 3 barobar tezmi? %s\n",
quvur_vaqti > xotira_vaqti * 3 ? "ha" : "yo'q");
printf("\nSabab: quvurda har uzatish 2 ta tizim chaqiruvi\n");
printf("va 2 marta nusxalash talab qiladi\n");
return 0;
}
20000 marta 256 bayt uzatildi
Bo'lishilgan xotira quvurdan tezmi? ha
Kamida 3 barobar tezmi? ha
Sabab: quvurda har uzatish 2 ta tizim chaqiruvi
va 2 marta nusxalash talab qiladi
Tizimdagi IPC obyektlarini ko'rish #
ipcs -a
------ Message Queues --------
key msqid owner perms used-bytes messages
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x00000000 32769 husanboy 600 4194304 2 dest
------ Semaphore Arrays --------
key semid owner perms nsems
ls -l /dev/shm/
ss -xl
Netid State Local Address:Port
u_str LISTEN /run/systemd/private 24567
u_str LISTEN /var/run/docker.sock 31204
u_str LISTEN /var/run/postgresql/.s.PGSQL.5432 42118
Jarayon qulaganda quvur va soketlar avtomatik yopiladi, lekin nomlangan obyektlar qoladi:
| Obyekt | Qayerda qoladi | Tozalash |
|---|---|---|
| FIFO | Fayl tizimida | rm /tmp/quvur |
| POSIX shm | /dev/shm/ | rm /dev/shm/nom |
| System V shm | Yadroda | ipcrm -m <id> |
| Unix soket fayli | Fayl tizimida | rm /tmp/soket |
Eng ko'p uchraydigan holat: server qulagach soket fayli qoladi va
qayta ishga tushganda bind EADDRINUSE beradi.
Shuning uchun serverlar bind dan oldin eski faylni o'chiradi:
unlink(yol);
bind(fd, ...);
System V bo'lishilgan xotirasi esa qayta yuklashgacha qoladi -
ipcs bilan muntazam tekshiring.
pipevaforkbilan boladan otaga xabar yuboring.- Ishlatilmaydigan uchni yopmasdan nima bo'lishini sinang.
dup2bilan ikki bosqichli konveyer quring.mkfifobilan nomlangan quvur yaratib, ikki terminaldan ishlating.MAP_SHAREDbilan bo'lishilgan sanoq o'zgaruvchisini yarating.- Semaforni
pshared = 1bilan sozlab, sinxronlang. shm_openbilan nomlangan xotira yaratib,/dev/shmda ko'ring.- Unix domen soketi orqali so'rov-javob almashing.
- Bo'lishilgan xotira va quvur tezligini o'lchang.
ipcs -avass -xlbilan tizimdagi IPC obyektlarini ko'ring.
Xulosa #
- Quvurning ishlatilmaydigan uchini albatta yoping.
- Barcha o'quvchilar yopilgach yozish
SIGPIPEberadi. dup2qobiq konveyerlari va yo'naltirishning asosi.- FIFO fayl tizimida nomga ega, begona jarayonlar ham ishlatadi.
- FIFO ochilishi ikkinchi tomonni kutadi.
- Bo'lishilgan xotira eng tez, lekin sinxronizatsiya sizda.
sem_initdapshared = 1bo'lmasa jarayonlar orasida ishlamaydi.shm_opennomlangan xotira beradi -/dev/shm/da ko'rinadi.- Unix soketi TCP dan tez va fd uzatishi mumkin.
- Nomlangan IPC obyektlari jarayon qulasa ham qoladi.
- Server
binddan oldin eski soket faylini o'chirishi kerak.
Keyingi bo'limda signallarni batafsil ko'rib chiqamiz.
O‘qish tarixini saqlamoqchimisiz?
Tizimga kirsangiz, tugatgan bo‘limlaringiz saqlanadi va qoldirgan joyingizdan davom etasiz.
Xatolik topdingizmi?
Imlo xatosi, ishlamaydigan kod yoki noto‘g‘ri ma‘lumotni ko‘rsangiz - bizga xabar bering. Har bir xabar administrator tomonidan ko‘rib chiqiladi.