Hvernig leiðslur eru útfærðar í Unix

Hvernig leiðslur eru útfærðar í Unix
Þessi grein lýsir útfærslu leiðslna í Unix kjarnanum. Ég varð fyrir nokkrum vonbrigðum með að nýleg grein bar yfirskriftina "Hvernig virka leiðslur í Unix?"reyndist ekki um innri uppbyggingu. Ég varð forvitinn og kafaði í gamlar heimildir til að finna svarið.

Hvað erum við að tala um?

Leiðslur, „sennilega mikilvægasta uppfinningin í Unix,“ eru einkennandi einkenni undirliggjandi Unix heimspeki um að tengja lítil forrit saman, auk kunnuglegs tákns á skipanalínunni:

$ echo hello | wc -c
6

Þessi virkni er háð kerfiskallinu sem útvegað er kjarna pipe, sem lýst er á skjalasíðunum pípa (7) и pípa (2):

Leiðslur veita einstefnurás fyrir samskipti milli vinnsluferla. Leiðslan hefur inntak (skrifenda) og úttak (lesenda). Gögn sem eru skrifuð á inntak leiðslunnar má lesa við úttakið.

Leiðslan er búin til með því að nota símtalið pipe(2), sem skilar tveimur skráarlýsingum: annar vísar til inntaks leiðslunnar, hinnar til úttaksins.

Rekja úttakið frá ofangreindri skipun sýnir sköpun leiðslunnar og flæði gagna í gegnum hana frá einu ferli til annars:

$ 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

Foreldraferlið kallar pipe()til að fá uppsetta skráarlýsingar. Eitt undirferli skrifar í eitt handfang og annað ferli les sömu gögn úr öðru handfangi. Skelin notar dup2 til að „endurnefna“ lýsingar 3 og 4 til að passa við stdin og stdout.

Án pípa þyrfti skelin að skrifa niðurstöðu eins ferlis í skrá og senda það í annað ferli til að lesa gögnin úr skránni. Fyrir vikið myndum við sóa meira fjármagni og plássi. Hins vegar eru leiðslur góðar ekki aðeins vegna þess að þær leyfa þér að forðast notkun tímabundinna skráa:

Ef ferli er að reyna að lesa úr tómri leiðslu þá read(2) mun loka þar til gögnin verða aðgengileg. Ef ferli reynir að skrifa í fulla leiðslu, þá write(2) mun loka þar til næg gögn hafa verið lesin úr leiðslunni til að framkvæma ritunina.

Eins og POSIX krafan er þetta mikilvægur eiginleiki: að skrifa í leiðsluna upp að PIPE_BUF bæti (að minnsta kosti 512) verða að vera atóm þannig að ferli geti átt samskipti sín á milli í gegnum leiðsluna á þann hátt sem venjulegar skrár (sem veita ekki slíkar tryggingar) geta ekki.

Þegar venjuleg skrá er notuð getur ferli skrifað allt úttak sitt á hana og sent það áfram í annað ferli. Eða ferli geta starfað í mjög samhliða ham, með því að nota utanaðkomandi merkjakerfi (eins og semafór) til að láta hvert annað vita þegar ritun eða lestri er lokið. Færibönd bjarga okkur frá öllu þessu veseni.

Hvað erum við að leita að?

Ég mun útskýra það á einfaldan hátt svo að það sé auðveldara fyrir þig að ímynda þér hvernig færiband getur virkað. Þú þarft að úthluta biðminni og einhverju ástandi í minni. Þú þarft aðgerðir til að bæta við og fjarlægja gögn úr biðminni. Þú þarft einhverja leið til að hringja í aðgerðir meðan á lestri og ritun stendur á skráarlýsingum. Og þú þarft læsingar til að framkvæma sérstaka hegðun sem lýst er hér að ofan.

Nú erum við tilbúin að yfirheyra frumkóðann kjarna undir björtu lampaljósi til að staðfesta eða afsanna óljósa andlega líkanið okkar. En vertu alltaf viðbúinn hinu óvænta.

Hvert erum við að leita?

Ég veit ekki hvar mitt eintak af bókinni frægu er “Lions bók"með Unix 6 frumkóða, en þökk sé Unix Heritage Society þú getur leitað á netinu á frumkóða jafnvel eldri útgáfur af Unix.

Að ráfa í gegnum skjalasafn TUHS er eins og að heimsækja safn. Við getum litið á sameiginlega sögu okkar og ég ber virðingu fyrir margra ára viðleitni til að endurheimta allt þetta efni smátt og smátt af gömlum segulböndum og prentum. Og ég geri mér fulla grein fyrir þessum brotum sem enn vantar.

Eftir að hafa fullnægt forvitni okkar um forna sögu færibanda, getum við litið á nútíma kjarna til samanburðar.

Tilviljun, pipe er kerfissímtal númer 42 í töflunni sysent[]. Tilviljun?

Hefðbundnir Unix kjarna (1970–1974)

Ég fann engin ummerki pipe(2) né inn PDP-7 Unix (janúar 1970), né í fyrsta útgáfa af Unix (nóvember 1971), né í ófullkomnum frumkóða önnur útgáfa (júní 1972).

TUHS segir það þriðja útgáfa af Unix (febrúar 1973) varð fyrsta útgáfan með færiböndum:

Unix 1973rd Edition var síðasta útgáfan með kjarna skrifuðum á samsetningartungumáli, en einnig fyrsta útgáfan með leiðslum. Árið XNUMX var unnið að endurbótum á þriðju útgáfunni, kjarninn var endurskrifaður í C ​​og því kom fjórða útgáfan af Unix.

Einn lesandi fann skanna af skjali þar sem Doug McIlroy lagði fram hugmyndina um að „tengja forrit eins og garðslöngu“.

Hvernig leiðslur eru útfærðar í Unix
Í bók Brian KernighanUnix: Saga og minningarbók", í sögu tilkomu færibanda er þetta skjal einnig nefnt: "... það hékk á veggnum á skrifstofunni minni á Bell Labs í 30 ár." Hérna viðtal við McIlroy, og önnur saga frá Verk McIlroy, skrifað árið 2014:

Þegar Unix kom út leiddi hrifning mín af coroutines til þess að ég bað höfund stýrikerfisins, Ken Thompson, um að leyfa gögnum sem skrifuð voru í ferli að fara ekki aðeins í tækið heldur einnig til úttaks í annað ferli. Ken ákvað að það væri mögulegt. Hins vegar, sem naumhyggjumaður, vildi hann að allar kerfisaðgerðir gegndu mikilvægu hlutverki. Er ritun beint á milli ferla í raun stór kostur fram yfir að skrifa í milliskrá? Það var aðeins þegar ég lagði fram ákveðna tillögu með grípandi nafninu „pípalína“ og lýsingu á setningafræðinni fyrir samskipti milli ferla sem Ken hrópaði að lokum: „Ég geri það!

Og gerði. Eitt örlagaríkt kvöld breytti Ken um kjarna og skel, lagaði nokkur stöðluð forrit til að staðla hvernig þau samþykktu inntak (sem gæti komið úr leiðslu) og breytti einnig skráarnöfnum. Daginn eftir var farið að nota leiðslur mjög víða í notkun. Í lok vikunnar voru ritarar að nota þá til að senda skjöl úr ritvinnslum í prentarann. Nokkru síðar skipti Ken út upprunalegu API og setningafræði til að vefja notkun leiðslna með hreinni venjum, sem hafa verið notaðar síðan.

Því miður hefur frumkóði fyrir þriðju útgáfu Unix kjarna glatast. Og þó að við höfum frumkóðann fyrir kjarnann skrifaðan í C fjórða útgáfa, gefin út í nóvember 1973, en hún kom út nokkrum mánuðum fyrir opinbera útgáfu og inniheldur ekki leiðsluútfærslur. Það er synd að frumkóði þessarar goðsagnakenndu Unix aðgerð er glataður, kannski að eilífu.

Við höfum textaskjöl fyrir pipe(2) úr báðum útgáfunum, svo þú getur byrjað á því að leita í skjölunum þriðja útgáfa (fyrir ákveðin orð, undirstrikuð „handvirkt“, strengur bókstafa ^H, á eftir með undirstrikun!). Þessi frum-pipe(2) er skrifað á samsetningartungumáli og skilar aðeins einum skráarlýsingu, en veitir nú þegar væntanlega grunnvirkni:

Kerfiskall pípa býr til inntaks/úttakskerfi sem kallast leiðsla. Hægt er að nota skilaða skráarlýsingu til að lesa og skrifa. Þegar eitthvað er skrifað í leiðsluna eru allt að 504 bæti af gögnum í biðminni, eftir það er skrifferlið stöðvað. Þegar lesið er úr leiðslunni eru biðminni gögnin fjarlægð.

Árið eftir hafði kjarninn verið endurskrifaður í C, og pípa(2) í fjórðu útgáfunni fékk nútímalegt útlit sitt með frumgerðinni "pipe(fildes)'

Kerfiskall pípa býr til inntaks/úttakskerfi sem kallast leiðsla. Hægt er að nota skilaða skráarlýsingar í lestrar- og skrifaðgerðum. Þegar eitthvað er skrifað í leiðsluna er handfangið sem skilað er í r1 (resp. fildes[1]), notað í biðminni í 4096 bæti af gögnum, eftir það er skrifferlið stöðvað. Þegar lesið er úr leiðslunni tekur handfangið aftur í r0 (resp. fildes[0]) gögnin.

Gert er ráð fyrir að þegar leiðsla hefur verið skilgreind séu tveir (eða fleiri) samskiptaferli (búnir til með síðari símtölum til Fork) mun flytja gögn úr leiðslunni með símtölum lesa и skrifa.

Skelin hefur setningafræði til að skilgreina línulega fylki ferla sem tengd eru með leiðslu.

Símtöl til að lesa úr tómri leiðslu (sem inniheldur engin biðminni gögn) sem hefur aðeins einn enda (allir ritskráarlýsingar eru lokaðar) skila „enda skráar“. Símtöl um að skrifa í svipuðum aðstæðum eru hunsuð.

Elstu varðveitt leiðsluframkvæmd tengist til fimmtu útgáfu af Unix (júní 1974), en það er nánast eins og það sem birtist í næstu útgáfu. Athugasemdum hefur verið bætt við, svo þú getur sleppt fimmtu útgáfunni.

Sjötta útgáfa af Unix (1975)

Við skulum byrja að lesa Unix frumkóða sjötta útgáfa (maí 1975). Að miklu leyti að þakka Lions það er miklu auðveldara að finna en heimildir fyrri útgáfur:

Í mörg ár bókin Lions var eina skjalið um Unix kjarnann sem er fáanlegt utan Bell Labs. Þrátt fyrir að sjötta útgáfan leyfi kennurum að nota frumkóðann, útilokaði sjöunda útgáfan þennan möguleika, svo bókinni var dreift í formi ólöglegra vélritaðra eintaka.

Í dag er hægt að kaupa endurútgáfu af bókinni en kápa hennar sýnir nemendur við ljósritunarvél. Og þökk sé Warren Toomey (sem byrjaði TUHS verkefnið) er hægt að hlaða niður PDF skjal með frumkóða fyrir sjöttu útgáfuna. Ég vil gefa þér hugmynd um hversu mikið átak fór í að búa til skrána:

Fyrir meira en 15 árum síðan skrifaði ég afrit af frumkóðanum sem gefinn er inn Lions, vegna þess að mér líkaði ekki gæði eintaksins míns frá óþekktum fjölda annarra eintaka. TUHS var ekki til ennþá og ég hafði ekki aðgang að gömlu heimildunum. En árið 1988 fann ég gamla 9 laga spólu sem innihélt öryggisafrit úr PDP11 tölvu. Það var erfitt að segja til um hvort það virkaði, en það var ósnortið /usr/src/ tré þar sem flestar skrárnar voru merktar með ártalinu 1979, sem jafnvel þá leit fornt út. Það var sjöunda útgáfan eða afleidd PWB hennar, eins og ég trúði.

Ég tók fundinn til grundvallar og handstýrði heimildunum í sjöttu útgáfuna. Sumt af kóðanum var óbreytt, en sumum þurfti að breyta örlítið og breytti nútíma += tákninu í úrelt =+. Sumt var einfaldlega eytt og sumt þurfti að endurskrifa alveg en ekki of mikið.

Og í dag getum við lesið á netinu á TUHS frumkóðann sjöttu útgáfunnar frá skjalasafn, sem Dennis Ritchie hafði hönd í bagga með.

Við the vegur, við fyrstu sýn, er aðalatriðið í C-kóðanum fyrir tímabilið Kernighan og Ritchie. stuttorð. Það er ekki oft sem ég get sett inn kóðastykki án mikillar breytinga til að passa við tiltölulega þröngt skjásvæði á síðunni minni.

Snemma /usr/sys/ken/pipe.c það er skýringar athugasemd (og já, það er meira /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

Stærð biðminni hefur ekki breyst síðan í fjórðu útgáfunni. En hér sjáum við, án nokkurra opinberra gagna, að leiðslur notuðu einu sinni skrár sem öryggisafrit!

Hvað LARG skrár varðar, þá samsvara þær inode flag LARG, sem er notað af "stóra netfangsalgríminu" til að vinna úr óbeinar blokkir til að styðja við stærri skráarkerfi. Þar sem Ken sagði að það væri betra að nota þær ekki, þá tek ég glaður undir orð hans.

Hér er hið raunverulega kerfiskall 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;
}

Athugasemdin lýsir vel því sem er að gerast hér. En að skilja kóðann er ekki svo auðvelt, að hluta til vegna þess hvernig "struct notanda u» og skráir R0 и R1 færibreytur kerfiskalla og skilagildi eru send.

Við skulum reyna með ialloc() setja á disk inode (vísitöluhandfang), og með hjálpinni falloc() - settu tvö í minni skrá. Ef allt gengur að óskum setjum við flögg til að auðkenna þessar skrár sem tvo enda leiðslunnar, beinum þeim á sömu innóðuna (þar sem viðmiðunarfjöldi verður stilltur á 2) og merkjum innóðuna sem breytta og í notkun. Gefðu gaum að beiðnum um ég setti() í villuleiðum til að draga úr viðmiðunarfjölda í nýju inóðunni.

pipe() verður í gegnum R0 и R1 skila skráarlýsingarnúmerum fyrir lestur og ritun. falloc() skilar bendi í skráarskipulagið, en „skilar“ líka í gegnum u.u_ar0[R0] og skráarlýsingu. Það er, kóðinn vistar inn r skráarlýsing til að lesa og úthlutar skráarlýsingu til að skrifa beint úr u.u_ar0[R0] eftir seinna símtalið falloc().

Flagga FPIPE, sem við stillum þegar leiðslan er búin til, stjórnar hegðun aðgerðarinnar rdwr() í sys2.ckalla á sérstakar I/O venjur:

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

Síðan aðgerðin readp() в pipe.c les gögn úr leiðslunni. En það er betra að rekja framkvæmdina frá og með writep(). Aftur hefur kóðinn orðið flóknari vegna hefðbundinnar röksemdafærslu, en hægt er að sleppa nokkrum smáatriðum.

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

Við viljum skrifa bæti í leiðsluinntakið u.u_count. Fyrst þurfum við að læsa inode (sjá hér að neðan plock/prele).

Þá athugum við inode tilvísunarteljarann. Svo lengi sem báðir endar leiðslunnar eru opnir, ætti teljarinn að vera jafn 2. Við höldum einum hlekk (frá kl. rp->f_inode), þannig að ef teljarinn er minni en 2 hlýtur það að þýða að lestrarferlið hafi lokað leiðslunni. Með öðrum orðum, við erum að reyna að skrifa í lokaða leiðslu og þetta er villa. Fyrsta skipti villukóði EPIPE og merki SIGPIPE birtist í sjöttu útgáfu Unix.

En jafnvel þótt færibandið sé opið getur það verið fullt. Í þessu tilviki losum við lásinn og förum að sofa í von um að annað ferli lesi úr leiðslunni og losi nóg pláss í henni. Eftir að hafa vaknað förum við aftur til byrjunar, hengjum upp lásinn aftur og byrjum nýja upptökulotu.

Ef það er nóg laust pláss í leiðslunni, þá skrifum við gögn til þess með því að nota skrifa()... Parameter i_size1 at inode (ef leiðslan er tóm getur hún verið jöfn 0) gefur til kynna lok gagna sem hún inniheldur þegar. Ef það er nóg upptökupláss getum við fyllt leiðsluna frá i_size1 í PIPESIZ. Síðan sleppum við lásnum og reynum að vekja hvaða ferli sem bíður þess að lesa úr leiðslunni. Við förum aftur til upphafsins til að sjá hvort við gætum skrifað eins mörg bæti og við þurftum. Ef það mistekst, þá byrjum við nýja upptökulotu.

Venjulega færibreytan i_mode inode er notað til að geyma heimildir r, w и x. En þegar um leiðslur er að ræða, gefum við merki um að eitthvað ferli bíði eftir skrifum eða lestri með því að nota bita IREAD и IWRITE í sömu röð. Ferlið setur fána og kallar sleep(), og gert er ráð fyrir að eitthvað annað ferli í framtíðinni muni valda wakeup().

Hinn raunverulegi galdur gerist í sleep() и wakeup(). Þau eru innleidd í slp.c, uppspretta hinnar frægu "Það er ekki ætlast til að þú skiljir þetta" athugasemd. Sem betur fer þurfum við ekki að skilja kóðann, skoðaðu bara nokkrar athugasemdir:

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

Ferlið sem veldur sleep() fyrir tiltekna rás, getur seinna verið vakin með öðru ferli, sem mun valda wakeup() fyrir sömu rás. writep() и readp() samræma aðgerðir sínar með slíkum pöruðum símtölum. athugið að pipe.c hefur alltaf forgang PPIPE þegar hringt er í sleep(), svo það er komið sleep() getur verið truflað af merki.

Nú höfum við allt til að skilja virknina 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);
}

Þú gætir átt auðveldara með að lesa þessa aðgerð frá botni til topps. „Lesa og skila“ greinin er venjulega notuð þegar einhver gögn eru í pípunum. Í þessu tilfelli notum við lesa () við lesum eins mikið af gögnum og til er frá og með núverandi f_offset lestur, og uppfærðu síðan gildi samsvarandi offset.

Við síðari lestur verður leiðslan tóm ef lesjöfnun hefur náð i_size1 á inode. Við endurstillum stöðuna á 0 og reynum að vekja hvaða ferli sem vill skrifa í leiðsluna. Við vitum að þegar færibandið er fullt, writep() mun sofna á ip+1. Og nú þegar leiðslan er tóm, getum við vakið hana til að hefja ritferilinn aftur.

Ef þú hefur ekkert að lesa, þá readp() getur sett fána IREAD og sofna áfram ip+2. Við vitum hvað mun vekja hann writep(), þegar það skrifar nokkur gögn í leiðsluna.

Athugasemdir við readi() og writei() mun hjálpa þér að skilja að í stað þess að senda breytur í gegnum "u„Við getum meðhöndlað þau eins og venjulegar I/O aðgerðir sem taka skrá, staðsetningu, biðminni í minni og telja fjölda bæta til að lesa eða skrifa.

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

Eins og fyrir "íhaldssama" blokkun, þá readp() и writep() loka fyrir inode þar til þeir klára vinnu sína eða fá niðurstöðu (þ.e. hringja wakeup). plock() и prele() vinna einfaldlega: nota annað sett af símtölum sleep и wakeup leyfðu okkur að vekja upp hvaða ferli sem er sem þarf á lásnum sem við gáfum út:

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

Í fyrstu gat ég ekki skilið hvers vegna readp() veldur ekki prele(ip) fyrir símtalið wakeup(ip+1). Það fyrsta er writep() veldur í hringrás sinni, þetta plock(ip), sem leiðir til dauðastöðu ef readp() hef ekki fjarlægt blokkina mína ennþá, svo einhvern veginn verður kóðinn að virka rétt. Ef þú horfir á wakeup(), þá kemur í ljós að það markar aðeins svefnferlið sem tilbúið til framkvæmdar, svo að í framtíðinni sched() setti það í alvörunni af stað. Svo readp() orsakir wakeup(), fjarlægir læsinguna, setur IREAD og hringingar sleep(ip+2)- allt þetta áður writep() byrjar hringrásina aftur.

Þetta lýkur lýsingu á færiböndum í sjöttu útgáfunni. Einfaldur kóða, víðtækar afleiðingar.

Sjöunda útgáfa af Unix (Janúar 1979) var ný meiriháttar útgáfa (fjórum árum síðar) sem kynnti mörg ný forrit og kjarnaeiginleika. Það tók einnig verulegar breytingar í tengslum við notkun tegundarsteypu, tenginga og vélritaðra vísa til mannvirkja. Hins vegar færibandakóða nánast óbreytt. Við getum sleppt þessari útgáfu.

Xv6, einfaldur Unix-líkur kjarni

Til að búa til kjarnann Xv6 undir áhrifum frá sjöttu útgáfu Unix, en hún er skrifuð í nútíma C til að keyra á x86 örgjörvum. Kóðinn er auðlesinn og skiljanlegur. Auk þess, ólíkt Unix heimildum með TUHS, geturðu sett það saman, breytt því og keyrt það á einhverju öðru en PDP 11/70. Þess vegna er þessi kjarni mikið notaður í háskólum sem fræðsluefni um stýrikerfi. Heimildir eru á Github.

Kóðinn inniheldur skýra og yfirvegaða útfærslu pípa.c, stutt af biðminni í minni í stað inode á diski. Hér gef ég aðeins skilgreininguna á "byggingarleiðslu" og virknina 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() setur stöðu restarinnar af útfærslunni, sem inniheldur aðgerðirnar piperead(), pipewrite() и pipeclose(). Raunverulegt kerfiskall sys_pipe er umbúðir útfærðar í sysfile.c. Ég mæli með að lesa allan kóðann hans. Flækjustigið er á stigi frumkóða sjöttu útgáfunnar, en það er miklu auðveldara og skemmtilegra aflestrar.

Linux 0.01

Linux 0.01 frumkóða er að finna. Það verður fróðlegt að kynna sér útfærslu lagna í hans fs/pipe.c. Þetta notar inode til að tákna leiðsluna, en leiðslan sjálf er skrifuð í nútíma C. Ef þú hefur unnið þig í gegnum 6. útgáfu kóða, munt þú ekki eiga í neinum vandræðum hér. Svona lítur aðgerðin út 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;
}

Án þess að skoða byggingarskilgreiningarnar, geturðu fundið út hvernig inode tilvísunarfjöldi er notaður til að athuga hvort ritaðgerð leiði til SIGPIPE. Auk þess að vinna bæti fyrir bæti er auðvelt að bera þessa aðgerð saman við hugmyndirnar sem lýst er hér að ofan. Jafnvel rökfræði sleep_on/wake_up lítur ekki svo framandi út.

Nútíma Linux kjarna, FreeBSD, NetBSD, OpenBSD

Ég fór fljótt í gegnum nokkra nútímakjarna. Enginn þeirra er lengur með diskútfærslu (ekki á óvart). Linux hefur sína eigin útfærslu. Þrátt fyrir að þrír nútíma BSD kjarnarnir innihaldi útfærslur byggðar á kóða sem var skrifaður af John Dyson, hafa þeir í gegnum árin orðið of ólíkir hver öðrum.

Að lesa fs/pipe.c (á Linux) eða sys/kern/sys_pipe.c (á *BSD), það krefst alvöru vígslu. Kóðinn í dag snýst um frammistöðu og stuðning við eiginleika eins og vektor og ósamstillt I/O. Og upplýsingar um minnisúthlutun, læsingar og kjarnastillingar eru mjög mismunandi. Þetta er ekki það sem framhaldsskólar þurfa fyrir kynningarnámskeið í stýrikerfum.

Allavega, ég hafði áhuga á að grafa upp nokkur gömul mynstur (eins og að búa til SIGPIPE og skila EPIPE þegar skrifað er í lokaða leiðslu) í öllum þessum mismunandi nútímakjarna. Ég mun sennilega aldrei sjá PDP-11 tölvu í raunveruleikanum, en það er samt margt að læra af kóða sem var skrifaður árum áður en ég fæddist.

Grein skrifuð af Divi Kapoor árið 2011:Linux kjarnaútfærsla á rörum og FIFO" gefur yfirlit yfir hvernig leiðslur (enn) virka í Linux. A nýleg skuldbinding í Linux sýnir leiðslulíkan af samskiptum, þar sem getu þeirra er meiri en tímabundnar skrár; og sýnir einnig hversu langt leiðslur eru komnar frá "mjög íhaldssamri læsingu" sjöttu útgáfu Unix kjarnans.

Heimild: www.habr.com

Bæta við athugasemd