Unix-da quvurlar qanday amalga oshiriladi

Unix-da quvurlar qanday amalga oshiriladi
Ushbu maqola Unix yadrosida quvurlarni amalga oshirishni tasvirlaydi. “Yaqinda chiqqan maqoladan biroz xafa bo‘ldim.Unix-da quvurlar qanday ishlaydi?" chiqdi yo'q ichki tuzilishi haqida. Men qiziqib qoldim va javob topish uchun eski manbalarni qazib oldim.

Biz nima haqida gapiryapmiz?

Pipelines, "ehtimol, Unix-dagi eng muhim ixtiro", kichik dasturlarni bir-biriga bog'lashning asosiy Unix falsafasining belgilovchi xususiyati, shuningdek, buyruq satridagi tanish belgidir:

$ echo hello | wc -c
6

Ushbu funksionallik yadro tomonidan taqdim etilgan tizim chaqiruviga bog'liq pipe, bu hujjatlar sahifalarida tasvirlangan quvur (7) и quvur (2):

Quvur quvurlari jarayonlararo aloqa uchun bir yo'nalishli kanalni ta'minlaydi. Quvur liniyasida kirish (yozish oxiri) va chiqish (o'qish oxiri) mavjud. Quvurning kirish qismiga yozilgan ma'lumotlar chiqishda o'qilishi mumkin.

Quvur liniyasi qo'ng'iroq yordamida yaratiladi pipe(2), bu ikkita fayl identifikatorini qaytaradi: biri quvur liniyasining kirishiga, ikkinchisi esa chiqishga.

Yuqoridagi buyruqdan olingan kuzatuv quvur liniyasining yaratilishini va u orqali bir jarayondan ikkinchisiga ma'lumotlar oqimini ko'rsatadi:

$ strace -qf -e execve,pipe,dup2,read,write 
    sh -c 'echo hello | wc -c'

execve("/bin/sh", ["sh", "-c", "echo hello | wc -c"], …)
pipe([3, 4])                            = 0
[pid 2604795] dup2(4, 1)                = 1
[pid 2604795] write(1, "hellon", 6)    = 6
[pid 2604796] dup2(3, 0)                = 0
[pid 2604796] execve("/usr/bin/wc", ["wc", "-c"], …)
[pid 2604796] read(0, "hellon", 16384) = 6
[pid 2604796] write(1, "6n", 2)        = 2

Ota-ona jarayoni chaqiradi pipe()o'rnatilgan fayl deskriptorlarini olish uchun. Bitta asosiy jarayon bitta tutqichga yozadi va boshqa jarayon boshqa tutqichdan bir xil ma'lumotlarni o'qiydi. Shell stdin va stdout-ga mos kelish uchun 2 va 3-deskriptorlarni "nomini o'zgartirish" uchun dup4-dan foydalanadi.

Quvurlar bo'lmasa, qobiq fayldan ma'lumotlarni o'qish uchun bir jarayonning natijasini faylga yozishi va uni boshqa jarayonga o'tkazishi kerak edi. Natijada, biz ko'proq resurslar va disk maydonini behuda sarflaymiz. Biroq, quvur liniyalari nafaqat yaxshi, chunki ular vaqtinchalik fayllardan foydalanishni oldini olishga imkon beradi:

Agar jarayon bo'sh quvurdan o'qishga harakat qilsa read(2) ma'lumotlar mavjud bo'lgunga qadar bloklanadi. Agar jarayon to'liq quvur liniyasiga yozishga harakat qilsa, u holda write(2) yozishni amalga oshirish uchun quvur liniyasidan yetarlicha ma'lumot o'qilmaguncha bloklanadi.

POSIX talabi kabi, bu muhim xususiyatdir: quvur liniyasiga qadar yozish PIPE_BUF bayt (kamida 512) atomik bo'lishi kerak, shuning uchun jarayonlar bir-biri bilan quvur liniyasi orqali oddiy fayllar (bunday kafolatlarni bermaydi) aloqa qila olmaydigan tarzda bog'lanishi mumkin.

Oddiy fayldan foydalanilganda, jarayon o'zining barcha natijalarini unga yozib, boshqa jarayonga o'tkazishi mumkin. Yoki jarayonlar juda parallel rejimda ishlashi mumkin, bunda yozish yoki o'qish tugallanganda bir-birini xabardor qilish uchun tashqi signalizatsiya mexanizmi (semafor kabi) qo'llaniladi. Konveyerlar bizni bu qiyinchiliklardan qutqaradi.

Biz nimani qidiryapmiz?

Konveyer qanday ishlashini tasavvur qilishingiz osonroq bo'lishi uchun men buni oddiy so'zlar bilan tushuntiraman. Xotirada bufer va ba'zi bir holatni ajratishingiz kerak bo'ladi. Buferdan ma'lumotlarni qo'shish va o'chirish uchun sizga funktsiyalar kerak bo'ladi. Fayl identifikatorlarida o'qish va yozish operatsiyalari paytida funksiyalarni chaqirish uchun sizga ba'zi vositalar kerak bo'ladi. Va yuqorida tavsiflangan maxsus xatti-harakatni amalga oshirish uchun sizga qulflar kerak bo'ladi.

Endi biz noaniq aqliy modelimizni tasdiqlash yoki rad etish uchun yorqin chiroq ostida yadro manba kodini so'roq qilishga tayyormiz. Lekin har doim kutilmagan hodisalarga tayyor bo'ling.

Qayerga qarayapmiz?

Mashhur kitobimning nusxasi qaerdaligini bilmayman "Arslonlar kitob"Unix 6 manba kodi bilan, lekin rahmat Unix meros jamiyati dan onlayn qidirishingiz mumkin manba kodi Unix ning hatto eski versiyalari ham.

TUHS arxivlarini kezish muzeyga tashrif buyurishga o'xshaydi. Biz umumiy tariximizga nazar tashlashimiz mumkin va men bu materialni eski lentalar va nashrlardan asta-sekin tiklash uchun ko'p yillik sa'y-harakatlarni hurmat qilaman. Va men hali ham yo'qolgan o'sha parchalarni juda yaxshi bilaman.

Konveyerlarning qadimiy tarixiga bo'lgan qiziqishimizni qondirib, taqqoslash uchun zamonaviy yadrolarga qarashimiz mumkin.

Ayni paytda, pipe jadvaldagi 42-sonli tizim chaqiruvi sysent[]. Tasodifmi?

An'anaviy Unix yadrolari (1970–1974)

Men hech qanday iz topolmadim pipe(2) ichida ham PDP-7 Unix (1970 yil yanvar), na ichida Unix ning birinchi nashri (1971 yil noyabr), to'liq bo'lmagan manba kodida ham ikkinchi nashr (1972 yil iyun).

Bu haqda TUHS xabar bermoqda Unix ning uchinchi nashri (1973 yil fevral) konveyerlar bilan birinchi versiya bo'ldi:

Unix 1973rd Edition assembleya tilida yozilgan yadroga ega oxirgi versiya, lekin ayni paytda quvur liniyasiga ega birinchi versiya edi. XNUMX yil davomida uchinchi nashrni takomillashtirish bo'yicha ishlar olib borildi, yadro C tilida qayta yozildi va shuning uchun Unixning to'rtinchi nashri paydo bo'ldi.

Bir o'quvchi hujjatning skanerini topdi, unda Dag MakIlroy "dasturlarni bog 'shlangi kabi ulash" g'oyasini taklif qildi.

Unix-da quvurlar qanday amalga oshiriladi
Brayan Kerniganning kitobidaUnix: tarix va xotiralar", konveyerlarning paydo bo'lishi tarixida ushbu hujjat ham eslatib o'tilgan: "... u 30 yil davomida Bell Labsdagi ofisimda devorga osilgan." Bu yerga McIlroy bilan intervyu, va yana bir hikoya MakIlroyning 2014 yilda yozilgan ishi:

Unix paydo bo'lgach, koroutinlarga bo'lgan qiziqishim meni OS muallifi Ken Tompsondan jarayonga yozilgan ma'lumotlarning nafaqat qurilmaga, balki boshqa jarayonga chiqishiga ham ruxsat berishini so'rashga majbur qildi. Ken buni mumkin deb qaror qildi. Biroq, minimalist sifatida u har bir tizim funktsiyasi muhim rol o'ynashini xohladi. To'g'ridan-to'g'ri jarayonlar o'rtasida yozish oraliq faylga yozishdan katta afzallikmi? Men “quvur” degan jozibali nomi va jarayonlar o‘rtasidagi o‘zaro ta’sir sintaksisi tavsifi bilan aniq taklif qilganimdagina Ken nihoyat: “Men buni qilaman!” deb xitob qildi.

Va qildi. Bir taqdirli oqshom Ken yadro va qobiqni o'zgartirdi, kirishni qanday qabul qilishini standartlashtirish uchun bir nechta standart dasturlarni tuzatdi (bu quvur liniyasidan kelishi mumkin) va shuningdek, fayl nomlarini o'zgartirdi. Ertasi kuni quvurlar dasturlarda juda keng qo'llanila boshlandi. Haftaning oxiriga kelib, kotiblar ularni matn protsessorlaridan printerga hujjatlarni yuborish uchun ishlatishdi. Biroz vaqt o'tgach, Ken asl API va quvurlardan foydalanishni o'sha vaqtdan beri ishlatib kelinayotgan toza konventsiyalar bilan o'rash uchun sintaksisni almashtirdi.

Afsuski, Unix yadrosining uchinchi nashri uchun manba kodi yo'qolgan. Va bizda yadro manba kodi C tilida yozilgan bo'lsa ham to'rtinchi nashr, 1973 yil noyabr oyida chiqarilgan, ammo u rasmiy nashrdan bir necha oy oldin chiqdi va quvur liniyasini o'z ichiga olmaydi. Ushbu afsonaviy Unix funksiyasi uchun manba kodi, ehtimol, abadiy yo'qolganligi juda achinarli.

Bizda matnli hujjatlar mavjud pipe(2) ikkala nashrdan ham, shuning uchun siz hujjatlarni qidirishdan boshlashingiz mumkin uchinchi nashr (ba'zi so'zlar uchun tagiga chizilgan "qo'lda", harflar qatori ^H, keyin pastki chiziq!). Bu proto-pipe(2) assembler tilida yozilgan va faqat bitta fayl identifikatorini qaytaradi, lekin kutilgan asosiy funksiyani allaqachon taqdim etadi:

Tizim chaqiruvi quvur quvur liniyasi deb ataladigan kirish/chiqish mexanizmini yaratadi. Qaytarilgan fayl deskriptoridan o'qish va yozish operatsiyalari uchun foydalanish mumkin. Quvurga biror narsa yozilsa, 504 baytgacha bo'lgan ma'lumotlar buferlanadi, shundan so'ng yozish jarayoni to'xtatiladi. Quvur liniyasidan o'qiyotganda, buferlangan ma'lumotlar olib tashlanadi.

Keyingi yil yadro C da qayta yozildi va quvur (2) to'rtinchi nashrda prototipi bilan zamonaviy ko'rinishga ega bo'ldi "pipe(fildes)"

Tizim chaqiruvi quvur quvur liniyasi deb ataladigan kirish/chiqish mexanizmini yaratadi. Qaytarilgan fayl deskriptorlari o'qish va yozish operatsiyalarida ishlatilishi mumkin. Quvur liniyasiga biror narsa yozilsa, r1 da qaytarilgan tutqich (resp. fildes [1]) ishlatiladi, 4096 bayt ma'lumotlarga buferlanadi, shundan so'ng yozish jarayoni to'xtatiladi. Quvur liniyasidan o'qiyotganda, r0 ga qaytarilgan tutqich (javob. fildes[0]) ma'lumotlarni oladi.

Quvurni aniqlagandan so'ng, ikkita (yoki undan ko'p) aloqa jarayoni (keyingi qo'ng'iroqlar natijasida yaratiladi) deb taxmin qilinadi. Vilkalar) qo'ng'iroqlar yordamida quvur liniyasidan ma'lumotlarni uzatadi o'qib и yozish.

Qobiqda quvur liniyasi orqali bog'langan jarayonlarning chiziqli qatorini aniqlash uchun sintaksis mavjud.

Faqat bitta uchi (barcha yozish fayli identifikatorlari yopiq) bo'sh quvur liniyasidan (buferlangan ma'lumotlarni o'z ichiga olmaydi) o'qish uchun qo'ng'iroqlar "fayl oxiri" ni qaytaradi. Xuddi shunday vaziyatda yozish uchun qo'ng'iroqlar e'tiborga olinmaydi.

Eng erta saqlanib qolgan quvur liniyasini amalga oshirish tashvishlar Unix ning beshinchi nashriga (1974 yil iyun), lekin u keyingi nashrda paydo bo'lgan bilan deyarli bir xil. Sharhlar endigina qo'shildi, shuning uchun siz beshinchi nashrni o'tkazib yuborishingiz mumkin.

Unixning oltinchi nashri (1975)

Unix manba kodini o'qishni boshlaylik oltinchi nashr (1975 yil may). Katta rahmat sherlar oldingi versiyalarning manbalaridan ko'ra topish osonroq:

Ko'p yillar davomida kitob sherlar Bell Laboratoriyalaridan tashqarida mavjud bo'lgan Unix yadrosidagi yagona hujjat edi. Oltinchi nashr litsenziyasi o'qituvchilarga o'zining manba kodidan foydalanishga ruxsat bergan bo'lsa-da, ettinchi nashr litsenziyasi bu imkoniyatni istisno qildi, shuning uchun kitob noqonuniy yozuv mashinkasi nusxalari ko'rinishida tarqatildi.

Bugun siz kitobning qayta nashrini sotib olishingiz mumkin, uning qopqog'ida talabalar nusxa ko'chirish mashinasida ko'rsatilgan. Va Uorren Toomey (TUHS loyihasini boshlagan) tufayli siz yuklab olishingiz mumkin Oltinchi nashr uchun manba kodi bilan PDF fayl. Men sizga faylni yaratish uchun qancha kuch sarflangani haqida fikr bermoqchiman:

15 yildan ko'proq vaqt oldin, men berilgan manba kodining nusxasini yozdim sherlar, chunki boshqa nusxalar soni noma'lum bo'lgan nusxaning sifati menga yoqmadi. TUHS hali mavjud emas edi va men eski manbalarga kira olmadim. Ammo 1988 yilda men PDP9 kompyuterining zaxira nusxasini o'z ichiga olgan eski 11 trekli lentani topdim. Uning ishlayotganligini aytish qiyin edi, lekin buzilmagan /usr/src/ daraxti bor edi, unda ko'pchilik fayllar 1979 yil bilan belgilangan va o'sha paytda ham qadimiy ko'rinardi. Men ishonganimdek, bu ettinchi nashr yoki uning hosilasi PWB edi.

Men topilmani asos qilib oldim va manbalarni oltinchi nashrga qo'lda tahrir qildim. Kodning ba'zilari o'zgarishsiz qoldi, lekin ba'zilari zamonaviy += belgisini eskirgan =+ ga o'zgartirib, biroz tahrirlanishi kerak edi. Ba'zi narsalar oddiygina o'chirildi, ba'zilari esa butunlay qayta yozilishi kerak edi, lekin juda ko'p emas.

Va bugun biz TUHS-da oltinchi nashrning manba kodini onlayn o'qishimiz mumkin arxiv, unga Dennis Ritchining qo'li bor edi.

Aytgancha, bir qarashda, Kernigan va Ritchi davridan oldingi C-kodning asosiy xususiyati uning qisqalik. Men saytimning nisbatan tor ko'rish maydoniga moslashish uchun keng ko'lamli tahrirlarsiz kod qismlarini kiritishim tez-tez emas.

Boshida /usr/sys/ken/pipe.c tushuntirish izohi mavjud (va ha, yana ko'p narsalar mavjud /usr/sys/dmr):

/*
 * Max allowable buffering per pipe.
 * This is also the max size of the
 * file created to implement the pipe.
 * If this size is bigger than 4096,
 * pipes will be implemented in LARG
 * files, which is probably not good.
 */
#define    PIPSIZ    4096

Bufer hajmi to'rtinchi nashrdan beri o'zgarmadi. Ammo bu erda biz hech qanday davlat hujjatisiz, quvurlar bir vaqtlar fayllarni zaxira saqlash sifatida ishlatganini ko'ramiz!

LARG fayllarga kelsak, ular mos keladi INOD bayrog'i LARG, uni qayta ishlash uchun "katta manzillash algoritmi" ishlatadi bilvosita bloklar katta fayl tizimlarini qo'llab-quvvatlash uchun. Ken ularni ishlatmaslik yaxshiroq degani uchun, men uning so'zini mamnuniyat bilan qabul qilaman.

Mana haqiqiy tizim chaqiruvi pipe:

/*
 * The sys-pipe entry.
 * Allocate an inode on the root device.
 * Allocate 2 file structures.
 * Put it all together with flags.
 */
pipe()
{
    register *ip, *rf, *wf;
    int r;

    ip = ialloc(rootdev);
    if(ip == NULL)
        return;
    rf = falloc();
    if(rf == NULL) {
        iput(ip);
        return;
    }
    r = u.u_ar0[R0];
    wf = falloc();
    if(wf == NULL) {
        rf->f_count = 0;
        u.u_ofile[r] = NULL;
        iput(ip);
        return;
    }
    u.u_ar0[R1] = u.u_ar0[R0]; /* wf's fd */
    u.u_ar0[R0] = r;           /* rf's fd */
    wf->f_flag = FWRITE|FPIPE;
    wf->f_inode = ip;
    rf->f_flag = FREAD|FPIPE;
    rf->f_inode = ip;
    ip->i_count = 2;
    ip->i_flag = IACC|IUPD;
    ip->i_mode = IALLOC;
}

Izoh bu erda nima sodir bo'layotganini aniq tasvirlab beradi. Ammo kodni tushunish unchalik oson emas, qisman yo'l tufayli "struct foydalanuvchisi u» va ro'yxatga olish R0 и R1 tizim chaqiruv parametrlari va qaytish qiymatlari uzatiladi.

Keling, sinab ko'raylik ialloc() diskka qo'ying inode (indeks tutqichi), va yordami bilan falloc() - ikkitasini xotiraga joylashtiring fayl. Agar hamma narsa yaxshi bo'lsa, biz ushbu fayllarni quvur liniyasining ikki uchi sifatida aniqlash uchun bayroqchalarni o'rnatamiz, ularni bir xil inodega yo'naltiramiz (uning mos yozuvlar soni 2 ga o'rnatiladi) va inodeni o'zgartirilgan va ishlatilayotgan deb belgilaymiz. Murojaatlarga e'tibor bering Men qo'yaman() yangi inodedagi mos yozuvlar sonini kamaytirish uchun xato yo'llarida.

pipe() o'tishi kerak R0 и R1 o'qish va yozish uchun fayl deskriptor raqamlarini qaytaring. falloc() fayl tuzilishiga ko'rsatgichni qaytaradi, balki orqali ham "qaytadi" u.u_ar0[R0] va fayl deskriptori. Ya'ni, kod saqlanadi r o'qish uchun fayl identifikatori va to'g'ridan-to'g'ri yozish uchun fayl deskriptorini tayinlaydi u.u_ar0[R0] ikkinchi qo'ng'iroqdan keyin falloc().

Bayroq FPIPE, biz quvur liniyasini yaratishda o'rnatamiz, funktsiyaning harakatini nazorat qiladi sys2.c da rdwr()maxsus kiritish/chiqarish tartiblarini chaqirish:

/*
 * common code for read and write calls:
 * check permissions, set base, count, and offset,
 * and switch out to readi, writei, or pipe code.
 */
rdwr(mode)
{
    register *fp, m;

    m = mode;
    fp = getf(u.u_ar0[R0]);
        /* … */

    if(fp->f_flag&FPIPE) {
        if(m==FREAD)
            readp(fp); else
            writep(fp);
    }
        /* … */
}

Keyin funksiya readp() в pipe.c quvur liniyasidan ma'lumotlarni o'qiydi. Ammo amalga oshirishni boshidan kuzatib borish yaxshiroqdir writep(). Shunga qaramay, argumentlarni uzatish konventsiyalari tufayli kod yanada murakkablashdi, ammo ba'zi tafsilotlarni o'tkazib yuborish mumkin.

writep(fp)
{
    register *rp, *ip, c;

    rp = fp;
    ip = rp->f_inode;
    c = u.u_count;

loop:
    /* If all done, return. */

    plock(ip);
    if(c == 0) {
        prele(ip);
        u.u_count = 0;
        return;
    }

    /*
     * If there are not both read and write sides of the
     * pipe active, return error and signal too.
     */

    if(ip->i_count < 2) {
        prele(ip);
        u.u_error = EPIPE;
        psignal(u.u_procp, SIGPIPE);
        return;
    }

    /*
     * If the pipe is full, wait for reads to deplete
     * and truncate it.
     */

    if(ip->i_size1 == PIPSIZ) {
        ip->i_mode =| IWRITE;
        prele(ip);
        sleep(ip+1, PPIPE);
        goto loop;
    }

    /* Write what is possible and loop back. */

    u.u_offset[0] = 0;
    u.u_offset[1] = ip->i_size1;
    u.u_count = min(c, PIPSIZ-u.u_offset[1]);
    c =- u.u_count;
    writei(ip);
    prele(ip);
    if(ip->i_mode&IREAD) {
        ip->i_mode =& ~IREAD;
        wakeup(ip+2);
    }
    goto loop;
}

Biz quvur liniyasi kiritishiga bayt yozmoqchimiz u.u_count. Avval biz inodeni qulflashimiz kerak (pastga qarang plock/prele).

Keyin inode mos yozuvlar hisoblagichini tekshiramiz. Quvurning har ikki uchi ochiq qolar ekan, hisoblagich 2 ga teng bo'lishi kerak. Biz bitta bo'g'inni ushlab turamiz (dan rp->f_inode), shuning uchun hisoblagich 2 dan kam bo'lsa, bu o'qish jarayoni quvur liniyasining oxirini yopib qo'yganligini anglatishi kerak. Boshqacha qilib aytganda, biz yopiq quvur liniyasiga yozishga harakat qilmoqdamiz va bu xato. Birinchi marta xato kodi EPIPE va signal SIGPIPE Unix ning oltinchi nashrida paydo bo'ldi.

Ammo konveyer ochiq bo'lsa ham, u to'la bo'lishi mumkin. Bunday holda, biz qulfni bo'shatamiz va boshqa jarayon quvur liniyasidan o'qiladi va unda etarli joy bo'shatiladi degan umidda uxlab qolamiz. Uyg'onganimizdan so'ng, biz boshiga qaytamiz, qulfni yana osib qo'yamiz va yangi yozish tsiklini boshlaymiz.

Agar quvur liniyasida bo'sh joy etarli bo'lsa, biz unga ma'lumotlarni yozamiz yozing(). Parametr i_size1 inode da (agar quvur liniyasi bo'sh bo'lsa, u 0 ga teng bo'lishi mumkin) u allaqachon o'z ichiga olgan ma'lumotlarning oxirini ko'rsatadi. Ro'yxatga olish joyi etarli bo'lsa, biz quvur liniyasini to'ldirishimiz mumkin i_size1 uchun PIPESIZ. Keyin biz qulfni bo'shatamiz va quvur liniyasidan o'qishni kutayotgan har qanday jarayonni uyg'otishga harakat qilamiz. Biz kerak bo'lganda qancha bayt yozishga muvaffaq bo'lganimizni bilish uchun boshiga qaytamiz. Agar u muvaffaqiyatsiz bo'lsa, biz yangi ro'yxatga olish tsiklini boshlaymiz.

Odatda parametr i_mode inode ruxsatlarni saqlash uchun ishlatiladi r, w и x. Ammo quvur liniyasi bo'lsa, biz bitlar yordamida qandaydir jarayon yozish yoki o'qishni kutayotganini bildiramiz IREAD и IWRITE mos ravishda. Jarayon bayroq va qo'ng'iroqlarni o'rnatadi sleep(), va kelajakda ba'zi boshqa jarayonlar sabab bo'lishi kutilmoqda wakeup().

Haqiqiy sehr ichida sodir bo'ladi sleep() и wakeup(). Ular joriy yilda amalga oshiriladi slp.c, mashhur "Siz buni tushunishingiz kutilmaydi" sharhining manbai. Yaxshiyamki, biz kodni tushunishimiz shart emas, shunchaki ba'zi izohlarga qarang:

/*
 * Give up the processor till a wakeup occurs
 * on chan, at which time the process
 * enters the scheduling queue at priority pri.
 * The most important effect of pri is that when
 * pri<0 a signal cannot disturb the sleep;
 * if pri>=0 signals will be processed.
 * Callers of this routine must be prepared for
 * premature return, and check that the reason for
 * sleeping has gone away.
 */
sleep(chan, pri) /* … */

/*
 * Wake up all processes sleeping on chan.
 */
wakeup(chan) /* … */

Bunga sabab bo'lgan jarayon sleep() ma'lum bir kanal uchun keyinchalik boshqa jarayon bilan uyg'onishi mumkin, bu esa sabab bo'ladi wakeup() Xuddi shu kanal uchun. writep() и readp() bunday juftlashgan qo'ng'iroqlar orqali o'z harakatlarini muvofiqlashtirish. shu esta tutilsinki pipe.c har doim ustuvorlikni beradi PPIPE chaqirilganda sleep(), shunday sleep() signal bilan uzilishi mumkin.

Endi bizda funktsiyani tushunish uchun hamma narsa bor readp():

readp(fp)
int *fp;
{
    register *rp, *ip;

    rp = fp;
    ip = rp->f_inode;

loop:
    /* Very conservative locking. */

    plock(ip);

    /*
     * If the head (read) has caught up with
     * the tail (write), reset both to 0.
     */

    if(rp->f_offset[1] == ip->i_size1) {
        if(rp->f_offset[1] != 0) {
            rp->f_offset[1] = 0;
            ip->i_size1 = 0;
            if(ip->i_mode&IWRITE) {
                ip->i_mode =& ~IWRITE;
                wakeup(ip+1);
            }
        }

        /*
         * If there are not both reader and
         * writer active, return without
         * satisfying read.
         */

        prele(ip);
        if(ip->i_count < 2)
            return;
        ip->i_mode =| IREAD;
        sleep(ip+2, PPIPE);
        goto loop;
    }

    /* Read and return */

    u.u_offset[0] = 0;
    u.u_offset[1] = rp->f_offset[1];
    readi(ip);
    rp->f_offset[1] = u.u_offset[1];
    prele(ip);
}

Ushbu funktsiyani pastdan yuqoriga qarab o'qish osonroq bo'lishi mumkin. "O'qish va qaytarish" filiali odatda quvur liniyasida ba'zi ma'lumotlar mavjud bo'lganda ishlatiladi. Bunday holda biz foydalanamiz Readi() biz joriy ma'lumotlardan boshlab mavjud bo'lgan ko'p ma'lumotlarni o'qiymiz f_offset o'qing va keyin mos keladigan ofset qiymatini yangilang.

Keyingi o'qishlarda, agar o'qish ofsetiga erishilgan bo'lsa, quvur liniyasi bo'sh bo'ladi i_size1 inode da. Biz pozitsiyani 0 ga qaytaramiz va quvur liniyasiga yozmoqchi bo'lgan har qanday jarayonni uyg'otishga harakat qilamiz. Biz bilamizki, konveyer to'lganida, writep() uxlab qoladi ip+1. Va endi quvur liniyasi bo'sh, biz uni yozish jarayonini davom ettirish uchun uyg'otishimiz mumkin.

Agar sizda o'qish uchun hech narsa bo'lmasa, unda readp() bayroqni o'rnatishi mumkin IREAD va uxlab qoling ip+2. Biz uni nima uyg'otishini bilamiz writep(), u quvur liniyasiga ba'zi ma'lumotlarni yozganda.

ga sharhlar readi() va writei() orqali parametrlarni o'tkazish o'rniga buni tushunishga yordam beradi "u"Biz ularga faylni, joylashuvni, xotirada buferni oladigan va o'qish yoki yozish uchun baytlar sonini hisoblaydigan oddiy kiritish-chiqarish funktsiyalari kabi muomala qilishimiz mumkin.

/*
 * Read the file corresponding to
 * the inode pointed at by the argument.
 * The actual read arguments are found
 * in the variables:
 *    u_base        core address for destination
 *    u_offset    byte offset in file
 *    u_count        number of bytes to read
 *    u_segflg    read to kernel/user
 */
readi(aip)
struct inode *aip;
/* … */

/*
 * Write the file corresponding to
 * the inode pointed at by the argument.
 * The actual write arguments are found
 * in the variables:
 *    u_base        core address for source
 *    u_offset    byte offset in file
 *    u_count        number of bytes to write
 *    u_segflg    write to kernel/user
 */
writei(aip)
struct inode *aip;
/* … */

"Konservativ" blokirovkaga kelsak, unda readp() и writep() o'z ishini tugatmaguncha yoki natija olmaguncha inodeni blokirovka qiling (ya'ni qo'ng'iroq qiling wakeup). plock() и prele() oddiygina ishlash: boshqa qo'ng'iroqlar to'plamidan foydalanish sleep и wakeup Biz endigina qo'yib yuborgan qulfni talab qiladigan har qanday jarayonni uyg'otishimizga imkon bering:

/*
 * Lock a pipe.
 * If its already locked, set the WANT bit and sleep.
 */
plock(ip)
int *ip;
{
    register *rp;

    rp = ip;
    while(rp->i_flag&ILOCK) {
        rp->i_flag =| IWANT;
        sleep(rp, PPIPE);
    }
    rp->i_flag =| ILOCK;
}

/*
 * Unlock a pipe.
 * If WANT bit is on, wakeup.
 * This routine is also used to unlock inodes in general.
 */
prele(ip)
int *ip;
{
    register *rp;

    rp = ip;
    rp->i_flag =& ~ILOCK;
    if(rp->i_flag&IWANT) {
        rp->i_flag =& ~IWANT;
        wakeup(rp);
    }
}

Avvaliga nima uchun ekanligini tushunolmadim readp() sabab bo'lmaydi prele(ip) qo'ng'iroqdan oldin wakeup(ip+1). Birinchi narsa writep() uning tsiklida sabab bo'ladi, bu plock(ip), bu esa boshi berk ko'chaga olib keladi readp() hali mening blokimni olib tashlamaganman, shuning uchun kod qandaydir tarzda to'g'ri ishlashi kerak. Agar qarasangiz wakeup(), keyin u faqat uxlash jarayonini amalga oshirishga tayyor deb belgilashi aniq bo'ladi, shunda kelajakda sched() haqiqatan ham ishga tushirdi. Shunday qilib readp() sabablari wakeup(), qulfni olib tashlaydi, o'rnatadi IREAD va qo'ng'iroqlar sleep(ip+2)- bularning barchasi oldin writep() tsiklni davom ettiradi.

Bu oltinchi nashrdagi konveyerlarning tavsifini yakunlaydi. Oddiy kod, keng qamrovli oqibatlar.

Unix ning yettinchi nashri (1979 yil yanvar) ko'plab yangi ilovalar va yadro xususiyatlarini taqdim etgan yangi yirik nashr (to'rt yildan keyin) edi. Shuningdek, u tipdagi quyma, birlashma va konstruksiyalarga yoziladigan ko'rsatgichlardan foydalanish bilan bog'liq holda sezilarli o'zgarishlarga duch keldi. Biroq konveyer kodi deyarli o'zgarmadi. Biz bu nashrni o'tkazib yuborishimiz mumkin.

Xv6, Unix-ga o'xshash oddiy yadro

Yadro yaratish uchun Xv6 Unix ning oltinchi nashri ta'sirida, lekin u x86 protsessorlarida ishlash uchun zamonaviy C tilida yozilgan. Kodni o'qish oson va tushunarli. Bundan tashqari, TUHS bilan Unix manbalaridan farqli o'laroq, siz uni kompilyatsiya qilishingiz, o'zgartirishingiz va PDP 11/70 dan boshqa narsada ishga tushirishingiz mumkin. Shuning uchun bu yadro universitetlarda operatsion tizimlar bo'yicha o'quv materiali sifatida keng qo'llaniladi. Manbalar Github-da.

Kod aniq va puxta o'ylangan dasturni o'z ichiga oladi quvur.c, diskdagi inode o'rniga xotiradagi bufer bilan quvvatlanadi. Bu erda men faqat "strukturaviy quvur liniyasi" va funksiyaning ta'rifini beraman pipealloc():

#define PIPESIZE 512

struct pipe {
  struct spinlock lock;
  char data[PIPESIZE];
  uint nread;     // number of bytes read
  uint nwrite;    // number of bytes written
  int readopen;   // read fd is still open
  int writeopen;  // write fd is still open
};

int
pipealloc(struct file **f0, struct file **f1)
{
  struct pipe *p;

  p = 0;
  *f0 = *f1 = 0;
  if((*f0 = filealloc()) == 0 || (*f1 = filealloc()) == 0)
    goto bad;
  if((p = (struct pipe*)kalloc()) == 0)
    goto bad;
  p->readopen = 1;
  p->writeopen = 1;
  p->nwrite = 0;
  p->nread = 0;
  initlock(&p->lock, "pipe");
  (*f0)->type = FD_PIPE;
  (*f0)->readable = 1;
  (*f0)->writable = 0;
  (*f0)->pipe = p;
  (*f1)->type = FD_PIPE;
  (*f1)->readable = 0;
  (*f1)->writable = 1;
  (*f1)->pipe = p;
  return 0;

 bad:
  if(p)
    kfree((char*)p);
  if(*f0)
    fileclose(*f0);
  if(*f1)
    fileclose(*f1);
  return -1;
}

pipealloc() funktsiyalarni o'z ichiga olgan amalga oshirishning qolgan qismining holatini belgilaydi piperead(), pipewrite() и pipeclose(). Haqiqiy tizim chaqiruvi sys_pipe da amalga oshirilgan oʻram hisoblanadi sysfile.c. Men uning butun kodini o'qishni tavsiya qilaman. Murakkablik oltinchi nashrning manba kodi darajasida, lekin uni o'qish ancha oson va yoqimli.

Linux 0.01

Linux 0.01 manba kodini topish mumkin. Uning ichida quvurlarni amalga oshirishni o'rganish foydali bo'ladi fs/pipe.c. Bu quvur liniyasini ifodalash uchun inodedan foydalanadi, lekin quvur liniyasining o'zi zamonaviy C tilida yozilgan. Agar siz 6-nashr kodini sinab ko'rgan bo'lsangiz, bu erda hech qanday muammo bo'lmaydi. Funktsiya shunday ko'rinadi write_pipe():

int write_pipe(struct m_inode * inode, char * buf, int count)
{
    char * b=buf;

    wake_up(&inode->i_wait);
    if (inode->i_count != 2) { /* no readers */
        current->signal |= (1<<(SIGPIPE-1));
        return -1;
    }
    while (count-->0) {
        while (PIPE_FULL(*inode)) {
            wake_up(&inode->i_wait);
            if (inode->i_count != 2) {
                current->signal |= (1<<(SIGPIPE-1));
                return b-buf;
            }
            sleep_on(&inode->i_wait);
        }
        ((char *)inode->i_size)[PIPE_HEAD(*inode)] =
            get_fs_byte(b++);
        INC_PIPE( PIPE_HEAD(*inode) );
        wake_up(&inode->i_wait);
    }
    wake_up(&inode->i_wait);
    return b-buf;
}

Tuzilish ta'riflariga qaramay, siz inode mos yozuvlar soni yozish operatsiyasi natijalarini tekshirish uchun qanday ishlatilishini aniqlashingiz mumkin. SIGPIPE. Bayt-bayt ishlashga qo'shimcha ravishda, bu funktsiyani yuqorida tavsiflangan g'oyalar bilan solishtirish oson. Hatto mantiq sleep_on/wake_up unchalik begona ko'rinmaydi.

Zamonaviy Linux yadrolari, FreeBSD, NetBSD, OpenBSD

Men tezda ba'zi zamonaviy yadrolar orqali yugurdim. Ularning hech birida endi disk dasturi yo'q (ajablanmaydi). Linuxning o'ziga xos ilovasi bor. Uchta zamonaviy BSD yadrolari Jon Dayson tomonidan yozilgan kodga asoslangan ilovalarni o'z ichiga olgan bo'lsa-da, yillar davomida ular bir-biridan juda farq qiladi.

O'qish fs/pipe.c (Linux-da) yoki sys/kern/sys_pipe.c (*BSD da), bu haqiqiy fidoyilikni talab qiladi. Bugungi kod vektor va asinxron kiritish-chiqarish kabi xususiyatlarni ishlash va qo'llab-quvvatlash haqida. Xotirani taqsimlash, qulflar va yadro konfiguratsiyasi tafsilotlari juda farq qiladi. Bu kollejlarga operatsion tizimlar bilan tanishish kursi uchun kerak bo'lgan narsa emas.

Qanday bo'lmasin, men eski naqshlarni (masalan, generating SIGPIPE va qaytish EPIPE yopiq quvur liniyasiga yozishda) barcha bu turli xil zamonaviy yadrolarda. Men PDP-11 kompyuterini real hayotda hech qachon ko‘rmayman, lekin men tug‘ilishimdan bir necha yillar oldin yozilgan koddan hali ko‘p o‘rganishim kerak.

2011 yilda Divi Kapur tomonidan yozilgan maqola:Quvurlar va FIFOlarning Linux yadrosini amalga oshirish" Linuxda quvurlar (hali ham) qanday ishlashi haqida umumiy ma'lumot beradi. A Linuxda so'nggi majburiyat Imkoniyatlari vaqtinchalik fayllarnikidan yuqori bo'lgan o'zaro ta'sirning quvur liniyasi modelini tasvirlaydi; va shuningdek, quvurlar oltinchi nashri Unix yadrosining "juda konservativ qulflanishi" dan qanchalik uzoqqa kelganligini ko'rsatadi.

Manba: www.habr.com

a Izoh qo'shish