Sut mae piblinellau'n cael eu gweithredu yn Unix

Sut mae piblinellau'n cael eu gweithredu yn Unix
Mae'r erthygl hon yn disgrifio gweithrediad piblinellau yn y cnewyllyn Unix. Roeddwn yn siomedig braidd bod erthygl ddiweddar o'r enw "Sut mae piblinellau'n gweithio yn Unix?" wedi troi mas dim am y strwythur mewnol. Deuthum yn chwilfrydig a chloddio i hen ffynonellau i ddod o hyd i'r ateb.

Am beth rydyn ni'n siarad?

Piblinellau yw "yn ôl pob tebyg y ddyfais bwysicaf yn Unix" - nodwedd ddiffiniol o athroniaeth sylfaenol Unix o roi rhaglenni bach at ei gilydd, a'r slogan llinell orchymyn cyfarwydd:

$ echo hello | wc -c
6

Mae'r swyddogaeth hon yn dibynnu ar yr alwad system a ddarperir gan gnewyllyn pipe, a ddisgrifir ar y tudalennau dogfennaeth pibell (7) и pibell (2):

Mae piblinellau yn darparu sianel un ffordd ar gyfer cyfathrebu rhwng prosesau. Mae gan y biblinell fewnbwn (diwedd ysgrifennu) ac allbwn (diwedd darllen). Gellir darllen data a ysgrifennwyd i fewnbwn y biblinell yn yr allbwn.

Mae'r biblinell yn cael ei chreu trwy ffonio pipe(2), sy'n dychwelyd dau ddisgrifydd ffeil: mae un yn cyfeirio at fewnbwn y biblinell, yr ail at yr allbwn.

Mae'r allbwn olrhain o'r gorchymyn uchod yn dangos creu piblinell a llif data trwyddo o un broses i'r llall:

$ 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

Mae'r broses rhiant yn galw pipe()i gael disgrifyddion ffeil ynghlwm. Mae proses un plentyn yn ysgrifennu at un disgrifydd ac mae proses arall yn darllen yr un data o ddisgrifydd arall. Mae'r gragen yn "ail-enwi" disgrifyddion 2 a 3 gyda dup4 i gyd-fynd â stdin a stdout.

Heb biblinellau, byddai'n rhaid i'r gragen ysgrifennu allbwn un broses i ffeil a'i bibellu i broses arall i ddarllen y data o'r ffeil. O ganlyniad, byddem yn gwastraffu mwy o adnoddau a gofod disg. Fodd bynnag, mae piblinellau yn dda ar gyfer mwy nag osgoi ffeiliau dros dro yn unig:

Os yw proses yn ceisio darllen o biblinell wag, yna read(2) yn rhwystro nes bod y data ar gael. Os yw proses yn ceisio ysgrifennu at biblinell lawn, yna write(2) yn rhwystro nes bod digon o ddata wedi'i ddarllen o'r biblinell i gwblhau'r ysgrifennu.

Fel gofyniad POSIX, mae hwn yn eiddo pwysig: ysgrifennu at y biblinell hyd at PIPE_BUF rhaid i beit (o leiaf 512) fod yn atomig fel y gall prosesau gyfathrebu â'i gilydd trwy'r biblinell mewn ffordd na all ffeiliau arferol (nad ydynt yn darparu gwarantau o'r fath).

Gyda ffeil reolaidd, gall proses ysgrifennu ei holl allbwn iddo a'i drosglwyddo i broses arall. Neu gall prosesau weithredu mewn modd paralel caled, gan ddefnyddio mecanwaith signalau allanol (fel semaffor) i hysbysu ei gilydd am gwblhau ysgrifennu neu ddarllen. Mae cludwyr yn ein hachub rhag yr holl drafferth hon.

Beth ydym yn chwilio amdano?

Byddaf yn esbonio ar fy mysedd i'w gwneud hi'n haws i chi ddychmygu sut y gall cludwr weithio. Bydd angen i chi ddyrannu byffer a rhywfaint o gyflwr yn y cof. Bydd angen swyddogaethau arnoch i ychwanegu a thynnu data o'r byffer. Bydd angen rhywfaint o gyfleuster arnoch i alw swyddogaethau yn ystod gweithrediadau darllen ac ysgrifennu ar ddisgrifyddion ffeiliau. Ac mae angen cloeon i weithredu'r ymddygiad arbennig a ddisgrifir uchod.

Rydym bellach yn barod i archwilio cod ffynhonnell y cnewyllyn o dan olau lamp llachar i gadarnhau neu wrthbrofi ein model meddwl amwys. Ond byddwch bob amser yn barod am yr annisgwyl.

Ble rydyn ni'n edrych?

Wn i ddim ble mae fy nghopi o'r llyfr enwog yn gorwedd.Llyfr y Llewod« gyda chod ffynhonnell Unix 6, ond diolch i Cymdeithas Treftadaeth Unix gellir ei chwilio ar-lein cod ffynhonnell fersiynau hŷn fyth o Unix.

Mae crwydro drwy archifau TUHS fel ymweld ag amgueddfa. Gallwn edrych ar ein hanes a rennir ac mae gennyf barch at y blynyddoedd o ymdrech i adennill yr holl ddeunydd hwn fesul tipyn o hen gasetiau ac allbrintiau. Ac rwy'n ymwybodol iawn o'r darnau hynny sy'n dal ar goll.

Ar ôl bodloni ein chwilfrydedd am hanes hynafol piblinellau, gallwn edrych ar greiddiau modern i'w cymharu.

Gyda llaw, pipe yw rhif galwad system 42 yn y tabl sysent[]. Cyd-ddigwyddiad?

Cnewyllyn Unix traddodiadol (1970-1974)

Wnes i ddim dod o hyd i unrhyw olion pipe(2) nac yn PDP-7 Unix (Ionawr 1970), nac yn argraffiad cyntaf Unix (Tachwedd 1971), nac mewn cod ffynhonnell anghyflawn ail argraffiad (Mehefin 1972).

Mae TUHS yn honni hynny trydydd argraffiad Unix (Chwefror 1973) oedd y fersiwn gyntaf gyda phiblinellau:

Trydydd argraffiad Unix oedd y fersiwn olaf gyda chnewyllyn wedi'i ysgrifennu mewn cyfosodwr, ond hefyd y fersiwn gyntaf gyda phiblinellau. Yn ystod 1973, roedd gwaith ar y gweill i wella'r trydydd argraffiad, ailysgrifennwyd y cnewyllyn yn C, ac felly ganwyd pedwerydd argraffiad Unix.

Daeth un darllenydd o hyd i sgan o ddogfen lle cynigiodd Doug McIlroy y syniad o "gysylltu rhaglenni fel pibell gardd."

Sut mae piblinellau'n cael eu gweithredu yn Unix
Yn llyfr Brian KernighanUnix: Hanes a Chofiant”, mae hanes ymddangosiad cludwyr hefyd yn sôn am y ddogfen hon: “... bu’n hongian ar y wal yn fy swyddfa yn Bell Labs am 30 mlynedd.” Yma cyfweliad gyda McIlroya stori arall gan Gwaith McIlroy, a ysgrifennwyd yn 2014:

Pan ymddangosodd Unix, gwnaeth fy angerdd am goroutines i mi ofyn i awdur yr OS, Ken Thompson, ganiatáu i ddata a ysgrifennwyd i ryw broses fynd nid yn unig i'r ddyfais, ond hefyd i'r allanfa i broses arall. Roedd Ken yn meddwl ei fod yn bosibl. Fodd bynnag, fel minimalaidd, roedd am i bob nodwedd system chwarae rhan arwyddocaol. A yw ysgrifennu uniongyrchol rhwng prosesau yn fantais fawr dros ysgrifennu at ffeil ganolraddol? A dim ond pan wnes i gynnig penodol gyda'r enw bachog "piblinell" a disgrifiad o gystrawen y rhyngweithio prosesau, ebychodd Ken o'r diwedd: "Fe wnaf hynny!".

A gwnaeth. Un noson dyngedfennol, newidiodd Ken y cnewyllyn a'r gragen, gosododd nifer o raglenni safonol i safoni sut maent yn derbyn mewnbwn (a allai ddod o biblinell), a newidiodd enwau ffeiliau. Y diwrnod wedyn, defnyddiwyd piblinellau yn eang iawn mewn cymwysiadau. Erbyn diwedd yr wythnos, roedd yr ysgrifenyddion yn eu defnyddio i anfon dogfennau o broseswyr geiriau i'r argraffydd. Ychydig yn ddiweddarach, disodlodd Ken yr API a'r gystrawen wreiddiol ar gyfer lapio'r defnydd o biblinellau â chonfensiynau glanach a ddefnyddiwyd byth ers hynny.

Yn anffodus, mae'r cod ffynhonnell ar gyfer y trydydd argraffiad cnewyllyn Unix wedi'i golli. Ac er bod gennym y cod ffynhonnell cnewyllyn wedi'i ysgrifennu yn C pedwerydd argraffiad, a ryddhawyd ym mis Tachwedd 1973, ond daeth allan ychydig fisoedd cyn y datganiad swyddogol ac nid yw'n cynnwys gweithredu piblinellau. Mae'n drueni bod y cod ffynhonnell ar gyfer y nodwedd Unix chwedlonol hon yn cael ei golli, efallai am byth.

Mae gennym destun dogfennaeth ar gyfer pipe(2) o'r ddau ddatganiad, felly gallwch chi ddechrau trwy chwilio'r ddogfennaeth trydydd argraffiad (ar gyfer rhai geiriau, wedi'i danlinellu "â llaw", cyfres o lythrennau ^H ac yna danlinelliad!). Mae'r proto hwn-pipe(2) wedi'i ysgrifennu yn y cydosodwr ac yn dychwelyd un disgrifydd ffeil yn unig, ond eisoes yn darparu'r swyddogaeth graidd ddisgwyliedig:

Galwad system bibell yn creu mecanwaith I/O a elwir yn biblinell. Gellir defnyddio disgrifydd y ffeil a ddychwelwyd ar gyfer gweithrediadau darllen ac ysgrifennu. Pan fydd rhywbeth yn cael ei ysgrifennu ar y gweill, mae'n clustogi hyd at 504 bytes o ddata, ac ar ôl hynny mae'r broses ysgrifennu yn cael ei hatal. Wrth ddarllen o'r biblinell, cymerir y data byffer.

Erbyn y flwyddyn ganlynol, yr oedd y cnewyllyn wedi ei ail-ysgrifenu yn C, a pib(2) pedwerydd argraffiad caffael ei olwg fodern gyda'r prototeip"pipe(fildes)'

Galwad system bibell yn creu mecanwaith I/O a elwir yn biblinell. Gellir defnyddio'r disgrifyddion ffeiliau a ddychwelwyd mewn gweithrediadau darllen ac ysgrifennu. Pan fydd rhywbeth yn cael ei ysgrifennu i'r biblinell, defnyddir y disgrifydd a ddychwelwyd yn r1 (rep. fildes[1]), wedi'i glustogi hyd at 4096 beit o ddata, ac ar ôl hynny mae'r broses ysgrifennu yn cael ei hatal. Wrth ddarllen o'r biblinell, mae'r disgrifydd a ddychwelwyd i r0 (resp. filedes[0]) yn cymryd y data.

Tybir unwaith y bydd piblinell wedi'i diffinio, dwy (neu fwy) o brosesau rhyngweithio (a grëwyd gan alwadau dilynol fforc) yn trosglwyddo data o'r biblinell gan ddefnyddio galwadau darllen и ysgrifennu.

Mae gan y gragen gystrawen ar gyfer diffinio cyfres llinol o brosesau sy'n gysylltiedig â phiblinell.

Mae galwadau i ddarllen o biblinell wag (sy'n cynnwys dim data byffer) sydd ag un pen yn unig (pob un yn ysgrifennu disgrifyddion ffeil ar gau) yn dychwelyd "diwedd ffeil". Anwybyddir galwadau ysgrifennu mewn sefyllfa debyg.

Cynharaf gweithredu piblinellau cadw yn cyfeirio i'r pumed argraffiad o Unix (Mehefin 1974), ond mae bron yn union yr un fath â'r un a ymddangosodd yn y datganiad nesaf. Dim ond sylwadau a ychwanegwyd, felly gellir hepgor y pumed rhifyn.

Chweched Argraffiad Unix (1975)

Dechrau darllen cod ffynhonnell Unix chweched argraffiad (Mai 1975). Diolch yn bennaf i Llewod mae'n llawer haws dod o hyd iddo na ffynonellau fersiynau cynharach:

Am flynyddoedd lawer y llyfr Llewod oedd yr unig ddogfen ar y cnewyllyn Unix sydd ar gael y tu allan i Bell Labs. Er bod trwydded y chweched argraffiad yn caniatáu i athrawon ddefnyddio ei god ffynhonnell, roedd trwydded y seithfed argraffiad yn eithrio'r posibilrwydd hwn, felly dosbarthwyd y llyfr mewn copïau wedi'u teipio'n anghyfreithlon.

Heddiw gallwch brynu copi ailargraffiad o'r llyfr, y mae ei glawr yn darlunio myfyrwyr wrth y copïwr. A diolch i Warren Toomey (a ddechreuodd y prosiect TUHS), gallwch chi lawrlwytho Ffynhonnell y Chweched Argraffiad PDF. Rwyf am roi syniad i chi o faint o ymdrech aeth i greu'r ffeil:

Dros 15 mlynedd yn ôl, fe wnes i deipio copi o'r cod ffynhonnell a ddarparwyd ynddo Llewodoherwydd nid oeddwn yn hoffi ansawdd fy nghopi o nifer anhysbys o gopïau eraill. Nid oedd TUHS yn bodoli eto, ac nid oedd gennyf fynediad at yr hen ffynonellau. Ond ym 1988 des i o hyd i hen dâp gyda 9 trac a oedd â chopi wrth gefn o gyfrifiadur PDP11. Roedd yn anodd gwybod a oedd yn gweithio, ond roedd coeden gyfan /usr/src/ lle nodwyd 1979 ar y rhan fwyaf o'r ffeiliau, a oedd hyd yn oed wedyn yn edrych yn hynafol. Y seithfed argraffiad ydoedd, neu ddeilliad PWB, meddyliais.

Cymerais y darganfyddiad fel sail a golygais y ffynonellau â llaw i gyflwr y chweched argraffiad. Arhosodd rhan o'r cod yr un peth, bu'n rhaid golygu rhywfaint, gan newid y tocyn modern += i'r darfodedig =+. Cafodd rhywbeth ei ddileu yn syml, a bu'n rhaid ailysgrifennu rhywbeth yn llwyr, ond dim gormod.

A heddiw gallwn ddarllen ar-lein yn TUHS god ffynhonnell chweched rhifyn archif, yr oedd gan Dennis Ritchie law iddo.

Gyda llaw, ar yr olwg gyntaf, prif nodwedd y cod C cyn cyfnod Kernighan a Ritchie yw ei byrder. Nid yn aml y gallaf fewnosod pytiau o god heb olygu helaeth i ffitio ardal arddangos gymharol gul ar fy ngwefan.

Ar y dechrau /usr/sys/ken/pipe.c mae sylw esboniadol (ac oes, mae mwy /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

Nid yw maint y byffer wedi newid ers y pedwerydd argraffiad. Ond yma gwelwn, heb unrhyw ddogfennaeth gyhoeddus, fod piblinellau unwaith yn defnyddio ffeiliau fel storfa wrth gefn!

Fel ar gyfer ffeiliau LARG, maent yn cyfateb i inod-baner LARG, a ddefnyddir gan yr "algorithm mynd i'r afael mawr" i brosesu blociau anuniongyrchol i gefnogi systemau ffeiliau mwy. Gan fod Ken wedi dweud ei bod yn well peidio â'u defnyddio, yna byddaf yn falch o gymryd ei air amdano.

Dyma'r alwad system go iawn 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;
}

Mae'r sylw yn disgrifio'n glir beth sy'n digwydd yma. Ond nid yw mor hawdd â hynny i ddeall y cod, yn rhannol oherwydd sut "strwythuro defnyddiwr u» a chofrestrau R0 и R1 paramedrau galwadau system a gwerthoedd dychwelyd yn cael eu pasio.

Gadewch i ni geisio gyda ialoc() gosod ar ddisg inod (inod), a chyda chymorth falloc() - storfa dau ffeil. Os aiff popeth yn iawn, byddwn yn gosod fflagiau i nodi'r ffeiliau hyn fel dau ben y biblinell, eu pwyntio at yr un inod (y mae eu cyfrif cyfeirnod yn dod yn 2), a marcio'r inod fel y'i haddaswyd ac yn cael ei ddefnyddio. Rhowch sylw i geisiadau i mewnbynnu () mewn camgymeriad llwybrau i ostyngiad y cyfrif cyfeirnod yn y inod newydd.

pipe() dyledus trwy R0 и R1 dychwelyd rhifau disgrifydd ffeil ar gyfer darllen ac ysgrifennu. falloc() yn dychwelyd pwyntydd i strwythur ffeil, ond hefyd yn "dychwelyd" trwy u.u_ar0[R0] a disgrifydd ffeil. Hynny yw, mae'r cod yn cael ei storio i mewn r disgrifydd ffeil i'w ddarllen ac yn aseinio disgrifydd i'w ysgrifennu yn uniongyrchol ohono u.u_ar0[R0] ar ôl ail alwad falloc().

Baner FPIPE, yr ydym yn ei osod wrth greu'r biblinell, yn rheoli ymddygiad y swyddogaeth rdwr() yn sys2.c, sy'n galw arferion I / O penodol:

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

Yna y swyddogaeth readp() в pipe.c yn darllen data o'r biblinell. Ond mae'n well olrhain y gweithredu gan ddechrau writep(). Unwaith eto, mae'r cod wedi dod yn fwy cymhleth oherwydd natur y ddadl yn pasio confensiwn, ond gellir hepgor rhai manylion.

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

Rydym am ysgrifennu beit i fewnbwn y biblinell u.u_count. Yn gyntaf mae angen i ni gloi'r inod (gweler isod plock/prele).

Yna rydym yn gwirio'r cyfrif cyfeirnod inod. Cyn belled â bod dau ben y biblinell yn parhau ar agor, dylai'r cownter fod yn 2. Rydym yn dal gafael ar un cyswllt (o rp->f_inode), felly os yw'r cownter yn llai na 2, yna dylai hyn olygu bod y broses ddarllen wedi cau diwedd y biblinell. Mewn geiriau eraill, rydym yn ceisio ysgrifennu at biblinell gaeedig, sy’n gamgymeriad. Cod gwall cyntaf EPIPE a signal SIGPIPE ymddangos yn y chweched rhifyn o Unix.

Ond hyd yn oed os yw'r cludwr ar agor, gall fod yn llawn. Yn yr achos hwn, rydyn ni'n rhyddhau'r clo ac yn mynd i gysgu yn y gobaith y bydd proses arall yn darllen o'r gweill ac yn rhyddhau digon o le ynddo. Pan fyddwn yn deffro, byddwn yn dychwelyd i'r dechrau, yn hongian y clo eto ac yn dechrau cylch ysgrifennu newydd.

Os oes digon o le rhydd ar y gweill, yna byddwn yn ysgrifennu data ato gan ddefnyddio ysgrifennu (). Paramedr i_size1 gall yr inode'a (gyda phiblinell wag fod yn hafal i 0) pwyntio at ddiwedd y data y mae eisoes yn ei gynnwys. Os oes digon o le i ysgrifennu, gallwn lenwi'r biblinell ohono i_size1 i PIPESIZ. Yna rydyn ni'n rhyddhau'r clo ac yn ceisio deffro unrhyw broses sy'n aros i ddarllen o'r biblinell. Awn yn ôl i'r dechrau i weld a wnaethom lwyddo i ysgrifennu cymaint o beit ag yr oedd ei angen arnom. Os na, yna rydym yn dechrau cylch recordio newydd.

Paramedr fel arfer i_mode defnyddir inode i storio caniatadau r, w и x. Ond yn achos piblinellau, rydym yn nodi bod rhywfaint o broses yn aros am ysgrifennu neu ddarllen gan ddefnyddio darnau IREAD и IWRITE yn y drefn honno. Mae'r broses yn gosod y faner a galwadau sleep(), a disgwylir y bydd rhyw broses arall yn galw yn y dyfodol wakeup().

Mae'r hud go iawn yn digwydd yn sleep() и wakeup(). Maent yn cael eu gweithredu yn slp.c, ffynhonnell yr enwog "Ni ddisgwylir i chi ddeall hyn" sylw. Yn ffodus, nid oes rhaid i ni ddeall y cod, dim ond edrych ar rai sylwadau:

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

Y broses sy'n galw sleep() ar gyfer sianel benodol, efallai y bydd yn cael ei deffro yn ddiweddarach gan broses arall, a fydd yn galw wakeup() ar gyfer yr un sianel. writep() и readp() cydlynu eu gweithredoedd trwy alwadau pâr o'r fath. nodi hynny pipe.c blaenoriaethu bob amser PPIPE pan y'i gelwir sleep(), felly i gyd sleep() gall signal ymyrryd.

Nawr mae gennym bopeth i ddeall y swyddogaeth 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);
}

Efallai y byddwch yn ei chael yn haws darllen y swyddogaeth hon o'r gwaelod i'r brig. Defnyddir y gangen "darllen a dychwelyd" fel arfer pan fo rhywfaint o ddata ar y gweill. Yn yr achos hwn, rydym yn defnyddio darllen () darllen cymaint o ddata ag sydd ar gael gan ddechrau o'r un cyfredol f_offset darllen, ac yna diweddaru gwerth y gwrthbwyso cyfatebol.

Ar ddarlleniadau dilynol, bydd y biblinell yn wag os yw'r gwrthbwyso darllen wedi cyrraedd i_size1 yn y inod. Rydym yn ailosod y sefyllfa i 0 ac yn ceisio deffro unrhyw broses sydd am ysgrifennu at y biblinell. Gwyddom pan fydd y cludwr yn llawn, writep() syrthio i gysgu ar ip+1. A nawr bod y biblinell yn wag, gallwn ei deffro i ailddechrau ei chylch ysgrifennu.

Os nad oes dim i'w ddarllen, yna readp() yn gallu gosod baner IREAD a syrthio i gysgu ar ip+2. Gwyddom beth fydd yn ei ddeffro writep()pan fydd yn ysgrifennu rhywfaint o ddata i'r biblinell.

Sylwadau ar darllen () ac ysgrifennu () yn eich helpu i ddeall hynny yn lle pasio paramedrau trwy "u» gallwn eu trin fel swyddogaethau I/O rheolaidd sy'n cymryd ffeil, safle, byffer yn y cof, a chyfrif nifer y beitau i'w darllen neu eu hysgrifennu.

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

Fel ar gyfer blocio "ceidwadol", felly readp() и writep() cloi inodes nes iddynt orffen neu gael canlyniad (h.y. galwad wakeup). plock() и prele() gweithio'n syml: defnyddio set wahanol o alwadau sleep и wakeup caniatáu inni ddeffro unrhyw broses sydd angen y clo yr ydym newydd ei ryddhau:

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

Ar y dechrau, ni allwn ddeall pam readp() ddim yn achosi prele(ip) cyn yr alwad wakeup(ip+1). Y peth cyntaf writep() yn galw yn ei ddolen, hwn plock(ip), sy'n arwain at sefyllfa ddiddatrys os readp() heb dynnu ei bloc eto, felly mae'n rhaid i'r cod weithio'n gywir rhywsut. Os edrychwch chi ar wakeup(), mae'n dod yn amlwg ei fod ond yn nodi bod y broses gysgu yn barod i'w gweithredu, fel ei fod yn y dyfodol sched() ei lansio mewn gwirionedd. Felly readp() achosion wakeup(), datgloi, setiau IREAD a galwadau sleep(ip+2)- hyn i gyd o'r blaen writep() yn ailgychwyn y cylch.

Mae hyn yn cwblhau'r disgrifiad o'r piblinellau yn y chweched rhifyn. Cod syml, goblygiadau pellgyrhaeddol.

Seithfed Argraffiad Unix (Ionawr 1979) yn ddatganiad mawr newydd (pedair blynedd yn ddiweddarach) a gyflwynodd lawer o gymwysiadau newydd a nodweddion cnewyllyn. Mae hefyd wedi mynd trwy newidiadau sylweddol mewn cysylltiad â defnyddio castio teip, undebau ac awgrymiadau teipiedig i strwythurau. Fodd bynnag cod piblinellau ni newidiodd yn ymarferol. Gallwn hepgor y rhifyn hwn.

Xv6, cnewyllyn syml tebyg i Unix

I greu cnewyllyn Xv6 dylanwadu gan chweched argraffiad Unix, ond wedi'i ysgrifennu yn C modern i redeg ar broseswyr x86. Mae'r cod yn hawdd i'w ddarllen ac yn ddealladwy. Hefyd, yn wahanol i ffynonellau Unix gyda TUHS, gallwch ei lunio, ei addasu, a'i redeg ar rywbeth heblaw PDP 11/70. Felly, defnyddir y craidd hwn yn eang mewn prifysgolion fel deunydd addysgu ar systemau gweithredu. Ffynonellau sydd ar Github.

Mae'r cod yn cynnwys gweithrediad clir a meddylgar pibell.c, wedi'i gefnogi gan byffer yn y cof yn lle inod ar ddisg. Yma rwy'n rhoi dim ond y diffiniad o "bibell strwythurol" a'r swyddogaeth 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() yn gosod cyflwr holl weddill y gweithredu, sy'n cynnwys swyddogaethau piperead(), pipewrite() и pipeclose(). Yr alwad system wirioneddol sys_pipe yn wrapper gweithredu yn sysfile.c. Rwy'n argymell darllen ei holl god. Mae'r cymhlethdod ar lefel cod ffynhonnell y chweched argraffiad, ond mae'n llawer haws ac yn fwy dymunol i'w ddarllen.

Linux 0.01

Gallwch ddod o hyd i'r cod ffynhonnell ar gyfer Linux 0.01. Bydd yn addysgiadol i astudio gweithrediad piblinellau yn ei fs/pipe.c. Yma, defnyddir inod i gynrychioli'r biblinell, ond mae'r biblinell ei hun wedi'i ysgrifennu yn C modern. Os ydych chi wedi hacio'ch ffordd trwy god y chweched argraffiad, ni chewch unrhyw drafferth yma. Dyma sut olwg sydd ar y swyddogaeth 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;
}

Hyd yn oed heb edrych ar y diffiniadau strwythur, gallwch chi ddarganfod sut mae'r cyfrif cyfeirnod inod yn cael ei ddefnyddio i wirio a yw gweithrediad ysgrifennu yn arwain at SIGPIPE. Yn ogystal â gwaith beit-wrth-beit, mae'n hawdd cymharu'r swyddogaeth hon â'r syniadau uchod. Hyd yn oed rhesymeg sleep_on/wake_up ddim yn edrych mor estron.

Cnewyllyn Linux Modern, FreeBSD, NetBSD, OpenBSD

Es yn gyflym dros rai cnewyllyn modern. Nid oes gan yr un ohonynt weithrediad ar ddisg eisoes (nid yw'n syndod). Mae gan Linux ei weithrediad ei hun. Ac er bod y tri chnewyllyn BSD modern yn cynnwys gweithrediadau yn seiliedig ar god a ysgrifennwyd gan John Dyson, dros y blynyddoedd maent wedi dod yn rhy wahanol i'w gilydd.

I ddarllen fs/pipe.c (ar Linux) neu sys/kern/sys_pipe.c (ar *BSD), mae angen ymroddiad gwirioneddol. Mae perfformiad a chefnogaeth ar gyfer nodweddion fel fector ac I/O asyncronig yn bwysig yn y cod heddiw. Ac mae manylion dyraniad cof, cloeon, a chyfluniad cnewyllyn i gyd yn amrywio'n fawr. Nid dyma sydd ei angen ar brifysgolion ar gyfer cwrs rhagarweiniol ar systemau gweithredu.

Beth bynnag, roedd yn ddiddorol i mi ddarganfod ychydig o hen batrymau (er enghraifft, cynhyrchu SIGPIPE a dychwelyd EPIPE wrth ysgrifennu at biblinell gaeedig) yn y rhain i gyd, mor wahanol, cnewyllyn modern. Mae'n debyg na fyddaf byth yn gweld cyfrifiadur PDP-11 yn fyw, ond mae llawer i'w ddysgu o hyd o'r cod a ysgrifennwyd ychydig flynyddoedd cyn i mi gael fy ngeni.

Ysgrifennwyd gan Divi Kapoor yn 2011, yr erthygl "Y Cnewyllyn Linux Gweithredu Pibellau a FIFOsyn drosolwg o sut mae piblinellau Linux (hyd yn hyn) yn gweithio. A ymrwymiad diweddar ar linux yn dangos y model piblinell o ryngweithio, y mae ei alluoedd yn uwch na rhai ffeiliau dros dro; ac mae hefyd yn dangos pa mor bell y mae piblinellau wedi mynd o "gloi ceidwadol iawn" yn y chweched rhifyn cnewyllyn Unix.

Ffynhonnell: hab.com

Ychwanegu sylw