Ta članek opisuje izvedbo cevovodov v jedru Unix. Nekoliko me je razočaral nedavni članek z naslovom "
Za kaj se gre?
Cevovodi so "verjetno najpomembnejši izum v Unixu" - značilnost Unixove temeljne filozofije sestavljanja majhnih programov in znani slogan ukazne vrstice:
$ echo hello | wc -c
6
Ta funkcionalnost je odvisna od sistemskega klica, ki ga zagotavlja jedro pipe
, ki je opisan na straneh dokumentacije
Cevovodi zagotavljajo enosmerni kanal za komunikacijo med procesi. Cevovod ima vhod (konec pisanja) in izhod (konec branja). Podatke, zapisane na vhod cevovoda, je mogoče prebrati na izhodu.
Cevovod se ustvari s klicanjem
pipe(2)
, ki vrne dva deskriptorja datoteke: eden se nanaša na vhod cevovoda, drugi na izhod.
Izhod sledenja iz zgornjega ukaza prikazuje ustvarjanje cevovoda in pretok podatkov skozi njega od enega procesa do drugega:
$ 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
Pokliče nadrejeni proces pipe()
da dobite priložene deskriptorje datotek. En podrejeni proces piše v en deskriptor, drugi proces pa bere iste podatke iz drugega deskriptorja. Lupina "preimenuje" deskriptorja 2 in 3 z dup4, da se ujemata s stdin in stdout.
Brez cevovodov bi lupina morala zapisati izhod enega procesa v datoteko in ga posredovati drugemu procesu, da prebere podatke iz datoteke. Posledično bi zapravili več virov in prostora na disku. Vendar so cevovodi dobri za več kot le izogibanje začasnim datotekam:
Če proces poskuša brati iz praznega cevovoda, potem
read(2)
bo blokiran, dokler podatki niso na voljo. Če proces poskuša pisati v polni cevovod, potemwrite(2)
bo blokiral, dokler se iz cevovoda ne prebere dovolj podatkov za dokončanje pisanja.
Tako kot zahteva POSIX je tudi to pomembna lastnost: pisanje v cevovod do PIPE_BUF
bajti (vsaj 512) morajo biti atomski, tako da lahko procesi komunicirajo med seboj prek cevovoda na način, kot običajne datoteke (ki ne zagotavljajo takšnih jamstev) ne morejo.
Pri običajni datoteki lahko proces vanjo zapiše ves svoj izhod in ga posreduje drugemu procesu. Lahko pa procesi delujejo v trdem vzporednem načinu z uporabo zunanjega signalnega mehanizma (kot je semafor), da drug drugega obvestijo o zaključku pisanja ali branja. Tekoči trakovi nas rešijo vseh teh težav.
Kaj iščemo?
Razložil bom na prste, da si boste lažje predstavljali, kako lahko deluje tekoči trak. V pomnilniku boste morali dodeliti medpomnilnik in nekaj stanja. Potrebovali boste funkcije za dodajanje in odstranjevanje podatkov iz medpomnilnika. Potrebovali boste nekaj zmogljivosti za klicanje funkcij med operacijami branja in pisanja deskriptorjev datotek. In ključavnice so potrebne za izvajanje zgoraj opisanega posebnega vedenja.
Zdaj smo pripravljeni preiskati izvorno kodo jedra pod močno svetlobo luči, da potrdimo ali ovržemo naš nejasen mentalni model. Vedno pa bodite pripravljeni na nepričakovano.
Kam iščemo?
Ne vem, kje leži moj izvod slavne knjige.
Potepanje po arhivih TUHS je kot obisk muzeja. Ogledamo si lahko našo skupno zgodovino in spoštujem leta prizadevanj, da bi vse to gradivo po delih povrnili iz starih kaset in izpisov. In zelo se zavedam tistih drobcev, ki še vedno manjkajo.
Ko smo potešili svojo radovednost o starodavni zgodovini cevovodov, si lahko za primerjavo ogledamo sodobna jedra.
Mimogrede, pipe
je številka sistemskega klica 42 v tabeli sysent[]
. Naključje?
Tradicionalna jedra Unix (1970–1974)
Nisem našel nobene sledi pipe(2)
niti noter
TUHS trdi, da
Tretja izdaja Unixa je bila zadnja različica z jedrom, napisanim v asemblerju, a tudi prva različica s cevovodi. Leta 1973 je potekalo delo za izboljšanje tretje izdaje, jedro je bilo na novo napisano v C in tako se je rodila četrta izdaja Unixa.
En bralec je našel optično prebran dokument, v katerem je Doug McIlroy predlagal idejo o "povezovanju programov kot vrtna cev."
V knjigi Briana Kernighana
Ko se je pojavil Unix, sem zaradi strasti do korutin prosil avtorja operacijskega sistema, Kena Thompsona, naj dovoli, da podatki, zapisani v neki proces, gredo ne samo v napravo, ampak tudi do izhoda v drug proces. Ken je mislil, da je to mogoče. Vendar pa je kot minimalist želel, da ima vsaka funkcija sistema pomembno vlogo. Ali je neposredno pisanje med procesi res velika prednost pred pisanjem v vmesno datoteko? In šele, ko sem dal poseben predlog s privlačnim imenom "cevovod" in opisom sintakse interakcije procesov, je Ken končno vzkliknil: "To bom naredil!".
In naredil. Nekega usodnega večera je Ken spremenil jedro in lupino, popravil več standardnih programov, da bi standardiziral, kako sprejemajo vnos (ki bi lahko prišel iz cevovoda), in spremenil imena datotek. Naslednji dan so bili cevovodi zelo razširjeni v aplikacijah. Do konca tedna so tajnice z njimi pošiljale dokumente iz urejevalnikov besedil v tiskalnik. Nekoliko kasneje je Ken zamenjal prvotni API in sintakso za zavijanje uporabe cevovodov s čistejšimi konvencijami, ki so se uporabljale vse od takrat.
Na žalost je bila izvorna koda za tretjo izdajo jedra Unix izgubljena. In čeprav imamo izvorno kodo jedra, napisano v C
Imamo besedilo dokumentacije za pipe(2)
iz obeh izdaj, tako da lahko začnete z iskanjem po dokumentaciji pipe(2)
je napisan v asemblerju in vrne samo en deskriptor datoteke, vendar že zagotavlja pričakovano osnovno funkcionalnost:
Sistemski klic cevi ustvari V/I mehanizem, imenovan cevovod. Vrnjeni deskriptor datoteke se lahko uporablja za operacije branja in pisanja. Ko se nekaj zapiše v cevovod, shrani v medpomnilnik do 504 bajtov podatkov, nato pa se postopek zapisovanja prekine. Pri branju iz cevovoda se vzamejo medpomnilniki.
Do naslednjega leta je bilo jedro prepisano v C in pipe(fildes)
»:
Sistemski klic cevi ustvari V/I mehanizem, imenovan cevovod. Vrnjene deskriptorje datotek je mogoče uporabiti v operacijah branja in pisanja. Ko se nekaj zapiše v cevovod, se uporabi deskriptor, vrnjen v r1 (oziroma fildes[1]), medpomnilnik do 4096 bajtov podatkov, po katerem se postopek pisanja prekine. Pri branju iz cevovoda deskriptor, vrnjen v r0 (oz. fildes[0]), prevzame podatke.
Predpostavlja se, da ko je cevovod definiran, sta dva (ali več) medsebojno delujočih procesov (ustvarjena z naknadnimi klici vilice) bo posredoval podatke iz cevovoda s pomočjo klicev preberite и pisati.
Lupina ima sintakso za definiranje linearnega niza procesov, povezanih prek cevovoda.
Klici za branje iz praznega cevovoda (ki ne vsebuje podatkov v medpomnilniku), ki ima samo en konec (vsi deskriptorji datotek za pisanje so zaprti), vrnejo "konec datoteke". Pisani klici v podobni situaciji so prezrti.
Najzgodnejši
Unix šesta izdaja (1975)
Začetek branja izvorne kode Unix
Že vrsto let knjiga Lions je bil edini dokument o jedru Unixa, ki je bil na voljo zunaj Bell Labs. Čeprav je licenca za šesto izdajo dovoljevala učiteljem uporabo njene izvorne kode, je licenca za sedmo izdajo to možnost izključevala, zato je bila knjiga razdeljena v nezakonitih tipkanih izvodih.
Danes lahko kupite ponatis knjige, katere naslovnica prikazuje študente za kopirnim strojem. In zahvaljujoč Warrenu Toomeyu (ki je začel projekt TUHS), lahko prenesete
Pred več kot 15 leti sem vtipkal kopijo izvorne kode Lionsker mi ni bila všeč kakovost moje kopije od neznanega števila drugih kopij. TUHS še ni obstajal, jaz pa nisem imel dostopa do starih virov. Toda leta 1988 sem našel staro kaseto z 9 skladbami, ki je imela varnostno kopijo iz računalnika PDP11. Težko je bilo vedeti, ali je delovalo, vendar je bilo nedotaknjeno drevo /usr/src/, v katerem je bila večina datotek označena z letom 1979, ki je bilo že takrat videti starodavno. Mislil sem, da je to sedma izdaja ali izpeljanka PWB.
Najdbo sem vzel za osnovo in ročno uredil vire do stanja šeste izdaje. Del kode je ostal enak, del je bilo treba nekoliko urediti, tako da sodoben žeton += spremenili v zastarelega =+. Nekaj je bilo preprosto izbrisano, nekaj pa je bilo treba v celoti prepisati, a ne preveč.
In danes lahko na spletu na TUHS preberemo izvorno kodo šeste izdaje
Mimogrede, na prvi pogled je glavna značilnost kode C pred obdobjem Kernighana in Ritchieja njena kratkost. Ne zgodi se pogosto, da bi lahko vstavil delčke kode brez obsežnega urejanja, da bi ustrezal razmeroma ozkemu območju prikaza na mojem spletnem mestu.
Zgodnje
/*
* 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
Velikost medpomnilnika se od četrte izdaje ni spremenila. Toda tukaj vidimo, brez kakršne koli javne dokumentacije, da so cevovodi nekoč uporabljali datoteke kot nadomestno shranjevanje!
Datoteke LARG ustrezajo
Tukaj je pravi sistemski klic 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;
}
Komentar jasno opisuje, kaj se tukaj dogaja. Vendar kode ni tako enostavno razumeti, delno zaradi tega, kako "R0
и R1
parametri sistemskega klica in vrnjene vrednosti so posredovani.
Poskusimo z
pipe()
zaradi R0
и R1
vrne številke deskriptorja datoteke za branje in pisanje. falloc()
vrne kazalec na strukturo datoteke, vendar tudi "vrne" prek u.u_ar0[R0]
in deskriptor datoteke. To pomeni, da je koda shranjena v r
deskriptor datoteke za branje in dodeli deskriptor za neposredno pisanje u.u_ar0[R0]
po drugem klicu falloc()
.
Zastava FPIPE
, ki ga nastavimo pri ustvarjanju cevovoda, nadzoruje obnašanje funkcije
/*
* 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);
}
/* … */
}
Nato funkcija readp()
в pipe.c
bere podatke iz cevovoda. Vendar je bolje slediti izvajanju od writep()
. Koda je ponovno postala bolj zapletena zaradi narave konvencije o posredovanju argumentov, vendar je mogoče nekatere podrobnosti izpustiti.
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;
}
Bajte želimo zapisati v vhod cevovoda u.u_count
. Najprej moramo zakleniti inode (glejte spodaj plock
/prele
).
Nato preverimo število referenc inode. Dokler sta oba konca cevovoda odprta, mora biti števec 2. Držimo se enega člena (od rp->f_inode
), torej če je števec manjši od 2, bi to moralo pomeniti, da je proces branja zaprl svoj konec cevovoda. Z drugimi besedami, poskušamo pisati v zaprt cevovod, kar je napaka. Prva koda napake EPIPE
in signal SIGPIPE
pojavil v šesti izdaji Unixa.
A tudi če je tekoči trak odprt, je lahko poln. V tem primeru sprostimo ključavnico in gremo spat v upanju, da bo drug proces prebral iz cevovoda in sprostil dovolj prostora v njem. Ko se zbudimo, se vrnemo na začetek, ponovno obesimo ključavnico in začnemo nov cikel pisanja.
Če je v cevovodu dovolj prostega prostora, vanj zapišemo podatke z uporabo i_size1
inode'a (s praznim cevovodom je lahko enak 0) kaže na konec podatkov, ki jih že vsebuje. Če je dovolj prostora za pisanje, lahko napolnimo cevovod iz i_size1
za PIPESIZ
. Nato sprostimo ključavnico in poskušamo prebuditi vse procese, ki čakajo na branje iz cevovoda. Vrnemo se na začetek, da vidimo, ali smo uspeli napisati toliko bajtov, kot smo jih potrebovali. Če ne, potem začnemo nov cikel snemanja.
Ponavadi parameter i_mode
inode se uporablja za shranjevanje dovoljenj r
, w
и x
. Toda v primeru cevovodov signaliziramo, da nek proces čaka na pisanje ali branje z uporabo bitov IREAD
и IWRITE
oz. Proces nastavi zastavico in pokliče sleep()
, v prihodnosti pa se pričakuje, da bo poklical kakšen drug proces wakeup()
.
Prava čarovnija se zgodi v sleep()
и wakeup()
. Izvajajo se v
/*
* 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) /* … */
Proces, ki kliče sleep()
za določen kanal, lahko pozneje prebudi drug proces, ki bo poklical wakeup()
za isti kanal. writep()
и readp()
usklajujejo svoja dejanja prek takih seznanjenih klicev. Upoštevajte to pipe.c
vedno daj prednost PPIPE
ob klicu sleep()
, torej vse sleep()
lahko prekine signal.
Zdaj imamo vse za razumevanje funkcije 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);
}
Morda boste lažje prebrali to funkcijo od spodaj navzgor. Veja »branje in vrnitev« se običajno uporablja, ko je v cevovodu nekaj podatkov. V tem primeru uporabljamo f_offset
preberite in nato posodobite vrednost ustreznega odmika.
Pri naslednjih branjih bo cevovod prazen, če je dosežen odmik branja i_size1
na inodu. Ponastavimo položaj na 0 in poskušamo prebuditi vsak proces, ki želi pisati v cevovod. Vemo, da ko je tekoči trak poln, writep()
zaspati naprej ip+1
. In zdaj, ko je cevovod prazen, ga lahko prebudimo, da nadaljuje svoj cikel pisanja.
Če ni kaj brati, potem readp()
lahko nastavi zastavo IREAD
in zaspi naprej ip+2
. Vemo, kaj ga bo prebudilo writep()
ko zapiše nekaj podatkov v cevovod.
Komentarji na u
» lahko jih obravnavamo kot običajne V/I funkcije, ki vzamejo datoteko, položaj, medpomnilnik v pomnilniku in štejejo število bajtov za branje ali pisanje.
/*
* 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;
/* … */
Glede "konzervativnega" blokiranja torej readp()
и writep()
zakleni inode, dokler ne končajo ali dobijo rezultat (tj. klic wakeup
). plock()
и prele()
deluje preprosto: z uporabo drugega nabora klicev sleep
и wakeup
nam omogočajo, da prebudimo vsak proces, ki potrebuje ključavnico, ki smo jo pravkar sprostili:
/*
* 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);
}
}
Najprej nisem mogel razumeti, zakaj readp()
ne povzroča prele(ip)
pred klicem wakeup(ip+1)
. Prva stvar writep()
klice v svoji zanki, to plock(ip)
, kar povzroči zastoj, če readp()
še ni odstranil svoje blokade, zato mora koda nekako delovati pravilno. Če pogledate wakeup()
, postane jasno, da le označi postopek spanja kot pripravljenega za izvedbo, tako da v prihodnje sched()
res lansiral. torej readp()
vzrokov wakeup()
, odklepa, postavlja IREAD
in klice sleep(ip+2)
- vse to prej writep()
ponovno zažene cikel.
S tem je opis cevovodov v šesti izdaji zaključen. Preprosta koda, daljnosežne posledice.
Xv6, preprosto Unixu podobno jedro
Za ustvarjanje jedra
Koda vsebuje jasno in premišljeno izvedbo 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()
nastavi stanje vse preostale izvedbe, ki vključuje funkcije piperead()
, pipewrite()
и pipeclose()
. Dejanski sistemski klic sys_pipe
je ovoj, implementiran v
Linux 0.01
Najdete lahko izvorno kodo za Linux 0.01. Poučno bo preučiti izvedbo cevovodov v njegovem fs
/pipe.c
. Tu se za predstavitev cevovoda uporablja inode, sam cevovod pa je napisan v sodobnem C. Če ste se prebili skozi kodo šeste izdaje, tukaj ne boste imeli težav. Tako izgleda 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;
}
Tudi brez pogleda na definicije strukture lahko ugotovite, kako se število referenc inode uporablja za preverjanje, ali operacija zapisovanja povzroči SIGPIPE
. Poleg dela bajt za bajtom je to funkcijo enostavno primerjati z zgornjimi zamislimi. Tudi logika sleep_on
/wake_up
ne izgleda tako tujec.
Sodobna jedra Linuxa, FreeBSD, NetBSD, OpenBSD
Na hitro sem pregledal nekaj sodobnih jeder. Nobeden od njih še nima diskovne izvedbe (kar ni presenetljivo). Linux ima svojo izvedbo. In čeprav tri sodobna jedra BSD vsebujejo implementacije, ki temeljijo na kodi, ki jo je napisal John Dyson, so z leti postala med seboj preveč različna.
Brati fs
/pipe.c
(na Linuxu) oz sys
/kern
/sys_pipe.c
(na *BSD), zahteva resnično predanost. Zmogljivost in podpora za funkcije, kot sta vektorski in asinhroni V/I, sta danes pomembni v kodi. Podrobnosti dodeljevanja pomnilnika, zaklepanja in konfiguracije jedra se zelo razlikujejo. To ni tisto, kar univerze potrebujejo za uvodni tečaj o operacijskih sistemih.
Vsekakor mi je bilo zanimivo odkriti nekaj starih vzorcev (npr. generiranje SIGPIPE
in se vrniti EPIPE
pri pisanju v zaprti cevovod) v vseh teh, tako različnih, sodobnih jedrih. Verjetno nikoli ne bom videl računalnika PDP-11 v živo, a še vedno se moram veliko naučiti iz kode, ki je bila napisana nekaj let pred mojim rojstvom.
Napisala Divi Kapoor leta 2011, članek "
Vir: www.habr.com