Jinsi mabomba yanatekelezwa katika Unix

Jinsi mabomba yanatekelezwa katika Unix
Nakala hii inaelezea utekelezaji wa bomba kwenye kernel ya Unix. Nilisikitishwa kwa kiasi fulani kwamba makala ya hivi majuzi yenye kichwa "Mabomba hufanyaje kazi katika Unix?"iliibuka hakuna kuhusu muundo wa ndani. Nikawa na hamu ya kutaka kujua na nikazama kwenye vyanzo vya zamani ili kupata jibu.

Tunazungumzia nini?

Mabomba, "labda uvumbuzi muhimu zaidi katika Unix," ni sifa bainifu ya falsafa ya msingi ya Unix ya kuunganisha programu ndogo pamoja, na pia ishara inayojulikana kwenye safu ya amri:

$ echo hello | wc -c
6

Utendaji huu unategemea simu ya mfumo iliyotolewa na kernel pipe, ambayo imeelezwa kwenye kurasa za nyaraka bomba(7) ΠΈ bomba(2):

Mabomba hutoa njia ya unidirectional kwa mawasiliano ya usindikaji. Bomba lina pembejeo (mwisho wa kuandika) na pato (mwisho wa kusoma). Data iliyoandikwa kwa pembejeo ya bomba inaweza kusomwa kwenye pato.

Bomba linaundwa kwa kutumia simu pipe(2), ambayo inarudisha maelezo mawili ya faili: moja ikimaanisha pembejeo ya bomba, ya pili kwa pato.

Matokeo ya ufuatiliaji kutoka kwa amri hapo juu inaonyesha uundaji wa bomba na mtiririko wa data kupitia hiyo kutoka kwa mchakato mmoja hadi mwingine:

$ 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

Mchakato wa mzazi unapiga simu pipe()kupata maelezo ya faili iliyowekwa. Mchakato mmoja wa mtoto huandika kwa mpini mmoja, na mchakato mwingine husoma data sawa kutoka kwa mpini mwingine. Ganda hutumia dup2 "kubadilisha jina" vifafanuzi 3 na 4 ili kufanana na stdin na stdout.

Bila bomba, ganda litalazimika kuandika matokeo ya mchakato mmoja kwa faili na kuipitisha kwa mchakato mwingine kusoma data kutoka kwa faili. Kama matokeo, tungepoteza rasilimali zaidi na nafasi ya diski. Walakini, bomba ni nzuri sio tu kwa sababu hukuruhusu kuzuia utumiaji wa faili za muda:

Ikiwa mchakato unajaribu kusoma kutoka kwa bomba tupu basi read(2) itazuia hadi data ipatikane. Ikiwa mchakato unajaribu kuandika kwa bomba kamili, basi write(2) itazuia hadi data ya kutosha isomwe kutoka kwa bomba kutekeleza uandishi.

Kama hitaji la POSIX, hii ni mali muhimu: kuandika kwa bomba hadi PIPE_BUF baiti (angalau 512) lazima ziwe za atomiki ili michakato iweze kuwasiliana kupitia bomba kwa njia ambayo faili za kawaida (ambazo hazitoi dhamana kama hizo) haziwezi.

Unapotumia faili ya kawaida, mchakato unaweza kuandika matokeo yake yote na kuipitisha kwa mchakato mwingine. Au michakato inaweza kufanya kazi katika hali inayolingana sana, kwa kutumia utaratibu wa kuashiria nje (kama semaphore) ili kuarifu kuandika au kusoma kumekamilika. Wasafirishaji hutuokoa kutoka kwa shida hii yote.

Tunatafuta nini?

Nitaielezea kwa maneno rahisi ili iwe rahisi kwako kufikiria jinsi conveyor inaweza kufanya kazi. Utahitaji kutenga bafa na hali fulani kwenye kumbukumbu. Utahitaji vipengele ili kuongeza na kuondoa data kutoka kwa bafa. Utahitaji baadhi ya njia za kupiga kazi wakati wa kusoma na kuandika shughuli kwenye maelezo ya faili. Na utahitaji kufuli kutekeleza tabia maalum iliyoelezwa hapo juu.

Sasa tuko tayari kuhoji msimbo wa chanzo cha kernel chini ya taa angavu ili kuthibitisha au kukanusha mtindo wetu wa kiakili usioeleweka. Lakini daima uwe tayari kwa zisizotarajiwa.

Tunaangalia wapi?

Sijui nakala yangu ya kitabu maarufu iko wapi"Kitabu cha Simba"na nambari ya chanzo ya Unix 6, lakini asante kwa Jumuiya ya Urithi wa Unix unaweza kutafuta mtandaoni kwa msimbo wa chanzo hata matoleo ya zamani ya Unix.

Kutembea katika kumbukumbu za TUHS ni kama kutembelea jumba la makumbusho. Tunaweza kuangalia historia yetu iliyoshirikiwa, na ninaheshimu miaka mingi ya juhudi za kurejesha nyenzo hii kidogo kidogo kutoka kwa kanda na chapa za zamani. Na ninafahamu sana vipande hivyo ambavyo bado havipo.

Baada ya kukidhi udadisi wetu kuhusu historia ya kale ya wasafirishaji, tunaweza kuangalia kokwa za kisasa kwa kulinganisha.

Kwa njia, pipe ni nambari ya simu ya mfumo 42 kwenye jedwali sysent[]. Bahati mbaya?

Kernels za Jadi za Unix (1970-1974)

Sikupata athari yoyote pipe(2) wala katika PDP-7 Unix (Januari 1970), wala katika toleo la kwanza la Unix (Novemba 1971), wala katika msimbo wa chanzo usio kamili toleo la pili (Juni 1972).

TUHS inasema hivyo toleo la tatu la Unix (Februari 1973) ikawa toleo la kwanza na conveyors:

Toleo la Unix la 1973 lilikuwa toleo la mwisho na punje iliyoandikwa kwa lugha ya kusanyiko, lakini pia toleo la kwanza lenye mabomba. Wakati wa XNUMX, kazi ilifanyika kuboresha toleo la tatu, kernel iliandikwa upya katika C, na hivyo toleo la nne la Unix likatokea.

Msomaji mmoja alipata nakala ya hati ambayo Doug McIlroy alipendekeza wazo la "kuunganisha programu kama hose ya bustani."

Jinsi mabomba yanatekelezwa katika Unix
Katika kitabu cha Brian KernighanUnix: Historia na Kumbukumbu", katika historia ya kuibuka kwa wasafirishaji, hati hii pia imetajwa: "... ilining'inia ukutani katika ofisi yangu ya Bell Labs kwa miaka 30." Hapa mahojiano na McIlroy, na hadithi nyingine kutoka Kazi ya McIlroy, iliyoandikwa mnamo 2014:

Wakati Unix ilipotoka, shauku yangu na coroutines iliniongoza kuuliza mwandishi wa OS, Ken Thompson, kuruhusu data iliyoandikwa kwa mchakato kwenda sio tu kwa kifaa, lakini pia kutoa kwa mchakato mwingine. Ken aliamua kuwa inawezekana. Walakini, kama mtu mdogo, alitaka kila kazi ya mfumo iwe na jukumu muhimu. Kuandika moja kwa moja kati ya michakato ni faida kubwa juu ya kuandika kwa faili ya kati? Ilikuwa tu nilipotoa pendekezo mahususi kwa jina la kuvutia "bomba" na maelezo ya sintaksia ya mwingiliano kati ya michakato ambapo Ken hatimaye alisema: "Nitafanya hivyo!"

Na alifanya. Jioni moja ya kutisha, Ken alibadilisha kernel na shell, akarekebisha programu kadhaa za kawaida ili kusawazisha jinsi walivyokubali uingizaji (ambao unaweza kutoka kwa bomba), na pia akabadilisha majina ya faili. Siku iliyofuata, mabomba yalianza kutumika sana katika maombi. Kufikia mwisho wa juma, makatibu walikuwa wakizitumia kutuma hati kutoka kwa vichakataji maneno hadi kwa kichapishi. Baadaye kidogo, Ken alibadilisha API na sintaksia asili kwa kufunga matumizi ya mabomba na mikusanyiko safi, ambayo imekuwa ikitumika tangu wakati huo.

Kwa bahati mbaya, msimbo wa chanzo wa toleo la tatu la Unix kernel umepotea. Na ingawa tunayo nambari ya chanzo cha kernel iliyoandikwa katika C toleo la nne, iliyotolewa mnamo Novemba 1973, lakini ilitoka miezi kadhaa kabla ya kutolewa rasmi na haina utekelezaji wa bomba. Ni aibu kwamba msimbo wa chanzo wa chaguo hili la kukokotoa la Unix umepotea, labda milele.

Tuna hati za maandishi kwa pipe(2) kutoka kwa matoleo yote mawili, kwa hivyo unaweza kuanza kwa kutafuta hati toleo la tatu (kwa maneno fulani, yaliyopigiwa mstari β€œkwa mikono”, mfuatano wa neno halisi ^H, ikifuatiwa na mstari chini!). Hii proto-pipe(2) imeandikwa kwa lugha ya kusanyiko na inarudisha kielezi kimoja tu cha faili, lakini tayari hutoa utendakazi wa kimsingi unaotarajiwa:

Simu ya mfumo bomba huunda utaratibu wa pembejeo/towe unaoitwa bomba. Kielezi cha faili kilichorejeshwa kinaweza kutumika kwa shughuli za kusoma na kuandika. Wakati kitu kimeandikwa kwa bomba, hadi byte 504 za data huhifadhiwa, baada ya hapo mchakato wa kuandika umesimamishwa. Wakati wa kusoma kutoka kwa bomba, data iliyohifadhiwa inachukuliwa.

Kufikia mwaka uliofuata punje ilikuwa imeandikwa upya katika C, na bomba(2) katika toleo la nne ilipata muonekano wake wa kisasa na mfano "pipe(fildes)'

Simu ya mfumo bomba huunda utaratibu wa pembejeo/towe unaoitwa bomba. Vielezi vya faili vilivyorejeshwa vinaweza kutumika katika shughuli za kusoma na kuandika. Kitu kinapoandikwa kwa bomba, mpini uliorejeshwa katika r1 (resp. fildes[1]) hutumiwa, ukiwa umeakibishwa kwa baiti 4096 za data, baada ya hapo mchakato wa kuandika umesimamishwa. Wakati wa kusoma kutoka kwa bomba, mpini ulirudishwa kwa r0 (resp. fildes[0]) huchukua data.

Inachukuliwa kuwa mara tu bomba linapofafanuliwa, michakato miwili (au zaidi) ya mawasiliano (iliyoundwa na simu zinazofuata kwa uma) itahamisha data kutoka kwa bomba kwa kutumia simu kusoma ΠΈ kuandika.

Ganda lina syntax ya kufafanua safu ya laini ya michakato iliyounganishwa na bomba.

Simu za kusoma kutoka kwa bomba tupu (isiyo na data iliyoakibishwa) ambayo ina ncha moja tu (maelezo yote ya faili ya uandishi yamefungwa) hurudisha "mwisho wa faili". Simu za kuandika katika hali sawa hazizingatiwi.

Mapema zaidi utekelezaji wa bomba lililohifadhiwa inatumika kwa toleo la tano la Unix (Juni 1974), lakini inakaribia kufanana na ile iliyoonekana katika toleo lililofuata. Maoni yameongezwa hivi punde, kwa hivyo unaweza kuruka toleo la tano.

Toleo la sita la Unix (1975)

Wacha tuanze kusoma nambari ya chanzo ya Unix toleo la sita (Mei 1975). Shukrani kwa kiasi kikubwa Lions ni rahisi kupata kuliko vyanzo vya matoleo ya awali:

Kwa miaka mingi kitabu Lions ilikuwa hati pekee kwenye kernel ya Unix inayopatikana nje ya Bell Labs. Ingawa leseni ya toleo la sita iliwaruhusu walimu kutumia msimbo wake wa chanzo, leseni ya toleo la saba haikujumuisha uwezekano huu, kwa hivyo kitabu kilisambazwa kwa njia ya nakala haramu za maandishi.

Leo unaweza kununua kuchapishwa tena kwa kitabu, kifuniko ambacho kinaonyesha wanafunzi kwenye mashine ya nakala. Na shukrani kwa Warren Toomey (aliyeanzisha mradi wa TUHS) unaweza kupakua Faili ya PDF yenye msimbo wa chanzo kwa toleo la sita. Ninataka kukupa wazo la ni juhudi ngapi zilitumika kuunda faili:

Zaidi ya miaka 15 iliyopita, niliandika nakala ya msimbo wa chanzo uliotolewa Lions, kwa sababu sikupenda ubora wa nakala yangu kutoka kwa nambari isiyojulikana ya nakala zingine. TUHS haikuwepo bado na sikuweza kufikia vyanzo vya zamani. Lakini mnamo 1988, nilipata mkanda wa zamani wa nyimbo 9 ambao ulikuwa na nakala kutoka kwa kompyuta ya PDP11. Ilikuwa ngumu kusema ikiwa ilikuwa inafanya kazi, lakini kulikuwa na mti /usr/src/ mti ambao faili nyingi ziliandikwa na mwaka wa 1979, ambao hata wakati huo ulionekana kuwa wa zamani. Lilikuwa toleo la saba au toleo lake la PWB, kama nilivyoamini.

Nilichukua utaftaji kama msingi na kuhariri vyanzo kwa toleo la sita. Baadhi ya nambari zilibaki sawa, lakini zingine zililazimika kuhaririwa kidogo, kubadilisha ishara ya kisasa += hadi ya zamani =+. Vitu vingine vilifutwa tu, na vingine vilipaswa kuandikwa upya kabisa, lakini sio sana.

Na leo tunaweza kusoma mtandaoni kwenye TUHS msimbo wa chanzo wa toleo la sita kutoka kumbukumbu, ambayo Dennis Ritchie alikuwa na mkono.

Kwa njia, kwa mtazamo wa kwanza, kipengele kikuu cha C-code kabla ya kipindi cha Kernighan na Ritchie ni yake. ufupi. Si mara nyingi ambapo ninaweza kuingiza vipande vya msimbo bila uhariri wa kina ili kutoshea eneo finyu la kuonyesha kwenye tovuti yangu.

Hapo mwanzo /usr/sys/ken/pipe.c kuna maoni ya kuelezea (na ndio, kuna zaidi /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

Ukubwa wa bafa haujabadilika tangu toleo la nne. Lakini hapa tunaona, bila nyaraka zozote za umma, kwamba mabomba yalitumia faili kama hifadhi mbadala!

Kama faili za LARG, zinalingana na ingizo bendera LARG, ambayo hutumiwa na "algorithm kubwa ya kushughulikia" kuchakata vitalu visivyo vya moja kwa moja kusaidia mifumo mikubwa ya faili. Kwa kuwa Ken alisema ni bora kutozitumia, nitapokea neno lake kwa furaha.

Hapa kuna simu halisi ya mfumo 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;
}

Maoni yanaeleza wazi kinachoendelea hapa. Lakini kuelewa nambari sio rahisi sana, kwa sababu ya njia "muundo wa mtumiaji uΒ»na rejista R0 ΠΈ R1 vigezo vya simu za mfumo na maadili ya kurudi hupitishwa.

Hebu jaribu na ialoki() weka kwenye diski ingizo (kipimo cha index), na kwa msaada falloc () - weka mbili kwenye kumbukumbu faili. Iwapo kila kitu kitaenda sawa, tutaweka bendera ili kutambua faili hizi kama ncha mbili za bomba, tuzielekeze kwenye ingizo sawa (ambayo hesabu yake ya marejeleo itawekwa 2), na uweke alama kwenye ingizo kuwa imerekebishwa na inatumika. Makini na maombi naweka() katika njia za makosa ili kupunguza hesabu ya marejeleo kwenye ingizo mpya.

pipe() lazima kupitia R0 ΠΈ R1 rudisha nambari za maelezo ya faili kwa kusoma na kuandika. falloc() inarudisha pointer kwenye muundo wa faili, lakini pia "hurudi" kupitia u.u_ar0[R0] na maelezo ya faili. Hiyo ni, nambari huhifadhi ndani r kifafanuzi cha faili kwa kusoma na kupeana kielezi cha faili kwa kuandika moja kwa moja kutoka u.u_ar0[R0] baada ya simu ya pili falloc().

Bendera FPIPE, ambayo tunaweka wakati wa kuunda bomba, inadhibiti tabia ya kazi rdwr() katika sys2.ckupiga simu kwa utaratibu maalum wa I/O:

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

Kisha kazi readp() Π² pipe.c inasoma data kutoka kwa bomba. Lakini ni bora kufuatilia utekelezaji kuanzia writep(). Tena, kanuni imekuwa ngumu zaidi kwa sababu ya kanuni za kupitisha hoja, lakini maelezo fulani yanaweza kuachwa.

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

Tunataka kuandika baiti kwa pembejeo ya bomba u.u_count. Kwanza tunahitaji kufunga ingizo (tazama hapa chini plock/prele).

Kisha tunaangalia counter ya kumbukumbu ya inode. Kwa muda mrefu kama ncha zote mbili za bomba zinabaki wazi, kaunta inapaswa kuwa sawa na 2. Tunashikilia kiunga kimoja (kutoka rp->f_inode), hivyo ikiwa counter ni chini ya 2, ni lazima ina maana kwamba mchakato wa kusoma umefunga mwisho wake wa bomba. Kwa maneno mengine, tunajaribu kuandika kwa bomba lililofungwa, na hii ni kosa. Msimbo wa hitilafu kwa mara ya kwanza EPIPE na ishara SIGPIPE ilionekana katika toleo la sita la Unix.

Lakini hata ikiwa conveyor iko wazi, inaweza kuwa imejaa. Katika kesi hii, tunatoa lock na kwenda kulala kwa matumaini kwamba mchakato mwingine utasoma kutoka kwa bomba na kutoa nafasi ya kutosha ndani yake. Baada ya kuamka, tunarudi mwanzo, funga kufuli tena na uanze mzunguko mpya wa kurekodi.

Ikiwa kuna nafasi ya kutosha ya bure kwenye bomba, basi tunaandika data kwa kutumia kuandika (). Kigezo i_size1 ingizo (ikiwa bomba ni tupu, inaweza kuwa sawa na 0) inaonyesha mwisho wa data ambayo tayari inayo. Ikiwa kuna nafasi ya kutosha ya kurekodi, tunaweza kujaza bomba kutoka i_size1 kwa PIPESIZ. Kisha tunatoa lock na jaribu kuamsha mchakato wowote unaosubiri kusoma kutoka kwa bomba. Tunarudi mwanzo ili kuona ikiwa tuliweza kuandika kadiri tulivyohitaji. Ikiwa itashindwa, basi tunaanza mzunguko mpya wa kurekodi.

Kawaida parameter i_mode ingizo hutumika kuhifadhi ruhusa r, w ΠΈ x. Lakini katika kesi ya mabomba, tunaashiria kwamba mchakato fulani unasubiri kuandika au kusoma kwa kutumia bits IREAD ΠΈ IWRITE kwa mtiririko huo. Mchakato huweka bendera na simu sleep(), na inatarajiwa kwamba mchakato mwingine katika siku zijazo utasababisha wakeup().

Uchawi wa kweli hutokea sleep() ΠΈ wakeup(). Zinatekelezwa ndani slp.c, chanzo cha maoni maarufu "Hutarajiwi kuelewa hili". Kwa bahati nzuri, sio lazima tuelewe nambari, angalia maoni kadhaa:

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

Mchakato unaosababisha sleep() kwa kituo fulani, inaweza baadaye kuamshwa na mchakato mwingine, ambao utasababisha wakeup() kwa chaneli hiyo hiyo. writep() ΠΈ readp() kuratibu vitendo vyao kupitia simu hizo zilizooanishwa. kumbuka hilo pipe.c daima inatoa kipaumbele PPIPE alipoitwa sleep(), ndivyo hivyo sleep() inaweza kuingiliwa na ishara.

Sasa tuna kila kitu kuelewa kazi 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);
}

Unaweza kupata rahisi kusoma chaguo hili kutoka chini hadi juu. Tawi la "kusoma na kurudi" kawaida hutumiwa wakati kuna data fulani kwenye bomba. Katika kesi hii, tunatumia kusoma () tunasoma data nyingi kadri inavyopatikana kuanzia hii ya sasa f_offset kusoma, na kisha kusasisha thamani ya kukabiliana sambamba.

Katika usomaji unaofuata, bomba litakuwa tupu ikiwa usawa wa kusoma umefikia i_size1 kwenye ingizo. Tunaweka upya nafasi hadi 0 na kujaribu kuamsha mchakato wowote unaotaka kuandika kwenye bomba. Tunajua kwamba wakati conveyor imejaa, writep() atalala ip+1. Na sasa kwa kuwa bomba ni tupu, tunaweza kuliamsha ili kuanza tena mzunguko wake wa uandishi.

Ikiwa huna chochote cha kusoma, basi readp() inaweza kuweka bendera IREAD na kulala usingizi ip+2. Tunajua kitakachomwamsha writep(), inapoandika data fulani kwa bomba.

Maoni kuhusu readi() na writei() itakusaidia kuelewa kuwa badala ya kupitisha vigezo kupitia "u"Tunaweza kuzichukulia kama vitendaji vya kawaida vya I/O ambavyo huchukua faili, nafasi, buffer kwenye kumbukumbu, na kuhesabu idadi ya baiti za kusoma au kuandika.

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

Kwa ajili ya kuzuia "kihafidhina", basi readp() ΠΈ writep() zuia ingizo hadi wamalize kazi yao au kupokea matokeo (ambayo ni, piga simu wakeup). plock() ΠΈ prele() fanya kazi kwa urahisi: kutumia seti tofauti za simu sleep ΠΈ wakeup turuhusu kuamsha mchakato wowote unaohitaji kufuli ambayo tumetoka hivi punde:

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

Mwanzoni sikuweza kuelewa kwa nini readp() haina kusababisha prele(ip) kabla ya simu wakeup(ip+1). Jambo la kwanza ni writep() husababisha katika mzunguko wake, hii plock(ip), ambayo husababisha msuguano ikiwa readp() sijaondoa kizuizi changu bado, kwa hivyo kwa njia fulani nambari lazima ifanye kazi kwa usahihi. Ukiangalia wakeup(), basi inakuwa wazi kuwa inaashiria tu mchakato wa kulala kuwa tayari kutekeleza, ili katika siku zijazo sched() kweli ilizindua. Hivyo readp() sababu wakeup(), huondoa kufuli, huweka IREAD na simu sleep(ip+2)- yote haya kabla writep() huanza tena mzunguko.

Hii inakamilisha maelezo ya conveyors katika toleo la sita. Kanuni rahisi, matokeo ya mbali.

Toleo la saba la Unix (Januari 1979) ilikuwa toleo jipya kuu (miaka minne baadaye) ambalo lilianzisha programu nyingi mpya na vipengele vya kernel. Pia ilipitia mabadiliko makubwa kuhusiana na utumiaji wa aina ya utupaji, miungano na viashirio vilivyochapwa kwa miundo. Hata hivyo msimbo wa conveyor kivitendo bila kubadilika. Tunaweza kuruka toleo hili.

Xv6, punje rahisi kama Unix

Ili kuunda kernel Xv6 imeathiriwa na toleo la sita la Unix, lakini imeandikwa katika C ya kisasa ili kuendeshwa kwenye vichakataji vya x86. Kanuni ni rahisi kusoma na kueleweka. Pamoja, tofauti na vyanzo vya Unix vilivyo na TUHS, unaweza kuikusanya, kuirekebisha, na kuiendesha kwa kitu kingine isipokuwa PDP 11/70. Kwa hivyo, kernel hii inatumika sana katika vyuo vikuu kama nyenzo za kielimu kwenye mifumo ya uendeshaji. Vyanzo ziko kwenye Github.

Kanuni ina utekelezaji wa wazi na wa kufikiria bomba.c, inayoungwa mkono na bafa kwenye kumbukumbu badala ya ingizo kwenye diski. Hapa mimi hutoa tu ufafanuzi wa "bomba la kimuundo" na kazi 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() huweka hali ya mapumziko ya utekelezaji, ambayo ni pamoja na kazi piperead(), pipewrite() ΠΈ pipeclose(). Simu halisi ya mfumo sys_pipe ni kanga inayotekelezwa ndani sysfile.c. Ninapendekeza kusoma nambari yake yote. Utata uko katika kiwango cha msimbo wa chanzo wa toleo la sita, lakini ni rahisi zaidi na ya kufurahisha zaidi kusoma.

Linux 0.01

Msimbo wa chanzo wa Linux 0.01 unaweza kupatikana. Itakuwa funzo kujifunza utekelezaji wa mabomba katika yake fs/pipe.c. Hii hutumia ingizo kuwakilisha bomba, lakini bomba lenyewe limeandikwa katika C ya kisasa. Ikiwa umeshughulikia msimbo wa toleo la 6, hutakuwa na shida hapa. Hivi ndivyo utendaji unavyoonekana 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;
}

Bila hata kuangalia ufafanuzi wa muundo, unaweza kujua jinsi hesabu ya kumbukumbu ya ingizo inatumiwa kuangalia ikiwa operesheni ya uandishi inasababisha. SIGPIPE. Mbali na kufanya kazi kwa byte-byte, kazi hii ni rahisi kulinganisha na mawazo yaliyoelezwa hapo juu. Hata mantiki sleep_on/wake_up haionekani kuwa mgeni sana.

Kernels za kisasa za Linux, FreeBSD, NetBSD, OpenBSD

Nilipita haraka kwenye kokwa za kisasa. Hakuna hata mmoja wao aliye na utekelezaji wa diski tena (haishangazi). Linux ina utekelezaji wake. Ingawa kokwa tatu za kisasa za BSD zina utekelezwaji kulingana na nambari iliyoandikwa na John Dyson, kwa miaka mingi zimekuwa tofauti sana kutoka kwa kila mmoja.

Kusoma fs/pipe.c (kwenye Linux) au sys/kern/sys_pipe.c (kwenye *BSD), inachukua kujitolea kwa kweli. Nambari ya leo inahusu utendakazi na usaidizi wa vipengele kama vile vekta na I/O isiyolingana. Na maelezo ya mgao wa kumbukumbu, kufuli na usanidi wa kernel zote hutofautiana sana. Hivi sivyo vyuo vinahitaji kwa kozi ya utangulizi ya mifumo ya uendeshaji.

Kwa hivyo, nilikuwa na nia ya kuchimba mifumo kadhaa ya zamani (kama kutengeneza SIGPIPE na kurudi EPIPE wakati wa kuandika kwa bomba lililofungwa) katika kokwa hizi zote tofauti za kisasa. Pengine sitawahi kuona kompyuta ya PDP-11 katika maisha halisi, lakini bado kuna mengi ya kujifunza kutokana na msimbo ambao uliandikwa miaka kabla sijazaliwa.

Nakala iliyoandikwa na Divi Kapoor mnamo 2011:Utekelezaji wa Kernel ya Linux ya Mabomba na FIFO" hutoa muhtasari wa jinsi mabomba (bado) yanafanya kazi katika Linux. A ahadi ya hivi karibuni katika Linux inaonyesha mfano wa bomba la mwingiliano, ambao uwezo wake unazidi wale wa faili za muda; na pia inaonyesha jinsi mabomba yametoka kwa "kufunga kihafidhina" kwa toleo la sita la Unix kernel.

Chanzo: mapenzi.com

Kuongeza maoni