Kaip dujotiekiai diegiami Unix

Kaip dujotiekiai diegiami Unix
Šiame straipsnyje aprašomas konvejerių diegimas Unix branduolyje. Buvau šiek tiek nusivylęs, kad neseniai pasirodė straipsnis "Kaip vamzdynai veikia Unix?» paaiškėjo ne apie vidinę struktūrą. Man pasidarė smalsu ir ieškojau atsakymo į senus šaltinius.

apie ką mes kalbame?

Vamzdynai yra „turbūt svarbiausias Unix išradimas“ – esminė Unix filosofijos mažų programų sudarymo ypatybė ir pažįstamas komandų eilutės šūkis:

$ echo hello | wc -c
6

Ši funkcija priklauso nuo branduolio pateikto sistemos iškvietimo pipe, kuris aprašytas dokumentacijos puslapiuose vamzdis (7) и vamzdis (2):

Vamzdynai suteikia vienpusį kanalą tarpprocesiniam ryšiui. Dujotiekis turi įvestį (rašymo pabaigą) ir išvestį (skaitymo pabaigą). Duomenys, įrašyti į dujotiekio įvestį, gali būti nuskaitomi išvestyje.

Dujotiekis sukuriamas skambinant pipe(2), kuris grąžina du failo deskriptorius: vienas nurodo konvejerio įvestį, antrasis – išvestį.

Aukščiau pateiktos komandos sekimo išvestis rodo dujotiekio sukūrimą ir duomenų srautą per jį iš vieno proceso į kitą:

$ 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

Tėvų procesas skambina pipe()norėdami gauti pridedamų failų aprašus. Vienas antrinis procesas rašo į vieną deskriptorių, o kitas procesas nuskaito tuos pačius duomenis iš kito deskriptoriaus. Apvalkalas „pervardija“ 2 ir 3 deskriptorius su dup4, kad atitiktų stdin ir stdout.

Be konvejerių, apvalkalas turėtų įrašyti vieno proceso išvestį į failą ir perkelti jį į kitą procesą, kad galėtų nuskaityti duomenis iš failo. Dėl to eikvotume daugiau išteklių ir vietos diske. Tačiau vamzdynai yra naudingi ne tik norint išvengti laikinųjų failų:

Jei procesas bando nuskaityti iš tuščio dujotiekio, tada read(2) bus užblokuotas, kol bus gauti duomenys. Jei procesas bando rašyti į visą konvejerį, tada write(2) blokuos, kol iš konvejerio bus nuskaityta pakankamai duomenų, kad būtų baigtas rašymas.

Kaip ir POSIX reikalavimas, tai yra svarbi savybė: rašymas į dujotiekį iki PIPE_BUF baitai (mažiausiai 512) turi būti atominiai, kad procesai galėtų bendrauti tarpusavyje per konvejerį taip, kaip įprasti failai (kurie nesuteikia tokių garantijų).

Naudodamas įprastą failą, procesas gali įrašyti į jį visą savo išvestį ir perduoti kitam procesui. Arba procesai gali veikti kietuoju lygiagrečiu režimu, naudodami išorinį signalizacijos mechanizmą (pvz., semaforą), kad informuotų vienas kitą apie rašymo ar skaitymo užbaigimą. Konvejeriai gelbsti mus nuo viso šito vargo.

Ko mes ieškome?

Paaiškinsiu ant pirštų, kad būtų lengviau įsivaizduoti, kaip gali veikti konvejeris. Turėsite priskirti buferį ir tam tikrą būseną atmintyje. Norėdami pridėti ir pašalinti duomenis iš buferio, jums reikės funkcijų. Jums reikės tam tikros funkcijos, kad galėtumėte iškviesti funkcijas skaitymo ir rašymo failų deskriptoriuose operacijose. Ir spynos reikalingos aukščiau aprašytam ypatingam elgesiui įgyvendinti.

Dabar esame pasirengę ištirti branduolio šaltinio kodą ryškioje lempos šviesoje, kad patvirtintume arba paneigtume mūsų neaiškų mąstymo modelį. Tačiau visada būkite pasirengę netikėtumams.

Kur mes ieškome?

Nežinau, kur slypi mano garsiosios knygos kopija.Liūtų knyga« su Unix 6 šaltinio kodu, bet dėka Unix paveldo draugija galima ieškoti internete pirminis kodas net senesnės Unix versijos.

Klaidžioti po TUHS archyvus prilygsta muziejaus lankymui. Galime pažvelgti į mūsų bendrą istoriją ir aš gerbiu ilgametes pastangas po truputį atkurti visą šią medžiagą iš senų kasečių ir spaudinių. Ir aš puikiai žinau tuos fragmentus, kurių vis dar trūksta.

Patenkinę savo smalsumą apie senąją vamzdynų istoriją, palyginimui galime pažvelgti į šiuolaikines šerdis.

Beje, pipe lentelėje yra sistemos skambučio numeris 42 sysent[]. Sutapimas?

Tradiciniai Unix branduoliai (1970–1974)

Neradau jokių pėdsakų pipe(2) nei viduje PDP-7 Unix (1970 m. sausis), nei in pirmasis Unix leidimas (1971 m. lapkričio mėn.), nei nepilname šaltinio kode antrasis leidimas (1972 m. birželis).

TUHS teigia, kad trečiasis Unix leidimas (1973 m. vasario mėn.) buvo pirmoji versija su vamzdynais:

Trečiasis Unix leidimas buvo paskutinė versija su branduoliu, parašyta asamblėjaus programa, bet taip pat pirmoji versija su konvejeriais. 1973 m. buvo vykdomas trečiojo leidimo tobulinimo darbas, branduolys buvo perrašytas C kalba ir taip gimė ketvirtasis Unix leidimas.

Vienas skaitytojas rado nuskaitytą dokumentą, kuriame Dougas McIlroy'us pasiūlė idėją „sujungti programas kaip sodo žarną“.

Kaip dujotiekiai diegiami Unix
Briano Kernighano knygojeUnix: istorija ir atsiminimai“, konvejerių atsiradimo istorijoje minimas ir šis dokumentas: „... jis 30 metų kabėjo ant sienos mano biure „Bell Labs“. Čia interviu su McIlroyir dar viena istorija iš McIlroy darbas, parašytas 2014 m:

Kai pasirodė Unix, mano aistra korutinėms privertė mane paprašyti OS autoriaus Keno Thompsono leisti duomenims, įrašytiems į kokį nors procesą, patekti ne tik į įrenginį, bet ir į išėjimą į kitą procesą. Kenas nusprendė, kad tai įmanoma. Tačiau, kaip minimalistas, jis norėjo, kad kiekviena sistemos ypatybė atliktų svarbų vaidmenį. Ar tiesioginis rašymas tarp procesų tikrai yra didelis pranašumas prieš rašymą į tarpinį failą? Ir tik kai pateikiau konkretų pasiūlymą skambiu pavadinimu „vamzdynas“ ir procesų sąveikos sintaksės aprašymu, Kenas galiausiai sušuko: „Padarysiu!“.

Ir padarė. Vieną lemtingą vakarą Kenas pakeitė branduolį ir apvalkalą, sutvarkė keletą standartinių programų, kad standartizuotų, kaip jos priima įvestį (kuri gali būti iš konvejerio), ir pakeitė failų pavadinimus. Kitą dieną vamzdynai buvo labai plačiai naudojami. Savaitės pabaigoje sekretorės jais siųsdavo dokumentus iš teksto rengyklės į spausdintuvą. Kiek vėliau Kenas pakeitė pradinę API ir sintaksę, skirtą vamzdynų naudojimui apvynioti švaresnėmis nuostatomis, kurios buvo naudojamos nuo tada.

Deja, trečiojo leidimo Unix branduolio šaltinio kodas buvo prarastas. Ir nors mes turime branduolio šaltinio kodą, parašytą C ketvirtasis leidimas, kuris buvo išleistas 1973 m. lapkritį, tačiau jis pasirodė likus keliems mėnesiams iki oficialaus išleidimo ir jame nėra vamzdynų diegimo. Gaila, kad šios legendinės Unix funkcijos šaltinio kodas yra prarastas, galbūt visam laikui.

Turime dokumentacijos tekstą pipe(2) iš abiejų leidimų, todėl galite pradėti ieškodami dokumentacijos trečias leidimas (tam tikriems žodžiams pabraukta „rankiniu būdu“, ^H raidžių eilutė, po kurios yra pabraukimas!). Šis proto-pipe(2) yra parašytas asamblieryje ir pateikia tik vieną failo deskriptorių, bet jau suteikia numatytą pagrindinę funkciją:

Sisteminis skambutis vamzdis sukuria įvesties/išvesties mechanizmą, vadinamą dujotiekiu. Grąžintas failo aprašas gali būti naudojamas skaitymo ir rašymo operacijoms. Kai kas nors įrašoma į konvejerį, jis saugo iki 504 baitų duomenų, o po to rašymo procesas sustabdomas. Skaitant iš konvejerio, paimami buferiniai duomenys.

Kitais metais branduolys buvo perrašytas C ir vamzdis(2) ketvirtajame leidime įgavo savo modernią išvaizdą su prototipu “pipe(fildes)"

Sisteminis skambutis vamzdis sukuria įvesties/išvesties mechanizmą, vadinamą dujotiekiu. Grąžinti failų aprašai gali būti naudojami skaitymo ir rašymo operacijose. Kai kas nors įrašoma į konvejerį, naudojamas r1 grąžintas deskriptorius (resp. fildes[1]), buferizuojamas iki 4096 baitų duomenų, po kurio rašymo procesas sustabdomas. Skaitant iš konvejerio, į r0 grąžintas deskriptorius (resp. fildes[0]) paima duomenis.

Daroma prielaida, kad apibrėžus konvejerį, du (ar daugiau) sąveikaujantys procesai (sukurti vėlesniais iškvietimais šakutė) perduos duomenis iš dujotiekio naudodamas skambučius skaityti и rašyti.

Apvalkalas turi sintaksę, skirtą apibrėžti tiesinį procesų, sujungtų per dujotiekį, masyvą.

Kvietimai nuskaityti iš tuščio konvejerio (be buferinių duomenų), turinčio tik vieną galą (visi rašymo failo deskriptoriai uždaryti), grąžina "failo pabaigą". Rašymo skambučiai panašioje situacijoje ignoruojami.

Anksčiausiai išsaugotas dujotiekio įgyvendinimas susirūpinęs iki penktojo Unix leidimo (1974 m. birželio mėn.), tačiau jis beveik identiškas tam, kuris pasirodė kitame leidime. Pridėta tik komentarų, todėl penktą leidimą galima praleisti.

Šeštasis Unix leidimas (1975 m.)

Pradedama skaityti Unix šaltinio kodą šeštasis leidimas (1975 m. gegužės mėn.). Daugiausia dėka Liūtai jį rasti daug lengviau nei ankstesnių versijų šaltinius:

Daug metų knyga Liūtai buvo vienintelis dokumentas apie Unix branduolį, prieinamas už Bell Labs ribų. Nors šeštojo leidimo licencija leido mokytojams naudoti šaltinio kodą, septintoji licencija atmetė tokią galimybę, todėl knyga buvo platinama nelegaliais spausdinimo mašinėle kopijomis.

Šiandien galite nusipirkti pakartotinį knygos egzempliorių, kurio viršelyje pavaizduoti studentai prie kopijavimo aparato. Ir dėka Warren Toomey (kuris pradėjo TUHS projektą), galite atsisiųsti Šeštasis leidimas PDF šaltinio. Noriu suteikti jums supratimą, kiek pastangų įdėjote kuriant failą:

Daugiau nei prieš 15 metų įvedžiau pateikto šaltinio kodo kopiją Liūtaines man nepatiko mano kopijos kokybė iš nežinomo skaičiaus kitų kopijų. TUHS dar nebuvo, ir aš neturėjau prieigos prie senųjų šaltinių. Bet 1988 metais radau seną juostą su 9 takeliais, kuri turėjo atsarginę kopiją iš PDP11 kompiuterio. Sunku buvo suprasti, ar tai veikė, bet buvo nepažeistas /usr/src/ medis, kuriame dauguma failų buvo pažymėti 1979 m., kuris net tada atrodė senovinis. Pagalvojau, kad tai buvo septintasis leidimas arba PWB darinys.

Paėmiau radinį kaip pagrindą ir rankiniu būdu redagavau šaltinius iki šeštojo leidimo būsenos. Dalis kodo liko toks pat, dalį teko šiek tiek redaguoti, pakeičiant šiuolaikinį žetoną += į pasenusį =+. Kažkas buvo tiesiog ištrintas ir kažkas turėjo būti visiškai perrašytas, bet ne per daug.

Ir šiandien TUHS internete galime perskaityti šeštojo leidimo šaltinio kodą archyvas, prie kurio prisidėjo Dennisas Ritchie.

Beje, iš pirmo žvilgsnio pagrindinė C kodo savybė prieš Kernighan ir Ritchie laikotarpį yra jo trumpumas. Nedažnai galiu įterpti kodo fragmentus be didelio redagavimo, kad mano svetainėje tilptų gana siaura rodymo sritis.

Anksti /usr/sys/ken/pipe.c yra aiškinamasis komentaras (ir taip, yra daugiau /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

Buferio dydis nepasikeitė nuo ketvirtojo leidimo. Bet čia matome, be jokių viešų dokumentų, kad vamzdynai kadaise naudojo failus kaip atsarginę saugyklą!

Kalbant apie LARG failus, jie atitinka inode-vėliava DIDELIS, kurį apdoroti naudoja „didelis adresavimo algoritmas“. netiesioginiai blokai palaikyti didesnes failų sistemas. Kadangi Kenas sakė, kad geriau jų nenaudoti, tai aš mielai imsiu jo žodį.

Čia yra tikrasis sistemos skambutis 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;
}

Komentaras aiškiai apibūdina, kas čia vyksta. Tačiau kodą suprasti nėra taip lengva, iš dalies dėl to, kaip "struct vartotojas u» ir registrai R0 и R1 perduodami sistemos iškvietimo parametrai ir grąžinimo reikšmės.

Pabandykime su ialloc() vieta diske inode (inode), ir su pagalba falloc () - saugoti du failą. Jei viskas bus gerai, nustatysime vėliavėles, kad identifikuosime šiuos failus kaip du konvejerio galus, nukreipsime juos į tą patį inodą (kurio nuorodų skaičius tampa 2) ir pažymime inodą kaip pakeistą ir naudojamą. Atkreipkite dėmesį į prašymus suktis () klaidų keliuose, kad sumažintumėte nuorodų skaičių naujame inode.

pipe() terminas R0 и R1 grąžinti failo deskriptorių numerius skaitymui ir rašymui. falloc() grąžina žymeklį į failo struktūrą, bet taip pat „grąžina“ per u.u_ar0[R0] ir failo aprašą. Tai yra, kodas yra saugomas r failo deskriptorius skaitymui ir priskiria deskriptorių rašymui tiesiai iš u.u_ar0[R0] po antro skambučio falloc().

Vėliava FPIPE, kurį nustatome kurdami konvejerį, valdo funkcijos veikimą rdwr() sistemoje sys2.c, kuri iškviečia konkrečias įvesties / išvesties procedūras:

/*
 * 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);
    }
        /* … */
}

Tada funkcija readp() в pipe.c nuskaito duomenis iš dujotiekio. Tačiau geriau sekti įgyvendinimą nuo writep(). Vėlgi, kodas tapo sudėtingesnis dėl argumentų perdavimo konvencijos pobūdžio, tačiau kai kurių detalių galima praleisti.

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;
}

Norime įrašyti baitus į konvejerio įvestį u.u_count. Pirmiausia turime užrakinti inodą (žr. toliau plock/prele).

Tada patikriname inodų nuorodų skaičių. Kol abu dujotiekio galai lieka atviri, skaitiklis turi būti 2. Mes laikomės už vienos jungties (nuo rp->f_inode), taigi, jei skaitiklis yra mažesnis nei 2, tai turėtų reikšti, kad skaitymo procesas uždarė dujotiekio galą. Kitaip tariant, mes bandome rašyti į uždarą dujotiekį, o tai yra klaida. Pirmas klaidos kodas EPIPE ir signalas SIGPIPE pasirodė šeštajame Unix leidime.

Bet net jei konvejeris atidarytas, jis gali būti pilnas. Tokiu atveju atleidžiame užraktą ir einame miegoti, tikėdamiesi, kad kitas procesas nuskaitys iš dujotiekio ir atlaisvins jame pakankamai vietos. Pabudę grįžtame į pradžią, vėl pakabiname užraktą ir pradedame naują rašymo ciklą.

Jei dujotiekyje yra pakankamai laisvos vietos, duomenis į jį įrašome naudodami rašyti (). Parametras i_size1 inode'a (su tuščiu konvejeriu gali būti lygus 0) nurodo jame jau esančių duomenų pabaigą. Jei yra pakankamai vietos rašyti, galime užpildyti dujotiekį iš i_size1 į PIPESIZ. Tada atleidžiame užraktą ir bandome pažadinti bet kokį procesą, kuris laukia skaitymo iš konvejerio. Grįžtame į pradžią, kad pamatytume, ar pavyko įrašyti tiek baitų, kiek reikia. Jei ne, pradedame naują įrašymo ciklą.

Paprastai parametras i_mode inode naudojamas leidimams saugoti r, w и x. Tačiau konvejerių atveju mes signalizuojame, kad koks nors procesas laukia įrašymo arba skaitymo naudojant bitus IREAD и IWRITE atitinkamai. Procesas nustato vėliavėlę ir skambučius sleep(), ir tikimasi, kad ateityje vyks koks nors kitas procesas wakeup().

Tikroji magija įvyksta sleep() и wakeup(). Jie įgyvendinami slp.c, garsaus komentaro „Jūs to nesitikima suprasti“ šaltinis. Laimei, mes neturime suprasti kodo, tiesiog pažvelkite į keletą komentarų:

/*
 * 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) /* … */

Procesas, kuris skambina sleep() tam tikram kanalui, vėliau gali būti pažadintas kito proceso, kuris iškvies wakeup() tam pačiam kanalui. writep() и readp() koordinuoti savo veiksmus per tokius suporuotus skambučius. Prisimink tai pipe.c visada teikti pirmenybę PPIPE kai skambina sleep(), taigi viskas sleep() gali būti nutrauktas signalu.

Dabar turime viską, kad suprastume funkciją 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);
}

Jums gali būti lengviau skaityti šią funkciją iš apačios į viršų. „Skaityti ir grąžinti“ šaka paprastai naudojama, kai yra tam tikrų duomenų. Šiuo atveju mes naudojame skaityti () skaityti tiek duomenų, kiek yra, pradedant nuo dabartinio f_offset perskaityti ir atnaujinti atitinkamo poslinkio reikšmę.

Vėlesniuose nuskaitymuose dujotiekis bus tuščias, jei bus pasiektas skaitymo poslinkis i_size1 ties inode. Iš naujo nustatome padėtį į 0 ir bandome pažadinti bet kokį procesą, kuris nori rašyti į dujotiekį. Mes žinome, kad kai konvejeris yra pilnas, writep() užmigti toliau ip+1. Ir dabar, kai dujotiekis tuščias, galime jį pažadinti, kad atnaujintume rašymo ciklą.

Jei nėra ką skaityti, tada readp() gali nustatyti vėliavą IREAD ir užmigti toliau ip+2. Mes žinome, kas jį pažadins writep()kai jis įrašo kai kuriuos duomenis į dujotiekį.

Komentarai apie skaityti () ir rašyti () padės suprasti, kad užuot perdavus parametrus per "u» galime jas traktuoti kaip įprastas įvesties/išvesties funkcijas, kurios užima failą, vietą, buferį atmintyje ir skaičiuoja baitų skaičių, kurį reikia skaityti ar rašyti.

/*
 * 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;
/* … */

Kalbant apie „konservatyvų“ blokavimą readp() и writep() užrakinti inodus, kol jie baigsis arba gaus rezultatą (t. y. skambinkite wakeup). plock() и prele() dirbti paprastai: naudojant kitą skambučių rinkinį sleep и wakeup Leiskite mums pažadinti bet kokį procesą, kuriam reikalingas užraktas, kurį ką tik atleidome:

/*
 * 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);
    }
}

Iš pradžių negalėjau suprasti, kodėl readp() nesukelia prele(ip) prieš skambutį wakeup(ip+1). Pirmas dalykas writep() skambučiai savo cikle, tai plock(ip), dėl ko atsiduria aklavietė, jei readp() dar nepašalino savo bloko, todėl kodas turi kažkaip tinkamai veikti. Jei pažiūrėsi wakeup(), tampa aišku, kad tai tik pažymi miego procesą kaip paruoštą vykdyti, kad ateityje sched() tikrai paleido. Taigi readp() priežastys wakeup(), atrakina, komplektuoja IREAD ir skambina sleep(ip+2)- visa tai anksčiau writep() paleidžia ciklą iš naujo.

Tai užbaigia vamzdynų aprašymą šeštajame leidime. Paprastas kodas, toli siekiančios pasekmės.

Septintasis Unix leidimas (1979 m. sausio mėn.) buvo naujas didelis leidimas (po ketverių metų), kuriame buvo pristatyta daug naujų programų ir branduolio funkcijų. Ji taip pat patyrė didelių pokyčių, susijusių su šrifto liejimo, jungčių ir tipinių rodyklių į konstrukcijas naudojimu. Tačiau vamzdynų kodas praktiškai nepasikeitė. Galime praleisti šį leidimą.

Xv6, paprastas Unix tipo branduolys

Norėdami sukurti branduolį Xv6 paveiktas šeštojo Unix leidimo, bet parašytas šiuolaikine C, kad veiktų su x86 procesoriais. Kodas yra lengvai skaitomas ir suprantamas. Be to, skirtingai nei Unix šaltiniai su TUHS, galite jį kompiliuoti, modifikuoti ir paleisti ne PDP 11/70. Todėl šis branduolys plačiai naudojamas universitetuose kaip mokomoji medžiaga apie operacines sistemas. Šaltiniai yra „Github“..

Kode yra aiškus ir apgalvotas įgyvendinimas vamzdis.c, palaikomas buferiu atmintyje, o ne inode diske. Čia pateikiu tik „struktūrinio vamzdyno“ ir funkcijos apibrėžimą 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() nustato viso likusio įgyvendinimo, kuris apima funkcijas, būseną piperead(), pipewrite() и pipeclose(). Tikrasis sistemos skambutis sys_pipe yra įvyniojimas, įdiegtas sysfile.c. Rekomenduoju perskaityti visą jo kodą. Sudėtingumas yra šeštojo leidimo šaltinio kodo lygyje, tačiau jį skaityti daug lengviau ir maloniau.

Linux 0.01

Galite rasti Linux 0.01 šaltinio kodą. Tai bus pamokoma ištirti vamzdynų diegimą jo fs/pipe.c. Čia konvejeriui atvaizduoti naudojamas inode, bet pats konvejeris parašytas šiuolaikine C. Jei įsilaužėte į šeštojo leidimo kodą, čia neturėsite jokių problemų. Taip atrodo funkcija 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;
}

Net nežiūrėdami į struktūrų apibrėžimus, galite išsiaiškinti, kaip inode nuorodų skaičius naudojamas norint patikrinti, ar rašymo operacija sukelia SIGPIPE. Be darbo po baito, šią funkciją lengva palyginti su aukščiau pateiktomis idėjomis. Net logika sleep_on/wake_up neatrodo toks svetimas.

Šiuolaikiniai Linux branduoliai, FreeBSD, NetBSD, OpenBSD

Greitai peržvelgiau kai kuriuos šiuolaikinius branduolius. Nė vienas iš jų dar neturi disko pagrindu sukurto diegimo (nenuostabu). Linux turi savo įgyvendinimą. Ir nors trijuose šiuolaikiniuose BSD branduoliuose yra įdiegimų, pagrįstų Johno Dysono parašytu kodu, bėgant metams jie per daug skiriasi vienas nuo kito.

Skaityti fs/pipe.c („Linux“ sistemoje) arba sys/kern/sys_pipe.c (*BSD), tam reikia tikro atsidavimo. Šių dienų kode svarbus našumas ir funkcijų, tokių kaip vektorinis ir asinchroninis įvestis / išvestis, palaikymas. Išsami informacija apie atminties paskirstymą, užraktus ir branduolio konfigūraciją labai skiriasi. Tai nėra tai, ko universitetams reikia įvadiniam operacinių sistemų kursui.

Bet kokiu atveju man buvo įdomu atkasti keletą senų raštų (pavyzdžiui, generuoti SIGPIPE ir grįžti EPIPE rašant į uždarą dujotiekį) visuose šiuose tokiuose skirtinguose šiuolaikiniuose branduoliuose. Tikriausiai niekada nepamatysiu kompiuterio PDP-11 gyvai, bet iš kodo, kuris buvo parašytas likus keleriems metams iki mano gimimo, dar galima daug ko pasimokyti.

2011 m. parašytas Divi Kapoor straipsnis "„Linux“ branduolio vamzdžių ir FIFO diegimasyra Linux vamzdynų (iki šiol) veikimo apžvalga. A neseniai įsipareigota naudoti linux iliustruoja sąveikos vamzdyno modelį, kurio galimybės viršija laikinųjų failų galimybes; taip pat parodo, kaip toli konvejeriai nuėjo nuo „labai konservatyvaus užrakinimo“ šeštojo leidimo Unix branduolyje.

Šaltinis: www.habr.com

Добавить комментарий