
Selles artiklis kÀsitletakse Unixis torude rakendust. Olin veidi pettunud, et hiljutine artikkel pealkirjaga "" osutus ei tuuma struktuuri kÀsitlemiseks. See tekitas minus huvi ja kaevusin vanadesse allikatesse, et leida vastus.
Millest jutt?
Torud - "ilmselt kĂ”ige olulisem leiutis Unixis" - on mÀÀrav omadus Unix'i filosoofias, mis ĂŒhendab vĂ€ikseid programme ja on tuttav vĂ€ljend kĂ€sureal:
$ echo hello | wc -c
6
See funktsionaalsus sĂ”ltub tuum poolt pakutavast sĂŒsteemi funktsioonist pipe, mis on kirjeldatud dokumentatsiooni lehtedel ja :
Torud pakuvad ĂŒhesuunalist kanalit protsesside vaheliseks suhtlemiseks. Torul on sisend (write end) ja vĂ€ljund (read end). Toru sisse kirjutatud andmed saab lugeda vĂ€ljundist.
Toru luuakse kutsungi abil
pipe(2), mis tagastab kaks failide descriptorit: ĂŒks viitab toru sisendile, teine vĂ€ljundile.
Ălaltoodud kĂ€su tutvustamiste tulemused nĂ€itavad toru loomist ja andmevoogu selle kaudu ĂŒhe protsessi juurest teise:
$ 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
Vanem protsess kutsub vĂ€lja pipe(), et saada ĂŒhendatud failide deskriptorid. Ăks alamsĂŒsteem kirjutab ĂŒhte deskriptorisse, teine loeb samu andmeid teisest deskriptorist. Shell kasutab dup2 deskriptorite 3 ja 4 "ĂŒmbernimetamiseks", et need vastaksid stdin ja stdout.
Ilma torudeta peaks shell kirjutama ĂŒhe protsessi tulemuse faili ja edastama selle teisele protsessile, et see saaks andmed failist lugeda. Nii kasutaksime rohkem ressursse ja kettaruumi. Torud ei ole head ainult seetĂ”ttu, et nad vĂ”imaldavad vĂ€ltida ajutiste failide kasutamist:
Kui protsess proovib lugeda tĂŒhi torust, siis
read(2)blokeerib, kuni andmed on saadaval. Kui protsess proovib kirjutada tÀis torusse, siiswrite(2)blokeerib kuni konveierist loetakse piisavalt andmeid kirjutamiseks.
Nagu POSIXâi nĂ”ue, on see oluline omadus: kirjutamine konveierisse kuni PIPE_BUF baiti (vĂ€hemalt 512) peab olema aatomaarne, et protsessid saaksid omavahel suhelda konveieri kaudu, nagu tavapĂ€rased failid (mis ei paku selliseid garantiisid) ei suuda.
Tavalise faili kasutamisel vÔib protsess kirjutada sinna kÔik oma vÀljundandmed ja edastada teisele protsessile. VÔi saavad protsessid tegutseda tihedas paralleelsuses, kasutades vÀlist signaalimehhanismi (nt semaforit) omavahelise kirjutamise vÔi lugemise lÔpetamisest teavitamiseks. Konveierid vabastavad meid kÔikidest neist muredest.
Mida me otsime?
Selgitan arusaadavamalt, et oleks lihtsam ette kujutada, kuidas konveier töötab. Teil tuleb eraldada mÀlus puhvers ja mingi olek. Vajate funktsioone andmete lisamiseks ja eemaldamiseks puhvers. Vajate mingit instrumenti, et kutsuda funktsioone vÀlja lugemise ja kirjutamise operatsioonide ajal failide deskriptoritele. Ja vajate lukustusi, et rakendada eelpool kirjeldatud erilist kÀitumist.
NĂŒĂŒd oleme valmis kirgliku valguse all kĂŒsima algkoodi, et kinnitada vĂ”i ĂŒmber lĂŒkata meie hĂ€gune vaimne mudel. Kuid olge alati valmis ĂŒllatusteks.
Kus me otsime?
Ma ei tea, kus asub minu eksemplar tuntud raamatust ââ Unix 6 algkoodiga, kuid tĂ€nu saab internetis otsida veelgi vanematest Unixâi versioonidest.
TUHS arhiivides ekslemine on nagu muuseumi kĂŒlastamine. Me saame heita pilgu meie ĂŒhisesse ajaloosse ja tunnen austust aastate kestnud pingutuste ĂŒle, et taastada kĂ”ik need materjalid bit bitilt vanadelt kassettidelt ja trĂŒkkidelt. Ja olen teravalt teadlik nendest fragmentidest, mis veel puuduvad.
Olles rahuldanud oma uudishimu iidse konveierite ajaloo osas, saame vÔrdlemiseks vaadata kaasaegseid yadrusid.
Muide, pipe on sĂŒsteemikutsung nr 42 tabelis sysent[]. Kokkusattumus?
Traditsioonilised Unixâi yadrid (1970â1974)
Ma ei leidnud mingeid jĂ€lgi pipe(2) ei jaanuaris 1970, ei esimeses Unixâi vĂ€ljaandes (juuni 1972). TUHS vĂ€idab, et
kolmas Unixâi vĂ€ljaanne (veebruari 1973) sai esimeseks versiooniks konveieritega:
Unix'i kolmas vĂ€ljaanne oli viimane versioon, mille sĂŒdamik oli kirjutatud assembleris, kuid samas esimene versioon, mis toetas torustikke. 1973. aastal tehti tööd kolmanda vĂ€ljaande parandamiseks, sĂŒdamik kirjutati ĂŒmber C keeles ja nii tekkis Unix'i neljas vĂ€ljaanne.
Ăks lugejatest leidis dokumendi skaneeringu, kus Doug MacIlroy pakkus vĂ€lja idee "programmide kokkuĂŒhendamisest nagu aiaveesĂŒsteem".

Bryan Kernighani raamatus "", mainitakse torustike ajalugu ka selles dokumendis: "⊠see oli minu Bell Labs'i kontoris seinal 30 aastat." Siin on , ja veel ĂŒks lugu MacIlroy' tööst, kirjutatud 2014. aastal. :
Ja ta tegi. Ăhel saatuslikul Ă”htul muutis Ken sĂŒdamikku ja kest, parandas mitu standardprogrammi ning standardiseeris nende sisendite vastuvĂ”tmise protseduuri (mis vĂ”ib olla pĂ€rit torustikust), samuti muutis failide nimesid. JĂ€rgmisel pĂ€eval hakati torustikku rakendama vĂ€ga laialdaselt rakendustes. NĂ€dala lĂ”puks saatsid sekretĂ€rid selle abil printimisele dokumente tekstiredaktorist. Veidi hiljem töötas Ken vĂ€lja originaalse API ja sĂŒntaksi torustike kasutamiseks puhtamateks kokkulepeteks, mis on pĂ€rast seda rakendatud.
Kahjuks on Unix'i kolmanda vĂ€ljaande sĂŒdamiku lĂ€htekood kadunud. Ja kuigi meil on neljanda vĂ€ljaande lĂ€htekood, mis on kirjutatud C keeles
, mis ilmus novembris 1973 ja mis ilmus paar kuud enne ametlikku vÀljaandmist ning ei sisalda torustike rakendust. Kahju, et legendaarse Unix'i funktsiooni lÀhtekood on kadunud, vÔib-olla igavesti. Meil on dokumentatsiooni tekst
kohta pipe(2) mĂ”lema vĂ€ljaande hulgast, seetĂ”ttu vĂ”ib dokumentatsioonis otsimist alustada (kindlate sĂ”nade jaoks, mis on «kĂ€si» all, rida literalidest ^H, millele jĂ€rgneb alakiri!). See proto-pipe(2) on kirjutatud assembleris ja tagastab vaid ĂŒhe faili deskriptiivi, kuid pakub juba oodatavat pĂ”hifunktsionaalsust:
SĂŒsteemi vĂ€ljakutse pipe loob sisendi ja vĂ€ljundi mehhanismi, mida nimetatakse toruks. Tagastatavat faili deskriptiivi saab kasutada lugemis- ja kirjutamistoimingutes. Kui torusse midagi kirjutatakse, siis puhverdatakse kuni 504 baitide andmeid, pĂ€rast mida kirjutamise protsess peatatakse. Torust lugemise korral korjatakse puhverdatud andmed.
JĂ€rgmiseks aastaks oli tuum ĂŒmber kirjutatud C-sse, ja kĂŒsis oma tĂ€napĂ€evast vormi prototĂŒĂŒbilt «pipe(fildes)»:
SĂŒsteemi vĂ€ljakutse pipe loob sisendi ja vĂ€ljundi mehhanismi, mida nimetatakse toruks. Tagastatavaid faili deskriptiive saab kasutada lugemis- ja kirjutamistoimingutes. Kui torusse midagi kirjutatakse, kasutatakse deskriptiivi, mis tagastatakse r1-s (st fildes[1]), seejĂ€rel puhverdatakse kuni 4096 baitide andmeid, pĂ€rast mida kirjutamise protsess peatatakse. Torust lugemise korral korjab deskriptiiv, mis tagastatakse r0-s (st fildes[0]), andmed.
Oletatakse, et pÀrast toru mÀÀratlemist edastavad kaks (vÔi rohkem) omavahel seotud protsessi (loob jÀrgmistel kutsungitel fork) edastavad andmeid torust, kasutades kutsungeid lugema ja kirjutama.
KĂ€sklus sisaldab sĂŒntaksit lineaarsete protsesside massiivi mÀÀratlemiseks, mis on ĂŒhendatud toru kaudu.
Lugemiskutsed tĂŒhjast torust (mis ei sisalda puhverdatud andmeid), millel on vaid ĂŒks ots (kĂ”ik kirjutavad faili deskriptiivid on suletud), tagastavad 'faili lĂ”pp'. Kirjutamiskutsed sarnases olukorras jĂ€etakse tĂ€helepanuta.
Varasem kuulub (juuni 1974), kuid see on peaaegu identne selle versiooniga, mis ilmus jÀrgmisel vÀljaandel. Lihtsalt lisati kommentaare, nii et viienda vÀljaande vÔib vahele jÀtta.
Kuues Unix'i vÀljaanne (1975)
Alustame Unix'i lÀhtekoodi lugemist (mai 1975). Suures osas tÀnu Lions leiab selle palju kergemini kui varasemate versioonide lÀhtekoodid:
Palju aastaid raamat Lions oli ainus Unix-i sĂŒdamiku dokument, mis oli saadaval vĂ€ljaspool Bell Labs'i seinu. Kuigi kuuenda vĂ€ljaande litsents lubas Ă”petajatel selle lĂ€htekoodi kasutada, siis seitsmenda vĂ€ljaande litsents vĂ€listas selle vĂ”imaluse, mistĂ”ttu raamatut levitati ebaseaduslike masinkopeeringutena.
TÀna saab osta raamatu reprint-versiooni, mille kaanel on kujutatud Ôpilasi koopiamasina juures. TÀnu Warren Tumi projekti TUHS, saate allalaadida . Tahan anda teile ettekujutuse, kui palju vaeva kulus faili loomisele:
Rohkem kui 15 aastat tagasi trĂŒkkisin ma ĂŒhe lĂ€htekoodi koopia, mis on esitatud Lions, sest mind ei rahuldanud minu koopia kvaliteet tundmatust arvust teistest koopiatest. TUHS-i ei olnud veel olemas ja mul ei olnud ligipÀÀsu vanadele allikatele. Kuid 1988. aastal leidsin ma vana lint 9 rajaga, millel oli reserveeritud varukoopia PDP11 arvutist. Oli keeruline aru saada, kas see töötab, kuid seal oli puutumatu puu /usr/src/, mille enamus faile olid markeeritud 1979. aastaga, mis nĂ€gi toona juba vĂ€lja nagu muistsed asjad. See oli seitsmes vĂ€ljaanne vĂ”i selle tĂŒvi PWB, nagu ma arvasin.
VĂ”tsin leiu aluseks ja redigeerisin lĂ€htekoodid kĂ€sitsi kuuenda vĂ€ljaande seisundisse. Osa koodist jĂ€i samaks, osa tuli veidi kohandada, muutes kaasaegse tokeni += vananenud =+. Midagi lihtsalt kustutasin, aga midagi pidin tĂ€ielikult ĂŒmber kirjutama, kuid mitte liiga palju.
Ja tÀna saame TUHS-is lugeda kuuenda vÀljaande lÀhtekoodi .
Muide, esmapilgul on C-koodi peamine omadus ajavahemikul Kernighani ja Ritchie ajal selle lĂŒhidus. Harva Ă”nnestub mul sisestada koodilĂ”ike ilma ulatusliku redigeerimiseta, et see sobiks mu veebisaidi suhteliselt kitsasse kuvamispiirkonda.
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
Puhversuurus pole muutunud alates neljandast vÀljaandest. Kuid siin nÀeme me ilma igasuguse avaliku dokumentatsioonita, et kunagi kasutasid konveierid faile varukohtadena!
LARG-failide osas vastavad need , mida kasutatakse "suure aadressimise" algoritmi jaoks töötlemiseks suuremate failisĂŒsteemide toetamiseks. Kuna Ken ĂŒtles, et parem on neid mitte kasutada, usun teda rÔÔmuga.
Siin on autentne sĂŒsteemikĂ”ne 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 siin toimub. Kuid koodist aru saada pole lihtne, osaliselt seetĂ”ttu, kuidas "" ja registrid R0 ja R1 edastavad sĂŒsteemikĂ”nede parameetreid ja tagastatavaid vÀÀrtusi.
Proovime kasutada diskil paigutamiseks , ning kasutada â kahte mĂ€lu paigutamiseks. Kui kĂ”ik lĂ€heb hĂ€sti, siis mÀÀrame lipud, et defineerida need failid kahe toru otstena, mĂ€rkime need samasse inode'i (mille viidete arv tĂ”useb 2) ja tĂ€histame inode'i kui muudetud ja kasutuses oleku. Pange tĂ€hele, et pöördumised vigaste teede (error paths) kaudu, et vĂ€hendada uue inode'i viidete arvu.
pipe() peab tagasi R0 ja R1 tagastama failide deskriptorite numbrid lugemiseks ja kirjutamiseks. falloc() tagastab viite failistruktuurile, kuid "tagastab" ka lÀbi u.u_ar0[R0] ja failide deskriptorit. See tÀhendab, et kood salvestab r failide deskriptor lugemiseks ja mÀÀrab kirjutamise deskriptorit otse u.u_ar0[R0] pÀrast teist kÔnet falloc().
Lipp FPIPE, mille me mÀÀrasime toru loomise ajal, juhib funktsiooni , mis kutsub esile konkreetsed sisendi-vÀljundi I/O alamprogrammid:
/*
* 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);
}
/* ⊠*/
}
SeejĂ€rel funktsioon readp() ja pipe.c loeb andmeid torust. Kuid on parem jĂ€lgida rakendust alates writep(). Kordan, kood on muutunud keerulisemaks argumentide edastamise lepingu eripĂ€rade tĂ”ttu, kuid mĂ”ned ĂŒksikasjad vĂ”ib vĂ€lja jĂ€tta.
writep(fp)
{
register *rp, *ip, c;
rp = fp;
ip = rp->f_inode;
c = u.u_count;
loop:
/* Kui kÔik on tehtud, tagastage. */
plock(ip);
if(c == 0) {
prele(ip);
u.u_count = 0;
return;
}
/*
* Kui torul pole aktiivseid lugemis- ja kirjutamispoole, tagastage viga ja signaalige samuti.
*/
if(ip->i_count < 2) {
prele(ip);
u.u_error = EPIPE;
psignal(u.u_procp, SIGPIPE);
return;
}
/*
* Kui toru on tÀis, oodake lugemisi, et rinnet vÀhendada
* ja see katkestada.
*/
if(ip->i_size1 == PIPSIZ) {
ip->i_mode =| IWRITE;
prele(ip);
sleep(ip+1, PPIPE);
goto loop;
}
/* Kirjutage, mida on vÔimalik, ja minge 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 soovime kirjutada baite u.u_count. Esiteks nÔuame indeksilisest deskriptorist blokeerimist (vt allpool plock/prele).
See you next time, evaluating the inode link counter. As long as both ends of the pipeline remain open, the counter should be equal to 2. We hold one link (from rp->f_inode), so if the counter is less than 2, it means that the reading process has closed its end of the pipeline. In other words, we are trying to write to a closed pipeline, which is an error. The error code EPIPE and the signal SIGPIPE appeared in the sixth edition of Unix.
But even if the pipeline is open, it can be filled. In this case, we release the lock and go to sleep, hoping that another process will read from the pipeline and free up enough space. Upon waking, we return to the start, lock again, and start a new write cycle.
If there is enough free space in the pipeline, we write data to it using . Parameeter i_size1 of the inode (when the pipeline is empty, it may be equal to 0) indicates the end of the data already contained within it. If there is enough space to write, we can fill the pipeline from i_size1 kuni PIPESIZ. Then we unlock and try to wake up any process waiting to read from the pipeline. We return to the start to see if we managed to write as many bytes as we needed. If not, we begin a new write cycle.
Usually the parameter i_mode of the inode is used to store permissions. r, w ja xBut in the case of pipelines, we signal waiting by some writing or reading process using the bits IREAD ja IWRITE respectively. The process sets a flag and calls sleep(), and it is expected that in the future some other process will call wakeup().
The real magic happens in sleep() ja wakeup(). They are implemented in , the source of the famous comment 'You are not expected to understand this.' Fortunately, we don't have to understand the code, just look at some comments:
/*
* 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) /* ⊠*/
The process that calls sleep() for a specific channel can later be awakened by another process that calls wakeup() for the same channel. writep() ja readp() coordinate their actions through such paired calls. Note that pipe.c always prioritizes PPIPE kÀskluse kutsel sleep(), so all sleep() can be interrupted by a signal.
Now we have everything we need to understand the function readp():
readp(fp)
int *fp;
{
register *rp, *ip;
rp = fp;
ip = rp->f_inode;
loop:
/* VĂ€ga ettevaatlik lukustamine. */
plock(ip);
/*
* Kui pea (lugemine) on jÔudnud
* saba (kirjutamise) juurde, siis lÀhtestame mÔlemad 0-le.
* /
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);
}
}
/*
* Kui ei ole aktiivseid loendajaid ja
* kirjutajat, siis tagastame ilma
* lugemist rahuldamata.
* /
prele(ip);
if(ip->i_count i_mode |= IREAD;
sleep(ip+2, PPIPE);
goto loop;
}
/* Loeme ja tagastame */
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);
}
VĂ”ib-olla on teil mugavam lugeda seda funktsiooni alt ĂŒles. PeatĂŒkki âloeme ja tagastameâ kasutatakse tavaliselt siis, kui torus on andmeid. Sellisel juhul teeme me lĂ€bi loeme nii palju andmeid, kui on saadaval alates praegusest f_offset lugemisse ja seejĂ€rel uuendame vastava nihke vÀÀrtust.
JĂ€rgmise lugemise korral on toru tĂŒhi, kui lugemissoov on saavutanud inode'i vÀÀrtuse. Me lĂ€htestame positsiooni 0-le ja ĂŒritame Ă€ratada igasuguseid protsesse, kes tahavad torusse kirjutada. Teame, et kui toru on tĂ€is, i_size1 asub see writep() ip+1 . Ja nĂŒĂŒd, kui toru on tĂŒhi, saame selle Ă€ratada, et see jĂ€tkaks kirjutamisringi.Kui lugemist ei ole, siis
vÔib seadistada lipu readp() ja magama jÀÀda IREAD ip+2 . Teame, et see Àrkab, kui torusse kirjutatakse andmeid.Kommentaarid seotud writep()readi() ja writei()
aitavad mĂ”ista, et selle asemel, et edastada parameetreid lĂ€bi â Mis puutub âettevaatlikkuâ lukustamist, siis), rĂŒhma (lukustame inode'i seni, kuni oleme lĂ”petanud töö vĂ”i saanud tulemuse (st kutsume
/*
* 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;
/* ⊠*/
wakeup readp() ja writep() plock() prele()). töötavad lihtsalt: teise kutsumisbogi komplektiga ja saame Àratada igasuguseid protsesse, kes vajavad lukustust, mille just vabastasime: Esialgu ei saanud ma aru, miks sleep ja prele() ei kutsu
/*
* 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);
}
}
prele(ip) readp() enne wakeup(ip+1) . Esimene, mida kutsutakse oma tsĂŒklis, onplock(ip) writep() , mis viib lukustamiseni, kui ei ole veel vabastanud oma lukku, seega peaks kood kuidagi Ă”igesti töötama. Kui vaadataei ole veel oma lukku eemaldanud, seega peab kood kuidagi Ă”igesti töötama. Kui vaadata readp() ei ole veel oma ploki eemaldanud, seega peaks kood kuidagi Ă”igesti töötama. Kui vaadata wakeup(), siis on selge, et see lihtsalt mĂ€rgib magava protsessi kui valmis tĂ€itmiseks, et tulevikus sched() tĂ”epoolest kĂ€ivitas selle. Nii et readp() tekitab wakeup(), vabastab lukustuse, mÀÀrab IREAD ja kutsub esile sleep(ip+2)â kĂ”ik see enne kui writep() taaskĂ€ivitab tsĂŒkli.
Sellega on kuue vÀljaande torude kirjeldus lÔpetatud. Lihtne kood, kaugeleulatuvad tagajÀrjed.
(jaanuar 1979) oli uus peamine vĂ€ljaanne (neli aastat hiljem), kus ilmus palju uusi rakendusi ja tuuma omadusi. Samuti toimusid seal mĂ€rkimisvÀÀrsed muudatused seoses tĂŒĂŒpide ĂŒmbersuunamise, union'ite ja strukturoitud tĂŒpiseeritud pointerite kasutamisega. Kuid praktiliselt ei ole muutunud. Saame selle vĂ€ljaande vahele jĂ€tta.
Xv6, lihtne Unix'i sarnane tuum
Xv6 tuuma loomisele asub Github'is .
, mida toetab mĂ€lu puhver, mitte inode kettal. Siin esitan ainult mÀÀratlust "struktuurist pipe" ja funktsiooni pipealloc() mÀÀrab oleku kogu ĂŒlejÀÀnud rakendusele, mis sisaldab funktsioone:
#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;
}
mÀÀrab oleku kogu ĂŒlejÀÀnud rakendusele, mis sisaldab funktsioone piperead() pipewrite(), pipeclose() ja . Konkreetsed sĂŒsteemi kutsesys_pipe on mĂ€his, mis on rakendatud failis sysfile.c Linux 0.01
Seda on vÔimalik leida Linux 0.01 lÀhtekood. See oleks hariv uurida torude rakendust selle
. Siin kasutatakse toru esitlemiseks inode'i, kuid ise toru on kirjutatud moodsas C-s. Kui olete kuue vÀljaande koodis lÀbi kÀinud, siis siin te ei oma raskusi. Nii nÀeb vÀlja funktsioon fs/pipe.cwrite_pipe() 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;
}
Isegi struktuuride mÀÀratlemata osas suudab inode'i viite loendureid kontrollida, kas kirjutamise operatsioon pĂ”hjustab SIGPIPE. Kui arvestada, et see funktsioon töötab byte-by-byte, saab seda hĂ”lpsasti vĂ”rrelda ĂŒlaltoodud ideedega. Isegi loogika sleep_on/wake_up ei tundu nii vÔÔrana.
Kaasaegsed Linuxi, FreeBSD, NetBSD, OpenBSD tuumad
Vaatasin kiiresti ĂŒle mĂ”ned kaasaegsed tuumad. Ăheski neist ei ole enam plaadi pĂ”hist rakendust (mis ei ĂŒllata). 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 erineda hakanud.
Et lugeda fs/pipe.c (Linuxis) vĂ”i sys/kern/sys_pipe.c ( *BSD ), on vajalik tĂ”eline pĂŒhendumine. TĂ€na on koodis olulised jĂ”udlus ja toetamine selliste funktsioonide nagu vektorite ja asĂŒnkroonsed sisend-vĂ€ljundoperatsioonid. Ja mĂ€lu jaotamise, lukustuste ja tuuma konfigureerimise ĂŒksikasjad - see kĂ”ik varieerub tugevalt. See ei ole see, mida ĂŒlikoolid vajavad operatsioonisĂŒsteemide sissejuhatava kursuse jaoks.
Igal juhul oli mulle huvitav kaevata ĂŒles mĂ”ned vanad mustrid (nĂ€iteks genereerimine SIGPIPE ja tagasivĂ”tmine EPIPE suletud toru kirjutamisel) kĂ”igis nendes, nii erinevates, kaasaegsetes tuumades. TĂ”enĂ€oliselt ei nĂ€e ma kunagi PDP-11 arvutit elus, aga koodist, mis kirjutati paar aastat enne minu sĂŒndi, on ikka veel palju Ă”ppida.
2011. aastal Divi Kapuri kirjutatud artikkel âon ĂŒlevaade sellest, kuidas (endiselt) torud Linuxis töötavad. Ja illustratsioon toru mudeli suhtlemisest, mille vĂ”imekus ĂŒletab ajutiste failide vĂ”imalusi; samuti nĂ€itab, kui kaugele torud on liikunud âvĂ€ga konservatiivsest lukustusestâ Unix'i kuuenda vĂ€ljaande tuumas.
Allikas: habr.com
