Ĉi tiu artikolo priskribas la efektivigon de duktoj en la Unikso-simila kerno. Mi estis iom seniluziigita, ke lastatempa artikolo titolita "
Pri kio ni parolas?
Duktoj, "verŝajne la plej grava invento en Unikso", estas difina karakterizaĵo de la subesta Unix-filozofio ligi malgrandajn programojn kune, same kiel konata signo sur la komandlinio:
$ echo hello | wc -c
6
Ĉi tiu funkcio dependas de la sistemvoko provizita de kerno pipe
, kiu estas priskribita sur la dokumentaj paĝoj
Duktoj disponigas unudirektan kanalon por interproceza komunikado. La dukto havas enigaĵon (skribi finon) kaj eligaĵon (legi finaĵon). Datenoj skribitaj al la enigo de la dukto povas esti legitaj ĉe la eligo.
La dukto estas kreita per la voko
pipe(2)
, kiu resendas du dosierpriskribilojn: unu rilatante al la enigo de la dukto, la dua al la eligo.
La spura eligo de la supra komando montras la kreadon de la dukto kaj la fluon de datumoj tra ĝi de unu procezo al alia:
$ 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
La gepatra procezo vokas pipe()
por akiri muntitajn dosierpriskribilojn. Unu infana procezo skribas al unu tenilo, kaj alia procezo legas la samajn datumojn de alia tenilo. La ŝelo uzas dup2 por "alinomi" priskribilojn 3 kaj 4 por kongrui kun stdin kaj stdout.
Sen pipoj, la ŝelo devus skribi la rezulton de unu procezo al dosiero kaj transdoni ĝin al alia procezo por legi la datumojn de la dosiero. Kiel rezulto, ni malŝparus pli da rimedoj kaj diskospaco. Tamen, duktoj estas bonaj ne nur ĉar ili permesas vin eviti la uzon de provizoraj dosieroj:
Se procezo provas legi de malplena dukto tiam
read(2)
blokos ĝis la datumoj estos disponeblaj. Se procezo provas skribi al plena dukto, tiamwrite(2)
blokos ĝis sufiĉaj datumoj estos legitaj de la dukto por plenumi la skribon.
Kiel la POSIX-postulo, ĉi tio estas grava eco: skribi al la dukto ĝis PIPE_BUF
bajtoj (almenaŭ 512) devas esti atomaj tiel ke procezoj povas komuniki unu kun la alia tra la dukto en maniero kiel ke regulaj dosieroj (kiuj ne disponigas tiajn garantiojn) ne povas.
Kiam oni uzas regulan dosieron, procezo povas skribi sian tutan produktaĵon al ĝi kaj transdoni ĝin al alia procezo. Aŭ procezoj povas funkcii en tre paralela reĝimo, uzante eksteran signalan mekanismon (kiel semaforo) por sciigi unu la alian kiam skribo aŭ legado finiĝis. Transportiloj savas nin de ĉi tiu tuta ĝeno.
Kion ni serĉas?
Mi klarigos ĝin per simplaj terminoj, por ke estu pli facile por vi imagi kiel transportilo povas funkcii. Vi devos asigni bufron kaj iom da stato en memoro. Vi bezonos funkciojn por aldoni kaj forigi datumojn el la bufro. Vi bezonos iujn rimedojn por voki funkciojn dum legado kaj skriba operacioj sur dosierpriskribiloj. Kaj vi bezonos serurojn por efektivigi la specialan konduton priskribitan supre.
Nun ni pretas pridemandi la kernan fontkodon sub hela lamplumo por konfirmi aŭ kontraŭpruvi nian neklaran mensan modelon. Sed ĉiam estu preta por la neatendita.
Kien ni serĉas?
Mi ne scias kie estas mia kopio de la fama libro "
Vagi tra la TUHS-arkivoj estas kiel viziti muzeon. Ni povas rigardi nian komunan historion, kaj mi respektas la multjaran penadon por retrovi ĉi tiun tutan materialon iom post iom el malnovaj bendoj kaj presaĵoj. Kaj mi akre konscias pri tiuj fragmentoj, kiuj ankoraŭ mankas.
Kontentigis nian scivolemon pri la antikva historio de transportiloj, ni povas rigardi modernajn kernojn por komparo.
Por iu, pipe
estas sistemvoko numero 42 en la tabelo sysent[]
. Koincido?
Tradiciaj Uniksaj kernoj (1970-1974)
Mi ne trovis spurojn pipe(2)
nek en
TUHS deklaras tion
Unikso 1973a Eldono estis la lasta versio kun kerno skribita en asembla lingvo, sed ankaŭ la unua versio kun duktoj. Dum XNUMX oni laboris por plibonigi la trian eldonon, la kerno estis reverkita en C, kaj tiel aperis la kvara eldono de Unikso.
Unu leganto trovis skanadon de dokumento, en kiu Doug McIlroy proponis la ideon "konekti programojn kiel ĝardenhoson".
En la libro de Brian Kernighan
Kiam Unikso aperis, mia fascino pri korutinoj igis min peti la aŭtoron de la OS, Ken Thompson, permesi ke datumoj skribitaj al procezo iri ne nur al la aparato, sed ankaŭ al eligo al alia procezo. Ken decidis ke ĝi eblas. Tamen, kiel minimumisto, li volis ke ĉiu sistema funkcio ludu gravan rolon. Ĉu skribi rekte inter procezoj vere estas granda avantaĝo super skribi al meza dosiero? Nur kiam mi faris specifan proponon kun la alloga nomo "dukto" kaj priskribo de la sintakso por interago inter procezoj, Ken finfine ekkriis: "Mi faros tion!"
Kaj faris. Iun fatalan vesperon, Ken ŝanĝis la kernon kaj ŝelon, riparis plurajn normajn programojn por normigi kiel ili akceptis enigaĵon (kiu povis veni de dukto), kaj ankaŭ ŝanĝis dosiernomojn. La sekvan tagon, duktoj komencis esti uzataj tre vaste en aplikoj. Je la fino de la semajno sekretarioj uzis ilin por sendi dokumentojn de tekstprilaboriloj al la presilo. Iom poste, Ken anstataŭigis la originan API kaj sintakson por envolvi la uzon de duktoj kun pli puraj konvencioj, kiuj estis uzataj ekde tiam.
Bedaŭrinde, la fontkodo por la tria eldono Unikso-kerno estas perdita. Kaj kvankam ni havas la kernan fontkodon skribitan en C
Ni havas tekstan dokumentadon por pipe(2)
de ambaŭ eldonoj, do vi povas komenci serĉante la dokumentaron pipe(2)
estas skribita en asembla lingvo kaj liveras nur unu dosierpriskribilon, sed jam provizas la atendatan bazan funkcion:
Sistemvoko pipo kreas enig/eligmekanismon nomitan dukto. La resendita dosierpriskribilo povas esti uzata por legado kaj skriba operacioj. Kiam io estas skribita al la dukto, ĝis 504 bajtoj da datumoj estas bufrigitaj, post kio la skribprocezo estas suspendita. Dum legado de la dukto, la bufritaj datumoj estas forprenitaj.
Antaŭ la sekva jaro la kerno estis reverkita en C, kaj pipe(fildes)
»:
Sistemvoko pipo kreas enig/eligmekanismon nomitan dukto. La resenditaj dosierpriskribiloj povas esti uzataj en legado kaj skriba operacioj. Kiam io estas skribita al la dukto, la tenilo resendita en r1 (resp. fildes[1]) estas uzata, bufrigita al 4096 bajtoj da datenoj, post kio la skribprocezo estas suspendita. Legante el la dukto, la tenilo resendita al r0 (resp. fildes[0]) prenas la datumojn.
Estas supozite ke post kiam dukto estas difinita, du (aŭ pli) komunikaj procezoj (kreitaj per postaj vokoj al forkon) transdonos datumojn de la dukto per vokoj legi и skribi.
La ŝelo havas sintakson por difinado de lineara aro de procezoj ligitaj per dukto.
Alvokoj por legi de malplena dukto (enhavanta neniujn bufrajn datenojn) kiu havas nur unu finon (ĉiuj skribaj dosierpriskribiloj estas fermitaj) resendas "finon de dosiero". Alvokoj por skribi en simila situacio estas ignorataj.
Plej frue
Sesa eldono de Unikso-similaj sistemoj (1975)
Ni komencu legi fontkodon de Unikso
Dum multaj jaroj la libro leonoj estis la nura dokumento pri la Unikso-similaj sistemoj havebla ekster Bell Labs. Kvankam la sesa eldona permesilo permesis al instruistoj uzi ĝian fontkodon, la sepa eldona permesilo ekskludis tiun eblecon, do la libro estis disdonita en la formo de kontraŭleĝaj maŝinskribitaj kopioj.
Hodiaŭ vi povas aĉeti represaĵon de la libro, kies kovrilo montras studentojn ĉe kopimaŝino. Kaj danke al Warren Toomey (kiu komencis la projekton TUHS) vi povas elŝuti
Antaŭ pli ol 15 jaroj, mi tajpis kopion de la fontkodo donita leonoj, ĉar mi ne ŝatis la kvaliton de mia kopio de nekonata nombro da aliaj ekzempleroj. TUHS ankoraŭ ne ekzistis kaj mi ne havis aliron al la malnovaj fontoj. Sed en 1988, mi trovis malnovan 9-trakan bendon kiu enhavis sekurkopion de komputilo PDP11. Estis malfacile diri ĉu ĝi funkcias, sed estis nerompita /usr/src/ arbo en kiu la plej multaj el la dosieroj estis etikeditaj kun la jaro 1979, kiu eĉ tiam aspektis antikva. Ĝi estis la sepa eldono aŭ ĝia derivaĵo PWB, kiel mi kredis.
Mi prenis la trovaĵon kiel bazon kaj mane redaktis la fontojn al la sesa eldono. Kelkaj el la kodo restis la samaj, sed kelkaj devis esti iomete redaktitaj, ŝanĝante la modernan += ĵetonon al la malmoderna =+. Kelkaj aferoj estis simple forigitaj, kaj iuj devis esti tute reverkitaj, sed ne tro multe.
Kaj hodiaŭ ni povas legi rete ĉe TUHS la fontkodon de la sesa eldono de
Cetere, unuavide, la ĉefa trajto de la C-kodo antaŭ la periodo de Kernighan kaj Ritchie estas ĝia mallongeco. Ne ofte mi kapablas enmeti pecojn de kodo sen ampleksa redaktado por konveni relative mallarĝan montran areon en mia retejo.
Komence
/*
* 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
La bufrograndeco ne ŝanĝiĝis ekde la kvara eldono. Sed ĉi tie ni vidas, sen ia publika dokumentado, ke duktoj iam uzis dosierojn kiel rezerva stokado!
Koncerne LARG-dosierojn, ili respondas al
Jen la vera sistemvoko 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;
}
La komento klare priskribas kio okazas ĉi tie. Sed kompreni la kodon ne estas tiel facila, parte pro la maniero "R0
и R1
sistemaj alvokaj parametroj kaj revenaj valoroj estas pasigitaj.
Ni provu kun
pipe()
devas tra R0
и R1
redonu dosierojn priskribaj nombroj por legado kaj skribo. falloc()
resendas montrilon al la dosierstrukturo, sed ankaŭ "revenas" per u.u_ar0[R0]
kaj dosierpriskribilo. Tio estas, la kodo ŝparas r
dosierpriskribilo por legi kaj asignas dosierpriskribilon por skribi rekte de u.u_ar0[R0]
post la dua voko falloc()
.
Flago FPIPE
, kiun ni starigas dum kreado de la dukto, kontrolas la konduton de la funkcio
/*
* 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);
}
/* … */
}
Tiam la funkcio readp()
в pipe.c
legas datumojn de la dukto. Sed estas pli bone spuri la efektivigon ekde writep()
. Denove, la kodo fariĝis pli kompleksa pro la konvencioj de preterpasaj argumentoj, sed iuj detaloj povas esti preterlasitaj.
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;
}
Ni volas skribi bajtojn al la dukto-enigo u.u_count
. Unue ni devas ŝlosi la inodon (vidu sube plock
/prele
).
Tiam ni kontrolas la inodan referenco-nombrilon. Dum ambaŭ finoj de la dukto restas malfermitaj, la vendotablo devas esti egala al 2. Ni tenas unu ligilon (de rp->f_inode
), do se la nombrilo estas malpli ol 2, ĝi devas signifi, ke la legado fermis sian finon de la dukto. Alivorte, ni provas skribi al fermita dukto, kaj ĉi tio estas eraro. Unuafoja erarkodo EPIPE
kaj signalo SIGPIPE
aperis en la sesa eldono de Unikso.
Sed eĉ se la transportilo estas malfermita, ĝi povas esti plena. En ĉi tiu kazo, ni liberigas la seruron kaj endormiĝas kun la espero, ke alia procezo legos el la dukto kaj liberigos sufiĉe da spaco en ĝi. Vekiĝinte, ni revenas al la komenco, denove pendigas la seruron kaj komencas novan registradon.
Se estas sufiĉe libera spaco en la dukto, tiam ni skribas datumojn al ĝi uzante i_size1
ĉe inodo (se la dukto estas malplena ĝi povas esti egala al 0) indikas la finon de la datumoj kiujn ĝi jam enhavas. Se estas sufiĉe da registra spaco, ni povas plenigi la dukton de i_size1
por PIPESIZ
. Tiam ni liberigas la seruron kaj provas veki ajnan procezon, kiu atendas legi el la dukto. Ni reiras al la komenco por vidi ĉu ni povis skribi tiom da bajtoj kiom ni bezonis. Se ĝi malsukcesas, tiam ni komencas novan registradciklon.
Kutime la parametro i_mode
inodo estas uzata por konservi permesojn r
, w
и x
. Sed en la kazo de duktoj, ni signalas, ke iu procezo atendas skribon aŭ legadon uzante bitojn IREAD
и IWRITE
respektive. La procezo metas la flagon kaj vokas sleep()
, kaj estas atendite ke iu alia procezo estonte kaŭzos wakeup()
.
La vera magio okazas en sleep()
и wakeup()
. Ili estas efektivigitaj en
/*
* 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) /* … */
La procezo kiu kaŭzas sleep()
por aparta kanalo, povas poste esti vekita per alia procezo, kiu kaŭzos wakeup()
por la sama kanalo. writep()
и readp()
kunordigi iliajn agojn per tiaj paritaj vokoj. rimarku tion pipe.c
ĉiam donas prioritaton PPIPE
kiam vokita sleep()
, do jen sleep()
povas esti interrompita de signalo.
Nun ni havas ĉion por kompreni la funkcion 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);
}
Vi eble trovos pli facile legi ĉi tiun funkcion de malsupre supren. La branĉo "legi kaj reveni" estas kutime uzata kiam estas iuj datumoj en la dukto. En ĉi tiu kazo, ni uzas f_offset
legado, kaj poste ĝisdatigi la valoron de la responda ofseto.
En postaj legoj, la dukto estos malplena se la legita ofseto atingis i_size1
ĉe inodo. Ni restarigas la pozicion al 0 kaj provas veki ajnan procezon, kiu volas skribi al la dukto. Ni scias, ke kiam la transportilo estas plena, writep()
endormiĝos ip+1
. Kaj nun kiam la dukto estas malplena, ni povas veki ĝin por rekomenci sian skribciklon.
Se vi havas nenion por legi, do readp()
povas agordi flagon IREAD
kaj ekdormis plu ip+2
. Ni scias, kio vekos lin writep()
, kiam ĝi skribas iujn datumojn al la dukto.
Komentoj al u
"Ni povas trakti ilin kiel normalajn I/O-funkciojn, kiuj prenas dosieron, pozicion, bufron en memoro, kaj kalkulas la nombron da bajtoj por legi aŭ skribi.
/*
* 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;
/* … */
Pri la "konservativa" blokado, do readp()
и writep()
bloku la inodon ĝis ili finas sian laboron aŭ ricevas rezulton (tio estas, vokas wakeup
). plock()
и prele()
labori simple: uzante malsaman aron de vokoj sleep
и wakeup
permesu al ni veki ajnan procezon, kiu bezonas la seruron, kiun ni ĵus liberigis:
/*
* 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);
}
}
Komence mi ne povis kompreni kial readp()
ne kaŭzas prele(ip)
antaŭ la voko wakeup(ip+1)
. La unua afero estas writep()
kaŭzas en sia ciklo, ĉi tion plock(ip)
, kiu kondukas al blokiĝo se readp()
ankoraŭ ne forigis mian blokadon, do iel la kodo devas funkcii ĝuste. Se vi rigardas wakeup()
, tiam evidentiĝas, ke ĝi nur markas la dormantan procezon kiel preta por ekzekuti, tiel ke estonte sched()
vere lanĉis ĝin. Do readp()
kaŭzoj wakeup()
, forigas la seruron, starigas IREAD
kaj vokas sleep(ip+2)
— ĉio ĉi antaŭe writep()
rekomencas la ciklon.
Ĉi tio kompletigas la priskribon de transportiloj en la sesa eldono. Simpla kodo, vastaj konsekvencoj.
Xv6, simpla Unikso-simila kerno
Por krei la kernon
La kodo enhavas klaran kaj pripenseman efektivigon 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()
fiksas la staton de la resto de la efektivigo, kiu inkluzivas la funkciojn piperead()
, pipewrite()
и pipeclose()
. Fakta sistemvoko sys_pipe
estas envolvaĵo efektivigita en
Linukso 0.01
Linukso 0.01 fontkodo troveblas. Estos instrua studi la efektivigon de duktoj en lia fs
/pipe.c
. Ĉi tio uzas inodon por reprezenti la dukto, sed la dukto mem estas skribita en moderna C. Se vi trairis 6-an eldonkodon, vi ne havos problemon ĉi tie. Jen kiel aspektas la funkcio 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;
}
Eĉ sen rigardi la strukturdifinojn, vi povas eltrovi kiel la inoda referenckalkulo estas uzata por kontroli ĉu skriba operacio rezultas en SIGPIPE
. Krom labori bito-post-bajto, ĉi tiu funkcio estas facile kompari kun la ideoj supre priskribitaj. Eĉ logiko sleep_on
/wake_up
ne aspektas tiel fremda.
Modernaj Linukso-kernoj, FreeBSD, NetBSD, OpenBSD
Mi rapide trakuris kelkajn modernajn kernojn. Neniu el ili plu havas disk-efektivigon (ne surprize). Linukso havas sian propran efektivigon. Kvankam la tri modernaj BSD-kernoj enhavas efektivigojn bazitajn sur kodo kiu estis skribita fare de John Dyson, tra la jaroj ili fariĝis tro malsamaj unu de la alia.
Legi fs
/pipe.c
(sur Linukso) aŭ sys
/kern
/sys_pipe.c
(sur *BSD), necesas vera dediĉo. La hodiaŭa kodo temas pri rendimento kaj subteno por funkcioj kiel vektora kaj nesinkrona I/O. Kaj la detaloj pri memoratribuo, seruroj kaj agordo de la kerno ĉiuj multe varias. Ĉi tio ne estas kion altlernejoj bezonas por enkonduka operaciuma kurso.
Ĉiuokaze, mi interesiĝis elfosi kelkajn malnovajn ŝablonojn (kiel generi SIGPIPE
kaj reveni EPIPE
skribante al fermita dukto) en ĉiuj ĉi tiuj malsamaj modernaj kernoj. Mi verŝajne neniam vidos komputilon PDP-11 en la reala vivo, sed estas ankoraŭ multe por lerni de kodo, kiu estis skribita jarojn antaŭ ol mi naskiĝis.
Artikolo skribita fare de Divi Kapoor en 2011:
fonto: www.habr.com