Dit artikel beschrijft de implementatie van pijplijnen in de Unix-kernel. Ik was enigszins teleurgesteld dat een recent artikel met de titel "
Waar hebben we het over?
Pipelines zijn "waarschijnlijk de belangrijkste uitvinding in Unix" - een bepalend kenmerk van Unix' onderliggende filosofie van het samenstellen van kleine programma's, en de bekende opdrachtregelslogan:
$ echo hello | wc -c
6
Deze functionaliteit is afhankelijk van de door de kernel geleverde systeemaanroep pipe
, die wordt beschreven op de documentatiepagina's
Pijpleidingen bieden een eenrichtingskanaal voor communicatie tussen processen. De pijplijn heeft een invoer (schrijfeinde) en een uitvoer (leeseinde). Gegevens die naar de ingang van de pijplijn zijn geschreven, kunnen aan de uitgang worden gelezen.
De pijplijn wordt gemaakt door aan te roepen
pipe(2)
, die twee bestandsdescriptors retourneert: de ene verwijst naar de invoer van de pijplijn, de tweede naar de uitvoer.
De traceeruitvoer van de bovenstaande opdracht toont het maken van een pijplijn en de stroom van gegevens er doorheen van het ene proces naar het andere:
$ 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
De bovenliggende procesaanroepen pipe()
om bijgevoegde bestandsbeschrijvingen te krijgen. Eén kindproces schrijft naar één descriptor en een ander proces leest dezelfde gegevens van een andere descriptor. De shell "hernoemt" descriptors 2 en 3 met dup4 om overeen te komen met stdin en stdout.
Zonder pijplijnen zou de shell de uitvoer van het ene proces naar een bestand moeten schrijven en naar een ander proces moeten leiden om de gegevens uit het bestand te lezen. Als gevolg hiervan zouden we meer bronnen en schijfruimte verspillen. Pijplijnen zijn echter goed voor meer dan alleen het vermijden van tijdelijke bestanden:
Als een proces probeert te lezen uit een lege pijplijn, dan
read(2)
zal blokkeren totdat de gegevens beschikbaar zijn. Als een proces naar een volledige pijplijn probeert te schrijven, danwrite(2)
zal blokkeren totdat er voldoende gegevens uit de pijplijn zijn gelezen om het schrijven te voltooien.
Net als de POSIX-vereiste is dit een belangrijke eigenschap: schrijven naar de pijplijn tot PIPE_BUF
bytes (minstens 512) moeten atomair zijn, zodat processen via de pijplijn met elkaar kunnen communiceren op een manier die normale bestanden (die dergelijke garanties niet bieden) niet kunnen.
Met een regulier bestand kan een proces al zijn uitvoer ernaartoe schrijven en doorgeven aan een ander proces. Of processen kunnen in een harde parallelle modus werken, gebruikmakend van een extern signaleringsmechanisme (zoals een semafoor) om elkaar te informeren over de voltooiing van een schrijf- of leesbewerking. Transportbanden besparen ons al dit gedoe.
Wat zoeken we?
Ik zal het op mijn vingers uitleggen om het je gemakkelijker te maken je voor te stellen hoe een lopende band kan werken. U moet een buffer en een bepaalde status in het geheugen toewijzen. U hebt functies nodig om gegevens toe te voegen aan en te verwijderen uit de buffer. U hebt enige mogelijkheid nodig om functies aan te roepen tijdens lees- en schrijfbewerkingen op bestandsdescriptors. En sloten zijn nodig om het hierboven beschreven speciale gedrag te implementeren.
We zijn nu klaar om de broncode van de kernel onder fel lamplicht te ondervragen om ons vage mentale model te bevestigen of te weerleggen. Maar wees altijd voorbereid op het onverwachte.
Waar zoeken we?
Ik weet niet waar mijn exemplaar van het beroemde boek ligt.
Dwalen door de TUHS-archieven is als een bezoek aan een museum. We kunnen naar onze gedeelde geschiedenis kijken en ik heb respect voor de jarenlange inspanning om al dit materiaal beetje bij beetje terug te halen uit oude cassettes en afdrukken. En ik ben me terdege bewust van die fragmenten die nog ontbreken.
Nu we onze nieuwsgierigheid naar de oude geschiedenis van pijpleidingen hebben bevredigd, kunnen we ter vergelijking naar moderne kernen kijken.
Overigens pipe
is systeemoproepnummer 42 in de tabel sysent[]
. Toeval?
Traditionele Unix-kernels (1970-1974)
Ik heb geen spoor gevonden pipe(2)
noch in
TUHS beweert dat
De derde editie van Unix was de laatste versie met een assembler-kernel, maar de eerste versie met pijplijnen. In 1973 werd gewerkt aan het verbeteren van de derde editie, de kernel werd herschreven in C, en zo werd de vierde editie van Unix geboren.
Een lezer vond een scan van een document waarin Doug McIlroy het idee opperde om "programma's als een tuinslang met elkaar te verbinden".
In het boek van Brian Kernighan
Toen Unix verscheen, zorgde mijn passie voor coroutines ervoor dat ik de auteur van het besturingssysteem, Ken Thompson, vroeg om gegevens die naar een bepaald proces waren geschreven, niet alleen naar het apparaat te laten gaan, maar ook naar de uitgang naar een ander proces. Ken besloot dat het mogelijk was. Als minimalist wilde hij echter dat elk systeemkenmerk een belangrijke rol zou spelen. Is direct schrijven tussen processen echt een groot voordeel ten opzichte van schrijven naar een tussenliggend bestand? En pas toen ik een specifiek voorstel deed met een pakkende naam "pijplijn" en een beschrijving van de syntaxis van de interactie van processen, riep Ken uiteindelijk uit: "Ik zal het doen!".
En deed. Op een noodlottige avond veranderde Ken de kernel en shell, repareerde verschillende standaardprogramma's om te standaardiseren hoe ze invoer accepteren (die mogelijk afkomstig is van een pijplijn), en veranderde bestandsnamen. De volgende dag werden pijpleidingen zeer veel gebruikt in toepassingen. Tegen het einde van de week gebruikten de secretaresses ze om documenten van tekstverwerkers naar de printer te sturen. Iets later verving Ken de originele API en syntaxis voor het verpakken van het gebruik van pijplijnen door schonere conventies die sindsdien zijn gebruikt.
Helaas is de broncode voor de Unix-kernel van de derde editie verloren gegaan. En hoewel we de kernelbroncode hebben geschreven in C
We hebben documentatietekst voor pipe(2)
van beide releases, dus u kunt beginnen met het doorzoeken van de documentatie pipe(2)
is geschreven in assembler en retourneert slechts één bestandsdescriptor, maar biedt al de verwachte kernfunctionaliteit:
Systeemoproep pijp creëert een I/O-mechanisme dat een pijplijn wordt genoemd. De geretourneerde bestandsdescriptor kan worden gebruikt voor lees- en schrijfbewerkingen. Wanneer er iets naar de pijplijn wordt geschreven, buffert het maximaal 504 bytes aan gegevens, waarna het schrijfproces wordt opgeschort. Bij het lezen van de pijplijn worden de gebufferde gegevens genomen.
Het volgende jaar was de kernel herschreven in C, en pipe(fildes)
'
Systeemoproep pijp creëert een I/O-mechanisme dat een pijplijn wordt genoemd. De geretourneerde bestandsdescriptors kunnen worden gebruikt bij lees- en schrijfbewerkingen. Wanneer er iets naar de pijplijn wordt geschreven, wordt de in r1 geretourneerde descriptor (resp. fildes[1]) gebruikt, gebufferd tot 4096 bytes aan gegevens, waarna het schrijfproces wordt opgeschort. Bij het lezen van de pijplijn neemt de descriptor terug naar r0 (resp. fildes[0]) de gegevens.
Aangenomen wordt dat zodra een pijplijn is gedefinieerd, twee (of meer) op elkaar inwerkende processen (gecreëerd door opeenvolgende aanroepen vork) zal gegevens van de pijplijn doorgeven met behulp van oproepen dit artikel lezen и schrijven.
De shell heeft een syntaxis voor het definiëren van een lineaire reeks processen die via een pijplijn zijn verbonden.
Oproepen om te lezen van een lege pijplijn (die geen gebufferde gegevens bevat) die maar één uiteinde heeft (alle schrijfbestandsdescriptors gesloten) retourneren "einde van bestand". Schrijfoproepen in een vergelijkbare situatie worden genegeerd.
vroegste
Unix zesde editie (1975)
Unix-broncode beginnen te lezen
Al jaren het boek Lions was het enige document over de Unix-kernel dat buiten Bell Labs beschikbaar was. Hoewel de licentie van de zesde editie leraren toestond de broncode te gebruiken, sloot de licentie van de zevende editie deze mogelijkheid uit, dus werd het boek verspreid in illegale getypte exemplaren.
Vandaag kun je een herdruk van het boek kopen, waarvan de omslag studenten bij het kopieerapparaat afbeeldt. En dankzij Warren Toomey (die het TUHS-project startte) kun je downloaden
Meer dan 15 jaar geleden typte ik een kopie van de broncode in Lionsomdat ik de kwaliteit van mijn exemplaar van een onbekend aantal andere exemplaren niet leuk vond. TUHS bestond nog niet en ik had geen toegang tot de oude bronnen. Maar in 1988 vond ik een oude band met 9 nummers die een back-up had van een PDP11-computer. Het was moeilijk om te weten of het werkte, maar er was een intacte /usr/src/ tree waarin de meeste bestanden gemarkeerd waren met 1979, wat er toen al oud uitzag. Het was de zevende editie, of een PWB-afgeleide, dacht ik.
Ik nam de vondst als basis en bewerkte de bronnen handmatig tot de staat van de zesde editie. Een deel van de code bleef hetzelfde, een deel moest enigszins worden aangepast, waarbij het moderne token += werd gewijzigd in het verouderde =+. Er werd gewoon iets verwijderd en er moest iets volledig worden herschreven, maar niet te veel.
En vandaag kunnen we online bij TUHS de broncode lezen van de zesde editie van
Trouwens, op het eerste gezicht is het belangrijkste kenmerk van de C-code vóór de periode van Kernighan en Ritchie de beknoptheid. Het komt niet vaak voor dat ik codefragmenten kan invoegen zonder uitgebreide bewerkingen om in een relatief smal weergavegebied op mijn site te passen.
Vroeg
/*
* 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
De buffergrootte is sinds de vierde editie niet gewijzigd. Maar hier zien we, zonder enige openbare documentatie, dat pijplijnen ooit bestanden gebruikten als fallback-opslag!
Wat betreft LARG-bestanden, ze komen overeen met
Hier is de echte systeemaanroep 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 opmerking beschrijft duidelijk wat hier gebeurt. Maar het is niet zo eenvoudig om de code te begrijpen, mede door hoe "R0
и R1
systeemaanroepparameters en retourwaarden worden doorgegeven.
Laten we het proberen met
pipe()
verschuldigd door R0
и R1
geef bestandsdescriptornummers terug voor lezen en schrijven. falloc()
geeft een pointer terug naar een bestandsstructuur, maar "retourneert" ook via u.u_ar0[R0]
en een bestandsdescriptor. Dat wil zeggen, de code is opgeslagen in r
bestandsdescriptor voor lezen en wijst een descriptor toe om rechtstreeks vanuit te schrijven u.u_ar0[R0]
na tweede oproep falloc()
.
vlag FPIPE
, die we instellen bij het maken van de pijplijn, bepaalt het gedrag van de functie
/*
* 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);
}
/* … */
}
Dan de functie readp()
в pipe.c
leest gegevens uit de pijplijn. Maar het is beter om de implementatie te traceren vanaf writep()
. Nogmaals, de code is gecompliceerder geworden vanwege de aard van de conventie voor het doorgeven van argumenten, maar sommige details kunnen worden weggelaten.
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;
}
We willen bytes naar de pijplijninvoer schrijven u.u_count
. Eerst moeten we de inode vergrendelen (zie hieronder plock
/prele
).
Vervolgens controleren we het aantal inode-referenties. Zolang beide uiteinden van de pijplijn open blijven, zou de teller op 2 moeten staan. We houden vast aan één schakel (van rp->f_inode
), dus als de teller minder dan 2 is, zou dit moeten betekenen dat het leesproces het einde van de pijplijn heeft afgesloten. Met andere woorden, we proberen naar een gesloten pijplijn te schrijven, wat een vergissing is. Eerste foutcode EPIPE
en signaal SIGPIPE
verscheen in de zesde editie van Unix.
Maar zelfs als de transportband open is, kan deze vol zijn. In dit geval laten we het slot los en gaan we slapen in de hoop dat een ander proces uit de pijplijn leest en er voldoende ruimte in vrijmaakt. Als we wakker worden, keren we terug naar het begin, hangen het slot weer op en beginnen een nieuwe schrijfcyclus.
Als er voldoende vrije ruimte in de pijplijn is, schrijven we er gegevens naar met behulp van i_size1
de inode'a (met een lege pijplijn kan gelijk zijn aan 0) wijst naar het einde van de gegevens die het al bevat. Als er voldoende ruimte is om te schrijven, kunnen we de pijplijn vullen i_size1
naar PIPESIZ
. Vervolgens geven we de vergrendeling vrij en proberen we elk proces wakker te maken dat wacht om uit de pijplijn te lezen. We gaan terug naar het begin om te zien of we erin geslaagd zijn om zoveel bytes te schrijven als we nodig hadden. Zo niet, dan starten we een nieuwe opnamecyclus.
Meestal parameters i_mode
inode wordt gebruikt om permissies op te slaan r
, w
и x
. Maar in het geval van pijplijnen geven we aan dat een proces wacht op schrijven of lezen met behulp van bits IREAD
и IWRITE
respectievelijk. Het proces stelt de vlag in en roept sleep()
, en de verwachting is dat in de toekomst een ander proces zal aanroepen wakeup()
.
De echte magie gebeurt binnen sleep()
и wakeup()
. Ze zijn geïmplementeerd in
/*
* 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) /* … */
Het proces dat roept sleep()
voor een bepaald kanaal, kan later worden gewekt door een ander proces, dat zal aanroepen wakeup()
voor hetzelfde kanaal. writep()
и readp()
coördineren hun acties door middel van dergelijke gepaarde oproepen. Let daar op pipe.c
altijd prioriteit geven PPIPE
wanneer gebeld sleep()
, dus allemaal sleep()
kan worden onderbroken door een signaal.
Nu hebben we alles om de functie te begrijpen 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);
}
Misschien vindt u het gemakkelijker om deze functie van beneden naar boven te lezen. De tak "lezen en retourneren" wordt meestal gebruikt als er wat gegevens in de pijplijn zitten. In dit geval gebruiken we f_offset
lezen en vervolgens de waarde van de overeenkomstige offset bijwerken.
Bij volgende uitlezingen is de pijplijn leeg als de leesoffset is bereikt i_size1
bij de inode. We resetten de positie naar 0 en proberen elk proces wakker te maken dat naar de pijplijn wil schrijven. We weten dat wanneer de transportband vol is, writep()
in slaap vallen ip+1
. En nu de pijplijn leeg is, kunnen we hem wakker maken om de schrijfcyclus te hervatten.
Als er niets te lezen valt, dan readp()
kan een vlag plaatsen IREAD
en in slaap vallen ip+2
. We weten waardoor hij wakker zal worden writep()
wanneer het wat gegevens naar de pijplijn schrijft.
Opmerkingen over u
» we kunnen ze behandelen als gewone I/O-functies die een bestand, een positie, een buffer in het geheugen nemen en het aantal bytes tellen dat moet worden gelezen of geschreven.
/*
* 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 betreft "conservatieve" blokkering, dan readp()
и writep()
vergrendel inodes totdat ze klaar zijn of een resultaat krijgen (d.w.z. call wakeup
). plock()
и prele()
werk eenvoudig: gebruik een andere set oproepen sleep
и wakeup
laat ons elk proces wakker maken dat het slot nodig heeft dat we zojuist hebben vrijgegeven:
/*
* 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);
}
}
Eerst begreep ik niet waarom readp()
Veroorzaakt geen prele(ip)
voor de oproep wakeup(ip+1)
. Het eerste ding writep()
roept in zijn lus, dit plock(ip)
, wat resulteert in een impasse als readp()
heeft zijn blokkering nog niet verwijderd, dus de code moet op de een of andere manier correct werken. Als je kijkt naar wakeup()
, wordt het duidelijk dat het alleen het slaapproces markeert als klaar voor uitvoering, dus dat in de toekomst sched()
echt gelanceerd. Dus readp()
oorzaken wakeup()
, ontgrendelt, zet IREAD
en roept sleep(ip+2)
- dit alles eerder writep()
start de cyclus opnieuw.
Hiermee is de beschrijving van pijpleidingen in de zesde editie voltooid. Eenvoudige code, verstrekkende gevolgen.
Xv6, een eenvoudige Unix-achtige kernel
Om een kern te creëren
De code bevat een heldere en doordachte implementatie 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()
stelt de status in van de rest van de implementatie, inclusief functies piperead()
, pipewrite()
и pipeclose()
. De eigenlijke systeemoproep sys_pipe
is een wrapper geïmplementeerd in
Linux 0.01
U kunt de broncode voor Linux 0.01 vinden. Het zal leerzaam zijn om de implementatie van pijpleidingen in zijn te bestuderen fs
/pipe.c
. Hier wordt een inode gebruikt om de pijplijn weer te geven, maar de pijplijn zelf is geschreven in moderne C. Als je je een weg door de code van de zesde editie hebt gehackt, zul je hier geen problemen hebben. Zo ziet de functie eruit 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;
}
Zelfs zonder naar de struct-definities te kijken, kunt u erachter komen hoe het aantal inode-referenties wordt gebruikt om te controleren of een schrijfbewerking resulteert in SIGPIPE
. Naast byte-voor-byte werk is deze functie goed te vergelijken met bovenstaande ideeën. Zelfs logica sleep_on
/wake_up
ziet er niet zo vreemd uit.
Moderne Linux-kernels, FreeBSD, NetBSD, OpenBSD
Ik heb snel enkele moderne kernels doorgenomen. Geen van hen heeft al een schijfgebaseerde implementatie (niet verwonderlijk). Linux heeft zijn eigen implementatie. En hoewel de drie moderne BSD-kernels implementaties bevatten die zijn gebaseerd op code die is geschreven door John Dyson, zijn ze in de loop der jaren te veel van elkaar gaan verschillen.
Lezen fs
/pipe.c
(op Linux) of sys
/kern
/sys_pipe.c
(op *BSD), er is echte toewijding voor nodig. Prestaties en ondersteuning voor functies zoals vector en asynchrone I/O zijn tegenwoordig belangrijk in code. En de details van geheugentoewijzing, vergrendelingen en kernelconfiguratie variëren allemaal enorm. Dat hebben universiteiten niet nodig voor een introductiecursus besturingssystemen.
Het was voor mij in ieder geval interessant om een paar oude patronen aan het licht te brengen (bijvoorbeeld het genereren van SIGPIPE
En terugkomen EPIPE
bij het schrijven naar een gesloten pijplijn) in al deze, zo verschillende, moderne kernels. Ik zal waarschijnlijk nooit een PDP-11-computer live zien, maar er valt nog veel te leren van de code die een paar jaar voordat ik werd geboren werd geschreven.
Geschreven door Divi Kapoor in 2011, het artikel "
Bron: www.habr.com