15-bo‘lim

Jarayonlararo aloqa (IPC)

Quvurlar, nomlangan quvurlar, dup2 va qobiq konveyeri, bo'lishilgan xotira, Unix soketlari va IPC usullarini taqqoslash.

🕑 17 daqiqa o‘qish 📄 861 so‘z 👁 1 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. IPC usullari
  2. Oddiy quvur
  3. Qobiq konveyerini qurish
  4. Nomlangan quvur (FIFO)
  5. Bo'lishilgan xotira
  6. Nomlangan bo'lishilgan xotira
  7. Unix domen soketi
  8. Usullarni taqqoslash
  9. Tizimdagi IPC obyektlarini ko'rish
  10. Xulosa

Jarayonlar bir-birining xotirasini ko'rmaydi - bu himoya. Lekin ularga baribir gaplashish kerak.

IPC usullari #

Jarayonlararo aloqa usullari Quvur (pipe) Bir yo'nalishli oqim Qarindosh jarayonlar ls | grep FIFO Fayl tizimida nomi bor Begona jarayonlar ham mkfifo Bo'lishilgan xotira Eng tez usul Sinxronizatsiya kerak shm_open + mmap Soket Ikki yo'nalishli Tarmoqqa ham chiqadi AF_UNIX / AF_INET Tanlash mezoni Oddiy oqim, ota-bola -> quvur Katta hajm, maksimal tezlik -> bo'lishilgan xotira Murakkab protokol, kelajakda tarmoq -> soket
Har bir usulning o'z o'rni bor

Oddiy quvur #

C
#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;
}
Natija
Otaga kelgan xabar: Salom, ota jarayon!
Uzunligi          : 19 bayt
Kerak bo'lmagan uchini yoping

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:

KimNimani yopadi
O'qiyotgan tomonYozish uchini (quvur[1])
Yozayotgan tomonO'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:

C
signal(SIGPIPE, SIG_IGN);

Keyin write xato o'rniga EPIPE qaytaradi - bu boshqarish osonroq.

Qobiq konveyerini qurish #

C
#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;
}
Natija
"ol" bilan boshlanadi: 2 ta
Konveyer tugadi
dup2 - qobiqning siri

ls | grep txt yozganingizda bash aynan shuni qiladi:

Natija
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:

C
int fd = open("fayl.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
dup2(fd, STDOUT_FILENO);
close(fd);
execvp("ls", argv);

Nomlangan quvur (FIFO) #

C
#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;
}
Natija
FIFO yaratildi, turi FIFO mi? ha
O'qilgan xabar: FIFO orqali xabar
Terminal
mkfifo /tmp/quvur
cat /tmp/quvur &
echo "salom" > /tmp/quvur
Natija
salom
FIFO ochilishi bloklaydi

open(nom, O_RDONLY) ikkinchi tomon ochmaguncha kutadi. Bu xossa ataylab shunday - u ikki jarayonni sinxronlaydi.

Kutmasdan ochish uchun:

C
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 #

C
#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;
}
Natija
Bo'lishilgan sanoq : 1000
Bo'lishilgan xabar : bola tugatdi
Nusxalash bo'ldimi?: yo'q - bir xil xotira
Bo'lishilgan xotira o'zi hech narsani sinxronlamaydi

Bu eng tez IPC usuli, chunki nusxalash yo'q - ikkala jarayon bir xil fizik sahifalarga yozadi.

Lekin aynan shu sababdan barcha poyga muammolari qaytadi:

MuammoYechim
Bir vaqtda yozishMutex yoki semafor
"Tayyor" ni bilishSemafor yoki shart o'zgaruvchisi
Yarim yozilgan strukturaSinxronizatsiya + 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 #

C
#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;
}
Natija
Yozilgan : nomlangan bo'lishilgan xotira
O'qilgan : nomlangan bo'lishilgan xotira
Bir xilmi? ha
Terminal
ls -l /dev/shm/
Natija
-rw------- 1 husanboy husanboy 4096 sen  9 11:42 sinov_shm

Unix domen soketi #

C
#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;
}
Natija
Server so'rov oldi: so'rov
Mijoz javob oldi : javob
Unix soketi TCP dan tez

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:

C
/* 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:

Terminal
psql -h /var/run/postgresql
redis-cli -s /var/run/redis/redis.sock

Usullarni taqqoslash #

UsulTezlikYo'nalishQarindoshlikSinxronizatsiya
QuvurO'rtaBir tomonlamaKerakAvtomatik
FIFOO'rtaBir tomonlamaKerak emasAvtomatik
Bo'lishilgan xotiraEng tezIkki tomonlamaKerak emasO'zingiz
Unix soketiYaxshiIkki tomonlamaKerak emasAvtomatik
TCP soketiSekinroqIkki tomonlamaKerak emasAvtomatik
SignalTezBir tomonlamaKerak emasYo'q (16-bo'lim)
C
#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;
}
Natija
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 #

Terminal
ipcs -a
Natija
------ 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
Terminal
ls -l /dev/shm/
ss -xl
Natija
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
Yetim IPC obyektlari

Jarayon qulaganda quvur va soketlar avtomatik yopiladi, lekin nomlangan obyektlar qoladi:

ObyektQayerda qoladiTozalash
FIFOFayl tizimidarm /tmp/quvur
POSIX shm/dev/shm/rm /dev/shm/nom
System V shmYadrodaipcrm -m <id>
Unix soket fayliFayl tizimidarm /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:

C
unlink(yol);
bind(fd, ...);

System V bo'lishilgan xotirasi esa qayta yuklashgacha qoladi - ipcs bilan muntazam tekshiring.

Amaliy topshiriq
  1. pipe va fork bilan boladan otaga xabar yuboring.
  2. Ishlatilmaydigan uchni yopmasdan nima bo'lishini sinang.
  3. dup2 bilan ikki bosqichli konveyer quring.
  4. mkfifo bilan nomlangan quvur yaratib, ikki terminaldan ishlating.
  5. MAP_SHARED bilan bo'lishilgan sanoq o'zgaruvchisini yarating.
  6. Semaforni pshared = 1 bilan sozlab, sinxronlang.
  7. shm_open bilan nomlangan xotira yaratib, /dev/shm da ko'ring.
  8. Unix domen soketi orqali so'rov-javob almashing.
  9. Bo'lishilgan xotira va quvur tezligini o'lchang.
  10. ipcs -a va ss -xl bilan tizimdagi IPC obyektlarini ko'ring.

Xulosa #

  • Quvurning ishlatilmaydigan uchini albatta yoping.
  • Barcha o'quvchilar yopilgach yozish SIGPIPE beradi.
  • dup2 qobiq 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_init da pshared = 1 bo'lmasa jarayonlar orasida ishlamaydi.
  • shm_open nomlangan xotira beradi - /dev/shm/ da ko'rinadi.
  • Unix soketi TCP dan tez va fd uzatishi mumkin.
  • Nomlangan IPC obyektlari jarayon qulasa ham qoladi.
  • Server bind dan oldin eski soket faylini o'chirishi kerak.

Keyingi bo'limda signallarni batafsil ko'rib chiqamiz.

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.