Selles artiklis kirjeldatakse torujuhtmete rakendamist Unixi tuumas. Olin mõnevõrra pettunud, et hiljuti ilmus artikkel pealkirjaga "
Millest me räägime?
Torujuhtmed on "tõenäoliselt Unixi kõige olulisem leiutis" - Unixi aluseks oleva väikeste programmide kokkupanemise filosoofia ja tuttava käsurea loosungi määrav tunnus:
$ echo hello | wc -c
6
See funktsioon sõltub kerneli pakutavast süsteemikutsest pipe
, mida on kirjeldatud dokumentatsiooni lehtedel
Torujuhtmed pakuvad protsessidevaheliseks suhtluseks ühesuunalist kanalit. Konveieril on sisend (kirjutusots) ja väljund (lugemisots). Konveieri sisendisse kirjutatud andmeid saab lugeda väljundis.
Torujuhe luuakse helistades
pipe(2)
, mis tagastab kaks failideskriptorit: üks viitab konveieri sisendile, teine väljundile.
Ülaltoodud käsu jälgimise väljund demonstreerib konveieri loomist ja selle kaudu toimuvat andmevoogu ühest protsessist 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
Vanemprotsess kutsub pipe()
manustatud failikirjelduste hankimiseks. Üks alamprotsess kirjutab ühele deskriptorile ja teine protsess loeb samu andmeid teisest deskriptorist. Kest "nimetab ümber" deskriptorid 2 ja 3 koos dup4-ga, et need sobiksid stdin ja stdout.
Ilma torujuhtmeteta peaks kest failist andmete lugemiseks ühe protsessi väljundi faili kirjutama ja teise protsessi juhtima. Selle tulemusena raiskaksime rohkem ressursse ja kettaruumi. Kuid torujuhtmed on kasulikud enamaks kui lihtsalt ajutiste failide vältimiseks:
Kui protsess proovib lugeda tühjast konveierist, siis
read(2)
blokeeritakse, kuni andmed on saadaval. Kui protsess üritab kirjutada täiskonveierile, siiswrite(2)
blokeerib seni, kuni konveierist on kirjutamise lõpuleviimiseks loetud piisavalt andmeid.
Nagu POSIX-i nõue, on ka see oluline omadus: konveierisse kirjutamine kuni PIPE_BUF
baiti (vähemalt 512) peavad olema atomaarsed, et protsessid saaksid konveieri kaudu omavahel suhelda viisil, mida tavalised failid (mis selliseid garantiisid ei anna) ei suuda.
Tavafaili puhul saab protsess kogu oma väljundi sinna kirjutada ja teisele protsessile edasi anda. Või võivad protsessid töötada kõvas paralleelrežiimis, kasutades välist signaalimismehhanismi (nagu semafor), et teavitada üksteist kirjutamise või lugemise lõpetamisest. Konveierid päästavad meid kogu sellest sekeldusest.
Mida me otsime?
Selgitan sõrmedel, et teil oleks lihtsam ette kujutada, kuidas konveier töötab. Peate eraldama mällu puhvri ja teatud oleku. Andmete puhvrist lisamiseks ja eemaldamiseks vajate funktsioone. Funktsioonide väljakutsumiseks failideskriptoritel lugemis- ja kirjutamistoimingute ajal on teil vaja mõnda võimalust. Ja lukke on vaja ülalkirjeldatud erikäitumise rakendamiseks.
Oleme nüüd valmis uurima kerneli lähtekoodi ereda lambivalguse all, et kinnitada või ümber lükata meie ebamäärast vaimset mudelit. Kuid olge alati valmis ootamatusteks.
Kuhu me vaatame?
Ma ei tea, kus asub minu kuulsa raamatu koopia.
TUHSi arhiivis ekslemine on nagu muuseumikülastus. Võime vaadata oma ühist ajalugu ja ma austan aastatepikkust pingutust kogu selle materjali osade kaupa vanadelt kassettidelt ja väljatrükkidelt taastada. Ja ma olen väga teadlik nendest fragmentidest, mis siiani puuduvad.
Olles rahuldanud oma uudishimu torujuhtmete iidse ajaloo vastu, saame võrdluseks vaadata tänapäevaseid südamikke.
Muide, pipe
on tabelis süsteemikõne number 42 sysent[]
. Kokkusattumus?
Traditsioonilised Unixi tuumad (1970–1974)
Ma ei leidnud jälgegi pipe(2)
ega ka sisse
TUHS väidab seda
Unixi kolmas väljaanne oli viimane versioon koos assembleris kirjutatud kerneliga, aga ka esimene torujuhtmetega versioon. 1973. aastal tehti tööd kolmanda väljaande täiustamiseks, kernel kirjutati ümber C-keeles ja nii sündis Unixi neljas väljaanne.
Üks lugeja leidis skannitud dokumendi, milles Doug McIlroy pakkus välja idee "ühendada programme nagu aiavoolik".
Brian Kernighani raamatus
Kui Unix ilmus, pani mu kirg korutiinide vastu mind paluma OS-i autoril Ken Thompsonil, et ta lubaks mõnesse protsessi kirjutatud andmetel minna mitte ainult seadmesse, vaid ka teise protsessi väljumisse. Ken pidas seda võimalikuks. Minimalistina soovis ta aga, et iga süsteemi funktsioon mängiks olulist rolli. Kas otse kirjutamine protsesside vahel on tõesti suur eelis vahefaili kirjutamise ees? Ja alles siis, kui tegin konkreetse ettepaneku tabava nimetusega "torujuhe" ja protsesside koosmõju süntaksi kirjeldusega, hüüdis Ken lõpuks: "Ma teen ära!".
Ja tegigi. Ühel saatuslikul õhtul muutis Ken tuuma ja kesta, parandas mitu standardprogrammi, et standardida sisendi vastuvõtmist (mis võib tulla torujuhtmest), ja muutis failinimesid. Järgmisel päeval kasutati torujuhtmeid rakendustes väga laialdaselt. Nädala lõpuks saatsid sekretärid nende abil tekstitöötlusprogrammidest dokumendid printerisse. Veidi hiljem asendas Ken algse API ja torujuhtmete kasutamise süntaksi puhtamate tavadega, mida on sellest ajast peale kasutatud.
Kahjuks on Unixi tuuma kolmanda väljaande lähtekood kadunud. Ja kuigi meil on tuuma lähtekood kirjutatud C-vormingus
Meil on dokumentatsiooni tekst pipe(2)
mõlemast versioonist, nii et võite alustada dokumentatsioonist otsimisega pipe(2)
on kirjutatud assembleris ja tagastab ainult ühe failideskriptori, kuid pakub juba oodatud põhifunktsioone:
Süsteemikõne toru loob I/O mehhanismi, mida nimetatakse torujuhtmeks. Tagastatud failideskriptorit saab kasutada lugemis- ja kirjutamistoiminguteks. Kui konveierisse midagi kirjutatakse, puhverdab see kuni 504 baiti andmeid, misjärel kirjutamisprotsess peatatakse. Konveierilt lugemisel võetakse puhverdatud andmed.
Järgmiseks aastaks oli kernel C-s ümber kirjutatud ja pipe(fildes)
"
Süsteemikõne toru loob I/O mehhanismi, mida nimetatakse torujuhtmeks. Tagastatud failideskriptoreid saab kasutada lugemis- ja kirjutamistoimingutes. Kui konveierisse midagi kirjutatakse, kasutatakse r1-s tagastatud deskriptorit (resp. fildes[1]), mis puhverdatakse kuni 4096 baiti andmeid, misjärel kirjutamisprotsess peatatakse. Konveierist lugemisel võtab andmed r0-le tagastatud deskriptor (resp. fildes[0]).
Eeldatakse, et kui konveier on määratletud, toimub kaks (või enam) vastastikku toimivat protsessi (mis on loodud järgnevate kutsumiste abil kahvel) edastab andmed torustikust kõnede abil lugenud и kirjutama.
Shellil on süntaks torujuhtme kaudu ühendatud protsesside lineaarse massiivi määratlemiseks.
Lugemiskutsed tühjast torujuhtmest (mis ei sisalda puhverdatud andmeid), millel on ainult üks ots (kõik kirjutamisfailide kirjeldused on suletud), tagastavad "faili lõpu". Sarnases olukorras tehtud kõnesid ignoreeritakse.
Kõige varem
Unixi kuues väljaanne (1975)
Unixi lähtekoodi lugemise alustamine
Palju aastaid raamat Lions oli ainus Unixi kerneli dokument, mis oli saadaval väljaspool Bell Labsi. Kuigi kuuenda väljaande litsents lubas õpetajatel kasutada selle lähtekoodi, välistas seitsmenda väljaande litsents selle võimaluse, mistõttu raamatut levitati ebaseaduslikes masinakirjas koopiates.
Täna saab osta raamatu kordustrükki, mille kaanel on kujutatud õpilasi koopiamasina juures. Ja tänu Warren Toomeyle (kes alustas TUHS-i projekti) saate alla laadida
Rohkem kui 15 aastat tagasi sisestasin ma sisestatud lähtekoodi koopia Lionssest mulle ei meeldinud minu koopia kvaliteet teadmata hulga teiste koopiate hulgast. TUHS-i veel ei eksisteerinud ja mul polnud juurdepääsu vanadele allikatele. Kuid 1988. aastal leidsin vana 9 rajaga lindi, millel oli varukoopia PDP11 arvutist. Raske oli teada, kas see töötas, kuid seal oli terve /usr/src/ puu, milles enamikele failidele oli märgitud 1979, mis isegi siis nägi välja vana. See oli seitsmes väljaanne või PWB tuletis, mõtlesin ma.
Võtsin leiu aluseks ja toimetasin allikad käsitsi kuuenda väljaande seisu. Osa koodist jäi samaks, osa tuli veidi redigeerida, muutes tänapäevase märgi += vananenud =+ vastu. Midagi lihtsalt kustutati ja midagi tuli täielikult ümber kirjutada, kuid mitte liiga palju.
Ja täna saame Internetis TUHS-is lugeda kuuenda väljaande lähtekoodi
Muide, esmapilgul on C-koodi peamine omadus enne Kernighani ja Ritchie perioodi selle lühidus. See ei juhtu sageli, kui mul õnnestub ilma põhjaliku redigeerimiseta sisestada koodijuppe, et need mahuksid oma saidi suhteliselt kitsale kuvaalale.
Varakult
/*
* 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 pärast neljandat väljaannet muutunud. Kuid siin näeme ilma avaliku dokumentatsioonita, et torujuhtmed kasutasid kunagi faile varumäluna!
Mis puutub LARG-failidesse, siis need vastavad
Siin on tegelik 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;
}
Kommentaar kirjeldab selgelt, mis siin toimub. Kuid koodist pole nii lihtne aru saada, osaliselt seetõttu, kuidas "R0
и R1
edastatakse süsteemikõne parameetrid ja tagastusväärtused.
Proovime koos
pipe()
läbi R0
и R1
tagastab lugemiseks ja kirjutamiseks failideskriptori numbrid. falloc()
tagastab kursori failistruktuurile, aga ka "tagastab" kaudu u.u_ar0[R0]
ja faili deskriptor. See tähendab, et kood on salvestatud r
failideskriptor lugemiseks ja määrab deskriptori otse kirjutamiseks u.u_ar0[R0]
peale teist kõnet falloc()
.
Lipp FPIPE
, mille seadsime konveieri loomisel, juhib funktsiooni käitumist
/*
* 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()
в pipe.c
loeb andmeid torujuhtmest. Kuid parem on jälgida rakendamist alates writep()
. Jällegi on kood muutunud keerulisemaks argumendi läbimise konventsiooni olemuse 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:
/* 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;
}
Tahame konveieri sisendisse kirjutada baite u.u_count
. Kõigepealt peame inode lukustama (vt allpool plock
/prele
).
Seejärel kontrollime inoodi viidete arvu. Kuni torujuhtme mõlemad otsad jäävad avatuks, peaks loendur olema 2. Hoiame kinni ühest lülist (alates rp->f_inode
), nii et kui loendur on väiksem kui 2, siis peaks see tähendama, et lugemisprotsess on torujuhtme otsa sulgenud. Teisisõnu, me üritame kirjutada suletud torujuhtmele, mis on viga. Esimene veakood EPIPE
ja signaali SIGPIPE
ilmus Unixi kuuendas väljaandes.
Kuid isegi kui konveier on avatud, võib see olla täis. Sel juhul vabastame luku ja läheme magama lootuses, et mõni teine protsess loeb torustikust ja vabastab selles piisavalt ruumi. Ärgates pöördume tagasi algusesse, riputame luku uuesti üles ja alustame uut kirjutamistsüklit.
Kui torustikus on piisavalt vaba ruumi, siis kirjutame sinna andmed kasutades i_size1
inode'a (tühja konveieriga võib olla 0-ga) osutab selles juba sisalduvate andmete lõppu. Kui kirjutamiseks on piisavalt ruumi, saame torujuhtme täita i_size1
kuni PIPESIZ
. Seejärel vabastame luku ja proovime äratada kõik protsessid, mis ootavad torujuhtmest lugemist. Läheme tagasi algusesse, et näha, kas õnnestus kirjutada nii palju baite, kui vaja. Kui ei, siis alustame uut salvestustsüklit.
Tavaliselt parameeter i_mode
inode kasutatakse õiguste salvestamiseks r
, w
и x
. Kuid torujuhtmete puhul anname signaali, et mõni protsess ootab bittide abil kirjutamist või lugemist IREAD
и IWRITE
vastavalt. Protsess seab lipu ja kutsub sleep()
, ja eeldatakse, et tulevikus helistab mõni muu protsess wakeup()
.
Tõeline maagia toimub sees sleep()
и wakeup()
. Neid rakendatakse aastal
/*
* 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()
konkreetse kanali puhul võib selle hiljem äratada mõni muu protsess, mis helistab wakeup()
sama kanali jaoks. writep()
и readp()
koordineerida oma tegevust selliste seotud kõnede kaudu. pane tähele seda pipe.c
alati prioriteediks PPIPE
kui kutsutakse sleep()
, nii et kõik sleep()
võib signaal katkestada.
Nüüd on meil kõik funktsiooni mõistmiseks 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);
}
Teil võib olla lihtsam lugeda seda funktsiooni alt üles. "Loe ja tagasta" haru kasutatakse tavaliselt siis, kui konveieril on andmeid. Sel juhul kasutame f_offset
lugeda ja seejärel värskendada vastava nihke väärtust.
Järgmistel lugemistel on konveier tühi, kui lugemisnihe on saavutatud i_size1
inoodi juures. Lähtestame positsiooni 0-le ja proovime äratada protsessi, mis soovib konveierile kirjutada. Teame, et kui konveier on täis, writep()
magama jääma ip+1
. Ja nüüd, kui torujuhe on tühi, saame selle üles äratada, et jätkata selle kirjutamistsüklit.
Kui midagi lugeda pole, siis readp()
oskab lipu panna IREAD
ja magama edasi ip+2
. Me teame, mis ta äratab writep()
kui ta kirjutab konveierile mingeid andmeid.
Kommentaarid u
» saame neid käsitleda nagu tavalisi I/O-funktsioone, mis võtavad faili, positsiooni, mällu puhvri ja loevad lugemiseks või kirjutamiseks vajalike 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 puutub siis "konservatiivsesse" blokeerimisse readp()
и writep()
lukustage inoodid, kuni need on lõpetatud või tulemuse saavutanud (st helistage wakeup
). plock()
и prele()
toimige lihtsalt: kasutage teistsugust kõnede komplekti sleep
и wakeup
võimaldab meil äratada kõik protsessid, mis vajavad just vabastatud lukku:
/*
* 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 põhjusta prele(ip)
enne kõnet wakeup(ip+1)
. Esimene asi writep()
kutsub oma tsüklis, see plock(ip)
, mille tulemuseks on ummikseisu, kui readp()
pole oma plokki veel eemaldanud, seega peab kood kuidagi õigesti töötama. Kui vaatate wakeup()
, saab selgeks, et see märgib ainult magamise protsessi täitmiseks valmis, nii et edaspidi sched()
tõesti käivitas selle. Niisiis readp()
põhjused wakeup()
, avab lukud, komplekteerib IREAD
ja kõned sleep(ip+2)
- kõik see enne writep()
taaskäivitab tsükli.
See lõpetab torujuhtmete kirjelduse kuuendas väljaandes. Lihtne kood, kaugeleulatuvad tagajärjed.
Xv6, lihtne Unixi-laadne kernel
Tuuma loomiseks
Kood sisaldab selget ja läbimõeldud teostust 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 kogu ülejäänud rakenduse oleku, mis sisaldab funktsioone piperead()
, pipewrite()
и pipeclose()
. Tegelik süsteemikõne sys_pipe
on ümbris, mis on rakendatud
Linux 0.01
Linuxi 0.01 lähtekoodi leiate. Õpetlik on uurida tema torujuhtmete rakendamist fs
/pipe.c
. Siin kasutatakse konveieri tähistamiseks inode, kuid konveier ise on kirjutatud kaasaegses C-keeles. Kui olete kuuenda väljaande koodi sisse häkkinud, pole teil siin probleeme. Funktsioon näeb välja selline 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;
}
Isegi ilma struktuuride definitsioone vaatamata saate aru saada, kuidas kasutatakse inoodi viidete arvu kontrollimaks, kas kirjutamistoimingu tulemuseks on SIGPIPE
. Lisaks bait-bait-tööle on seda funktsiooni lihtne ülaltoodud ideedega võrrelda. Isegi loogika sleep_on
/wake_up
ei tundu nii võõras.
Kaasaegsed Linuxi tuumad, FreeBSD, NetBSD, OpenBSD
Käisin kiiresti üle mõned kaasaegsed tuumad. Ühelgi neist pole juba kettapõhist teostust (pole üllatav). Linuxil on oma rakendus. Ja kuigi kolm kaasaegset BSD tuuma sisaldavad rakendusi, mis põhinevad John Dysoni kirjutatud koodil, on need aastate jooksul üksteisest liiga erinevad.
Lugema fs
/pipe.c
(Linuxis) või sys
/kern
/sys_pipe.c
(*BSD-l) nõuab see tõelist pühendumist. Selliste funktsioonide nagu vektor ja asünkroonne I/O jõudlus ja tugi on tänapäeval koodis olulised. Ja mälu eraldamise, lukkude ja tuuma konfiguratsiooni üksikasjad on kõik väga erinevad. Seda pole ülikoolidel operatsioonisüsteemide sissejuhatava kursuse jaoks vaja.
Igal juhul oli minu jaoks huvitav mõned vanad mustrid välja kaevata (näiteks genereerida SIGPIPE
ja tagasi EPIPE
suletud torujuhtmele kirjutamisel) kõigis neis nii erinevates kaasaegsetes tuumades. Tõenäoliselt ei näe ma kunagi PDP-11 arvutit otseülekandes, kuid paar aastat enne minu sündi kirjutatud koodist on veel palju õppida.
Divi Kapoori poolt 2011. aastal kirjutatud artikkel "
Allikas: www.habr.com