
Selles artiklis kĂ€sitletakse Unix-i tuumas konveierite rakendust. Olin mĂ”nevĂ”rra pettunud, et hiljutine artikkel pealkirjaga ââ osutus ei sisekĂŒsimuseks. See Ă€ratas minu huvi ja hakkasin sĂŒvitsi kaevama vanadesse allikatesse, et vastust leida.
Mille ĂŒle jutt kĂ€ib?
Konveierid on âtĂ”enĂ€oliselt kĂ”ige olulisem leiutis Unix-isâ â need on mÀÀratlev omadus, mis pĂ”hineb Unix-i filosoofial, mille eesmĂ€rk on vĂ€ikeste programmide ĂŒhendamine, samuti tuttav tekst kĂ€sureas:
$ echo hello | wc -c
6
See funktsionaalsus sĂ”ltub tuuma poolt pakutavast sĂŒsteemikĂ”nest pipe, mis on dokumentatsiooni lehekĂŒlgedel kirjeldatud ja :
Konveierid tagavad ĂŒhesuunalise kanali protsessidevaheliseks suhtlemiseks. Konveieril on sisend (write end) ja vĂ€ljund (read end). Konveierisse kirjutatud andmeid saab lugeda vĂ€ljundist.
Konveier luuakse vÀljundi kaudu
pipe(2), mis tagastab kaks failideskriptorit: ĂŒks viitab konveieri sisendile, teine vĂ€ljundile.
Ălaltoodud kĂ€su jĂ€lgimisandmed nĂ€itavad konveieri loomist ja andmevoogu ĂŒhelt protsessilt teisele:
$ 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
Vanemprotsess kutsub vĂ€lja pipe(), et saada ĂŒhendatud failikirjeldajad. Ăks alamprotsess kirjutab ĂŒhte kirjelduse, teine aga loeb neid andmeid teisest kirjelduse.
Ilma torudeta peaks shell salvestama ĂŒhe protsessi tulemuse faili ja edastama selle teisele protsessile, et viimane saaks faili andmeid lugeda. SeetĂ”ttu raiskaksime rohkem ressursse ja kettaruumi. Kuid torud ei ole head mitte ainult selle poolest, et saavad vĂ€ltida ajutiste failide kasutamist:
Kui protsess proovib lugeda tĂŒhjast torust, siis
read(2)blokeerib seni, kuni andmed on saadaval. Kui protsess proovib kirjutada tÀidetud torusse, siiswrite(2)blokeerib kuni konveierist loetakse piisavalt andmeid kirje tÀitmiseks.
Nagu POSIX-nĂ”ue, on see oluline omadus: kirje konveierisse kuni PIPE_BUF baiti (vĂ€hemalt 512) peab olema aatomaarne, et protsessid saaksid ĂŒksteisega konveieri kaudu suhelda, nagu tavalised failid (mis ei paku selliseid garantiisid) ei suuda.
Tavalise faili kasutamisel saab protsess kirjutada sinna kĂ”ik oma vĂ€ljundid ja edastada need teisele protsessile. VĂ”i protsessid saavad tegutseda rangelt paralleelselt, informeerides ĂŒksteist kirjutamise vĂ”i lugemise lĂ”petamisest mĂ”ne vĂ€lise signaalimehhanismi (nt semafori) kaudu. Konveierid vabastavad meid kĂ”igist neist vaevadest.
Mida me otsime?
Selgitan lihtsalt, et oleksite paremini ette kujutada, kuidas konveier töötab. Teil on vaja eraldada mÀlu puhver ja mingi olek. Vajate funktsioone andmete lisamiseks ja eemaldamiseks puhvrist. Vajalik on mingi mehhanism, et kutsuda funktsioone vÀlja lugemise ja kirjutamise operatsioonide kÀigus failidesse. Ja vajate lukke, et rakendada eespool kirjeldatud spetsiifilist kÀitumist.
NĂŒĂŒd oleme valmis valgustama lĂ€htekoodi tuuma, et kinnitada vĂ”i ĂŒmber lĂŒkata meie Ă€hmast mĂ”ttemudelit. Kuid olge alati valmis ĂŒllatusteks.
Kust me otsime?
Ma ei tea, kus mu eksemplar tuntud raamatust ââ Unix 6 lĂ€htekoodiga asub, kuid tĂ€nu saame internetis otsida veel vanemate versioonide Unix.
TUHS arhiivides ekslemine on nagu muuseumis kĂ€imine. Saame vaadata meie ĂŒhist ajalugu ning tunnen austust aastatepikkuste jĂ”upingutuste vastu, et kĂ”iki neid materjale bit haaval taastada vanadelt kassettidelt ja printimistelt. Ja tunnen teravalt puuduvaid fragmente.
Uurides konveierite iidset ajalugu, vĂ”ime seniseks vĂ”rdluseks vaadata kaasaegseid sĂŒdamikke.
Muide, pipe on sĂŒsteemikĂ”ne number 42 tabelis sysent[]. Kokkć¶tus?
Traditsioonilised Unix sĂŒdamikud (1970â1974)
Ma ei leidnud mingeid jÀlgi pipe(2) ei (jaanuar 1970), ega (november 1971), ega puudulikust lÀhtekoodist (juuni 1972).
TUHS vÀidab, et (veebruar 1973) oli esimene versioon konveieritega:
Kolmas Unix vĂ€ljaanne oli viimane versioon, mille sĂŒdamik oli kirjutatud assambleekeeltes, kuid samas esimene versioon konveieridega. 1973. aastal tehti parandusi kolmandale vĂ€ljaandele, sĂŒdamik kirjutati ĂŒmber C-sse ja nii tekkis neljas Unix vĂ€ljaanne.
Ăks lugejatest leidis dokumendi koopia, kus Doug McIlroy pakkus ideed âprogrammide ĂŒhendamisest vooliku printsiibi jĂ€rgidesâ.

Bryan Kernighani raamatus ââ mainitakse samuti seda dokumenti: â⊠see seisis 30 aastat mu Bell Labsâi kontori seinal.â Siin on , ja veel ĂŒks lugu :
Kui Unix esmakordselt ilmus, pĂ”hjustas minu huvi korutinate vastu, et kĂŒsisin operatsioonisĂŒsteemi autori, Ken Thompsoni, kĂ€est, kas andmed, mis on mĂ”nes protsessis, saaksid suunata mitte ainult seadmele, vaid ka teise protsessi. Ken leidis, et see on vĂ”imalik. Siiski soovis ta minimalistina, et iga sĂŒsteemifunktsioon mĂ€ngiks olulist rolli. Kas tĂ”eliselt on otseprotsesside vahel kirjutamisel mĂ€rkimisvÀÀrne eelis vĂ”rreldes ajutise faili kirjutamisega? Ja ainult siis, kui esitasin konkreetse ettepaneku silmatorkava nimega âtoruâ koos protsessidevahelise suhtluse sĂŒntaksikirjeldusega, hĂŒĂŒdis Ken lĂ”puks: âMa teen selle!â
Ja ja tegin. Ăhel saatuslikul Ă”htul muutis Ken tuuma ja kestat, parandades mitmeid standardprogramme ning standardiseerides nende sisestamisprotseduurid (mida saab edastada konveieride kaudu) ning vahetas ka failide nimesid. JĂ€rgmisel pĂ€eval hakkasid konveierid rakendustes laialdaselt kasutusele minema. NĂ€dala lĂ”puks saatis sekretĂ€ride abiga printerisse dokumente tekstiredaktorist. Veidi hiljem asendas Ken algse API ja konveierite kasutamise sĂŒntaksi puhtamate lepetega, mis on alates sellest ajast kasutusele vĂ”etud.
Kahjuks on kolmanda redaktsiooni Unixâi tuumakaallika kood kadunud. Ja kuigi meil on C keeles kirjutatud neljanda redaktsiooni tuumakaallika kood , mis ilmus 1973. aasta novembris, ilmus see siiski paar kuud enne ametlikku vĂ€ljalaskmist ning ei sisalda konveiere. Kahju, et Unixâi legendaarse funktsiooni algne kood on kadunud, vĂ”ib-olla igaveseks.
Meil on dokumentatsiooni tekst pipe(2) mĂ”lemast vĂ€ljaandest, seega oleks mĂ”ttekas alustada dokumentatsiooni otsimisest (teatud sĂ”nadega, mis on "kĂ€si" alla joonitud, rida literiaalidest ^H, millele jĂ€rgneb allajoonimine!). See proto-pipe(2) on kirjutatud assembleris ja tagastab ainult ĂŒhe failide kirjeldaja, kuid juba pakub oodatud pĂ”hifunktsionaalsust:
SĂŒsteemikĂ”ne pipe loob sisse- ja vĂ€ljundimehhanismi, mida nimetatakse toruks. Tagastatavat failide kirjeldajat saab kasutada lugemise ja kirjutamise operatsioonide jaoks. Kui torusse midagi kirjutatakse, siis puhverdub kuni 504 baitide andmeid, pĂ€rast mida kirjutamisprotsess peatub. Torust lugemisel vĂ”etakse puhverdatud andmed.
JĂ€rgmiseks aastaks tuum kirjutati ĂŒmber C-sse, ja omandab oma kaasaegse kuju prototĂŒĂŒbiga "pipe(fildes)»:
SĂŒsteemikĂ”ne pipe loob sisend-vĂ€ljund mehhanismi, mida nimetatakse toruks. Tagastatud failideskriptorit saab kasutada lugemis- ja kirjutamistoimingutes. Kui torusse midagi kirjutatakse, kasutatakse r1-s tagastatud deskriptorit (jĂ€relikult fildes[1]), mis puhverdab andmeid kuni 4096 baiti, pĂ€rast mida kirjutamisprotsess peatub. Torust lugemisel kasutab r0-s tagastatud deskriptor (jĂ€relikult fildes[0]) andmete saamiseks.
Oletatakse, et pÀrast toru mÀÀratlemist kahe (vÔi enama) omavahel seotava protsessi (mis loodi jÀrjestikuste kutsumistega fork) andmeid torust edastatakse, kasutades kutsungeid read ja write.
KĂ€sklusel on sĂŒntaks lineaarsete protsesside massiivi mÀÀramiseks, mis on ĂŒhendatud toru kaudu.
Kutsungid, mis loevad tĂŒhjast torust (millel ei ole puhverdatud andmeid ja millel on ainult ĂŒks ots, sest kĂ”ik kirjutavad failideskriptorid on suletud), tagastavad "faili lĂ”pu". Sarnastes olukordades kirjutamiskutsungid ignoreeritakse.
Varaseim kuulub (juuni 1974), kuid see on peaaegu identne jÀrgmise versiooniga. Ainult kommentaarid lisandusid, seega vÔib viienda redaktsiooni vahele jÀtta.
Unix'i kuues redaktsioon (1975)
Alustame Unix'i allika koodi lugemist (mai 1975). Suur osa tÀnu Lions on seda palju lihtsam leida kui varasemate versioonide allikaid:
Paljude aastate jooksul oli raamat Lions ainus Unix'i tuuma dokument, mis oli saadaval vĂ€ljaspool Bell Labs'i. Kuigi kuue redaktsiooni litsents lubas Ă”petajatel seda allika koodi kasutada, vĂ€listas seitsmenda redaktsiooni litsents selle vĂ”imaluse, seega levis raamat ebaseaduslike trĂŒki koopiatena.
TĂ€napĂ€eval on saadaval raamat, mille kaanel on kujutatud ĂŒliĂ”pilasi koopiamasinas. Ja tĂ€nu Warren Tumile (kes kĂ€ivitas TUHS-i projekti) saate alla laadida . Tahan anda teile ĂŒlevaate, kui palju vaeva kulus faili loomisele:
Ăle 15 aasta tagasi tippisin ma allika koodi koopia, mida toodi vĂ€lja Lions, kuna mulle ei meeldinud minu koopia kvaliteet tundmatute teiste koopiate hulgast. TUHS ei olnud veel olemas ja mul ei olnud ligi vanadele lĂ€htefailidele. Kuid 1988. aastal leidsin vana lintide, millel oli 9 rada ja millel oli koopia PDP11 arvutist. Oli raske mĂ”ista, kas see töötab, kuid seal oli puutumatu puu /usr/src/, kus enamik faile oli dateeritud 1979. aastaga, mis tundus juba toona muistsena. See oli seitsmes vĂ€ljaanne vĂ”i selle derivaat PWB, nagu ma arvasin.
VĂ”tsin leiust aluseks ja redigeerisin lĂ€htefailid kĂ€sitsi kuuenda vĂ€ljaande seisundisse. Osa koodist jĂ€i samaks, osa tuli veidi kohandada, muutes kaasaegse tokeni += vananenud =+. Midagi eemaldasin lihtsalt ja mĂ”ne pidin tĂ€ielikult ĂŒmber kirjutama, kuid mitte liiga palju.
Ja tÀna saame veebis lugeda TUHS kuuenda vÀljaande lÀhtekoodi .
Muide, esmapilgul on C-koodi peamine omadus kuni Kernighani ja Ritchie perioodini selle lĂŒhidus. Mul ei Ă”nnestu sageli koodi fragmente ilma ulatusliku redigeerimiseta lisada, et need vastaksid minu saidil suhteliselt kitsale kuvamispiirkonnale.
Alguses on selgitav kommentaar (ja jah, seal on veel ):
/*
* 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
Puhvri suurus ei ole muutunud alates neljandast vÀljaandest. Kuid siin nÀeme, ilma igasuguse avaliku dokumentatsioonita, et kunagi kasutasid konveierid faile varunduslussu jaoks!
Mis puutub LARG-failidesse, siis need vastavad , mida kasutatakse âsuure aadressimise algoritmiâ kaudu toetamiseks suuremate failisĂŒsteemide jaoks. Kui Ken ĂŒtles, et parem on neid mitte kasutada, siis ma usun teda hea meelega.
Siin on tĂ”eline sĂŒsteemikutse 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;
}
Kommentaaris on selgelt kirjas, mis toimub. Kuid koodis navigeerimine ei ole sugugi lihtne, osaliselt seetĂ”ttu, kuidas "" ja registrid R0 ja R1 edastavad sĂŒsteemikĂ”nede parameetrid ja tagastatavad vÀÀrtused.
Proovime kasutada kettal , ning kasutada â mĂ€lu haldamiseks kahte . Kui kĂ”ik lĂ€heb hĂ€sti, seame lippu, et mÀÀrata need failid kahe otsa kaudu, mĂ€rkides need sama inoodi (kus viidete loend saab 2) ja mĂ€rkides inoodi muudetuks ja kasutatavaks. Pange tĂ€hele viiteid vea radade (error paths) jaoks, et vĂ€hendada viidete loendit uues inoodis.
pipe() peab olema kaudu R0 ja R1 tagastama failide deskriptorite numbrid lugemiseks ja kirjutamiseks. falloc() tagastab viite failistruktuurile, kuid ka 'tagastab' lÀbi u.u_ar0[R0] ja failide deskriptorit. See tÀhendab, et kood salvestab r failide deskriptor lukemiseks ja mÀÀrab kirjutamisekdeskriptori otse u.u_ar0[R0] pÀrast teist kutsumist falloc().
Lipp FPIPE, mille seadsime konveieri loomisel, juhib funktsiooni , mis kutsub ĂŒles konkreetseid sisendi-vĂ€ljundi alprogrammes I/O:
/*
* 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);
}
/* ⊠*/
}
Siis funktsioon readp() ĂŒhes pipe.c loeb andmeid konveierist. Kuid parim on jĂ€lgida rakendust alates writep().Kordan, et kood on muutunud keerulisemaks argumentide edastamise kokkuleppe pĂ”hjustel, kuid mĂ”ned detailid vĂ”ib vahele jĂ€tta.
writep(fp)
{
register *rp, *ip, c;
rp = fp;
ip = rp->f_inode;
c = u.u_count;
loop:
/* Kui kÔik on tehtud, tagastame. */
plock(ip);
if(c == 0) {
prele(ip);
u.u_count = 0;
return;
}
/*
* Kui torul ei ole samaaegselt lugemist ja kirjutamist,
* tagastame vea ja anname mÀrku.
*/
if(ip->i_count i_size1 == PIPSIZ) {
ip->i_mode |= IWRITE;
prele(ip);
sleep(ip + 1, PPIPE);
goto loop;
}
/* Kirjutame, mis on vÔimalik, ja tuleme tagasi. */
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;
}
Torusse tahame kirjutada baite u.u_count. Esiteks nÔuame indeksdessaatori lukku (vt allpool plock/prele).
Siis kontrollime inode'i viidete loendurit. Niikaua kui mĂ”lemad toru otsad jÀÀvad avatuks, peab loendur olema 2. Me hoiame ĂŒhe viite (nagu rp->f_inode), nii et kui loendur on alla 2, siis peab see tĂ€hendama, et lugemisprotsess on oma toru otsa sulgenud. TeisisĂ”nu, me ĂŒritame kirjutada suletud torusse, mis on viga. Vea kood on esmakordselt EPIPE ja signaal SIGPIPE ilmnes kuues Unix'i versioonis.
Kuid isegi kui toru on avatud, vĂ”ib see olla tĂ€is. Sellel juhul vabastame blokeeringu ja lĂ€heme magama lootuses, et teine protsess loeb torust ja vabastab piisavalt ruumi. Kui Ă€rkame, naaseme algusesse, paneme jĂ€lle blokeeringu ja kĂ€ivitame uue kirjutamistsĂŒkli.
Kui torus on piisavalt vaba ruumi, kirjutame andmed selle kaudu . Parameeter i_size1 inode'i (tĂŒhjas torus vĂ”ib olla 0) nĂ€itab andmete lĂ”ppu, mis seal juba on. Kui ruumi on piisavalt, saame toru tĂ€ita i_size1 kuni PIPESIZ. SeejĂ€rel vabastame blokeeringu ja proovime Ă€ratada mistahes protsessi, mis ootab vĂ”imalust torust lugeda. Naaseme algusesse, et vaadata, kas suutsime kirjutada nii palju baite, kui vaja. Kui ei, siis alustame uut kirjutamistsĂŒklit.
Tavaliselt kasutatakse parameetrit i_mode inode'is Ă”iguste salvestamiseks r, w ja x. Kuid torude puhul mĂ€rgime kirjutamise vĂ”i lugemise ootamise mĂ”ne protsessi jaoks bitidega IREAD ja IWRITE vastavalt. Protsess seab lipu ja kutsub vĂ€lja sleep(), ja oodatakse, et tulevikus kutsub mĂ”ni muu protsess ĂŒles wakeup().
TĂ”eline maagia toimub sleep() ja wakeup(). Need on ellu viidud , tuntud kommentaari âTe ei pea seda mĂ”istmaâ (You are not expected to understand this) all. Ănneks ei pea me koodi mĂ”istma, vaatame lihtsalt mĂ”ningaid kommentaare:
/*
* 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) /* ⊠*/
Protsess, mis kutsub sleep() kindla kanali jaoks, vÔib hiljem olla Àratatud teise protsessi poolt, mis kutsub wakeup() sama kanali jaoks. writep(). ja readp() koordineerivad oma tegevust selliste paariliste kutsungite kaudu. Pange tÀhele, et pipe.c annab alati prioriteedi PPIPE kui kutsutakse sleep(), seega vÔivad kÔik sleep() saatmist signaali kaudu katkestada.
NĂŒĂŒd on meil kĂ”ik, et aru saada funktsioonist 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 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);
}
Selle funktsiooni vĂ”ib-olla on lihtsam lugeda alt ĂŒles. Haru «read and return» kasutatakse tavaliselt, kui torustikus on andmeid. Sel juhul teeme seda loe piisavalt andmeid alates praegusest f_offset lugemisest ja seejĂ€rel uuendame vastava nihke vÀÀrtust.
JĂ€rgneva lugemise ajal on toru tĂŒhi, kui lugemise nihke vÀÀrtus on jĂ”udnud i_size1 inode'i vÀÀrtuseni. Me lĂ€htestame positsiooni 0-le ja proovime Ă€ratada mistahes protsessi, mis tahab toru kirjutada. Teame, et kui toru on tĂ€is, writep(). on see magama ip+1. NĂŒĂŒd, kui konveier on tĂŒhi, saame selle kĂ€ivitada, et see jĂ€tkaks oma salvestustsĂŒklit.
Kui lugeda pole midagi, siis readp() vÔib seada lipu IREAD ja jÀÀda magama ip+2.Teame, et see Àratatakse writep()., kui konveierisse kirjutatakse mingeid andmeid.
Kommentaarid aitavad mÔista, et "u" asemel saame nendega kÀituda nagu tavaliste sisend-vÀljundfunktsioonidega, mis vÔtavad faili, positsiooni, mÀlubufferi ja loevad vÔi kirjutavad baitide arvu.
/*
* 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;
/* ⊠*/
Mis puudutab "konservatiivset" lukustamist, siis readp() ja writep(). lukustavad inode'i kuni töö lĂ”petamiseni vĂ”i tulemuse saamiseni (st kuni kutsuvad ĂŒles wakeup). plock() ja prele() töötab lihtsalt: teise kutsungite kogumiga uni ja wakeup kĂ”ikidesse protsessidesse, mis vajavad just Ă€sja eemaldatud lukku, Ă€ratamiseks:
/*
* 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);
}
}
Alguses ei saanud ma aru, miks readp() ei kutsu prele(ip) enne wakeup(ip+1). Esimene, mida writep(). kutsutakse ĂŒles oma tsĂŒklis, on plock(ip), mis viib ummikusse, kui readp() ta ei ole oma lukku veel eemaldanud, seega peab kood mingil viisil Ă”igesti töötama. Kui vaadata wakeup(), siis saab selgeks, et see vaid mĂ€rgib magava protsessi kui tĂ€itmiseks valmis, et tulevikus sched() tĂ”epoolest kĂ€ivitas selle. Nii et readp() kutsub wakeup(), eemaldab lukustuse, mÀÀrab IREAD ja kutsub sleep(ip+2)â kĂ”ik see enne, kui writep(). taas alustab tsĂŒklit.
Sellega on kuue versiooni kanalite kirjeldamine lÔppenud. Lihtne kood, kaugeleulatuvad tagajÀrjed.
(jaanuar 1979) oli uus peamine vĂ€ljaanne (neli aastat hiljem), milles ilmus palju uusi rakendusi ja tuuma omadusi. Samuti toimusid selles olulised muudatused seoses tĂŒĂŒbivalimise, liitude ja tĂŒĂŒbitud nĂ€idikute kasutamisega struktuuride osas. Siiski praktiliselt ei muutunud. VĂ”ime selle vĂ€ljaande vahele jĂ€tta.
Xv6, lihtne Unix-tĂŒĂŒpi tuum
Xv6 tuuma loomine Unix'i kuues versioon mĂ”jutas seda, kuid see on kirjutatud modernses C-s, et seda saaks kĂ€itada x86 protsessoritel. Kood on kergesti loetav ja arusaadav. Lisaks, erinevalt Unix'i algkoodist TUHS-is, saate selle kompileerida, muuta ja kĂ€itada midagi muud peale PDP 11/70. SeetĂ”ttu kasutatakse tuuma laialdaselt ĂŒlikoolides Ă”ppetöös operatsioonisĂŒsteemide teemal. Algkood .
Kood sisaldab arusaadavat ja hoolikalt lĂ€bi mĂ”eldud rakendust , toetatud mĂ€lupuhvriga asemel inode'ist kettal. Siin tooin ainult âstruktuurse toruâ mÀÀratluse ja funktsioonid 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ÀÀrab seisundi kogu ĂŒlejÀÀnud rakendusele, mis sisaldab funktsioone piperead(), pipewrite() ja pipeclose(). Tegelik sĂŒsteemi kĂ”ne sys_pipe on ĂŒmbris, mille on rakendanud . Soovitan lugeda kogu selle koodi. Raskusaste on kuues versioon, kuid lugeda on see mĂ€rksa kergem ja meeldivam.
Linux 0.01
Algkoodi Linux 0.01 vÔib leida. On hariv uurida torude rakendust selle fs/pipe.c. Siin kasutatakse toru esitlemiseks inode'i, kuid toru on kirjutatud modernses C-s. Kui olete kuues versioonis koodi lÀbi uurinud, siis ei tohiks teil siin raskusi olla. Nii nÀeb vÀlja funktsioon 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) { /* lugejat pole */
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;
}
Isegi struktuuride mÀÀratlemata jÀtmist on vÔimalik mÔista, kuidas inode'i viidete loendit kasutatakse, et kontrollida, kas kirjutamine SIGPIPE. Lisaks bite-by-bite tööle on selle funktsiooni lihtne seostada eespool kirjeldatud ideedega. Isegi loogika sleep_on/wake_up ei tundu nii vÔÔras.
Kaasaegsed Linuxi tuumad, FreeBSD, NetBSD, OpenBSD
Kiirelt vaatasin ĂŒle mĂ”ned kaasaegsed tuumad. Ăheski neist ei ole enam rakendust, mis kasutaks ketast (mis pole ĂŒllatav). Linuxil on oma rakendus. Ja kuigi kolm kaasaegset BSD tuuma sisaldavad rakendusi, mis pĂ”hinevad John Dysoni kirjutatud koodil, on nad aastate jooksul ĂŒksteisest liiga palju erinevaks muutunud.
Lugemiseks fs/pipe.c (Linuxis) vĂ”i sys/kern/sys_pipe.c (BSD peal) on tĂ”eline pĂŒhendumine vajalik. TĂ€na on koodis olulised jĂ”udlus ja toetatud funktsioonid, nagu vektoriaalsed ja asĂŒnkroonsed sisend-vĂ€ljundoperatsioonid. Ja mĂ€luhaldamise, lukustamise ja tuuma konfiguratsiooni ĂŒksikasjad - kĂ”ik see varieerub vĂ€ga. See ei ole midagi, mida kĂ”rgharidusasutused vajavad operatsioonisĂŒsteemide sissejuhatavaks kursuseks.
Igatahes, oli huvitav kaevuda vĂ€lja mĂ”ned vanad mustrid (nĂ€iteks, genereerimine SIGPIPE ja tagastamine EPIPE sulgemise toru kirjutamisel) kĂ”igis nendes, nii erinevates, kaasaegsetes tuumades. TĂ”enĂ€oliselt ma ei nĂ€e kunagi PDP-11 arvutit elus, kuid koodist, mis on kirjutatud aastatel enne minu sĂŒndi, on ikka veel palju Ă”ppida.
Divi Kapuri 2011. aastal kirjutatud artikkel "» annab ĂŒlevaate, kuidas (siiani) torustikud Linuxis töötavad. Ja ilustrib torustiku mudelit, mille vĂ”imalused ĂŒletavad ajutiste failide omadusi; ning nĂ€itab, kui kaugele on torustikud jĂ”udnud «vĂ€ga konservatiivsest lukustusest» Unix 6. vĂ€ljaandes.
Allikas: habr.com
