Cumu i pipeline sò implementati in Unix

Cumu i pipeline sò implementati in Unix
Questu articulu descrive l'implementazione di pipeline in u kernel Unix. Eru un pocu dispiaciutu chì un articulu recente intitulatu "Cumu funziona i pipelines in Unix?» risultò ùn nantu à a struttura interna. Mi sò curiosu è scavatu in vechji fonti per truvà a risposta.

Di chì parlemu ?

Pipelines sò "probabilmente l'invenzione più impurtante in Unix" - una caratteristica di definizione di a filusufìa sottostante di Unix di mette inseme picculi prugrammi, è u slogan familiar di linea di cummanda:

$ echo hello | wc -c
6

Sta funziunalità dipende da a chjama di u sistema furnita da u kernel pipe, chì hè descritta nantu à e pagine di documentazione pipa (7) и pipa (2):

Pipelines furnisce un canale unidirezionale per a cumunicazione inter-processu. U pipeline hà un input (scrittura) è un output (lettura). I dati scritti à l'input di u pipeline ponu esse leghjite à u output.

U pipeline hè creatu da chjamà pipe(2), chì torna dui descriptori di file: unu si riferisce à l'input di u pipeline, u sicondu à l'output.

L'output di traccia da u cumandamentu sopra mostra a creazione di una pipeline è u flussu di dati attraversu da un prucessu à l'altru:

$ 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

U prucessu parent chjama pipe()per uttene descrittori di file allegati. Un prucessu di u zitellu scrive à un descrittore è un altru prucessu leghje i stessi dati da un altru descrittore. A shell "rinomina" i descrittori 2 è 3 cù dup4 per currisponde à stdin è stdout.

Senza pipeline, a cunchiglia duveria scrive l'output di un prucessu à un schedariu è u pipe à un altru prucessu per leghje e dati da u schedariu. In u risultatu, avariamu perdi più risorse è spaziu di discu. Tuttavia, i pipelines sò boni per più di evità i fugliali temporanei:

Se un prucessu prova di leghje da un pipeline viotu, allora read(2) bluccarà finu à chì i dati sò dispunibili. Se un prucessu prova di scrive à un pipeline cumpletu, allora write(2) bluccarà finu à chì abbastanza dati hè statu lettu da u pipeline per compie a scrittura.

Cum'è u requisitu POSIX, questu hè una pruprietà impurtante: scrive à u pipeline finu à PIPE_BUF bytes (almenu 512) deve esse atomicu per chì i prucessi ponu cumunicà cù l'altri attraversu u pipeline in una manera chì i schedarii normali (chì ùn furnisce micca tali garanzii) ùn ponu micca.

Cù un schedariu regulare, un prucessu pò scrive tuttu u so output à ellu è trasmette à un altru prucessu. O i prucessi ponu operà in un modu parallelu duru, utilizendu un mecanismu di signalamentu esternu (cum'è un semaforu) per informà l'altri nantu à a fine di una scrittura o lettura. I trasportatori ci salvanu da tuttu stu fastidiu.

Chì circhemu ?

Vi spiegheraghju nantu à e mo dite per fà più faciule per voi imagine cumu un trasportatore pò travaglià. Avete bisognu di assignà un buffer è qualchì statu in memoria. Avete bisognu di funzioni per aghjunghje è sguassà dati da u buffer. Averete bisognu di qualchì facilità per chjamà funzioni durante l'operazioni di lettura è scrittura nantu à i descrittori di file. È i chjusi sò necessarii per implementà u cumpurtamentu speciale descrittu sopra.

Avà simu pronti à interrogà u codice fonte di u kernel sottu un lume luminoso per cunfirmà o dispruvà u nostru vagu mudellu mentale. Ma sempre esse preparatu per l'imprevisu.

Induve circhemu ?

Ùn sò micca induve si trova a mo copia di u famosu libru.Libru Lions« cù u codice fonte Unix 6, ma grazia à A Società Unix Heritage pò esse cercatu in linea codice fonte ancu versioni più vechje di Unix.

Passà per l'archivi TUHS hè cum'è visità un museu. Pudemu guardà a nostra storia cumuna è aghju rispettu per l'anni di sforzu per ricuperà tuttu stu materiale pocu à pocu da vechji cassette è stampe. È sò assai cuscente di quelli frammenti chì sò sempre mancanti.

Dopu avè sappiutu a nostra curiosità nantu à a storia antica di i pipelines, pudemu fighjà nuclei muderni per paragunà.

In modu, pipe hè u numeru di chjama di u sistema 42 in a tavula sysent[]. Coincidenza ?

Kernels Unix tradiziunali (1970-1974)

Ùn aghju trovu traccia pipe(2) nè in PDP-7 Unix (ghjennaghju 1970), nè in prima edizione Unix (nuvembre 1971), nè in codice fonte incompletu seconda edizione (ghjugnu 1972).

TUHS dice chì terza edizione Unix (ferraghju 1973) era a prima versione cù pipelines:

A terza edizione di Unix era l'ultima versione cù un kernel scrittu in assembler, ma ancu a prima versione cù pipelines. Duranti u 1973, u travagliu era in corso per migliurà a terza edizione, u kernel hè stata riscritta in C, è cusì hè nata a quarta edizione di Unix.

Un lettore hà truvatu una scansione di un documentu in u quale Doug McIlroy hà prupostu l'idea di "collega programmi cum'è una manguera di giardinu".

Cumu i pipeline sò implementati in Unix
In u libru di Brian KernighanUnix: Una storia è una memoria", a storia di l'apparizione di i trasportatori ammenta ancu stu documentu: "... hè appiccicatu nantu à u muru in u mo uffiziu à i Bell Labs per 30 anni". Quì intervista cù McIlroyè una altra storia da U travagliu di McIlroy, scrittu in u 2014:

Quandu Unix apparsu, a mo passione per i coroutines m'hà fattu dumandà à l'autore di u SO, Ken Thompson, per permette à e dati scritti à qualchì prucessu per andà micca solu à u dispusitivu, ma ancu à a surtita à un altru prucessu. Ken hà pensatu chì era pussibule. Tuttavia, cum'è minimalista, vulia chì ogni funzione di u sistema ghjucassi un rolu significativu. A scrittura diretta trà i prucessi hè veramente un grande vantaghju nantu à a scrittura à un schedariu intermediu? È solu quandu aghju fattu una pruposta specifica cù u nome catchy "pipeline" è una descrizzione di a sintassi di l'interazzione di i prucessi, Ken infine esclamò: "Aghju da fà!".

È hà fattu. Una sera fatale, Ken hà cambiatu u kernel è a cunchiglia, hà riparatu parechji prugrammi standard per standardizà cumu accettanu l'input (chì puderia vene da una pipeline), è hà cambiatu i nomi di file. U ghjornu dopu, i pipelines eranu assai usati in l'applicazioni. À a fine di a settimana, i secretarii anu utilizatu per mandà documenti da i prucessori di testu à l'impresora. Un pocu dopu, Ken hà rimpiazzatu l'API è a sintassi originali per impacchendu l'usu di pipeline cù cunvenzioni più pulite chì sò state aduprate dapoi.

Sfurtunatamente, u codice fonte per a terza edizione kernel Unix hè stata persa. E ancu s'è avemu u codice fonte di u kernel scrittu in C quarta edizione, chì hè stata liberata in nuvembre 1973, ma hè surtitu uni pochi di mesi prima di a liberazione ufficiale è ùn cuntene micca l'implementazione di pipelines. Hè una disgrazia chì u codice fonte per sta funzione leggendaria Unix hè persu, forse per sempre.

Avemu testu di documentazione per pipe(2) da e duie versioni, cusì pudete principià per circà a documentazione terza edizione (per certe parolle, sottolineate "manualmente", una stringa di ^H literali seguita da un underscore !). Stu proto-pipe(2) hè scrittu in assembler è torna solu un descrittore di u schedariu, ma furnisce digià a funziunalità di u core prevista:

Chjama di sistema Pipe crea un mecanismu I/O chjamatu pipeline. U descrittore di u schedariu restituitu pò esse usatu per operazioni di lettura è scrittura. Quandu qualcosa hè scrittu à u pipeline, buffers finu à 504 bytes di dati, dopu chì u prucessu di scrittura hè suspesu. Quandu leghje da u pipeline, i dati buffered sò pigliati.

À l'annu dopu, u kernel hè statu riscritto in C, è pipe(2) quarta edizione hà acquistatu u so aspettu mudernu cù u prototipu "pipe(fildes)»:

Chjama di sistema Pipe crea un mecanismu I/O chjamatu pipeline. I descrittori di u schedariu restituiti ponu esse utilizati in operazioni di lettura è scrittura. Quandu qualcosa hè scrittu à u pipeline, u descrittore torna in r1 (resp. fildes [1]) hè utilizatu, buffered up to 4096 bytes of data, dopu chì u prucessu di scrittura hè suspesu. Quandu leghje da u pipeline, u descrittore torna à r0 (resp. fildes[0]) piglia a dati.

Si assume chì una volta chì un pipeline hè statu definitu, dui (o più) prucessi interagiscendu (creati da invucazioni successive). Furcculu) passerà dati da u pipeline usendu chjamate leghje и scrivemu.

A shell hà una sintassi per definisce un array lineare di prucessi cunnessi via un pipeline.

Chjamate à leghje da un pipeline viotu (chì ùn cuntene micca dati buffered) chì hà solu una fine (tutti i descrittori di u schedariu di scrittura chjusi) tornanu "fine di u schedariu". Scrive chjamate in una situazione simili sò ignorati.

Prima implementazione di pipeline preservata si raporta à a quinta edizione di Unix (ghjugnu 1974), ma hè quasi identica à quellu chì hè apparsu in a prossima liberazione. Solu aghjustatu cumenti, cusì a quinta edizione pò esse saltata.

Sesta edizione Unix (1975)

Cumincià à leghje u codice fonte Unix sesta edizione (Maghju 1975). In gran parte grazie à Lions hè assai più faciule di truvà cà e fonti di e versioni precedenti:

Per parechji anni u libru Lions era u solu documentu nantu à u kernel Unix dispunibule fora di Bell Labs. Ancu se a licenza di a sesta edizione hà permessu à i prufessori di utilizà u so codice fonte, a licenza di a sesta edizione escludeva sta pussibilità, cusì u libru hè statu distribuitu in copie illegale da tipografia.

Oghje pudete cumprà una copia di ristampa di u libru, a copertina di quale raffigura i studienti à a copiatrice. È grazia à Warren Toomey (chì hà iniziatu u prughjettu TUHS), pudete scaricà Sesta Edizione Fonte PDF. Vogliu dà una idea di quantu sforzu hè fattu per creà u schedariu:

Più di 15 anni fà, aghju scrittu una copia di u codice fonte furnitu Lionsperchè ùn mi piaceva micca a qualità di a mo copia da un numeru scunnisciutu di altre copie. TUHS ùn esiste micca ancu, è ùn aghju micca accessu à e vechji fonti. Ma in u 1988 aghju trovu una vechja cinta cù 9 piste chì avianu una copia di salvezza da un computer PDP11. Era difficiuli di sapè s'ellu hà travagliatu, ma ci era un arburu intactu /usr/src/ in quale a maiò parte di i schedari eranu marcati 1979, chì ancu allora parevanu anticu. Era a settima edizione, o un derivativu PWB, pensu.

Aghju pigliatu a ricerca cum'è una basa è editatu manualmente e fonti à u statu di a sesta edizione. Una parte di u codice hè stata a listessa, una parte hà da esse ligeramente editata, cambiendu u token mudernu += à l'obsoletu =+. Qualcosa hè stata simplicemente sguassata, è qualcosa avia da esse riscritta cumplettamente, ma micca troppu.

È oghje pudemu leghje in linea à TUHS u codice fonte di a sesta edizione di archiviu, à quale Dennis Ritchie hà avutu una manu.

In modu, à u primu sguardu, a funzione principale di u codice C prima di u periodu di Kernighan è Ritchie hè u so brevità. Ùn hè micca spessu chì sò capaci di inserisce snippets di codice senza una edizione estensiva per adattà una zona di visualizazione relativamente stretta in u mo situ.

À u principiu /usr/sys/ken/pipe.c ci hè un cumentu esplicativu (è iè, ci hè più /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

A dimensione di u buffer ùn hè micca cambiatu da a quarta edizione. Ma quì vedemu, senza alcuna ducumentazione publica, chì i pipelines usavanu una volta i fugliali cum'è almacenamentu di riserva!

In quantu à i schedarii LARG, currispondenu à inode-flag LARG, chì hè utilizatu da u "grande algoritmu di indirizzu" per processà blocchi indiretti per sustene sistemi di fugliale più grande. Siccomu Ken hà dettu chì hè megliu ùn aduprà micca, allora aghju cun piacè di piglià a so parolla.

Eccu u veru sistema chjamatu 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;
}

U cumentu descrive chjaramente ciò chì succede quì. Ma ùn hè micca cusì faciule per capisce u codice, in parte per via di cumu "struct user u» è i registri R0 и R1 i paràmetri di chjama di u sistema è i valori di ritornu sò passati.

Pruvemu cun ialloc() postu nantu à u discu inode (inode), è cù l'aiutu falloc() - magazzini dui schedariu. Se tuttu va bè, seremu bandieri per identificà questi schedari cum'è e duie estremità di u pipeline, indicà à u stessu inode (chì u cuntu di riferimentu diventa 2), è marcate l'inode cum'è mudificatu è in usu. Prestate attenzione à e dumande iput() in i camini d'errore per decrementà u conte di riferimentu in u novu inode.

pipe() dovutu à traversu R0 и R1 rinvià i numeri di descrittore di u schedariu per leghje è scrive. falloc() torna un punteru à una struttura di u schedariu, ma ancu "ritorna" via u.u_ar0[R0] è un descrittore di file. Questu hè, u codice hè guardatu in r descriptore di file per leghje è assigna un descrittore per scrive direttamente da u.u_ar0[R0] dopu a seconda chjama falloc().

Bandiera FPIPE, chì avemu stabilitu quandu creanu u pipeline, cuntrolla u cumpurtamentu di a funzione rdwr() in sys2.c, chì chjama routine I / O specifiche:

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

Allora a funzione readp() в pipe.c leghje dati da u pipeline. Ma hè megliu à seguità a implementazione partendu da writep(). In novu, u codice hè diventatu più cumplicatu per via di a natura di l'argumentu chì passa a cunvenzione, ma alcuni dettagli ponu esse omessi.

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

Vulemu scrive byte à l'input di pipeline u.u_count. Prima avemu bisognu di chjude l'inode (vede quì sottu plock/prele).

Allora cuntrollemu u conte di riferimentu inode. Mentre i dui estremità di u pipeline restanu aperti, u cuntatore deve esse 2. Tenemu à un ligame (da rp->f_inode), dunque se u contatore hè menu di 2, allora questu significa chì u prucessu di lettura hà chjusu a fine di u pipeline. In altri palori, avemu pruvatu à scrive à un pipeline chjusu, chì hè un sbagliu. U primu codice d'errore EPIPE è signale SIGPIPE apparsu in a sesta edizione di Unix.

Ma ancu se u trasportatore hè apertu, pò esse pienu. In questu casu, liberamu a serratura è andemu à dorme in a speranza chì un altru prucessu leghjerà da u pipeline è liberà abbastanza spaziu in questu. Quandu avemu svegliatu, vultemu à u principiu, appiccà u serratura di novu è principià un novu ciclu di scrittura.

Se ci hè abbastanza spaziu liberu in u pipeline, allora scrivimu dati cù l'usu scrivi (). Parametru i_size1 l'inode'a (cù un pipeline viotu pò esse uguali à 0) punta à a fine di e dati chì cuntene digià. Se ci hè abbastanza spaziu per scrive, pudemu riempie u pipeline da i_size1 à PIPESIZ. Allora liberamu a serratura è pruvate à sveglià ogni prucessu chì aspetta per leghje da u pipeline. Riturnemu à l'iniziu per vede s'ellu ci hè riesciutu à scrive quant'è byte avemu bisognu. Se no, allora avemu principiatu un novu ciclu di registrazione.

Di solitu paràmetru i_mode inode hè utilizatu per almacenà i permessi r, w и x. Ma in u casu di pipelines, signalemu chì qualchì prucessu aspetta per una scrittura o leghje cù bits IREAD и IWRITE rispettivamente. U prucessu stabilisce a bandiera è chjama sleep(), è hè previstu chì in u futuru qualchì altru prucessu chjamarà wakeup().

A vera magia succede in sleep() и wakeup(). Sò implementati in slp.c, a surgente di u famosu cummentariu "Ùn vi aspetta micca di capisce questu". Per furtuna, ùn avemu micca bisognu di capisce u codice, basta à fighjà qualchi cumenti:

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

U prucessu chì chjama sleep() per un canale particulari, pò dopu esse svegliatu da un altru prucessu, chì chjamarà wakeup() per u listessu canale. writep() и readp() coordinà e so azzioni attraversu tali chiamate accoppiate. nota chì pipe.c sempre priorità PPIPE quandu chjamatu sleep(), cusì tuttu sleep() pò esse interrotta da un signalu.

Avà avemu tuttu per capiscenu a funzione 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);
}

Puderete truvà più faciule per leghje sta funzione da u fondu à a cima. U ramu "leghje è ritornu" hè generalmente utilizatu quandu ci sò qualchi dati in u pipeline. In questu casu, avemu aduprà leghje () leghje quant'è dati dispunibuli cuminciendu da l'attuale f_offset leghje, è dopu aghjurnà u valore di l'offset currispundenti.

In letture successive, u pipeline serà viotu se l'offset di lettura hà righjuntu i_size1 à l'inode. Resetemu a pusizione à 0 è pruvate à sveglià ogni prucessu chì vole scrive à u pipeline. Sapemu chì quandu u trasportatore hè pienu, writep() dorme nantu ip+1. È avà chì u pipeline hè viotu, pudemu svegliate per ripiglià u so ciclu di scrittura.

Se ùn ci hè nunda di leghje, allora readp() pò mette una bandiera IREAD è si addormenta ip+2. Sapemu ciò chì u svegliarà writep()quandu scrive qualchi dati à u pipeline.

Cumenti nantu à leghje () è scrive () vi aiuterà à capisce chì invece di passà i parametri attraversu "u» Pudemu trattà cum'è funzioni I / O regulari chì piglianu un schedariu, una pusizioni, un buffer in memoria, è cuntà u numeru di bytes per leghje o scrive.

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

In quantu à u bloccu "cunservatore", allora readp() и writep() bluccà inodi finu à ch'elli finiscinu o ottene un risultatu (vale à dì, chjamanu wakeup). plock() и prele() travaglià simpliciamente: utilizendu un altru gruppu di chjama sleep и wakeup permettenu di sveglià ogni prucessu chì hà bisognu di a serratura chì avemu appena liberatu:

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

In prima ùn pudia capisce perchè readp() ùn causa micca prele(ip) prima di a chjama wakeup(ip+1). A prima cosa writep() chjama in u so ciclu, questu plock(ip), chì si traduce in un bloccu se readp() ùn hà micca sguassatu u so bloccu ancu, cusì u codice deve in qualchì modu funziona bè. S'è vo guardate wakeup(), diventa chjaru chì marca solu u prucessu di dorme cum'è prontu per l'esekzione, perchè in u futuru sched() l'hà veramente lanciatu. Allora readp() cause wakeup(), sblocca, mette IREAD è chjama sleep(ip+2)- tuttu questu prima writep() ripiglia u ciclu.

Questu cumpleta a descrizzione di pipelines in a sesta edizione. Codici simplice, implicazioni di vasta portata.

Settima Edizione Unix (ghjennaghju 1979) era una nova liberazione maiò (quattru anni dopu) chì hà introduttu parechje applicazioni novi è funzioni di kernel. Hè ancu subitu cambiamenti significativu in cunnessione cù l'usu di u casting di tipu, unioni è punters typed à strutture. Tuttavia codice pipelines praticamente ùn hà micca cambiatu. Pudemu saltà sta edizione.

Xv6, un kernel simplice simile à Unix

Per creà un nucleu Xv6 influenzatu da a sesta edizione di Unix, ma scrittu in C mudernu per eseguisce nantu à i processori x86. U codice hè facile à leghje è capiscenu. Inoltre, à u cuntrariu di e fonti Unix cù TUHS, pudete cumpilà, mudificà, è eseguisce nantu à qualcosa altru ch'è PDP 11/70. Per quessa, stu core hè largamente utilizatu in l'università cum'è materiale didatticu nantu à i sistemi operativi. Fonti sò nantu à Github.

U codice cuntene una implementazione chjara è pensativa pipa.c, sustinutu da un buffer in memoria invece di un inode nantu à u discu. Quì aghju datu solu a definizione di "pipeline strutturale" è a funzione 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() stabilisce u statu di tuttu u restu di l'implementazione, chì include e funzioni piperead(), pipewrite() и pipeclose(). A chjama di u sistema attuale sys_pipe hè un wrapper implementatu in sysfile.c. Vi cunsigliu di leghje tuttu u so codice. A cumplessità hè à u livellu di u codice fonte di a sesta edizione, ma hè assai più faciule è più piacevule à leghje.

Linux 0.01

Pudete truvà u codice fonte per Linux 0.01. Serà istruttivu per studià l'implementazione di pipelines in u so fs/pipe.c. Quì, un inode hè utilizatu per rapprisintà u pipeline, ma u pipeline stessu hè scrittu in C mudernu. Se avete pirate u vostru modu à traversu u codice di a sesta edizione, ùn avete micca prublemi quì. Questu hè ciò chì a funzione pare 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;
}

Ancu senza guardà e definizioni di strutture, pudete capisce cumu u conte di riferimentu inode hè utilizatu per verificà se una operazione di scrittura risulta in SIGPIPE. In più di u travagliu byte-by-byte, sta funzione hè faciule da paragunà cù l'idee sopra. Ancu a logica sleep_on/wake_up ùn pare micca cusì straneru.

Modern Linux Kernels, FreeBSD, NetBSD, OpenBSD

Aghju passatu rapidamente alcuni kernel muderni. Nisunu di elli hà digià una implementazione basata in discu (micca sorpresa). Linux hà a so propria implementazione. E ancu s'è i trè kernels BSD muderni cuntenenu implementazioni basate nantu à u codice chì hè statu scrittu da John Dyson, cù l'anni sò diventati troppu diffirenti l'un l'altru.

Per leghje fs/pipe.c (nantu à Linux) o sys/kern/sys_pipe.c (nantu à *BSD), ci vole una vera dedicazione. U rendiment è u supportu per e funzioni cum'è l'I / O vettoriali è asincroni sò impurtanti in u codice oghje. È i dettagli di l'allocazione di memoria, i chjusi è a cunfigurazione di u kernel varianu assai. Ùn hè micca ciò chì l'università necessitanu per un cursu introduttivu à i sistemi operativi.

In ogni casu, era interessante per mè di scopre uni pochi di mudelli antichi (per esempiu, generà SIGPIPE è torna EPIPE quandu scrive à un pipeline chjusu) in tutti questi, cusì sfarenti, kernels muderni. Probabilmente ùn vi mai vede un urdinatore PDP-11 in diretta, ma ci hè ancu assai per amparà da u codice chì hè statu scrittu uni pochi d'anni prima ch'e sia natu.

Scrittu da Divi Kapoor in u 2011, l'articulu "L'implementazione di u Kernel Linux di Pipes è FIFOhè una visione generale di cumu funzionanu i pipelines Linux (finu à avà). A recente commit in linux illustra u mudellu di pipeline di interazzione, chì e so capacità superanu quelli di i schedarii tempuranee; è mostra ancu quantu i pipeline sò andati da un "locking assai cunservatore" in a sesta edizione di u kernel Unix.

Source: www.habr.com

Add a comment