Çawa boriyên li Unix-ê têne bicîh kirin

Çawa boriyên li Unix-ê têne bicîh kirin
Ev gotar pêkanîna lûleyên di kernel Unix de vedibêje. Ez hinekî xemgîn bûm ku gotara dawî ya bi navê "Li Unix-ê xetên boriyê çawa dixebitin?" derket ne li ser avahiya navxweyî. Min meraq kir û li çavkaniyên kevn geriyan da ku bersivê bibînim.

Em behsa çi dikin?

Pipelines "dibe ku di Unix-ê de dahênana herî girîng" - taybetmendiyek diyarker a felsefeya bingehîn a Unix-ê ya berhevkirina bernameyên piçûk, û dirûşmeya rêza fermanê ya naskirî ye:

$ echo hello | wc -c
6

Vê fonksiyonê bi banga pergala kernel ve girêdayî ye pipe, ku li ser rûpelên belgeyê têne vegotin boriya (7) и boriya (2):

Xetên boriyan ji bo pêwendiya nav-pêvajoyê kanalek yekalî peyda dikin. Di boriyê de têketinek (dawiya nivîsandinê) û derketinek (dawiya xwendinê) heye. Daneyên ku li ser têketina lûleyê hatine nivîsandin dikarin li derketinê werin xwendin.

Xeta boriyê bi bangkirinê tê çêkirin pipe(2), ku du ravekerên pelan vedigerîne: yek têketina lûleyê vedigire, ya duyemîn jî dergehê.

Derketina şopê ya ji fermana jorîn çêkirina lûleyek û herikîna daneyan bi navgîniya wê ji pêvajoyek berbi pêvajoyek din nîşan dide:

$ 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

Pêvajoya dêûbav bang dike pipe()da ku ravekerên pelê yên pêvekirî bistînin. Pêvajoyek zarokê ji ravekerekê re dinivîse û pêvajoyek din heman daneyan ji diyarkerek din dixwîne. Şel bi dup2-ê ravekerên 3 û 4-ê "guhezîne" da ku stdin û stdout li hev bikin.

Bêyî boriyan, şêl neçar e ku encamek pêvajoyek li pelek binivîsîne û wê bi pêvajoyek din re bişîne da ku daneyên ji pelê bixwîne. Wekî encamek, em ê bêtir çavkanî û cîhê dîskê winda bikin. Lêbelê, boriyên boriyan ji dûrxistina pelên demkî zêdetir ji bo baş in:

Ger pêvajoyek hewl dide ku ji boriyek vala bixwînin, wê hingê read(2) heta ku dane peyda bibin dê asteng bike. Ger pêvajoyek hewl dide ku li boriyek tevahî binivîse, wê hingê write(2) dê bloke bike heya ku têra daneyên ji boriyê neyê xwendin da ku nivîsandinê temam bike.

Mîna pêdiviya POSIX, ev taybetmendiyek girîng e: nivîsandina lûleyê heya PIPE_BUF Bytes (kêmtirîn 512) divê atomî bin da ku pêvajo bi riya boriyê bi hevûdu re têkilî daynin bi rengek ku pelên normal (yên ku garantiyên weha peyda nakin) nikaribin.

Bi pelek birêkûpêk, pêvajoyek dikare hemî hilbera xwe jê re binivîsîne û wê derbasî pêvajoyek din bike. An jî pêvajo dikarin di moda paralel a hişk de bixebitin, ku mekanîzmayek nîşana derveyî (wek semafor) bikar bînin da ku hevdu li ser qedandina nivîsandinê an xwendinê agahdar bikin. Veguheztin me ji van hemû aloziyan rizgar dikin.

Em li çi digerin?

Ez ê li ser tiliyên xwe rave bikim da ku hûn ji we re hêsantir xeyal bikin ka guhezek çawa dikare bixebite. Hûn ê hewce bikin ku di bîranînê de tamponek û hin dewletek veqetînin. Hûn ê hewceyê fonksiyonan bikin ku daneyan ji tampon zêde bikin û jê bikin. Hûn ê hewceyê hin sazûmanan bikin ku hûn di dema xebatên xwendin û nivîsandinê de li ser ravekerên pelan bang bikin. Û ji bo pêkanîna tevgera taybetî ya ku li jor hatî destnîşan kirin kilît hewce ne.

Naha em amade ne ku koda çavkaniyê ya kernelê di bin ronahiya ronahiyê de bipirsin da ku modela meya derûnî ya nezelal piştrast bikin an red bikin. Lê her gav ji tiştên nediyar re amade bin.

Em li ku digerin?

Ez nizanim nusxeya min a pirtûka navdar li ku ye.Pirtûka Şêr«bi koda çavkaniya Unix 6, lê spas Civata Mîrata Unix dikare li ser înternetê were lêgerîn koda çavkaniyê guhertoyên kevntir ên Unix jî.

Gera di nav arşîvên TUHS de mîna serdana muzeyekê ye. Em dikarin li dîroka xwe ya hevpar binerin û rêzdariya min ji hewildana salan re heye ku ez hemî van materyalan ji kaset û çapên kevn piç bi bîsk vegerînim. Û haya min ji wan perçeyên ku hîna jî winda ne, ez bi baldar im.

Ji ber ku meraqa me ya di derbarê dîroka kevnar a boriyan de têr kir, em dikarin ji bo berhevdanê li navokên nûjen binêrin.

Bi awayê pipe di tabloyê de hejmara telefona pergalê 42 ye sysent[]. Hevcivîn?

Kernelên Unix ên kevneşopî (1970-1974)

Min tu şop nedît pipe(2) ne jî nav PDP-7 Unix (Çile 1970), ne jî di çapa yekem Unix (November 1971), ne jî di koda çavkaniyê ya netemam de çapa duyemîn (Hezîran 1972).

TUHS îdîa dike çapa sêyemîn Unix (Sibat 1973) guhertoya yekem bi boriyan bû:

Çapa sêyemîn a Unix-ê guhertoya dawîn bû ku bi kernelek di assembler de hatî nivîsandin, lê di heman demê de guhertoya yekem a bi lûleyan jî bû. Di sala 1973-an de, xebat ji bo baştirkirina çapa sêyemîn dihat kirin, kernel di C de ji nû ve hate nivîsandin, û bi vî rengî çapa çaremîn a Unix çêbû.

Xwendevanek şopek belgeyek dît ku tê de Doug McIlroy ramana "girêdana bernameyên mîna qulikê baxçê" pêşniyar kir.

Çawa boriyên li Unix-ê têne bicîh kirin
Di pirtûka Brian Kernighan deUnix: Dîrok û Bîranînek”, di dîroka xuyabûna gemaran de ev belge jî tê gotin: “... ew 30 sal li ofîsa min a li Bell Labs li dîwarê daleqandî bû.” Vir hevpeyvîna bi McIlroyû çîrokek din ji Karê McIlroy, di sala 2014 de hatî nivîsandin:

Dema ku Unix xuya bû, dilşewatiya min a ji bo korûtînên min kir ku ez ji nivîskarê OS-ê, Ken Thompson, bipirsim ku destûrê bide daneyên ku li hin pêvajoyê hatine nivîsandin ne tenê bi cîhazê, lê di heman demê de berbi pêvajoyek din ve jî biçin. Ken difikirî ku ew gengaz bû. Lêbelê, wekî minimalist, wî dixwest ku her taybetmendiya pergalê rolek girîng bilîze. Ma nivîsandina rasterast di navbera pêvajoyan de bi rastî li ser nivîsandina pelek navîn avantajek mezin e? Û tenê gava ku min pêşniyarek taybetî bi navê balkêş "pipeline" û ravekirina hevoksaziya pêwendiya pêvajoyan kir, Ken di dawiyê de got: "Ez ê wiya bikim!".

Û kir. Êvarek çarenûssaz, Ken kernel û şêl guhert, çend bernameyên standard rast kir da ku standard bike ka ew çawa têketinê qebûl dikin (ku dibe ku ji boriyek were), û navên pelan guhert. Dotira rojê, boriyên di sepanan de pir berfireh hatin bikar anîn. Heya dawiya hefteyê, sekreteran ew bikar anîn ku belgeyên ji peyvsazkeran bişînin çapxaneyê. Hin şûnda, Ken API-ya orîjînal û hevoksaziyê ji bo pêça karanîna lûleyan bi peymanên paqijtir ên ku ji wê hingê ve hatine bikar anîn veguhezand.

Mixabin, koda çavkaniyê ji bo çapa sêyemîn Unix kernel winda bûye. Û her çend me koda çavkaniya kernelê bi C-yê hatî nivîsandin heye çapa çaremîn, ku di Çiriya Paşîn 1973 de derketiye, lê ew çend meh beriya serbestberdana fermî derketiye û pêkanîna boriyan tê de nîne. Heyf e ku koda çavkanî ya vê taybetmendiya efsanewî ya Unix winda bibe, dibe ku her û her.

Ji bo me nivîsa belgekirinê heye pipe(2) ji herdu berdanan, ji ber vê yekê hûn dikarin bi lêgerîna belgeyê dest pê bikin çapa sêyemîn (ji bo hin peyvan, binê "bi destan" tê xêzkirin, rêzikek ji tîpên ^H li pey xêzeke jêrîn!). Ev proto-pipe(2) di assembler de tê nivîsandin û tenê yek ravekerê pelê vedigerîne, lê jixwe fonksiyona bingehîn a hêvîdar peyda dike:

Banga pergalê lûle mekanîzmayeke I/O ku jê re boriye tê gotin diafirîne. Danasîna pelê vegerî dikare ji bo xebatên xwendin û nivîsandinê were bikar anîn. Dema ku tiştek li ser boriyê tê nivîsandin, ew heya 504 byte daneyan tampon dike, piştî ku pêvajoya nivîsandinê tê sekinandin. Dema ku ji boriyê tê xwendin, daneyên tampon têne girtin.

Di sala pêş de, kernel di C de ji nû ve hatibû nivîsandin, û boriya (2) çapa çaremîn bi prototîpa xwe rûyê xwe yê nûjen girt."pipe(fildes)»:

Banga pergalê lûle mekanîzmayeke I/O ku jê re boriye tê gotin diafirîne. Danasînên pelê yên vegerî dikarin di operasyonên xwendin û nivîsandinê de werin bikar anîn. Dema ku tiştek li ser boriyê tê nivîsandin, ravekera ku di r1 de tê vegerandin (resp. fildes[1]) tê bikar anîn, heya 4096 baytên daneyê tampon dike, pişt re pêvajoya nivîsandinê tê sekinandin. Dema ku ji boriyê dixwîne, ravekerê ku vedigere r0 (resp. fildes[0]) daneyan digire.

Tê texmîn kirin ku gava ku boriyek hate diyarkirin, du (an jî bêtir) pêvajoyên têkilî (ji hêla bangên paşîn ve têne afirandin milêvdanî) dê daneyên ji boriyê bi karanîna bangan derbas bike xwendin и nivîsîn.

Di şêlê de hevoksaziyek heye ji bo diyarkirina rêzek rêzik a pêvajoyên ku bi hêlekê ve girêdayî ne.

Bangên ji bo xwendinê ji boriyek vala (ku daneya tampon tune ye) ku tenê dawiya wê heye (hemû ravekerên pelê yên nivîsandinê girtî) vedigere "dawiya pelê". Di rewşek wekhev de bangên nivîsandinê têne paşguh kirin.

Earliest pêkanîna xeta boriyê parastin vedibêje ji bo çapa pêncemîn ya Unix (Hezîran 1974), lê ew hema hema bi ya ku di serbestberdana paşîn de xuya bû yek e. Tenê şîroveyên zêde kirin, ji ber vê yekê çapa pêncemîn dikare were paşguh kirin.

Unix Sixth Edition (1975)

Dest bi xwendina koda çavkaniya Unix-ê dike çapa şeşemîn (Gulan 1975). Bi piranî spas Lions dîtina wê ji çavkaniyên guhertoyên berê pir hêsantir e:

Gelek salan pirtûk Lions yekane belgeya li ser kernel Unix-ê li derveyî Bell Labs peyda bû. Tevî ku lîsansa çapa şeşan rê da mamosteyan ku koda wê ya çavkaniyê bikar bînin, lîsansa çapa heftemîn ev îmkan ji holê rakir, ji ber vê yekê pirtûk di nusxeyên daktîloyî yên neqanûnî de hate belavkirin.

Îro hûn dikarin kopiyek ji nû ve çapkirinê ya pirtûkê bikirin, ku berga wê xwendekaran li ber kopîkirinê nîşan dide. Û spas ji Warren Toomey (ku projeya TUHS dest pê kir), hûn dikarin dakêşin Çavkaniya Çapa Şeşemîn PDF. Ez dixwazim ji we re ramanek bidim ka çiqas hewil da ku pelê çêbikin:

Zêdetirî 15 sal berê, min kopiyek koda çavkaniyê ya ku tê de hatî peyda kirin nivîsand Lionsji ber ku min ji kalîteya nusxeya xwe ya ji hejmarek nenas a kopiyên din hez nekir. TUHS hê tune bû, û min negihîşt çavkaniyên kevn. Lê di sala 1988-an de min kasetek kevn dît ku 9 şopên wê hene ku ji komputerek PDP11 vekêşana wê hebû. Zehmet bû ku meriv bizane gelo ew kar dike, lê darek /usr/src/ saxlem hebû ku tê de piraniya pelan sala 1979-an hatine nîşankirin, ku wê hingê kevnar xuya dikir. Ez fikirîm ku ew çapa heftemîn, an jêderek PWB bû.

Min vedîtin wek bingeh girt û bi destan çavkanî bi rewşa çapa şeşan veguherand. Beşek ji kodê wekî xwe ma, beşek pêdivî bû ku hinekî were guheztin, nîşana nûjen += biguhezîne ya kevinbûyî =+. Tiştek bi tenê hate jêbirin, û tiştek pêdivî bû ku bi tevahî ji nû ve were nivîsandin, lê ne pir zêde.

Û îro em dikarin li ser înternetê li TUHS koda çavkanî ya çapa şeşemîn bixwînin arşîv, ku destê Dennis Ritchie hebû.

Bi awayê, di nihêrîna pêşîn de, taybetmendiya sereke ya koda C ya beriya serdema Kernighan û Ritchie ew e. kurtî. Pir caran ne pêkan e ku ez nikaribim perçeyên kodê bêyî guheztinek berfireh têxim cîh da ku li ser malpera xwe cîhek nîsbetek teng li ser malpera xwe bicîh bikim.

Di destpêkê de /usr/sys/ken/pipe.c şîroveyek raveker heye (û erê, bêtir heye /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

Mezinahiya tampon ji çapa çaremîn ve nehatiye guhertin. Lê li vir em dibînin, bêyî belgeyek giştî, ku boriyan carekê pelan wekî hilanîna paşverû bikar anîne!

Ji bo pelên LARG, ew bi hev re têkildar in inode-ala LARG, ya ku ji hêla "algorîtmaya navnîşana mezin" ve tê bikar anîn ji bo pêvajoyê blokên nerasterast ji bo piştgirîkirina pergalên pelan ên mezintir. Ji ber ku Ken got ku neyên bikar anîn çêtir e, hingê ez ê bi dilxweşî gotina wî bigirim.

Li vir banga pergala rastîn e 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;
}

Şîrove bi zelalî tiştê ku li vir diqewime diyar dike. Lê ne ew çend hêsan e ku meriv kodê fam bike, hinekî jî ji ber ku "struct user u» û qeyd dike R0 и R1 Parametreyên banga pergalê û nirxên vegerê têne derbas kirin.

Ka em biceribînin ialloc() cîh li ser dîskê inode (inode), û bi alîkariyê falloc () - dikana du dosî. Ger her tişt baş biçe, em ê alayan destnîşan bikin ku van pelan wekî du dawiya boriyê nas bikin, wan nîşanî heman inodê bidin (ku hejmartina referansa wê dibe 2), û inode wekî guherandin û bikar anîn nîşan bikin. Bala xwe bidin daxwazên ku iput() di rêyên çewtiyê de ji bo kêmkirina jimareya referansê di inoda nû de.

pipe() ji ber R0 и R1 ji bo xwendin û nivîsandinê hejmarên danasîna pelan vedigerînin. falloc() nîşankerek vedigerîne avahiyek pelê, lê bi riya "vegere" jî u.u_ar0[R0] û ravekerek pelê. Ango kod tê de tê hilanîn r ravekera pelê ji bo xwendinê û ji bo nivîsandinê rasterast ji ravekerek destnîşan dike u.u_ar0[R0] piştî banga duyemîn falloc().

Flag FPIPE, ya ku em dema çêkirina boriyê saz dikin, tevgera fonksiyonê kontrol dike rdwr () li sys2.c, ku bi rêkûpêk I / O taybetî re dibêjin:

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

Piştre fonksiyonê readp() в pipe.c daneyên ji boriyê dixwîne. Lê çêtir e ku meriv pêkanîna ji destpêkê ve bişopîne writep(). Dîsa, kod ji ber cewherê peymana derbaskirina argumanê tevlihevtir bûye, lê hin hûrgulî dikarin bêne paşve xistin.

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

Em dixwazin ji bo têketina boriyê byte binivîsin u.u_count. Pêşî divê em inode kilît bikin (li jêr binêre plock/prele).

Dûv re em hejmara referansa inode kontrol dikin. Heya ku her du dawiya boriyê vekirî bimîne, divê jimarvan 2 be. Em li yek lînka (ji rp->f_inode), ji ber vê yekê heke hejmar ji 2 kêmtir be, wê hingê ev tê vê wateyê ku pêvajoya xwendinê dawiya xwe ya boriyê girtiye. Bi gotineke din, em hewl didin ku ji bo boriyek girtî binivîsin, ev xeletiyek e. Koda xeletiya yekem EPIPE û sînyala SIGPIPE di çapa şeşemîn a Unix de derket.

Lê her çend veguhêz vekirî be jî, dibe ku tijî be. Di vê rewşê de, em qeflê berdidin û di xew re diçin bi hêviya ku pêvajoyek din dê ji boriyê bixwîne û têra wê cîhê azad bike. Dema ku em şiyar dibin, em vedigerin destpêkê, dîsa qeflê daleqînin û çerxek nû ya nivîsandinê dest pê dikin.

Ger di boriyê de têra xwe cîhê belaş hebe, wê hingê em bi karanîna wê daneyan dinivîsin binivîse (). Parametre i_size1 inode'a (bi boriyek vala dikare bibe 0) dawiya daneya ku jixwe tê de ye nîşan dide. Ger cîhê têra nivîsandinê hebe, em dikarin boriyê jê tijî bikin i_size1 ber PIPESIZ. Dûv re em qeflê berdidin û hewl didin ku pêvajoyek ku li benda xwendina ji boriyê ye hişyar bikin. Em vedigerin destpêkê da ku bibînin ka me karî bi qasî ku ji me re hewce be binivîsin. Heke ne, wê hingê em dest bi çerxa tomarek nû dikin.

Bi gelemperî parametre i_mode inode ji bo hilanîna destûran tê bikar anîn r, w и x. Lê di mijara lûleyan de, em nîşan didin ku hin pêvajo li benda nivîsandinê an xwendinê ye ku bi karanîna bitkan tê bikar anîn IREAD и IWRITE herwiha. Pêvajo ala saz dike û bang dike sleep(), û tê çaverêkirin ku di pêşerojê de hin pêvajoyek din bang bike wakeup().

Sêrbaziya rastîn tê de çêdibe sleep() и wakeup(). Ew di nav de têne bicîh kirin slp.c, çavkaniya şîroveya navdar "Tu çaverê nakî ku hûn vê yekê fêm bikin". Xwezî, ne hewce ye ku em kodê fam bikin, tenê li hin şîroveyan binêrin:

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

Pêvajoya ku bang dike sleep() ji bo kanalek taybetî, dibe ku paşê ji hêla pêvajoyek din ve were şiyar kirin, ku dê bang bike wakeup() ji bo heman kanalê. writep() и readp() çalakiyên xwe bi rêya bangên bi vî rengî hevrêz bikin. not bikin ku pipe.c her tim pêşanî bidin PPIPE dema gazî kirin sleep(), lewra hemî sleep() dikare bi sînyalek bête birîn.

Naha me her tişt heye ku em fonksiyonê fam bikin 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);
}

Dibe ku xwendina vê fonksiyonê ji binî ber bi jor ve hêsantir be. Şaxa "xwendin û vegerandin" bi gelemperî dema ku di lûleyê de hin dane hebin tê bikar anîn. Di vê rewşê de, em bikar tînin xwendin() ji ya heyî dest pê dike bi qasî daneyan bixwîne f_offset bixwînin, û dûv re nirxa guheztina têkildar nûve bikin.

Di xwendina paşerojê de, ger guheztina xwendinê bigihîje boriyê dê vala bimîne i_size1 li inode. Em pozîsyonê vediguhezînin 0 û hewl didin ku pêvajoyek ku dixwaze li boriyê binivîsîne hişyar bikin. Em dizanin ku gava veguhêz tije ye, writep() li xew ketin ip+1. Û naha ku xeta boriya vala ye, em dikarin wê hişyar bikin da ku çerxa nivîsandina xwe ji nû ve bidomîne.

Ger tiştek xwendin tune, hingê readp() dikare alekê saz bike IREAD û di xew de dikevin ip+2. Em dizanin dê çi wî şiyar bike writep()dema ku ew hin daneyan li boriyê dinivîse.

Comments on xwendin () û nivîsandin () dê ji we re bibe alîkar ku hûn fêm bikin ku li şûna ku hûn pîvanan derbas bikin "u» em dikarin wan wekî fonksiyonên I/O yên birêkûpêk ên ku pelek, cîhek, tamponek di bîranînê de digirin û hejmara baytên xwendin an nivîsandinê dihejmêrin.

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

Ji bo astengkirina "muhafezekar", hingê readp() и writep() inodes kilît bikin heya ku ew biqedin an encamek bistînin (ango bang wakeup). plock() и prele() bi hêsanî bixebitin: komek bangên cûda bikar bînin sleep и wakeup rê bidin me ku em her pêvajoyek ku pêdiviya wê qefila ku em nû berdane hişyar bikin:

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

Di destpêkê de min nikarîbû fêm bikim çima readp() naye sedema prele(ip) berî bangê wakeup(ip+1). Ya yekem writep() di çerxa xwe de bang dike, ev plock(ip), ku di encama xitimandinê de eger readp() hîna bloka xwe ranekiriye, ji ber vê yekê kod divê bi rengek rast bixebite. Ger hûn lê binêrin wakeup(), eşkere dibe ku ew tenê pêvajoya razanê wekî ji bo darvekirinê amade nîşan dide, da ku di pêşerojê de sched() bi rastî dest pê kir. Wiha readp() dibe sedema wakeup(), vedike, saz dike IREAD û bang dike sleep(ip+2)- ev hemû berê writep() çerxê ji nû ve dest pê dike.

Ev danasîna boriyan di çapa şeşemîn de temam dike. Koda hêsan, bandorên dûr.

Çapa heftemîn Unix (Çile 1979) serbestberdana mezin a nû bû (çar sal şûnda) ku gelek serîlêdanên nû û taybetmendiyên kernel destnîşan kir. Di heman demê de di girêdana bi karanîna tîpên avêtinê, sendîkayan û nîşankerên tîpkirî yên avahiyan de jî guherînên girîng derbas kirin. Lebê koda boriyan di pratîkê de nayê guhertin. Em dikarin vê çapê berdin.

Xv6, kernelek sade ya mîna Unix

Ji bo afirandina navokek Xv6 ji hêla çapa şeşemîn a Unix-ê ve hatî bandor kirin, lê bi C-ya nûjen hatî nivîsandin ku li ser pêvajoyên x86-ê dimeşîne. Kod bi hêsanî tê xwendin û tê fêm kirin. Di heman demê de, berevajî çavkaniyên Unix-ê bi TUHS re, hûn dikarin wê berhev bikin, biguhezînin û li ser tiştek din ji bilî PDP 11/70 bimeşînin. Ji ber vê yekê, ev bingeh bi berfirehî li zanîngehan wekî materyalek hînkirinê li ser pergalên xebitandinê tê bikar anîn. Çavkanî li ser Github in.

Kod pêkanînek zelal û bifikar dihewîne boriya.c, li şûna inode li ser dîskê ji hêla tamponek bîranînê ve tê piştgirî kirin. Li vir ez tenê pênaseya "borriya strukturel" û fonksiyonê didim 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() rewşa hemî pêkanînên mayî, ku fonksiyonan vedihewîne, destnîşan dike piperead(), pipewrite() и pipeclose(). Banga pergalê ya rastîn sys_pipe pêçekek e ku tê de hatî bicîh kirin sysfile.c. Ez pêşniyar dikim ku hemî kodên wî bixwînin. Tevlihevî di asta koda çavkaniyê ya çapa şeşemîn de ye, lê xwendina wê pir hêsantir û xweştir e.

Linux 0.01

Hûn dikarin koda çavkaniyê ji bo Linux 0.01 bibînin. Ew ê hînker be ku meriv li ser pêkanîna lûleyan lêkolîn bike fs/pipe.c. Li vir, inode ji bo temsîlkirina boriyê tê bikar anîn, lê boriyê bixwe bi C-ya nûjen tê nivîsandin. Ger we riya xwe bi koda çapa şeşemîn hack kiribe, hûn ê li vir çu kêşe nebin. Ev e ku fonksiyonê xuya dike 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;
}

Tewra bêyî ku li pênaseyên strukturê mêze bikin, hûn dikarin fêr bibin ka jimareya referansa inode çawa tê bikar anîn da ku kontrol bikin ka operasyonek nivîsandinê encam dide. SIGPIPE. Ji bilî xebata byte-byte, ev fonksiyon bi ramanên jorîn re hêsan e. Heta mantiqê sleep_on/wake_up ew qas biyanî xuya nake.

Kernelên nûjen ên Linux, FreeBSD, NetBSD, OpenBSD

Ez zû çûm ser hin kernelên nûjen. Yek ji wan jixwe xwedan pêkanînek dîskê ye (ne ecêb e). Linux pêkanîna xwe heye. Her çend sê kernelên BSD-ya nûjen pêkanînên li ser bingeha koda ku ji hêla John Dyson ve hatî nivîsandin hene, bi salan ew ji hevûdu pir cûda bûne.

Xwandin fs/pipe.c (li ser Linux) an sys/kern/sys_pipe.c (li ser * BSD), ew dilsoziya rastîn digire. Performansa û piştgirî ji bo taybetmendiyên wekî vektor û I/O asynchronous îro di kodê de girîng in. Û hûrguliyên veqetandina bîranînê, kilît, û veavakirina kernelê hemî pir cûda dibin. Ev ne ya ku zanîngehan ji bo qursek destpêkê ya pergalên xebitandinê hewce ne.

Di her rewşê de, ji min re balkêş bû ku çend qalibên kevn derxînim (mînak, hilberîn SIGPIPE û vegere EPIPE dema ku ji bo boriyek girtî dinivîsin) di van hemî, pir cûda, kernelên nûjen de. Ez ê çu carî komputerek PDP-11 zindî nebînim, lê hîn jî gelek tişt hene ku ji koda ku çend sal berî ji dayikbûna min hatî nivîsandin fêr bibin.

Di 2011 de ji hêla Divi Kapoor ve hatî nivîsandin, gotara "Pêkanîna Kernel Linux ya Pipes û FIFOsnihêrînek e ku meriv lûleyên Linux (heta nuha) çawa dixebite. YEK commit vê dawîyê li ser linux modela boriyê ya danûstendinê nîşan dide, ku kapasîteyên wê ji yên pelên demkî zêdetir in; û her weha destnîşan dike ka boriyên boriyan çiqas ji "qefilkirina pir muhafezekar" di çapa şeşemîn a Unix kernel de dûr ketine.

Source: www.habr.com

Add a comment