Šiame straipsnyje aprašomas konvejerių diegimas Unix branduolyje. Buvau šiek tiek nusivylęs, kad neseniai pasirodė straipsnis "
apie ką mes kalbame?
Vamzdynai yra „turbūt svarbiausias Unix išradimas“ – esminė Unix filosofijos mažų programų sudarymo ypatybė ir pažįstamas komandų eilutės šūkis:
$ echo hello | wc -c
6
Ši funkcija priklauso nuo branduolio pateikto sistemos iškvietimo pipe
, kuris aprašytas dokumentacijos puslapiuose
Vamzdynai suteikia vienpusį kanalą tarpprocesiniam ryšiui. Dujotiekis turi įvestį (rašymo pabaigą) ir išvestį (skaitymo pabaigą). Duomenys, įrašyti į dujotiekio įvestį, gali būti nuskaitomi išvestyje.
Dujotiekis sukuriamas skambinant
pipe(2)
, kuris grąžina du failo deskriptorius: vienas nurodo konvejerio įvestį, antrasis – išvestį.
Aukščiau pateiktos komandos sekimo išvestis rodo dujotiekio sukūrimą ir duomenų srautą per jį iš vieno proceso į kitą:
$ 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
Tėvų procesas skambina pipe()
norėdami gauti pridedamų failų aprašus. Vienas antrinis procesas rašo į vieną deskriptorių, o kitas procesas nuskaito tuos pačius duomenis iš kito deskriptoriaus. Apvalkalas „pervardija“ 2 ir 3 deskriptorius su dup4, kad atitiktų stdin ir stdout.
Be konvejerių, apvalkalas turėtų įrašyti vieno proceso išvestį į failą ir perkelti jį į kitą procesą, kad galėtų nuskaityti duomenis iš failo. Dėl to eikvotume daugiau išteklių ir vietos diske. Tačiau vamzdynai yra naudingi ne tik norint išvengti laikinųjų failų:
Jei procesas bando nuskaityti iš tuščio dujotiekio, tada
read(2)
bus užblokuotas, kol bus gauti duomenys. Jei procesas bando rašyti į visą konvejerį, tadawrite(2)
blokuos, kol iš konvejerio bus nuskaityta pakankamai duomenų, kad būtų baigtas rašymas.
Kaip ir POSIX reikalavimas, tai yra svarbi savybė: rašymas į dujotiekį iki PIPE_BUF
baitai (mažiausiai 512) turi būti atominiai, kad procesai galėtų bendrauti tarpusavyje per konvejerį taip, kaip įprasti failai (kurie nesuteikia tokių garantijų).
Naudodamas įprastą failą, procesas gali įrašyti į jį visą savo išvestį ir perduoti kitam procesui. Arba procesai gali veikti kietuoju lygiagrečiu režimu, naudodami išorinį signalizacijos mechanizmą (pvz., semaforą), kad informuotų vienas kitą apie rašymo ar skaitymo užbaigimą. Konvejeriai gelbsti mus nuo viso šito vargo.
Ko mes ieškome?
Paaiškinsiu ant pirštų, kad būtų lengviau įsivaizduoti, kaip gali veikti konvejeris. Turėsite priskirti buferį ir tam tikrą būseną atmintyje. Norėdami pridėti ir pašalinti duomenis iš buferio, jums reikės funkcijų. Jums reikės tam tikros funkcijos, kad galėtumėte iškviesti funkcijas skaitymo ir rašymo failų deskriptoriuose operacijose. Ir spynos reikalingos aukščiau aprašytam ypatingam elgesiui įgyvendinti.
Dabar esame pasirengę ištirti branduolio šaltinio kodą ryškioje lempos šviesoje, kad patvirtintume arba paneigtume mūsų neaiškų mąstymo modelį. Tačiau visada būkite pasirengę netikėtumams.
Kur mes ieškome?
Nežinau, kur slypi mano garsiosios knygos kopija.
Klaidžioti po TUHS archyvus prilygsta muziejaus lankymui. Galime pažvelgti į mūsų bendrą istoriją ir aš gerbiu ilgametes pastangas po truputį atkurti visą šią medžiagą iš senų kasečių ir spaudinių. Ir aš puikiai žinau tuos fragmentus, kurių vis dar trūksta.
Patenkinę savo smalsumą apie senąją vamzdynų istoriją, palyginimui galime pažvelgti į šiuolaikines šerdis.
Beje, pipe
lentelėje yra sistemos skambučio numeris 42 sysent[]
. Sutapimas?
Tradiciniai Unix branduoliai (1970–1974)
Neradau jokių pėdsakų pipe(2)
nei viduje
TUHS teigia, kad
Trečiasis Unix leidimas buvo paskutinė versija su branduoliu, parašyta asamblėjaus programa, bet taip pat pirmoji versija su konvejeriais. 1973 m. buvo vykdomas trečiojo leidimo tobulinimo darbas, branduolys buvo perrašytas C kalba ir taip gimė ketvirtasis Unix leidimas.
Vienas skaitytojas rado nuskaitytą dokumentą, kuriame Dougas McIlroy'us pasiūlė idėją „sujungti programas kaip sodo žarną“.
Briano Kernighano knygoje
Kai pasirodė Unix, mano aistra korutinėms privertė mane paprašyti OS autoriaus Keno Thompsono leisti duomenims, įrašytiems į kokį nors procesą, patekti ne tik į įrenginį, bet ir į išėjimą į kitą procesą. Kenas nusprendė, kad tai įmanoma. Tačiau, kaip minimalistas, jis norėjo, kad kiekviena sistemos ypatybė atliktų svarbų vaidmenį. Ar tiesioginis rašymas tarp procesų tikrai yra didelis pranašumas prieš rašymą į tarpinį failą? Ir tik kai pateikiau konkretų pasiūlymą skambiu pavadinimu „vamzdynas“ ir procesų sąveikos sintaksės aprašymu, Kenas galiausiai sušuko: „Padarysiu!“.
Ir padarė. Vieną lemtingą vakarą Kenas pakeitė branduolį ir apvalkalą, sutvarkė keletą standartinių programų, kad standartizuotų, kaip jos priima įvestį (kuri gali būti iš konvejerio), ir pakeitė failų pavadinimus. Kitą dieną vamzdynai buvo labai plačiai naudojami. Savaitės pabaigoje sekretorės jais siųsdavo dokumentus iš teksto rengyklės į spausdintuvą. Kiek vėliau Kenas pakeitė pradinę API ir sintaksę, skirtą vamzdynų naudojimui apvynioti švaresnėmis nuostatomis, kurios buvo naudojamos nuo tada.
Deja, trečiojo leidimo Unix branduolio šaltinio kodas buvo prarastas. Ir nors mes turime branduolio šaltinio kodą, parašytą C
Turime dokumentacijos tekstą pipe(2)
iš abiejų leidimų, todėl galite pradėti ieškodami dokumentacijos pipe(2)
yra parašytas asamblieryje ir pateikia tik vieną failo deskriptorių, bet jau suteikia numatytą pagrindinę funkciją:
Sisteminis skambutis vamzdis sukuria įvesties/išvesties mechanizmą, vadinamą dujotiekiu. Grąžintas failo aprašas gali būti naudojamas skaitymo ir rašymo operacijoms. Kai kas nors įrašoma į konvejerį, jis saugo iki 504 baitų duomenų, o po to rašymo procesas sustabdomas. Skaitant iš konvejerio, paimami buferiniai duomenys.
Kitais metais branduolys buvo perrašytas C ir pipe(fildes)
"
Sisteminis skambutis vamzdis sukuria įvesties/išvesties mechanizmą, vadinamą dujotiekiu. Grąžinti failų aprašai gali būti naudojami skaitymo ir rašymo operacijose. Kai kas nors įrašoma į konvejerį, naudojamas r1 grąžintas deskriptorius (resp. fildes[1]), buferizuojamas iki 4096 baitų duomenų, po kurio rašymo procesas sustabdomas. Skaitant iš konvejerio, į r0 grąžintas deskriptorius (resp. fildes[0]) paima duomenis.
Daroma prielaida, kad apibrėžus konvejerį, du (ar daugiau) sąveikaujantys procesai (sukurti vėlesniais iškvietimais šakutė) perduos duomenis iš dujotiekio naudodamas skambučius skaityti и rašyti.
Apvalkalas turi sintaksę, skirtą apibrėžti tiesinį procesų, sujungtų per dujotiekį, masyvą.
Kvietimai nuskaityti iš tuščio konvejerio (be buferinių duomenų), turinčio tik vieną galą (visi rašymo failo deskriptoriai uždaryti), grąžina "failo pabaigą". Rašymo skambučiai panašioje situacijoje ignoruojami.
Anksčiausiai
Šeštasis Unix leidimas (1975 m.)
Pradedama skaityti Unix šaltinio kodą
Daug metų knyga Liūtai buvo vienintelis dokumentas apie Unix branduolį, prieinamas už Bell Labs ribų. Nors šeštojo leidimo licencija leido mokytojams naudoti šaltinio kodą, septintoji licencija atmetė tokią galimybę, todėl knyga buvo platinama nelegaliais spausdinimo mašinėle kopijomis.
Šiandien galite nusipirkti pakartotinį knygos egzempliorių, kurio viršelyje pavaizduoti studentai prie kopijavimo aparato. Ir dėka Warren Toomey (kuris pradėjo TUHS projektą), galite atsisiųsti
Daugiau nei prieš 15 metų įvedžiau pateikto šaltinio kodo kopiją Liūtaines man nepatiko mano kopijos kokybė iš nežinomo skaičiaus kitų kopijų. TUHS dar nebuvo, ir aš neturėjau prieigos prie senųjų šaltinių. Bet 1988 metais radau seną juostą su 9 takeliais, kuri turėjo atsarginę kopiją iš PDP11 kompiuterio. Sunku buvo suprasti, ar tai veikė, bet buvo nepažeistas /usr/src/ medis, kuriame dauguma failų buvo pažymėti 1979 m., kuris net tada atrodė senovinis. Pagalvojau, kad tai buvo septintasis leidimas arba PWB darinys.
Paėmiau radinį kaip pagrindą ir rankiniu būdu redagavau šaltinius iki šeštojo leidimo būsenos. Dalis kodo liko toks pat, dalį teko šiek tiek redaguoti, pakeičiant šiuolaikinį žetoną += į pasenusį =+. Kažkas buvo tiesiog ištrintas ir kažkas turėjo būti visiškai perrašytas, bet ne per daug.
Ir šiandien TUHS internete galime perskaityti šeštojo leidimo šaltinio kodą
Beje, iš pirmo žvilgsnio pagrindinė C kodo savybė prieš Kernighan ir Ritchie laikotarpį yra jo trumpumas. Nedažnai galiu įterpti kodo fragmentus be didelio redagavimo, kad mano svetainėje tilptų gana siaura rodymo sritis.
Anksti
/*
* 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
Buferio dydis nepasikeitė nuo ketvirtojo leidimo. Bet čia matome, be jokių viešų dokumentų, kad vamzdynai kadaise naudojo failus kaip atsarginę saugyklą!
Kalbant apie LARG failus, jie atitinka
Čia yra tikrasis sistemos skambutis 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;
}
Komentaras aiškiai apibūdina, kas čia vyksta. Tačiau kodą suprasti nėra taip lengva, iš dalies dėl to, kaip "R0
и R1
perduodami sistemos iškvietimo parametrai ir grąžinimo reikšmės.
Pabandykime su
pipe()
terminas R0
и R1
grąžinti failo deskriptorių numerius skaitymui ir rašymui. falloc()
grąžina žymeklį į failo struktūrą, bet taip pat „grąžina“ per u.u_ar0[R0]
ir failo aprašą. Tai yra, kodas yra saugomas r
failo deskriptorius skaitymui ir priskiria deskriptorių rašymui tiesiai iš u.u_ar0[R0]
po antro skambučio falloc()
.
Vėliava FPIPE
, kurį nustatome kurdami konvejerį, valdo funkcijos veikimą
/*
* 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);
}
/* … */
}
Tada funkcija readp()
в pipe.c
nuskaito duomenis iš dujotiekio. Tačiau geriau sekti įgyvendinimą nuo writep()
. Vėlgi, kodas tapo sudėtingesnis dėl argumentų perdavimo konvencijos pobūdžio, tačiau kai kurių detalių galima praleisti.
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;
}
Norime įrašyti baitus į konvejerio įvestį u.u_count
. Pirmiausia turime užrakinti inodą (žr. toliau plock
/prele
).
Tada patikriname inodų nuorodų skaičių. Kol abu dujotiekio galai lieka atviri, skaitiklis turi būti 2. Mes laikomės už vienos jungties (nuo rp->f_inode
), taigi, jei skaitiklis yra mažesnis nei 2, tai turėtų reikšti, kad skaitymo procesas uždarė dujotiekio galą. Kitaip tariant, mes bandome rašyti į uždarą dujotiekį, o tai yra klaida. Pirmas klaidos kodas EPIPE
ir signalas SIGPIPE
pasirodė šeštajame Unix leidime.
Bet net jei konvejeris atidarytas, jis gali būti pilnas. Tokiu atveju atleidžiame užraktą ir einame miegoti, tikėdamiesi, kad kitas procesas nuskaitys iš dujotiekio ir atlaisvins jame pakankamai vietos. Pabudę grįžtame į pradžią, vėl pakabiname užraktą ir pradedame naują rašymo ciklą.
Jei dujotiekyje yra pakankamai laisvos vietos, duomenis į jį įrašome naudodami i_size1
inode'a (su tuščiu konvejeriu gali būti lygus 0) nurodo jame jau esančių duomenų pabaigą. Jei yra pakankamai vietos rašyti, galime užpildyti dujotiekį iš i_size1
į PIPESIZ
. Tada atleidžiame užraktą ir bandome pažadinti bet kokį procesą, kuris laukia skaitymo iš konvejerio. Grįžtame į pradžią, kad pamatytume, ar pavyko įrašyti tiek baitų, kiek reikia. Jei ne, pradedame naują įrašymo ciklą.
Paprastai parametras i_mode
inode naudojamas leidimams saugoti r
, w
и x
. Tačiau konvejerių atveju mes signalizuojame, kad koks nors procesas laukia įrašymo arba skaitymo naudojant bitus IREAD
и IWRITE
atitinkamai. Procesas nustato vėliavėlę ir skambučius sleep()
, ir tikimasi, kad ateityje vyks koks nors kitas procesas wakeup()
.
Tikroji magija įvyksta sleep()
и wakeup()
. Jie įgyvendinami
/*
* 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) /* … */
Procesas, kuris skambina sleep()
tam tikram kanalui, vėliau gali būti pažadintas kito proceso, kuris iškvies wakeup()
tam pačiam kanalui. writep()
и readp()
koordinuoti savo veiksmus per tokius suporuotus skambučius. Prisimink tai pipe.c
visada teikti pirmenybę PPIPE
kai skambina sleep()
, taigi viskas sleep()
gali būti nutrauktas signalu.
Dabar turime viską, kad suprastume funkciją 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);
}
Jums gali būti lengviau skaityti šią funkciją iš apačios į viršų. „Skaityti ir grąžinti“ šaka paprastai naudojama, kai yra tam tikrų duomenų. Šiuo atveju mes naudojame f_offset
perskaityti ir atnaujinti atitinkamo poslinkio reikšmę.
Vėlesniuose nuskaitymuose dujotiekis bus tuščias, jei bus pasiektas skaitymo poslinkis i_size1
ties inode. Iš naujo nustatome padėtį į 0 ir bandome pažadinti bet kokį procesą, kuris nori rašyti į dujotiekį. Mes žinome, kad kai konvejeris yra pilnas, writep()
užmigti toliau ip+1
. Ir dabar, kai dujotiekis tuščias, galime jį pažadinti, kad atnaujintume rašymo ciklą.
Jei nėra ką skaityti, tada readp()
gali nustatyti vėliavą IREAD
ir užmigti toliau ip+2
. Mes žinome, kas jį pažadins writep()
kai jis įrašo kai kuriuos duomenis į dujotiekį.
Komentarai apie u
» galime jas traktuoti kaip įprastas įvesties/išvesties funkcijas, kurios užima failą, vietą, buferį atmintyje ir skaičiuoja baitų skaičių, kurį reikia skaityti ar rašyti.
/*
* 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;
/* … */
Kalbant apie „konservatyvų“ blokavimą readp()
и writep()
užrakinti inodus, kol jie baigsis arba gaus rezultatą (t. y. skambinkite wakeup
). plock()
и prele()
dirbti paprastai: naudojant kitą skambučių rinkinį sleep
и wakeup
Leiskite mums pažadinti bet kokį procesą, kuriam reikalingas užraktas, kurį ką tik atleidome:
/*
* 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);
}
}
Iš pradžių negalėjau suprasti, kodėl readp()
nesukelia prele(ip)
prieš skambutį wakeup(ip+1)
. Pirmas dalykas writep()
skambučiai savo cikle, tai plock(ip)
, dėl ko atsiduria aklavietė, jei readp()
dar nepašalino savo bloko, todėl kodas turi kažkaip tinkamai veikti. Jei pažiūrėsi wakeup()
, tampa aišku, kad tai tik pažymi miego procesą kaip paruoštą vykdyti, kad ateityje sched()
tikrai paleido. Taigi readp()
priežastys wakeup()
, atrakina, komplektuoja IREAD
ir skambina sleep(ip+2)
- visa tai anksčiau writep()
paleidžia ciklą iš naujo.
Tai užbaigia vamzdynų aprašymą šeštajame leidime. Paprastas kodas, toli siekiančios pasekmės.
Xv6, paprastas Unix tipo branduolys
Norėdami sukurti branduolį
Kode yra aiškus ir apgalvotas įgyvendinimas 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()
nustato viso likusio įgyvendinimo, kuris apima funkcijas, būseną piperead()
, pipewrite()
и pipeclose()
. Tikrasis sistemos skambutis sys_pipe
yra įvyniojimas, įdiegtas
Linux 0.01
Galite rasti Linux 0.01 šaltinio kodą. Tai bus pamokoma ištirti vamzdynų diegimą jo fs
/pipe.c
. Čia konvejeriui atvaizduoti naudojamas inode, bet pats konvejeris parašytas šiuolaikine C. Jei įsilaužėte į šeštojo leidimo kodą, čia neturėsite jokių problemų. Taip atrodo funkcija 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;
}
Net nežiūrėdami į struktūrų apibrėžimus, galite išsiaiškinti, kaip inode nuorodų skaičius naudojamas norint patikrinti, ar rašymo operacija sukelia SIGPIPE
. Be darbo po baito, šią funkciją lengva palyginti su aukščiau pateiktomis idėjomis. Net logika sleep_on
/wake_up
neatrodo toks svetimas.
Šiuolaikiniai Linux branduoliai, FreeBSD, NetBSD, OpenBSD
Greitai peržvelgiau kai kuriuos šiuolaikinius branduolius. Nė vienas iš jų dar neturi disko pagrindu sukurto diegimo (nenuostabu). Linux turi savo įgyvendinimą. Ir nors trijuose šiuolaikiniuose BSD branduoliuose yra įdiegimų, pagrįstų Johno Dysono parašytu kodu, bėgant metams jie per daug skiriasi vienas nuo kito.
Skaityti fs
/pipe.c
(„Linux“ sistemoje) arba sys
/kern
/sys_pipe.c
(*BSD), tam reikia tikro atsidavimo. Šių dienų kode svarbus našumas ir funkcijų, tokių kaip vektorinis ir asinchroninis įvestis / išvestis, palaikymas. Išsami informacija apie atminties paskirstymą, užraktus ir branduolio konfigūraciją labai skiriasi. Tai nėra tai, ko universitetams reikia įvadiniam operacinių sistemų kursui.
Bet kokiu atveju man buvo įdomu atkasti keletą senų raštų (pavyzdžiui, generuoti SIGPIPE
ir grįžti EPIPE
rašant į uždarą dujotiekį) visuose šiuose tokiuose skirtinguose šiuolaikiniuose branduoliuose. Tikriausiai niekada nepamatysiu kompiuterio PDP-11 gyvai, bet iš kodo, kuris buvo parašytas likus keleriems metams iki mano gimimo, dar galima daug ko pasimokyti.
2011 m. parašytas Divi Kapoor straipsnis "
Šaltinis: www.habr.com