
I den hÀr artikeln beskrivs implementeringen av pipelines i Unix-kÀrnan. Jag blev ganska besviken över att en nyligen publicerad artikel med titeln "" visade sig vara inte om den interna strukturen. Jag blev nyfiken och grÀvde ner mig i gamla kÀllor för att hitta svaret.
Vad handlar det om?
Pipelines â "förmodligen den viktigaste uppfinningen inom Unix" â Ă€r en avgörande egenskap av den centrala Unix-filosofin om att kombinera smĂ„ program, samt den vĂ€lkĂ€nda prompten i kommandoraden:
$ echo hello | wc -c
6
Denna funktionalitet beror pÄ det systemanrop som tillhandahÄlls av kÀrnan pipe, vilket beskrivs i dokumentationen pÄ sidorna och :
Pipelines tillhandahÄller en ensidig kanal för mellanprocesskommunikation. En pipeline har en ingÄng (write end) och en utgÄng (read end). Data som skrivs till ingÄngen av pipelinen kan lÀsas pÄ utgÄngen.
En pipeline skapas med hjÀlp av anropet
pipe(2), vilket returnerar tvÄ filbeskrivare: en hÀnvisar till ingÄngen av pipelinen, den andra till utgÄngen.
Resultaten av spÄrningen av det angivna kommandot visar skapandet av en pipeline och strömmen av data genom den frÄn en process till en annan:
$ 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
FörÀldraprocessen anropar pipe(), för att fÄ anslutna filbeskrivare. En barnprocess skriver till en filbeskrivare, medan en annan process lÀser samma data frÄn en annan filbeskrivare. Skalet "byter namn" pÄ filbeskrivarna 3 och 4 med hjÀlp av dup2 för att de ska matcha stdin och stdout.
Utan pipelines skulle skalet behöva skriva utdata frÄn en process till en fil och sedan överföra den till en annan process för att lÀsa data frÄn filen. Resultatet skulle vara att vi skulle anvÀnda mer resurser och diskutrymme. Men pipelines Àr inte bara bra för att undvika anvÀndningen av temporÀra filer:
Om en process försöker lÀsa frÄn en tom pipeline, kommer dÄ
read(2)att blockera tills data blir tillgÀngliga. Om en process försöker skriva till en full pipeline, dÄwrite(2)blockeras tills tillrÀckligt med data har lÀsts frÄn pipelinen för att utföra en skrivning.
Enligt POSIX-kraven Àr denna egenskap viktig: skrivning till pipelinen upp till PIPE_BUF bytes (minst 512) mÄste vara atomÀr sÄ att processer kan interagera med varandra via pipelinen, pÄ ett sÀtt som vanliga filer (som inte erbjuder sÄdana garantier) inte kan.
NÀr du anvÀnder en vanlig fil kan en process skriva ut all sin utdata och överföra den till en annan process. Eller sÄ kan processerna agera i strikt parallellisering, med hjÀlp av en extern signalmekanism (som en semafor) för att informera varandra om att skrivning eller lÀsning Àr klar. Pipelines befriar oss frÄn alla dessa besvÀr.
Vad söker vi?
Jag ska förklara enkelt sÄ att du lÀttare kan förestÀlla dig hur en pipeline kan fungera. Du behöver allokera en buffert i minnet och nÄgot tillstÄnd. Du kommer behöva funktioner för att lÀgga till och ta bort data frÄn bufferten. Du behöver nÄgot sÀtt att anropa funktioner vid lÀs- och skrivoperationer till filbeskrivare. Och du behöver lÄs för att implementera det speciella beteende som beskrivits ovan.
Nu Àr vi redo att i starkt ljus granska kÀllkoden till kÀrnan för att bekrÀfta eller motbevisa vÄr vaga mentala modell. Men var alltid beredd pÄ överraskningar.
Var söker vi?
Jag vet inte var jag hittar mitt exemplar av den kÀnda boken "" med kÀllkoden till Unix 6, men tack vare kan man söka online i Ànnu Àldre versioner av Unix.
Att vandra genom TUHS-arkiven Àr som att besöka ett museum. Vi kan se pÄ vÄr gemensamma historia, och jag kÀnner respekt för de mÄnga Ärens anstrÀngningar att Äterskapa allt detta material, bit för bit, frÄn gamla kasetter och utskrifter. Och jag Àr skarpt medveten om de fragment som fortfarande saknas.
Efter att ha tillfredsstÀllt min nyfikenhet om den antika historien om pipeliner, kan vi för jÀmförelse se pÄ moderna kÀrnor.
Förresten, pipe Àr systemanrop nummer 42 i tabellen sysent[]. En tillfÀllighet?
Traditionella Unix-kĂ€rnor (1970â1974)
Jag har inte funnit nÄgra spÄr pipe(2) varken i (januari 1970), varken i (november 1971), eller i den ofullstÀndiga kÀllkoden (juni 1972).
TUHS hÀvdar att (februari 1973) blev den första versionen med pipeliner:
Den tredje versionen av Unix var den sista utgÄvan med en kÀrna skriven i assembler, men ocksÄ den första versionen med pipelining. Under 1973 arbetade man pÄ att förbÀttra den tredje versionen, och kÀrnan skrevs om i C, vilket ledde till den fjÀrde versionen av Unix.
En av lÀsarna hittade en skanning av ett dokument dÀr Doug McIlroy föreslog idén om "att koppla program enligt principen för en trÀdgÄrdsslang".

I Brian Kernighans bok "", nÀmns ocksÄ detta dokument i historien om pipelining: "...den hÀngde pÄ vÀggen i mitt kontor pÄ Bell Labs i 30 Är". HÀr Àr , och Ànnu en historia frÄn :
NÀr Unix kom, fick min fascination för koroutiner mig att be operativsystemets skapare, Ken Thompson, att lÄta data som skrivs av en process gÄ inte bara till en enhet, utan ocksÄ till en annan process. Ken tyckte att det var möjligt. Men som minimalisten han var ville han att varje systemfunktion skulle spela en betydande roll. Har direkt skrivning mellan processer verkligen stora fördelar jÀmfört med skrivning till en mellanlagrad fil? Och först nÀr jag kom med ett specifikt förslag med den snÀrtiga titeln "pipeline" och en beskrivning av syntaxen för processinteraktion, utbrast Ken finally "Jag ska göra det!".
Och han gjorde. En ödesdiger kvÀll Àndrade Ken kÀrnan och skalet, rÀttade nÄgra standardprogram och standardiserade deras indatahanteringsprocedurer (som kan komma frÄn pipelinan), och Àndrade ocksÄ filnamnen. NÀsta dag började pipeliner anvÀndas i stor utstrÀckning i applikationer. I slutet av veckan anvÀnde sekreterarna dem för att skicka dokument frÄn textredigerare till skrivaren. Strax dÀrefter ersatte Ken den ursprungliga API:n och syntaxen för skalanvÀndning av pipeliner med renare konventioner som sedan dess har anvÀnts.
TyvĂ€rr har kĂ€llkoden till kĂ€rnan i den tredje versionen av Unix gĂ„tt förlorad. Ăven om vi har den skrivna i C kĂ€llkoden till den , som slĂ€pptes i november 1973, slĂ€pptes den flera mĂ„nader innan den officiella lanseringen och innehĂ„ller inte implementeringen av pipeliner. Det Ă€r trĂ„kigt att kĂ€llkoden till den legendariska Unix-funktionen har gĂ„tt förlorad, kanske för alltid.
Vi har texten i dokumentationen om pipe(2) frÄn bÄda versionerna, sÄ man kan börja med att söka i dokumentationen (genom vissa ord som Àr markerade "manuellt", en rad av litteraler ^H, efter vilken det finns ett understreck!). Denna proto-pipe(2) Àr skriven i assembler och returnerar endast en filbeskrivare, men erbjuder redan den förvÀntade grundlÀggande funktionen:
Systemanropet pipe skapar en in-/utgÄngsmekanism som kallas en pipeline. Den returnerade filbeskrivaren kan anvÀndas för lÀs- och skrivoperationer. NÀr nÄgot skrivs in i pipelinen buffras det upp till 504 byte data, varefter skrivprocessen pausas. Vid lÀsning frÄn pipelinen tas de buffrade uppgifterna.
Till nÀsta Är hade kÀrnan skrivits om i C, och fick sin moderna form med prototypen "pipe(fildes)»:
Systemanropet pipe skapar en in-/utgÄngsmekanism som kallas en pipeline. De returnerade filbeskrivarna kan anvÀndas i lÀs- och skrivoperationer. NÀr nÄgot skrivs in i pipelinen, anvÀnds den beskrivare som returneras i r1 (dvs. fildes[1]), och det buffras upp till 4096 byte data, varefter skrivprocessen pausas. Vid lÀsning frÄn pipelinen hÀmtar den beskrivare som returneras i r0 (dvs. fildes[0]) data.
Det förutsÀtts att efter att pipelinen har definierats kommer tvÄ (eller fler) interagerande processer (skapade av efterföljande anrop fork) att överföra data frÄn pipelinen via anrop lÀs och skriv.
I skalet finns det syntax för att definiera en linjÀr array av processer kopplade via pipelinen.
Anrop för att lÀsa frÄn en tom pipeline (som inte innehÄller buffrade data), som endast har en Ànde (alla skrivande filbeskrivare Àr stÀngda), returnerar "Ànde pÄ filen". Anrop för att skriva i en liknande situation ignoreras.
Den tidigaste tillhör (juni 1974), men den Àr nÀstan identisk med den som dök upp i nÀsta version. Endast kommentarer lades till, sÄ den femte upplagan kan utelÀmnas.
Den sjÀtte upplagan av Unix (1975)
Vi börjar lÀsa kÀllkoden för Unix (maj 1975). Mycket tack vare Lions Àr det mycket lÀttare att hitta Àn kÀllkoden till tidigare versioner:
MĂ„nga Ă„r boken Lions var det enda dokumentet om Unix-kĂ€rnan som var tillgĂ€ngligt utanför vĂ€ggarna pĂ„ Bell Labs. Ăven om licensen för den sjĂ€tte utgĂ„van tillĂ€t lĂ€rare att anvĂ€nda dess kĂ€llkod, exkluderade licensen för den sjunde utgĂ„van denna möjlighet, sĂ„ boken spreds i form av olagliga maskingranskningar.
Idag kan man köpa en reprotryck av boken, med en omslagsbild som visar studenter vid en kopieringsmaskin. Tack vare Warren Toomey (som startade projektet TUHS) kan du ladda ner . Jag vill ge er en uppfattning om hur mycket arbete som lagts ner pÄ att skapa filen:
För mer Àn 15 Är sedan knÀppte jag en kopia av kÀllkoden som ges i Lions, eftersom jag inte gillade kvaliteten pÄ min kopia jÀmfört med det okÀnda antal andra kopior. TUHS existerade inte Àn, och jag hade inte tillgÄng till nÄgra gamla kÀllor. Men 1988 hittade jag en gammal band med 9 spÄr som innehöll en sÀkerhetskopia frÄn PDP11-datorn. Det var svÄrt att avgöra om den fungerade, men dÀr fanns en oskadad mapp \/usr\/src\/ dÀr de flesta filerna var mÀrkta med Ärtalet 1979, vilket redan dÄ sÄg urÄldrigt ut. Detta var den sjunde utgÄvan eller en avkomma till PWB, som jag trodde.
Jag tog fyndet som grund och redigerade manuellt kÀllorna till tillstÄndet för den sjÀtte utgÄvan. Delar av koden förblev desamma, medan andra fick göras nÄgot justerade genom att byta ut den moderna token += mot den förÄldrade =+. Vissa delar raderade jag helt, medan andra fick skrivas om helt, men inte alltför mÄnga.
Idag kan vi online lÀsa kÀllkoden för den sjÀtte utgÄvan pÄ TUHS frÄn .
För övrigt, vid första anblick verkar den frÀmsta egenskapen hos C-koden innan Kernighan och Ritchie vara dess korthet. Jag fÄr sÀllan in fragment av kod utan omfattande redigering för att det ska passa en relativt smal visningsyta pÄ min webbplats.
I början finns det en förklarande kommentar (och ja, det finns mer ):
/*
* 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
Buffertstorleken har inte förÀndrats sedan den fjÀrde utgÄvan. Men hÀr, utan nÄgon offentlig dokumentation, ser vi att rörledningar nÄgon gÄng anvÀnde filer som en backup-lagring!
Vad gÀller LARG-filer, sÄ motsvarar de , som anvÀnds av "den stora adresseringsalgoritmen" för bearbetning. för att stödja större filsystem. Eftersom Ken sagt att det Àr bÀst att inte anvÀnda dem, litar jag gladeligen pÄ hans ord.
HÀr Àr ett riktigt systemanrop 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;
}
I kommentaren beskrivs tydligt vad som hÀnder hÀr. Men att förstÄ koden Àr inte sÄ enkelt, delvis pÄ grund av hur "" och registren R0 och R1 anvÀnds för att överföra parametrar för systemanrop och returnerade vÀrden.
Vi ska försöka med hjĂ€lp av placera pĂ„ disken , och med hjĂ€lp av â placera i minnet tvĂ„ . Om allt gĂ„r bra, stĂ€ller vi in flaggorna för att identifiera dessa filer som de tvĂ„ Ă€ndarna av en pipeline, vi anger dem i samma inode (vars referensrĂ€knare blir 2), och markerar inode som Ă€ndrad och anvĂ€nd. iput()
pipe() i felvÀgar (error paths) för att minska referensrÀknaren i den nya inode. R0 och R1 mÄste genom att falloc() ÄtervÀnda filbeskrivare nummer för lÀsning och skrivning. ÄtervÀnder en pekare till filstrukturen, men "ÄtervÀnder" ocksÄ genom u.u_ar0[R0] Àr lika med noll. och filbeskrivaren. Det vill sÀga att koden sparar i ÄtervÀnder en pekare till filstrukturen, men "ÄtervÀnder" ocksÄ genom filbeskrivaren för lÀsning och tilldelar beskrivaren för skrivning direkt frÄn falloc().
Flagga efter den andra anropetFPIPE rdwr() i sys2.c
/*
* 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);
}
/* ⊠*/
}
, som anropar specifika I/O underprogram: Sedan lÀser funktionen pÄ readp() pipe.c data frÄn pipen. Men att följa implementeringen Àr bÀttre att börja medwritep().
Jag upprepar, koden har blivit mer komplicerad pÄ grund av sÀrskilda överenskommelser om argumentöverföring, men vissa detaljer kan förbises.
writep(fp) { register *rp, *ip, c;rp = fp; ip = rp->f_inode; c = u.u_count;loop: /* Om allt Àr klart, ÄtervÀnd. */plock(ip); if(c == 0) { prele(ip); u.u_count = 0; return; }/* * Om det inte finns bÄde lÀs- och skrivsidor av * pipen aktiva, ÄtervÀnd med fel och signalera ocksÄ. */if(ip->i_count i_size1 == PIPSIZ) { ip->i_mode |= IWRITE; prele(ip); sleep(ip+1, PPIPE); goto loop; }/* Skriv vad som Àr möjligt och loopa tillbaka. */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; } Till pipens ingÄng vill vi skriva byteu.u_count. Först mÄste vi krÀva att indexdescriptoren lÄses (se nedan/plock, prele).
Sedan kontrollerar vi inode-lÀnkrÀknaren. SÄ lÀnge bÄda Àndar av pipen förblir öppna, bör rÀknaren vara lika med 2. Vi hÄller en lÀnk (frÄn rp->f_inode), sÄ om rÀknaren Àr mindre Àn 2, betyder det att lÀsprocessen har stÀngt sin Ànde av pipen. Med andra ord försöker vi skriva till en stÀngd pip, och det Àr ett fel. Felkoden EPIPE och signalen SIGPIPE kom i sjÀtte upplagan av Unix.
Men Àven om pipen Àr öppen kan den vara full. I sÄ fall tar vi bort lÄsningen och gÄr och lÀgger oss i hopp om att en annan process lÀser frÄn pipen och frigör tillrÀckligt med utrymme. NÀr vi vaknar gÄr vi tillbaka till början, hÀnger pÄ lÄsningen igen och startar en ny skrivcykel.
Om det finns tillrÀckligt med ledigt utrymme i pipen skriver vi data med . Parametern i_size1 frÄn inode (i en tom pip kan detta vara 0) anger slutet pÄ de data som redan finns i den. Om det finns utrymme för att skriva kan vi fylla pipen frÄn i_size1 till PIPESIZ. Sedan tar vi bort lÄsningen och försöker vÀcka vilken process som helst som vÀntar pÄ att lÀsa frÄn pipen. Vi gÄr tillbaka till början för att se om vi lyckades skriva sÄ mÄnga byte som vi behövde. Om vi inte lyckas, börjar vi en ny skrivcykel.
Normalt anvĂ€nds parametern i_mode frĂ„n inode för att lagra behörigheter. Ă€r lika med noll., â kolumnvektorer, och $inline$(.)^H$inline$ Ă€r operationen och xMen i fallet med pipar signalerar vi vĂ€ntan av nĂ„gon skrivande eller lĂ€sande process med hjĂ€lp av bitarna IREAD och IWRITE respektive. Processen sĂ€tter en flagga och anropar sleep(), och det förvĂ€ntas att i framtiden kommer nĂ„gon annan process att anropa wakeup().
Det verkliga magin sker i sleep() och wakeup(). De Àr implementerade i , kÀllan till den berömda kommentaren "Du behöver inte förstÄ detta" (You are not expected to understand this). Lyckligtvis behöver vi inte förstÄ koden, utan vi kollar bara nÄgra 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) /* ⊠*/
Processen som anropar sleep() för en viss kanal kan senare vÀckas av en annan process som anropar wakeup() för samma kanal. data frÄn pipen. Men att följa implementeringen Àr bÀttre att börja med och Sedan lÀser funktionen Koordinerar sina ÄtgÀrder genom sÄdana parvisa anrop. Observera att readp() alltid prioriterar PPIPE vid anropet sleep(), sÄ att alla sleep() kan avbrytas av signalen.
Nu har vi allt för att förstÄ funktionen Sedan lÀser funktionen:
readp(fp)
int *fp;
{
register *rp, *ip;
rp = fp;
ip = rp->f_inode;
loop:
/* Mycket konservativ lÄsning. */
plock(ip);
/*
* Om huvudet (lÀs) har ikapp med
* svansen (skriv), ÄterstÀll bÄda till 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);
}
}
/*
* Om det inte finns bÄde lÀsare och
* skrivare aktiva, ÄterstÀll utan
* att tillfredsstÀlla lÀsning.
* /
prele(ip);
if(ip->i_count i_mode |= IREAD;
sleep(ip+2, PPIPE);
goto loop;
}
/* LĂ€s och returnera */
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);
}
Det kan vara lÀttare för dig att lÀsa denna funktion uppifrÄn och ner. Grenen "lÀs och returnera" anvÀnds vanligtvis nÀr det finns data i pipeline. I det hÀr fallet anvÀnder vi för att lÀsa sÄ mycket data som Àr tillgÀngligt frÄn den aktuella f_offset lÀspositionen, och uppdaterar sedan vÀrdet för motsvarande förskjutning.
Vid nÀsta lÀsning kommer pipelinen att vara tom om lÀspositionen har nÄtt vÀrdet i_size1 för inode. Vi ÄterstÀller positionen till 0 och försöker vÀcka alla processer som vill skriva till pipelinen. Vi vet att nÀr pipelinen Àr full, data frÄn pipen. Men att följa implementeringen Àr bÀttre att börja med kommer den att somna pÄ ip+1. Och nu, nÀr pipelinen Àr tom, kan vi vÀcka den sÄ att den Äterupptar sin skrivcykel.
Om det inte finns nÄgot att lÀsa, kan den stÀlla en flagga Sedan lÀser funktionen och somna pÄ IREAD ip+2 . Vi vet att den kommer att vÀckasnÀr den skriver data till pipelinen. data frÄn pipen. Men att följa implementeringen Àr bÀttre att börja medKommentarer till
readi() och writei() ", kan vi hantera dem som vanliga in- och utdata funktioner som tar en fil, position, minnesbuffert och rÀknar antalet byte att lÀsa eller skriva.uVad gÀller "konservativ" lÄsning, lÄser
/*
* 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;
/* ⊠*/
inode tills den har avslutat arbetet eller fÄtt resultat (det vill sÀga kallat Sedan lÀser funktionen och data frÄn pipen. Men att följa implementeringen Àr bÀttre att börja med wakeup plock()). prele() och fungerar helt enkelt: med hjÀlp av en annan uppsÀttning anrop kan vi vÀcka vilken process som helst som behöver den lÄs som vi just har slÀppt: sleep och plock() Till en början kunde jag inte förstÄ varför
/*
* 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);
}
}
inte kallar Sedan lĂ€ser funktionen prele(ip) före anropet wakeup(ip+1). Det första som anropar i sin loop Ă€rplock(ip), vilket leder till dödlĂ„sning om data frĂ„n pipen. Men att följa implementeringen Ă€r bĂ€ttre att börja med den inte redan har slĂ€ppt sin lĂ„s, sĂ„ koden mĂ„ste pĂ„ nĂ„got sĂ€tt fungera korrekt. Om vi ser pĂ„ plock(ip), som leder till en död lĂ„sning om Sedan lĂ€ser funktionen han fortfarande inte har tagit bort sin lĂ„s, sĂ„ koden mĂ„ste pĂ„ nĂ„got sĂ€tt fungera korrekt. Om man tittar pĂ„ wakeup(), blir det tydligt att det bara markerar den sovande processen som redo för körning, sĂ„ att den i framtiden sched() verkligen kan köras. SĂ„ att Sedan lĂ€ser funktionen orsakar wakeup(), tar bort lĂ„sningen, stĂ€ller in IREAD och anropar sleep(ip+2)â allt detta innan data frĂ„n pipen. Men att följa implementeringen Ă€r bĂ€ttre att börja med den Ă„terupptar cykeln.
Med detta Àr beskrivningen av rör i den sjÀtte utgÄvan avslutad. Enkel kod, lÄngtgÄende konsekvenser.
(januari 1979) var en ny stor release (efter fyra Är), dÀr mÄnga nya applikationer och kÀrnegenskaper introducerades. Det har ocksÄ genomgÄtt betydande förÀndringar pÄ grund av typkonvertering, unioner och typade pekare till strukturer. Men har praktiskt taget inte förÀndrats. Vi kan hoppa över denna utgÄva.
Xv6, en enkel Unix-liknande kÀrna
Kreationen av kÀrnan pÄverkades av den sjÀtte utgÄvan av Unix, men den Àr skriven i modern C för att köras pÄ x86-processorer. Koden Àr lÀttlÀst, den Àr begriplig. Dessutom, till skillnad frÄn Unix-kÀllorna frÄn TUHS, kan du kompilera, modifiera och köra den pÄ nÄgot annat Àn PDP 11/70. DÀrför anvÀnds denna kÀrna ofta pÄ universitet som undervisningsmaterial om operativsystem. KÀllkoden .
Koden innehÄller en tydlig och genomtÀnkt implementering , som stöds av en buffert i minnet istÀllet för en inode pÄ disken. HÀr ger jag bara definitionen av "strukturell rör" och funktionerna 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() sÀtter tillstÄndet för hela den ÄterstÄende implementeringen, som inkluderar funktionerna piperead(), pipewrite() och pipeclose(). Det faktiska systemanropet sys_pipe Àr ett wrap, implementerat i . Jag rekommenderar att du lÀser hela dess kod. Komplexiteten Àr pÄ nivÄn av den sjÀtte utgÄvans kod, men det Àr mycket lÀttare och trevligare att lÀsa.
Linux 0.01
Du kan hitta kÀllkoden för Linux 0.01. Det kommer att vara vÀrdefullt att studera implementeringen av rör i dess fs/readp(). HÀr anvÀnds inode för att representera röret, men sjÀlva röret Àr skrivet i modern C. Om du har tagit dig igenom koden för den sjÀtte utgÄvan, sÄ kommer du inte att ha nÄgra svÄrigheter hÀr. SÄ hÀr ser funktionen 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< 0) {
while (PIPE_FULL(*inode)) {
wake_up(&inode->i_wait);
if (inode->i_count != 2) {
current->signal |= (1<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;
}
Ăven utan att se definitionsstrukturerna kan man förstĂ„ hur referensrĂ€knaren inode anvĂ€nds för att kontrollera om skrivoperationen leder till SIGPIPE. Förutom byte-för-byte-arbetet Ă€r det lĂ€tt att koppla den hĂ€r funktionen till de tidigare nĂ€mnda idĂ©erna. Ăven logiken sleep_on/wake_up ser inte sĂ„ frĂ€mmande ut.
Moderna Linux-kÀrnor, FreeBSD, NetBSD, OpenBSD
Jag gick snabbt igenom nÄgra moderna kÀrnor. Ingen av dem har lÀngre en implementation som anvÀnder disken (inte förvÄnande). I Linux finns en egen implementation. Och Àven om de tre moderna BSD-kÀrnorna innehÄller implementationer baserade pÄ kod som skrevs av John Dyson, har de under Ären blivit alltför olika varandra.
För att lĂ€sa fs/readp() (pĂ„ Linux) eller sys/kern/sys_pipe.c (pĂ„ *BSD) krĂ€vs det verkligt engagemang. Idag Ă€r prestanda och stöd för funktioner som vektorisering och asynkrona I/O-operationer viktiga i koden. Och detaljer kring minnesallokering, lĂ„s och kĂ€rnkonfigurationâallt detta varierar kraftigt. Det Ă€r inte nĂ„got som universitet skulle anvĂ€nda för en introduktionskurs i operativsystem.
I vilket fall som helst var det intressant att grÀva fram nÄgra gamla mönster (t.ex. generering SIGPIPE och ÄtervÀndande EPIPE vid skrivning till en stÀngd pipelin) i alla dessa, sÄ olika, moderna kÀrnor. Troligtvis kommer jag aldrig att se en PDP-11-dator i verkligheten, men det finns fortfarande mycket att lÀra av koden som skrevs flera Är före min födelse.
Den artikel som skrevs av Divi Kapoor 2011 "" ger en översikt över hur (fortfarande) pipelinare fungerar i Linux. Och illustrerar pipelinemodellen för interaktion vars möjligheter övergÄr de temporÀra filernas; den visar dessutom hur lÄngt pipelinerna har kommit frÄn "den mycket konservativa lÄsningen" i den sjÀtte versionen av Unix.
KĂ€lla: habr.com
