Tässä artikkelissa kuvataan liukuputkien käyttöönottoa Unix-ytimessä. Olin hieman pettynyt äskettäin julkaistuun artikkeliin "
Mistä me puhumme?
Putkilinjat ovat "luultavasti tärkein keksintö Unixissa" - Unixin perustavanlaatuisen pienten ohjelmien kokoamisfilosofian ja tutun komentorivin iskulauseen määrittelevä piirre:
$ echo hello | wc -c
6
Tämä toiminto riippuu ytimen toimittamasta järjestelmäkutsusta pipe
, joka on kuvattu dokumentaatiosivuilla
Putkilinjat tarjoavat yksisuuntaisen kanavan prosessien väliseen viestintään. Liukuhihnalla on tulo (kirjoituspää) ja lähtö (lukupää). Liukuhihnan tuloon kirjoitetut tiedot voidaan lukea lähdöstä.
Liukulinja luodaan soittamalla
pipe(2)
, joka palauttaa kaksi tiedostokuvaajaa: yksi viittaa liukuhihnan tuloon ja toinen tulosteeseen.
Yllä olevan komennon jäljitystulos näyttää liukuhihnan luomisen ja tietovirran sen läpi prosessista toiseen:
$ 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
Vanhempi prosessi kutsuu pipe()
saadaksesi liitetiedostojen kuvaukset. Yksi lapsiprosessi kirjoittaa yhdelle kuvaajalle ja toinen prosessi lukee saman datan toisesta kuvaajasta. Kuori "nimeää uudelleen" kuvailevat 2 ja 3 dup4:lla vastaamaan stdin ja stdout.
Ilman liukuhihnaa komentotulkin pitäisi kirjoittaa yhden prosessin tulos tiedostoon ja siirtää se toiseen prosessiin lukeakseen tiedot tiedostosta. Tämän seurauksena tuhlaamme enemmän resursseja ja levytilaa. Liukuputket ovat kuitenkin hyviä muuhunkin kuin vain väliaikaisten tiedostojen välttämiseen:
Jos prosessi yrittää lukea tyhjästä liukuhihnasta, niin
read(2)
estetään, kunnes tiedot ovat saatavilla. Jos prosessi yrittää kirjoittaa koko liukuhihnalle, niinwrite(2)
estetään, kunnes liukuhihnasta on luettu tarpeeksi dataa kirjoittamisen suorittamiseksi loppuun.
Kuten POSIX-vaatimus, tämä on tärkeä ominaisuus: kirjoittaminen liukuhihnaan asti PIPE_BUF
tavujen (vähintään 512) on oltava atomisia, jotta prosessit voivat kommunikoida toistensa kanssa liukuhihnan kautta tavalla, jota tavalliset tiedostot (jotka eivät tarjoa tällaisia takuita) eivät voi.
Tavallisella tiedostolla prosessi voi kirjoittaa kaiken tuotoksensa siihen ja välittää sen toiselle prosessille. Tai prosessit voivat toimia kovassa rinnakkaistilassa käyttämällä ulkoista signalointimekanismia (kuten semaforia) ilmoittamaan toisilleen kirjoittamisen tai lukemisen valmistumisesta. Kuljettimet säästävät meidät kaikelta tältä vaivalta.
Mitä me etsimme?
Selitän sormillani, jotta sinun olisi helpompi kuvitella, kuinka kuljetin voi toimia. Sinun on varattava puskuri ja jokin tila muistista. Tarvitset toimintoja tietojen lisäämiseen ja poistamiseen puskurista. Tarvitset jonkin toiminnon kutsuaksesi toimintoja tiedostokuvaajien luku- ja kirjoitustoimintojen aikana. Ja lukot tarvitaan toteuttamaan yllä kuvattu erityiskäyttäytyminen.
Olemme nyt valmiita tutkimaan ytimen lähdekoodia kirkkaassa lampunvalossa vahvistaaksemme tai kumotaksemme epämääräisen mentaalimallimme. Mutta ole aina valmis odottamattomiin.
Mistä me etsimme?
En tiedä missä kopioni kuuluisasta kirjasta on.
TUHS:n arkistossa vaeltaminen on kuin museossa käyntiä. Voimme katsoa yhteistä historiaamme ja kunnioitan vuosien ponnisteluja saada kaikki tämä materiaali pala kerrallaan talteen vanhoista kasetteista ja tulosteista. Ja olen erittäin tietoinen niistä fragmenteista, jotka edelleen puuttuvat.
Kun olemme tyydyttäneet uteliaisuutemme putkilinjojen muinaisesta historiasta, voimme tarkastella nykyaikaisia ytimiä vertailun vuoksi.
muuten, pipe
on järjestelmäkutsunumero 42 taulukossa sysent[]
. Yhteensattuma?
Perinteiset Unix-ytimet (1970–1974)
En löytänyt jälkeäkään pipe(2)
ei sisälläkään
TUHS väittää näin
Unixin kolmas painos oli viimeinen versio, jossa oli assemblerillä kirjoitettu ydin, mutta myös ensimmäinen liukuhihnalla varustettu versio. Vuoden 1973 aikana työskenneltiin kolmannen painoksen parantamiseksi, ydin kirjoitettiin uudelleen C-kielellä ja näin syntyi Unixin neljäs painos.
Yksi lukija löysi skannauksen asiakirjasta, jossa Doug McIlroy ehdotti ajatusta "ohjelmien yhdistämisestä puutarhaletkun tapaan".
Brian Kernighanin kirjassa
Kun Unix ilmestyi, intohimoni korutiineihin sai minut pyytämään käyttöjärjestelmän kirjoittajaa Ken Thompsonia sallimaan johonkin prosessiin kirjoitetun datan siirtyä laitteeseen, mutta myös poistumiseen toisesta prosessista. Ken piti sitä mahdollisena. Minimalistina hän kuitenkin halusi jokaisen järjestelmän ominaisuuden olevan tärkeässä roolissa. Onko suora kirjoittaminen prosessien välillä todella suuri etu verrattuna välitiedostoon kirjoittamiseen? Ja vasta kun tein erityisen ehdotuksen tarttuvalla nimellä "pipeline" ja kuvauksella prosessien vuorovaikutuksen syntaksista, Ken lopulta huudahti: "Minä teen sen!".
Ja teki. Eräänä kohtalokkaana iltana Ken vaihtoi ytimen ja komentotulkin, korjasi useita vakioohjelmia standardoidakseen syötteiden hyväksymisen (joka saattaa tulla putkistosta) ja muutti tiedostonimiä. Seuraavana päivänä putkia käytettiin erittäin laajasti sovelluksissa. Viikon loppuun mennessä sihteerit lähettivät niillä asiakirjoja tekstinkäsittelyohjelmista tulostimeen. Jonkin verran myöhemmin Ken korvasi alkuperäisen API:n ja syntaksin putkien käytön käärimiseksi puhtaammilla käytännöillä, joita on käytetty siitä lähtien.
Valitettavasti kolmannen painoksen Unix-ytimen lähdekoodi on kadonnut. Ja vaikka meillä on ytimen lähdekoodi kirjoitettu C-kielellä
Meillä on dokumentaatioteksti pipe(2)
molemmista julkaisuista, joten voit aloittaa hakemalla dokumentaatiosta pipe(2)
on kirjoitettu assemblerissä ja palauttaa vain yhden tiedostokuvaajan, mutta tarjoaa jo odotetun ydintoiminnon:
Järjestelmäpuhelu putki luo I/O-mekanismin, jota kutsutaan liukuhihnaksi. Palautettua tiedostokuvaajaa voidaan käyttää luku- ja kirjoitustoimintoihin. Kun liukuhihnaan kirjoitetaan jotain, se puskuroi jopa 504 tavua dataa, minkä jälkeen kirjoitusprosessi keskeytyy. Liukuhihnasta luettaessa puskuroitu data otetaan.
Seuraavaan vuoteen mennessä ydin oli kirjoitettu uudelleen C-kielellä ja pipe(fildes)
»:
Järjestelmäpuhelu putki luo I/O-mekanismin, jota kutsutaan liukuhihnaksi. Palautettuja tiedostokuvauksia voidaan käyttää luku- ja kirjoitusoperaatioissa. Kun liukuhihnaan kirjoitetaan jotain, käytetään r1:ssä palautettua kuvaajaa (vastaa fildes[1]), puskuroituna 4096 tavuun dataa, minkä jälkeen kirjoitusprosessi keskeytyy. Liukuhihnasta luettaessa arvoon r0 palautettu kuvaaja (vastaavasti fildes[0]) ottaa tiedot.
Oletetaan, että kun liukuhihna on määritelty, kaksi (tai useampia) vuorovaikutuksessa olevaa prosessia (jotka luovat myöhemmillä kutsuilla haarukka) välittää dataa putkesta puheluiden avulla luettu и kirjoittaa.
Kuoressa on syntaksi, jolla määritellään lineaarinen joukko prosesseja, jotka on yhdistetty liukuhihnan kautta.
Kutsut lukemaan tyhjästä liukuhihnasta (jossa ei ole puskuroitua dataa), jolla on vain yksi pää (kaikki kirjoitustiedoston kuvaajat suljettuina), palauttavat "tiedoston lopun". Kirjoituspuhelut vastaavassa tilanteessa ohitetaan.
Varhaisin
Unix Sixth Edition (1975)
Unix-lähdekoodin lukeminen alkaa
Kirja monta vuotta Lions oli ainoa Unix-ytimen asiakirja, joka oli saatavilla Bell Labsin ulkopuolella. Vaikka kuudennen painoksen lisenssi antoi opettajille mahdollisuuden käyttää sen lähdekoodia, seitsemännen painoksen lisenssi sulki tämän mahdollisuuden pois, joten kirjaa jaettiin laittomina koneella kirjoitettuina kopioina.
Tänään voit ostaa kirjan uusintapainoksen, jonka kannessa on kopiokoneen opiskelijat. Ja kiitos Warren Toomeyn (joka aloitti TUHS-projektin), voit ladata
Yli 15 vuotta sitten kirjoitin kopion mukana tulevasta lähdekoodista Lionskoska en pitänyt kopioni laadusta tuntemattomasta määrästä muita kopioita. TUHS:ää ei ollut vielä olemassa, enkä päässyt vanhoihin lähteisiin. Mutta vuonna 1988 löysin vanhan nauhan, jossa oli 9 raitaa ja jolla oli varmuuskopio PDP11-tietokoneesta. Oli vaikea tietää, toimiko se, mutta siellä oli ehjä /usr/src/-puu, jossa suurin osa tiedostoista oli merkitty 1979, joka näytti silloinkin vanhalta. Se oli seitsemäs painos tai PWB-johdannainen, ajattelin.
Otin löydön pohjaksi ja muokkasin lähteet manuaalisesti kuudennen painoksen tilaan. Osa koodista pysyi ennallaan, osaa jouduttiin hieman muokkaamaan, muuttamalla moderni merkki += vanhentuneeksi =+. Jotain yksinkertaisesti poistettiin, ja jotain piti kirjoittaa kokonaan uudelleen, mutta ei liikaa.
Ja tänään voimme lukea verkossa TUHS:sta kuudennen painoksen lähdekoodia
Muuten, ensi silmäyksellä C-koodin pääominaisuus ennen Kernighanin ja Ritchien jaksoa on sen lyhyys. Harvoin pystyn lisäämään koodinpätkiä ilman laajaa muokkausta, jotta ne mahtuvat sivustoni suhteellisen kapealle näyttöalueelle.
Varhainen
/*
* 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
Puskurin koko ei ole muuttunut neljännen painoksen jälkeen. Mutta tässä näemme, ilman mitään julkista dokumentaatiota, että liukuhihnat käyttivät aikoinaan tiedostoja varamuistina!
Mitä tulee LARG-tiedostoihin, ne vastaavat
Tässä on todellinen järjestelmäkutsu 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;
}
Kommentti kuvaa selvästi mitä täällä tapahtuu. Mutta koodin ymmärtäminen ei ole niin helppoa, osittain siksi, että "R0
и R1
järjestelmäkutsuparametrit ja palautusarvot välitetään.
Kokeillaan kanssa
pipe()
kautta R0
и R1
palauttaa tiedostokuvausnumerot lukemista ja kirjoittamista varten. falloc()
palauttaa osoittimen tiedostorakenteeseen, mutta myös "palauttaa" kautta u.u_ar0[R0]
ja tiedostokuvaaja. Eli koodi on tallennettu r
tiedostokuvaaja lukemista varten ja määrittää kuvauksen suoraan kirjoittamista varten u.u_ar0[R0]
toisen puhelun jälkeen falloc()
.
lippu FPIPE
, jonka asetimme liukuhihnaa luotaessa, ohjaa funktion toimintaa
/*
* 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);
}
/* … */
}
Sitten toiminto readp()
в pipe.c
lukee dataa putkistosta. Mutta on parempi seurata toteutusta alkaen writep()
. Koodi on jälleen monimutkaistunut argumentin läpivientikäytännön luonteen vuoksi, mutta joitain yksityiskohtia voidaan jättää pois.
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;
}
Haluamme kirjoittaa tavuja liukuhihnan tuloon u.u_count
. Ensin meidän on lukittava inode (katso alla plock
/prele
).
Sitten tarkistamme inodien viitemäärän. Niin kauan kuin putken molemmat päät ovat auki, laskurin tulee olla 2. Pidämme kiinni yhdestä linkistä (alkaen rp->f_inode
), joten jos laskuri on pienempi kuin 2, tämän pitäisi tarkoittaa, että lukuprosessi on sulkenut liukuhihnan pään. Toisin sanoen yritämme kirjoittaa suljettuun liukuhihnaan, mikä on virhe. Ensimmäinen virhekoodi EPIPE
ja signaali SIGPIPE
ilmestyi Unixin kuudennessa painoksessa.
Mutta vaikka kuljetin olisi auki, se voi olla täynnä. Tässä tapauksessa vapautamme lukon ja menemme nukkumaan siinä toivossa, että toinen prosessi lukee putkistosta ja vapauttaa siihen tarpeeksi tilaa. Kun heräämme, palaamme alkuun, suljemme lukon uudelleen ja aloitamme uuden kirjoitussyklin.
Jos liukuhihnassa on tarpeeksi vapaata tilaa, kirjoitamme siihen dataa käyttämällä i_size1
inode'a (tyhjällä liukuhihnalla voi olla 0) osoittaa sen jo sisältämien tietojen loppuun. Jos kirjoitustilaa on tarpeeksi, voimme täyttää putkilinjan i_size1
до PIPESIZ
. Sitten vapautamme lukon ja yritämme herättää kaikki prosessit, jotka odottavat lukemistaan liukuhihnasta. Palaamme alkuun nähdäksemme, onnistuimmeko kirjoittamaan niin monta tavua kuin tarvitsimme. Jos ei, aloitamme uuden tallennusjakson.
Yleensä parametri i_mode
inodea käytetään käyttöoikeuksien tallentamiseen r
, w
и x
. Mutta liukuputkien tapauksessa annamme signaalin, että jokin prosessi odottaa kirjoitusta tai lukua bittien avulla IREAD
и IWRITE
vastaavasti. Prosessi asettaa lipun ja kutsuu sleep()
, ja on odotettavissa, että tulevaisuudessa jokin muu prosessi vaatii wakeup()
.
Todellinen taika tapahtuu sisällä sleep()
и wakeup()
. Ne toteutetaan vuonna
/*
* 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) /* … */
Prosessi, joka kutsuu sleep()
tietylle kanavalle, voidaan myöhemmin herättää toisella prosessilla, joka kutsuu wakeup()
samalle kanavalle. writep()
и readp()
koordinoi toimintaansa tällaisten puhelujen avulla. ota huomioon, että pipe.c
aina priorisoida PPIPE
kun soitetaan sleep()
, siis kaikki sleep()
voidaan katkaista signaalilla.
Nyt meillä on kaikki toiminnon ymmärtämiseksi 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);
}
Saatat olla helpompi lukea tämä toiminto alhaalta ylös. "Lue ja palauta" -haaraa käytetään yleensä, kun dataa on käsittelyssä. Tässä tapauksessa käytämme f_offset
lue ja päivitä sitten vastaavan siirtymän arvo.
Seuraavissa lukemissa liukuhihna on tyhjä, jos lukupoikkeama on saavutettu i_size1
inodissa. Nollaamme sijainnin 0:ksi ja yritämme herättää kaikki prosessit, jotka haluavat kirjoittaa liukuhihnaan. Tiedämme, että kun kuljetin on täynnä, writep()
nukahtaa päälle ip+1
. Ja nyt, kun liukuhihna on tyhjä, voimme herättää sen jatkaaksemme kirjoitusjaksoaan.
Jos ei ole mitään luettavaa, niin readp()
voi asettaa lipun IREAD
ja nukahtaa ip+2
. Tiedämme, mikä hänet herättää writep()
kun se kirjoittaa tietoja liukuhihnaan.
Kommentteja u
» voimme käsitellä niitä kuten tavallisia I/O-toimintoja, jotka ottavat tiedoston, sijainnin, puskurin muistissa ja laskevat luettavien tai kirjoitettavien tavujen määrän.
/*
* 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;
/* … */
Mitä tulee "konservatiiviseen" estoon readp()
и writep()
lukitse inodit, kunnes ne ovat valmiit tai saavat tuloksen (eli soita wakeup
). plock()
и prele()
toimii yksinkertaisesti: käyttämällä erilaista puhelusarjaa sleep
и wakeup
anna meidän herättää kaikki prosessit, jotka tarvitsevat juuri vapauttamamme lukon:
/*
* 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);
}
}
Aluksi en ymmärtänyt miksi readp()
ei aiheuta prele(ip)
ennen puhelua wakeup(ip+1)
. Ensimmäinen asia writep()
kutsuu silmukassaan, tämä plock(ip)
, mikä johtaa lukkiutumiseen, jos readp()
ei ole vielä poistanut estoaan, joten koodin on toimittava jotenkin oikein. Jos katsot wakeup()
, käy selväksi, että se merkitsee vain nukkumisprosessin valmiiksi suoritettaviksi, jotta tulevaisuudessa sched()
todella käynnisti sen. Niin readp()
syyt wakeup()
, avaa, asettaa IREAD
ja puhelut sleep(ip+2)
- kaikki tämä ennen writep()
käynnistää syklin uudelleen.
Tämä täydentää putkilinjojen kuvauksen kuudennessa painoksessa. Yksinkertainen koodi, kauaskantoiset vaikutukset.
Xv6, yksinkertainen Unix-tyyppinen ydin
Ytimen luomiseksi
Koodi sisältää selkeän ja harkitun toteutuksen 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()
määrittää kaiken muun toteutuksen tilan, joka sisältää toimintoja piperead()
, pipewrite()
и pipeclose()
. Varsinainen järjestelmäkutsu sys_pipe
on kääre, joka on toteutettu
Linux 0.01
Löydät lähdekoodin Linux 0.01:lle. On opettavaista tutkia putkistojen toteutusta hänen fs
/pipe.c
. Tässä inodea käytetään edustamaan liukuhihnaa, mutta itse liukuhihna on kirjoitettu nykyaikaisella C-kielellä. Jos olet murtautunut kuudennen painoksen koodin läpi, sinulla ei ole ongelmia tässä. Tältä toiminto näyttää 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;
}
Jopa katsomatta struct-määrityksiä, voit selvittää, kuinka inode-viittausmäärää käytetään tarkistamaan, johtaako kirjoitustoiminto SIGPIPE
. Tavu-byte-työn lisäksi tätä toimintoa on helppo verrata yllä oleviin ideoihin. Jopa logiikka sleep_on
/wake_up
ei näytä niin vieraalta.
Nykyaikaiset Linux-ytimet, FreeBSD, NetBSD, OpenBSD
Kävin nopeasti läpi joitakin moderneja ytimiä. Yhdelläkään niistä ei ole jo levypohjaista toteutusta (ei yllättävää). Linuxilla on oma toteutus. Ja vaikka kolme modernia BSD-ydintä sisältävät toteutuksia, jotka perustuvat John Dysonin kirjoittamaan koodiin, ne ovat vuosien mittaan tulleet liian erilaisiksi toisistaan.
Lukea fs
/pipe.c
(Linuxissa) tai sys
/kern
/sys_pipe.c
(*BSD:llä), se vaatii todellista omistautumista. Suorituskyky ja tuki ominaisuuksille, kuten vektori ja asynkroninen I/O, ovat tärkeitä koodissa nykyään. Ja muistin varaamisen, lukkojen ja ytimen kokoonpanon yksityiskohdat vaihtelevat suuresti. Tätä yliopistot eivät tarvitse käyttöjärjestelmien johdantokurssille.
Minusta oli joka tapauksessa mielenkiintoista kaivaa esiin muutamia vanhoja kuvioita (esim SIGPIPE
ja palata EPIPE
kirjoitettaessa suljettuun liukuhihnaan) kaikissa näissä niin erilaisissa nykyaikaisissa ytimissä. En luultavasti koskaan näe PDP-11-tietokonetta livenä, mutta muutama vuosi ennen syntymääni kirjoitetusta koodista on vielä paljon opittavaa.
Kirjoitti Divi Kapoor vuonna 2011, artikkeli "
Lähde: will.com