Wéi Pipelines an Unix implementéiert ginn

Wéi Pipelines an Unix implementéiert ginn
Dësen Artikel beschreift d'Ëmsetzung vu Pipelines am Unix Kernel. Ech war e bëssen enttäuscht datt e rezenten Artikel mam Titel "Wéi funktionnéiere Pipelines an Unix?» erausgestallt Net iwwer déi intern Struktur. Ech sinn virwëtzeg ginn an an al Quelle gegruewen fir d'Äntwert ze fannen.

Vu wat schwätz du?

Pipelines sinn "wahrscheinlech déi wichtegst Erfindung an Unix" - eng definéierend Feature vun der Unix senger Basisphilosophie fir kleng Programmer zesummenzestellen, an de vertraute Kommandozeil Slogan:

$ echo hello | wc -c
6

Dës Funktionalitéit hänkt vum Kernel geliwwert System Uruff of pipe, déi op den Dokumentatiounssäiten beschriwwe gëtt Päif (7) и Päif (2):

Pipelines bidden en een-Wee Kanal fir Inter-Prozess Kommunikatioun. D'Pipeline huet en Input (Schreifend) an en Ausgang (Liesend). Daten, déi un den Input vun der Pipeline geschriwwe sinn, kënnen um Ausgang gelies ginn.

D'Pipeline gëtt duerch Uruff erstallt pipe(2), déi zwee Dateideskriptoren zréckginn: een bezitt sech op den Input vun der Pipeline, déi zweet op d'Ausgab.

De Spuerausgang vum uewe genannte Kommando weist d'Schafung vun enger Pipeline an de Flux vun Daten duerch et vun engem Prozess an en aneren:

$ 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

D'Elteren Prozess rifft pipe()fir befestegt Dateideskriptoren ze kréien. Ee Kandprozess schreift un een Deskriptor an en anere Prozess liest déiselwecht Daten vun engem aneren Deskriptor. D'Schuel "benannt" Descriptoren 2 a 3 mat dup4 fir stdin an stdout ze passen.

Ouni Pipelines muss d'Schuel den Ausgang vun engem Prozess an eng Datei schreiwen an en an en anere Prozess schreiwen fir d'Donnéeën aus der Datei ze liesen. Als Resultat wäerte mir méi Ressourcen an Disk Space verschwenden. Wéi och ëmmer, Pipelines si gutt fir méi wéi just temporär Dateien ze vermeiden:

Wann e Prozess probéiert aus enger eidel Pipeline ze liesen, dann read(2) blockéiert bis d'Donnéeën verfügbar sinn. Wann e Prozess probéiert op eng voll Pipeline ze schreiwen, dann write(2) wäert blockéieren bis genuch Daten aus der Pipeline gelies gi fir d'Schreiwen ze kompletéieren.

Wéi d'POSIX Fuerderung, ass dëst eng wichteg Eegeschafte: Schreiwen un d'Pipeline bis PIPE_BUF Bytes (op d'mannst 512) mussen atomarer sinn, sou datt Prozesser matenee kënnen duerch d'Pipeline kommunizéieren op eng Manéier déi normal Dateien (déi keng esou Garantien ubidden) net kënnen.

Mat enger regulärer Datei kann e Prozess all seng Ausgab drop schreiwen an en an en anere Prozess weiderginn. Oder Prozesser kënnen an engem haarde parallele Modus operéieren, mat engem externen Signalmechanismus (wéi e Semaphore) fir géigesäiteg iwwer d'Réalisatioun vun engem Schreiwen oder Liesen z'informéieren. Conveyors retten eis vun all deem Stress.

Wat sichen mir?

Ech wäert op meng Fangeren erklären fir et méi einfach ze maachen fir Iech virzestellen wéi e Fërderband funktionnéiert. Dir musst e Puffer an e puer Staat an der Erënnerung verdeelen. Dir braucht Funktiounen fir Daten aus dem Puffer ze addéieren an ze läschen. Dir braucht e puer Ariichtungen fir Funktiounen ze ruffen wärend Lies- a Schreifoperatiounen op Dateideskriptoren. A Schleisen sinn néideg fir de spezielle Verhalen ëmzesetzen hei uewen beschriwwen.

Mir sinn elo prett fir de Quellcode vum Kernel ënner helle Luuchten ze interrogéieren fir eise vague mentale Modell ze bestätegen oder ze widderhuelen. Awer ëmmer virbereet op dat Onerwaart.

Wou sichen mir?

Ech weess net wou meng Kopie vum berühmte Buch läit.Léiw Buch« mat Unix 6 Quellcode, awer dank D'Unix Heritage Society kann online gesicht ginn Quelltext souguer eeler Versioune vun Unix.

Wandern duerch d'TUHS Archiven ass wéi e Musée ze besichen. Mir kënnen eis gemeinsam Geschicht kucken an ech hunn Respekt fir d'Jore vun Effort fir all dëst Material bëssen no bëssen aus alen Kassetten an Drécken ze recuperéieren. An ech sinn déi Fragmenter ganz bewosst, déi nach feelen.

Nodeems mir eis Virwëtz iwwer déi antik Geschicht vu Pipelines zefridden hunn, kënne mir modern Käre kucken fir ze vergläichen.

Iwwrégens, pipe ass System Opruff Nummer 42 an der Tabell sysent[]. Zoufall?

Traditionell Unix Kernels (1970–1974)

Ech hu keng Spuer fonnt pipe(2) weder an PDP-7 Unix (Januar 1970), nach an éischt Editioun Unix (November 1971), nach am onkomplett Quellcode zweet Editioun (Juni 1972).

TUHS behaapt dat drëtt Editioun Unix (Februar 1973) war déi éischt Versioun mat Pipelines:

Déi drëtt Editioun vun Unix war déi lescht Versioun mat engem Kärel geschriwwen am Assembler, awer och déi éischt Versioun mat Pipelines. Am Laf vun 1973 gouf geschafft fir déi drëtt Editioun ze verbesseren, de Kär gouf am C ëmgeschriwwen, an domat ass déi véiert Editioun vun Unix gebuer.

Ee Lieser huet e Scan vun engem Dokument fonnt, an deem den Doug McIlroy d'Iddi proposéiert huet "Programmer wéi e Gaardeschlauch ze verbannen."

Wéi Pipelines an Unix implementéiert ginn
Am Brian Kernighan sengem BuchUnix: A History and a Memoir", d'Geschicht vun der Erscheinung vun de Fërderer ernimmt och dëst Dokument: "... et hänkt 30 Joer un der Mauer a mengem Büro bei Bell Labs." Hei Interview mam McIlroyan eng aner Geschicht aus Dem McIlroy seng Aarbecht, geschriwwen am Joer 2014:

Wéi d'Unix erschéngt, huet meng Leidenschaft fir Coroutinen mech den OS Autor Ken Thompson gefrot fir Daten, déi an e puer Prozess geschriwwe goufen, net nëmmen op den Apparat ze goen, awer och op d'Ausfahrt an en anere Prozess. Ken decidéiert et méiglech. Wéi och ëmmer, als Minimalist wollt hien datt all System Feature eng bedeitend Roll spillt. Ass direkt Schreiwen tëscht Prozesser wierklech e grousse Virdeel iwwer Schreiwen op eng Zwëschendatei? An nëmmen wann ech eng spezifesch Propositioun mam opfällegen Numm "Pipeline" an enger Beschreiwung vun der Syntax vun der Interaktioun vu Prozesser gemaach hunn, huet de Ken endlech geruff: "Ech wäert et maachen!".

An huet. Ee schicksal Owend huet de Ken de Kärel an d'Schuel geännert, verschidde Standardprogrammer fixéiert fir ze standardiséieren wéi se Input akzeptéieren (wat aus enger Pipeline kënnt kommen), a geännert Dateinumm. Den nächsten Dag goufen Pipelines ganz wäit an Uwendungen benotzt. Bis Enn vun der Woch hunn d'Sekretären se benotzt fir Dokumenter vum Textveraarbechter un den Drécker ze schécken. E bësse méi spéit huet de Ken den ursprénglechen API an d'Syntax ersat fir d'Verwäertung vu Pipelines mat méi proppere Konventiounen ze wéckelen, déi zënterhier benotzt goufen.

Leider ass den Unix drëtt Editioun Kernel Quellcode verluer gaangen. An och wann mir de Kernel Quellcode an C geschriwwen hunn véiert Editioun, déi am November 1973 verëffentlecht gouf, awer et koum e puer Méint virun der offizieller Verëffentlechung eraus an enthält net d'Ëmsetzung vu Pipelines. Et ass schued datt de Quellcode fir dës legendär Unix Feature verluer ass, vläicht fir ëmmer.

Mir hunn Dokumentatioun Text fir pipe(2) vu béide Verëffentlechungen, also kënnt Dir ufänken andeems Dir d'Dokumentatioun sicht drëtt Editioun (fir bestëmmte Wierder, ënnersträicht "manuell", eng String vun ^H literals gefollegt vun engem Ënnersträich!). Dëse Proto-pipe(2) ass am Assembler geschriwwen a gëtt nëmmen ee Dateideskriptor zréck, awer liwwert schonn déi erwaart Kärfunktionalitéit:

System Opruff Päif ugeet erstellt en I / O Mechanismus genannt Pipeline. De zréckginn Dateideskriptor ka fir Lies- a Schreifoperatioune benotzt ginn. Wann eppes op d'Pipeline geschriwwe gëtt, puffert se bis zu 504 Bytes vun Daten, duerno gëtt de Schreifprozess suspendéiert. Wann Dir aus der Pipeline liest, ginn déi gebuffert Daten geholl.

Vum Joer duerno war de Kärel an C ëmgeschriwwe ginn, an Pipe (2) véiert Editioun krut säi modernen Look mam Prototyp "pipe(fildes)"

System Opruff Päif ugeet erstellt en I/O Mechanismus genannt Pipeline. Déi zréckginn Dateibeschreiwunge kënnen a Lies- a Schreifoperatioune benotzt ginn. Wann eppes op d'Pipeline geschriwwe gëtt, gëtt den Deskriptor, deen am r1 (bzw. Fildes[1]) zréckkomm ass, benotzt, bis zu 4096 Bytes vun Daten gebuffert, duerno gëtt de Schreifprozess suspendéiert. Wann Dir aus der Pipeline liest, hëlt den Deskriptor zréck op r0 (bzw. Fildes [0]) d'Donnéeën.

Et gëtt ugeholl datt eemol eng Pipeline definéiert ass, zwee (oder méi) interagéierend Prozesser (erstallt duerch spéider Invokatiounen Forschett) wäert Daten aus der Pipeline mat Uruff passéieren gelies и schreiwen.

D'Schuel huet eng Syntax fir eng linear Array vu Prozesser ze definéieren, déi iwwer eng Pipeline verbonne sinn.

Rufft fir aus enger eidel Pipeline ze liesen (keng gebufferten Donnéeën) déi nëmmen een Enn huet (all Schreifdateibeschreiwungen zougemaach) ginn "Enn vum Fichier" zréck. Schreiwen Appellen an enger ähnlecher Situatioun sinn ignoréiert.

Fréierst erhale Pipeline Ëmsetzung bezitt op déi fënneft Editioun vun Unix (Juni 1974), awer et ass bal identesch mat deem deen an der nächster Verëffentlechung opgetaucht ass. Nëmmen Kommentarer bäigefüügt, sou datt déi fënneft Editioun kann iwwersprongen ginn.

Unix Sixth Edition (1975)

Start Unix Quellcode ze liesen sechsten Editioun (Mee 1975). Grousse Merci un Lions et ass vill méi einfach ze fannen wéi d'Quelle vu fréiere Versiounen:

Fir vill Joer d'Buch Lions war dat eenzegt Dokument op der Unix Kernel verfügbar ausserhalb vu Bell Labs. Och wann déi sechst Editioun Lizenz erlaabt Enseignanten hire Quellcode ze benotzen, déi siwent Editioun Lizenz huet dës Méiglechkeet ausgeschloss, sou datt d'Buch an illegal typeschgeschriwwene Kopie verdeelt gouf.

Haut kënnt Dir eng Kopie vum Buch kafen, op deem Cover d'Studenten am Copier duerstellt. An dank dem Warren Toomey (deen den TUHS Projet ugefaang huet), kënnt Dir eroflueden Sechster Editioun Source PDF. Ech wëll Iech eng Iddi ginn wéi vill Ustrengung fir d'Datei ze kreéieren:

Viru méi wéi 15 Joer hunn ech eng Kopie vum Quellcode aginn Lionswell d'Qualitéit vu menger Kopie vun enger onbekannter Zuel vun aneren Exemplaren net gutt gefall huet. TUHS existéiert nach net, an ech hu keen Zougang zu den ale Quellen. Mee 1988 hunn ech eng al Band fonnt mat 9 Bunnen, déi e Backup vun engem PDP11 Computer haten. Et war schwéier ze wëssen ob et funktionnéiert, awer et war en intakten /usr/src/ Bam an deem déi meescht Dateien 1979 markéiert waren, deen och deemools antik ausgesinn huet. Et war déi siwent Editioun, oder eng PWB-Derivat, hunn ech geduecht.

Ech hunn d'Find als Basis geholl an d'Quellen manuell op den Zoustand vun der sechster Editioun geännert. En Deel vum Code ass d'selwecht bliwwen, en Deel huet misse liicht geännert ginn, de modernen += Token an den eelere =+ änneren. Eppes gouf einfach geläscht, an eppes huet misse komplett nei geschriwwe ginn, awer net ze vill.

An haut kënne mir online op TUHS de Quellcode vun der sechster Editioun vun liesen Archiv, un deem Dennis Ritchie eng Hand hat.

Iwwregens, op den éischte Bléck, ass d'Haaptfunktioun vum C-Code virun der Period vu Kernighan a Ritchie seng kuerzheet. Et ass net dacks datt ech fäeg sinn Code-Snippets ouni extensiv Redaktioun anzeginn fir e relativ schmuele Displaygebitt op menger Säit ze passen.

Am Ufank /usr/sys/ken/pipe.c et gëtt en Erklärungskommentar (a jo, et gëtt méi /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

D'Puffergréisst ass zënter der véierter Editioun net geännert. Awer hei gesi mir, ouni ëffentlech Dokumentatioun, datt Pipelines eemol Dateien als Réckfalllagerung benotzt hunn!

Wat LARG Dateien ugeet, entspriechen se inode-Fändel LARG, dee vum "grousse Adressalgorithmus" benotzt gëtt fir ze veraarbecht indirekt Blocks fir méi grouss Dateiesystemer z'ënnerstëtzen. Well de Ken gesot huet, et ass besser se net ze benotzen, da wäert ech him gären op d'Wuert huelen.

Hei ass de richtege Systemruff 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;
}

De Kommentar beschreift kloer wat hei geschitt. Awer et ass net sou einfach de Code ze verstoen, deelweis wéinst wéi "struct user u» a Registere R0 и R1 System Call Parameteren a Retour Wäerter ginn passéiert.

Loosst d'probéieren mat ialloc() op Disk setzen Inode (Inode), a mat der Hëllef falloc() - Buttek zwee Datei. Wann alles gutt geet, setzen mir Fändelen fir dës Dateien als déi zwee Enden vun der Pipeline z'identifizéieren, weisen se op déiselwecht Inode (déi hir Referenzzuel 2 gëtt), a markéiert d'Inode als geännert an am Gebrauch. Opgepasst op Ufroe fir iput() a Feelerweeër fir d'Referenzzuel an der neier Inode ze reduzéieren.

pipe() wéinst duerch R0 и R1 Retour Datei Descriptor Zuelen fir Liesen a Schreiwen. falloc() gëtt e Pointer op eng Dateistruktur zréck, awer och "zréck" via u.u_ar0[R0] an e Fichier Descriptor. Dat ass, de Code ass gespäichert an r Fichier Descriptor fir Liesen an zougewisen engem Descriptor fir Schreiwen direkt aus u.u_ar0[R0] nom zweeten Opruff falloc().

Fändel FPIPE, déi mir setzen beim Schafe vun der Pipeline, kontrolléiert d'Behuele vun der Funktioun rdwr() an sys2.c, déi spezifesch I/O Routine nennt:

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

Dann d'Funktioun readp() в pipe.c liest Daten aus der Pipeline. Awer et ass besser d'Ëmsetzung ze verfollegen ab writep(). Nach eng Kéier ass de Code méi komplizéiert ginn wéinst der Natur vum Argument, deen d'Konventioun passéiert, awer e puer Detailer kënnen ausgelooss ginn.

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

Mir wëllen Bytes un de Pipeline-Input schreiwen u.u_count. Als éischt musse mir d'Inode spären (kuckt hei ënnen plock/prele).

Da kontrolléiere mir d'Inode Referenzzuel. Soulaang béid Enden vun der Pipeline oppe bleiwen, soll de Comptoir 2. Mir halen un engem Link (vun rp->f_inode), also wann de Konter manner wéi 2 ass, da sollt dat heeschen datt de Liesprozess säin Enn vun der Pipeline zougemaach huet. An anere Wierder, mir probéieren op eng zougemaach Pipeline ze schreiwen, wat e Feeler ass. Éischt Feeler Code EPIPE an Signal SIGPIPE erschéngt an der sechster Editioun vun Unix.

Awer och wann de Fërderband op ass, kann et voll sinn. An dësem Fall befreien mir d'Schloss a schlofen an der Hoffnung datt en anere Prozess aus der Pipeline liest a genuch Plaz dran befreit. Wa mir erwächen, komme mir zréck an den Ufank, hänken d'Spär erëm op a starten en neie Schreifzyklus.

Wann et genuch fräi Plaz an der Pipeline ass, da schreiwen mir Daten op et mat schreiwen(). Parameter i_size1 d'Inode'a (mat enger eidel Pipeline kann gläich 0 sinn) weist op d'Enn vun den Donnéeën, déi et scho enthält. Wann et genuch Plaz ass fir ze schreiwen, kënne mir d'Pipeline ausfëllen i_size1 ze PIPESIZ. Da loosse mir d'Schloss eraus a probéieren all Prozess z'erwächen, dee waart fir aus der Pipeline ze liesen. Mir ginn zréck an den Ufank fir ze kucken ob mir et fäerdeg bruecht hunn esou vill Bytes ze schreiwen wéi mir gebraucht hunn. Wann net, da starten mir en neien Opnamzyklus.

Normalerweis Parameter i_mode inode gëtt benotzt fir Permissiounen ze späicheren r, w и x. Mä am Fall vun Pipelines, mir Signal, datt e puer Prozess fir eng schreiwen gewaart oder liesen Bits benotzt IREAD и IWRITE respektiv. De Prozess setzt de Fändel a rifft sleep(), an et gëtt erwaart datt an Zukunft e puer anere Prozess ruffen wakeup().

Déi richteg Magie geschitt an sleep() и wakeup(). Si sinn ëmgesat an slp.c, d'Quell vum berühmten "Dir sidd net erwaart dëst ze verstoen" Kommentar. Glécklecherweis musse mir de Code net verstoen, kuckt just e puer Kommentarer:

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

De Prozess deen rifft sleep() fir e bestëmmte Kanal, kann spéider vun engem anere Prozess erwächt ginn, dee wäert ruffen wakeup() fir déi selwecht Kanal. writep() и readp() koordinéieren hir Handlungen duerch sou gepaart Uruff. notéiert dat pipe.c ëmmer Prioritéit PPIPE wann genannt sleep(), also all sleep() kann duerch e Signal ënnerbrach ginn.

Elo hu mir alles fir d'Funktioun ze verstoen 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);
}

Dir kënnt et méi einfach fannen dës Funktioun vun ënnen no uewen ze liesen. D'Branche "liesen an zréck" gëtt normalerweis benotzt wann et e puer Daten an der Pipeline ass. An dësem Fall benotze mir liesen () liesen esou vill Donnéeën wéi disponibel ab der aktueller f_offset liesen, an dann update de Wäert vun der entspriechend Offset.

Bei spéider Liesungen ass d'Pipeline eidel wann de Liesoffset erreecht ass i_size1 an der Inod. Mir setzen d'Positioun op 0 zréck a probéieren all Prozess z'erwächen, deen op d'Pipeline schreiwen wëllt. Mir wëssen datt wann de Fërderband voll ass, writep() schlofen op ip+1. An elo, datt d'Pipeline eidel ass, kënne mir et erwächen fir säi Schreifzyklus erëmzefannen.

Wann et näischt ze liesen ass, dann readp() kann e Fändel setzen IREAD a schlofen op ip+2. Mir wësse wat hien erwächt writep()wann et e puer Donnéeën un d'Pipeline schreift.

Kommentaren op liesen () a schreiwen () hëlleft Iech ze verstoen datt amplaz Parameteren duerch "u» Mir kënnen se wéi normal I/O Funktiounen behandelen, déi e Fichier, eng Positioun, e Puffer an der Erënnerung huelen an d'Zuel vun de Bytes zielen fir ze liesen oder ze schreiwen.

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

Wat "konservativ" Blockéierung ugeet, dann readp() и writep() blockéieren Inoden bis se fäerdeg sinn oder e Resultat kréien (dat ass, se ruffen wakeup). plock() и prele() Aarbecht einfach: eng aner Formatioun vun Uriff benotzt sleep и wakeup erlaabt eis all Prozess z'erwächen, deen de Spär brauch, dee mir just verëffentlecht hunn:

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

Am Ufank konnt ech net verstoen firwat readp() net verursaacht prele(ip) virum Opruff wakeup(ip+1). Déi éischt Saach writep() rifft a senger Loop, dëst plock(ip), wat zu engem Deadlock resultéiert wann readp() huet säi Block nach net geläscht, also muss de Code iergendwéi richteg funktionnéieren. Wann Dir kuckt wakeup(), gëtt kloer datt et nëmmen de Schlofprozess als fäerdeg fir d'Ausféierung markéiert, sou datt an Zukunft sched() wierklech lancéiert et. Also readp() verursaacht wakeup(), Spär, setzt IREAD an rifft sleep(ip+2)- all dëst virun writep() Restart den Zyklus.

Dëst fäerdeg d'Beschreiwung vu Pipelines an der sechster Editioun. Einfach Code, wäitreegend Konsequenzen.

Siwent Editioun Unix (Januar 1979) war eng nei grouss Verëffentlechung (véier Joer méi spéit) déi vill nei Uwendungen a Kernel Feature agefouert huet. Et huet och bedeitendst Ännerungen am Zesummenhang mat der Benotzung vun Typ Goss weiderentwéckelt, Gewerkschaften an getippten Zeeche fir Strukturen. Allerdéngs Pipelines Code praktesch net geännert. Mir kënnen dës Editioun iwwersprangen.

Xv6, en einfachen Unix-ähnlechen Kernel

Fir e Kär ze kreéieren xv6 beaflosst vun der sechster Editioun vun Unix, awer am modernen C geschriwwen fir op x86 Prozessoren ze lafen. De Code ass einfach ze liesen a verständlech. Och, am Géigesaz zu Unix Quelle mat TUHS, kënnt Dir et kompiléieren, änneren an op eppes anescht wéi PDP 11/70 lafen. Dofir gëtt dëse Kär wäit an den Universitéite benotzt als Léiermaterial iwwer Betribssystemer. Quellen sinn op Github.

De Code enthält eng kloer an Duerchduechte Ëmsetzung pipe.c, ënnerstëtzt vun engem Puffer am Erënnerung amplaz vun enger Inode op Disk. Hei ginn ech nëmmen d'Definitioun vu "strukturell Pipeline" an d'Funktioun 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() setzt den Zoustand vun all de Rescht vun der Ëmsetzung, déi Funktiounen enthält piperead(), pipewrite() и pipeclose(). Déi aktuell System Opruff sys_pipe ass e Wrapper implementéiert an sysfile.c. Ech recommandéieren all säi Code ze liesen. D'Komplexitéit ass um Niveau vum Quellcode vun der sechster Editioun, awer et ass vill méi einfach a méi agreabel ze liesen.

Linux 0.01

Dir fannt de Quellcode fir Linux 0.01. Et wäert léierräich sinn der Ëmsetzung vun Pipelines ze studéieren a sengem fs/pipe.c. Hei gëtt eng Inode benotzt fir d'Pipeline ze representéieren, awer d'Pipeline selwer ass am modernen C geschriwwen. Wann Dir de Wee duerch de Code vun der sechster Editioun gehackt hutt, hutt Dir hei keng Problemer. Dëst ass wéi d'Funktioun ausgesäit 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;
}

Och ouni d'Strukt Definitiounen ze kucken, kënnt Dir erausfannen wéi d'Inode Referenzzuel benotzt gëtt fir ze kontrolléieren ob eng Schreifoperatioun resultéiert SIGPIPE. Zousätzlech zu Byte-by-Byte-Aarbecht ass dës Funktioun einfach mat den uewe genannten Iddien ze vergläichen. Souguer Logik sleep_on/wake_up gesäit net sou alien aus.

Modern Linux Kernels, FreeBSD, NetBSD, OpenBSD

Ech sinn séier iwwer e puer modern Kärelen gaang. Keen vun hinnen huet schonn eng Disk-baséiert Implementatioun (net iwwerraschend). Linux huet seng eege Implementatioun. An och wann déi dräi modern BSD Kernelen Implementatiounen enthalen baséiert op Code dee vum John Dyson geschriwwe gouf, si si iwwer d'Joren ze ënnerschiddlech vuneneen ginn.

Liesen fs/pipe.c (op Linux) oder sys/kern/sys_pipe.c (op *BSD), et hëlt wierklech Engagement. Leeschtung an Ënnerstëtzung fir Funktiounen wéi Vecteure an asynchronous I / O si wichteg am Code haut. An d'Detailer vun der Erënnerungsallokatioun, Spären a Kernelkonfiguratioun variéieren all immens. Dëst ass net wat d'Universitéiten brauchen fir en Aféierungscours iwwer Betribssystemer.

Op alle Fall war et interessant fir mech e puer al Musteren z'entdecken (zum Beispill generéieren SIGPIPE an zréck EPIPE wann Dir op eng zougemaach Pipeline schreift) an all dësen, sou ënnerschiddlech, modernen Kären. Ech wäert wahrscheinlech ni e PDP-11 Computer live gesinn, awer et gëtt nach vill ze léieren aus dem Code deen e puer Joer geschriwwe gouf ier ech gebuer sinn.

Geschriwwen vum Divi Kapoor am Joer 2011, den Artikel "D'Linux Kernel Ëmsetzung vu Pipes a FIFOsass en Iwwerbléck iwwer wéi Linux Pipelines (bis elo) funktionnéieren. A rezent engagéieren op Linux illustréiert de Pipelinemodell vun der Interaktioun, deenen hir Fäegkeeten déi vun temporäre Dateien iwwerschreiden; a weist och wéi wäit Pipelines vun "ganz konservativ Sperrung" an der sechster Editioun Unix Kernel fort sinn.

Source: will.com

Setzt e Commentaire