Inilalarawan ng artikulong ito ang pagpapatupad ng mga pipeline sa Unix kernel. Medyo nabigo ako na ang isang kamakailang artikulo na pinamagatang "
Tungkol Saan yan?
Ang mga pipeline ay "marahil ang pinakamahalagang imbensyon sa Unix" - isang pagtukoy sa katangian ng pinagbabatayan ng pilosopiya ng Unix sa pagsasama-sama ng maliliit na programa, at ang pamilyar na command-line slogan:
$ echo hello | wc -c
6
Nakadepende ang functionality na ito sa system call na ibinigay ng kernel pipe
, na inilalarawan sa mga pahina ng dokumentasyon
Ang mga pipeline ay nagbibigay ng one-way na channel para sa inter-process na komunikasyon. Ang pipeline ay may input (write end) at isang output (read end). Ang data na nakasulat sa input ng pipeline ay mababasa sa output.
Ang pipeline ay nilikha sa pamamagitan ng pagtawag
pipe(2)
, na nagbabalik ng dalawang descriptor ng file: ang isa ay tumutukoy sa input ng pipeline, ang pangalawa sa output.
Ang bakas na output mula sa utos sa itaas ay nagpapakita ng paglikha ng isang pipeline at ang daloy ng data sa pamamagitan nito mula sa isang proseso patungo sa isa pa:
$ 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
Ang proseso ng magulang ay tumatawag pipe()
upang makakuha ng mga kalakip na deskriptor ng file. Nagsusulat ang isang proseso ng bata sa isang descriptor at ang isa pang proseso ay nagbabasa ng parehong data mula sa isa pang descriptor. Ang shell ay "pinangalanan" ang mga deskriptor 2 at 3 na may dup4 upang tumugma sa stdin at stdout.
Kung walang mga pipeline, kailangang isulat ng shell ang output ng isang proseso sa isang file at i-pipe ito sa isa pang proseso upang mabasa ang data mula sa file. Bilang resulta, mag-aaksaya kami ng mas maraming mapagkukunan at espasyo sa disk. Gayunpaman, ang mga pipeline ay mabuti para sa higit pa sa pag-iwas sa mga pansamantalang file:
Kung ang isang proseso ay sumusubok na magbasa mula sa isang walang laman na pipeline, kung gayon
read(2)
haharangan hanggang sa maging available ang data. Kung ang isang proseso ay sumusubok na sumulat sa isang buong pipeline, kung gayonwrite(2)
haharangan hanggang sa sapat na data ang nabasa mula sa pipeline upang makumpleto ang pagsulat.
Tulad ng kinakailangan ng POSIX, ito ay isang mahalagang katangian: pagsulat sa pipeline hanggang sa PIPE_BUF
Ang mga byte (hindi bababa sa 512) ay dapat na atomic upang ang mga proseso ay maaaring makipag-ugnayan sa isa't isa sa pamamagitan ng pipeline sa paraang hindi magagawa ng mga normal na file (na hindi nagbibigay ng mga ganoong garantiya).
Sa isang regular na file, maaaring isulat ng isang proseso ang lahat ng output nito dito at ipasa ito sa isa pang proseso. O ang mga proseso ay maaaring gumana sa isang hard parallel mode, gamit ang isang panlabas na mekanismo ng pagbibigay ng senyas (tulad ng isang semaphore) upang ipaalam sa isa't isa ang tungkol sa pagkumpleto ng isang pagsulat o pagbasa. Iniligtas tayo ng mga conveyor mula sa lahat ng abala na ito.
Ano ang hinahanap natin?
Ipapaliwanag ko sa aking mga daliri para mas madali mong isipin kung paano gumagana ang isang conveyor. Kakailanganin mong maglaan ng buffer at ilang estado sa memorya. Kakailanganin mo ang mga function upang magdagdag at mag-alis ng data mula sa buffer. Kakailanganin mo ang ilang pasilidad upang tumawag sa mga function sa panahon ng pagbabasa at pagsulat ng mga operasyon sa mga deskriptor ng file. At kailangan ang mga kandado upang maipatupad ang espesyal na gawi na inilarawan sa itaas.
Handa na kaming tanungin ang source code ng kernel sa ilalim ng maliwanag na lamplight para kumpirmahin o pabulaanan ang aming malabong mental na modelo. Ngunit laging maging handa sa hindi inaasahan.
Saan tayo nakatingin?
Hindi ko alam kung saan nakalagay ang kopya ko ng sikat na libro.
Ang paggala sa TUHS archive ay parang pagbisita sa isang museo. Maaari nating tingnan ang ating ibinahaging kasaysayan at iginagalang ko ang mga taon ng pagsisikap na mabawi ang lahat ng materyal na ito nang paunti-unti mula sa mga lumang cassette at printout. At lubos kong nalalaman ang mga fragment na iyon na nawawala pa rin.
Dahil nasiyahan ang aming pagkamausisa tungkol sa sinaunang kasaysayan ng mga pipeline, maaari naming tingnan ang mga modernong core para sa paghahambing.
Sa pamamagitan ng paraan, pipe
ay system call number 42 sa talahanayan sysent[]
. Pagkakataon?
Tradisyonal na Unix kernels (1970β1974)
Wala akong nakitang bakas pipe(2)
ni sa
Sinasabi iyon ng TUHS
Ang ikatlong edisyon ng Unix ay ang huling bersyon na may kernel na nakasulat sa assembler, ngunit din ang unang bersyon na may mga pipeline. Noong 1973, ang trabaho ay isinasagawa upang pahusayin ang ikatlong edisyon, ang kernel ay muling isinulat sa C, at sa gayon ay ipinanganak ang ikaapat na edisyon ng Unix.
Natagpuan ng isang mambabasa ang isang pag-scan ng isang dokumento kung saan iminungkahi ni Doug McIlroy ang ideya ng "pagkonekta ng mga programa tulad ng isang hose sa hardin."
Sa aklat ni Brian Kernighan
Nang lumitaw ang Unix, ang hilig ko sa mga coroutine ay nagtanong sa may-akda ng OS, si Ken Thompson, na payagan ang data na nakasulat sa ilang proseso na pumunta hindi lamang sa device, kundi pati na rin sa exit sa isa pang proseso. Akala ni Ken pwede. Gayunpaman, bilang isang minimalist, gusto niyang ang bawat feature ng system ay may mahalagang papel. Ang direktang pagsulat ba sa pagitan ng mga proseso ay talagang isang malaking kalamangan sa pagsusulat sa isang intermediate na file? At kapag gumawa ako ng isang partikular na panukala na may kaakit-akit na pangalan na "pipeline" at isang paglalarawan ng syntax ng pakikipag-ugnayan ng mga proseso, sa wakas ay bumulalas si Ken: "Gagawin ko ito!".
At ginawa. Isang nakamamatay na gabi, binago ni Ken ang kernel at shell, inayos ang ilang karaniwang mga programa upang i-standardize kung paano sila tumatanggap ng input (na maaaring nagmula sa isang pipeline), at binago ang mga filename. Kinabukasan, ang mga pipeline ay napakalawak na ginamit sa mga aplikasyon. Sa pagtatapos ng linggo, ginamit sila ng mga kalihim para magpadala ng mga dokumento mula sa mga word processor patungo sa printer. Maya-maya, pinalitan ni Ken ang orihinal na API at syntax para sa pagbabalot ng paggamit ng mga pipeline na may mas malinis na mga convention na ginamit mula noon.
Sa kasamaang palad, nawala ang source code para sa ikatlong edisyon ng Unix kernel. At kahit na mayroon kaming kernel source code na nakasulat sa C
Mayroon kaming dokumentasyong teksto para sa pipe(2)
mula sa parehong mga release, para makapagsimula ka sa paghahanap sa dokumentasyon pipe(2)
ay nakasulat sa assembler at nagbabalik lamang ng isang file descriptor, ngunit nagbibigay na ng inaasahang pangunahing pag-andar:
System call tubo lumilikha ng mekanismo ng I/O na tinatawag na pipeline. Ang ibinalik na deskriptor ng file ay maaaring gamitin para sa mga operasyon sa pagbasa at pagsulat. Kapag may isinulat sa pipeline, nag-buffer ito ng hanggang 504 bytes ng data, pagkatapos nito ay sinuspinde ang proseso ng pagsulat. Kapag nagbabasa mula sa pipeline, kinukuha ang buffered data.
Sa sumunod na taon, ang kernel ay muling naisulat sa C, at pipe(fildes)
Β»:
System call tubo lumilikha ng mekanismo ng I/O na tinatawag na pipeline. Ang ibinalik na mga deskriptor ng file ay maaaring gamitin sa mga operasyon sa pagbasa at pagsulat. Kapag may isinulat sa pipeline, ginagamit ang descriptor na ibinalik sa r1 (resp. fildes[1]), na na-buffer ng hanggang 4096 bytes ng data, pagkatapos nito ay sinuspinde ang proseso ng pagsulat. Kapag nagbabasa mula sa pipeline, bumalik ang descriptor sa r0 (resp. fildes[0]) na kumukuha ng data.
Ipinapalagay na kapag natukoy na ang isang pipeline, dalawa (o higit pa) na mga prosesong nakikipag-ugnayan (nagawa ng mga kasunod na invocations tinidor) ay magpapasa ng data mula sa pipeline gamit ang mga tawag basahin ΠΈ magsulat.
Ang shell ay may syntax para sa pagtukoy ng isang linear array ng mga proseso na konektado sa pamamagitan ng pipeline.
Ang mga tawag para magbasa mula sa isang walang laman na pipeline (na naglalaman ng walang buffered na data) na may isang dulo lamang (lahat ng write file descriptor sarado) ay nagbabalik ng "end of file". Ang pagsusulat ng mga tawag sa isang katulad na sitwasyon ay hindi pinapansin.
pinakauna
Unix Sixth Edition (1975)
Nagsisimulang basahin ang Unix source code
Sa loob ng maraming taon ang libro Lions ay ang tanging dokumento sa Unix kernel na magagamit sa labas ng Bell Labs. Bagama't pinahintulutan ng lisensya ng ikaanim na edisyon ang mga guro na gamitin ang source code nito, hindi isinama ng lisensya ng ikapitong edisyon ang posibilidad na ito, kaya ipinamahagi ang aklat sa mga ilegal na makinilya na kopya.
Ngayon ay maaari kang bumili ng kopya ng muling pag-print ng aklat, na ang pabalat nito ay nagpapakita ng mga mag-aaral sa copier. At salamat kay Warren Toomey (na nagsimula ng proyekto ng TUHS), maaari mong i-download
Mahigit 15 taon na ang nakalipas, nag-type ako ng kopya ng source code na ibinigay sa Lionsdahil hindi ko nagustuhan ang kalidad ng aking kopya mula sa hindi kilalang bilang ng iba pang mga kopya. Wala pa ang TUHS, at wala akong access sa mga lumang source. Ngunit noong 1988 nakakita ako ng lumang tape na may 9 na track na may backup mula sa isang PDP11 na computer. Mahirap malaman kung gumagana ito, ngunit mayroong isang buo na /usr/src/ puno kung saan ang karamihan sa mga file ay minarkahan ng 1979, na kahit noon ay mukhang sinaunang. Iyon ay ang ikapitong edisyon, o isang PWB derivative, naisip ko.
Kinuha ko ang paghahanap bilang batayan at manu-manong na-edit ang mga mapagkukunan sa estado ng ikaanim na edisyon. Ang bahagi ng code ay nanatiling pareho, ang bahagi ay kailangang bahagyang i-edit, binabago ang modernong token += sa hindi na ginagamit na =+. May na-delete lang, at kailangang ganap na isulat muli, ngunit hindi masyado.
At ngayon mababasa natin online sa TUHS ang source code ng ikaanim na edisyon ng
Sa pamamagitan ng paraan, sa unang tingin, ang pangunahing tampok ng C-code bago ang panahon ng Kernighan at Ritchie ay ang kaiklian. Hindi madalas na nakakapagpasok ako ng mga snippet ng code nang walang malawak na pag-edit upang magkasya sa isang medyo makitid na lugar ng display sa aking site.
Maagang
/*
* 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
Ang laki ng buffer ay hindi nagbago mula noong ika-apat na edisyon. Ngunit dito nakikita natin, nang walang anumang pampublikong dokumentasyon, na ang mga pipeline ay minsang gumamit ng mga file bilang fallback storage!
Tulad ng para sa mga file na LARG, tumutugma sila sa
Narito ang tunay na tawag sa sistema 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;
}
Ang komento ay malinaw na naglalarawan kung ano ang nangyayari dito. Ngunit hindi ganoon kadaling maunawaan ang code, bahagyang dahil sa kung paano "R0
ΠΈ R1
naipasa ang mga parameter ng system call at return value.
Subukan natin
pipe()
dahil sa pamamagitan ng R0
ΠΈ R1
ibalik ang mga numero ng deskriptor ng file para sa pagbabasa at pagsulat. falloc()
nagbabalik ng pointer sa isang istraktura ng file, ngunit "nagbabalik" din sa pamamagitan ng u.u_ar0[R0]
at isang file descriptor. Iyon ay, ang code ay naka-imbak sa r
file descriptor para sa pagbabasa at nagtatalaga ng descriptor para sa pagsusulat nang direkta mula sa u.u_ar0[R0]
pagkatapos ng pangalawang tawag falloc()
.
I-flag FPIPE
, na itinakda namin kapag lumilikha ng pipeline, kumokontrol sa pag-uugali ng function
/*
* 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);
}
/* β¦ */
}
Pagkatapos ang function readp()
Π² pipe.c
nagbabasa ng data mula sa pipeline. Ngunit mas mahusay na subaybayan ang pagpapatupad simula sa writep()
. Muli, ang code ay naging mas kumplikado dahil sa likas na katangian ng argumentong pumasa sa convention, ngunit ang ilang mga detalye ay maaaring tanggalin.
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;
}
Gusto naming magsulat ng mga byte sa input ng pipeline u.u_count
. Una kailangan nating i-lock ang inode (tingnan sa ibaba plock
/prele
).
Pagkatapos ay suriin namin ang bilang ng sanggunian ng inode. Hangga't mananatiling bukas ang magkabilang dulo ng pipeline, dapat na 2 ang counter. Kumapit kami sa isang link (mula sa rp->f_inode
), kaya kung ang counter ay mas mababa sa 2, nangangahulugan ito na ang proseso ng pagbabasa ay isinara ang dulo nito ng pipeline. Sa madaling salita, sinusubukan naming sumulat sa isang saradong pipeline, na isang pagkakamali. Unang error code EPIPE
at signal SIGPIPE
lumitaw sa ikaanim na edisyon ng Unix.
Ngunit kahit na bukas ang conveyor, maaari itong puno. Sa kasong ito, pinakawalan namin ang lock at natutulog sa pag-asa na ang isa pang proseso ay magbabasa mula sa pipeline at magbakante ng sapat na espasyo sa loob nito. Kapag nagising tayo, bumalik tayo sa umpisa, isasabit muli ang lock at magsimula ng bagong yugto ng pagsulat.
Kung mayroong sapat na libreng espasyo sa pipeline, pagkatapos ay isusulat namin ang data dito gamit i_size1
ang inode'a (na may walang laman na pipeline ay maaaring katumbas ng 0) ay tumuturo sa dulo ng data na naglalaman na nito. Kung may sapat na espasyo para magsulat, maaari naming punan ang pipeline mula sa i_size1
sa PIPESIZ
. Pagkatapos ay ilalabas namin ang lock at subukang gisingin ang anumang proseso na naghihintay na basahin mula sa pipeline. Bumalik kami sa simula upang makita kung nagawa naming magsulat ng maraming byte hangga't kailangan namin. Kung hindi, magsisimula kami ng bagong ikot ng pag-record.
Karaniwang parameter i_mode
inode ay ginagamit upang mag-imbak ng mga pahintulot r
, w
ΠΈ x
. Ngunit sa kaso ng mga pipeline, senyales namin na ang ilang proseso ay naghihintay para sa pagsulat o pagbabasa gamit ang mga bit IREAD
ΠΈ IWRITE
ayon sa pagkakabanggit. Ang proseso ay nagtatakda ng bandila at mga tawag sleep()
, at inaasahan na sa hinaharap ay tatawag ang ibang proseso wakeup()
.
Ang totoong magic ay nangyayari sa sleep()
ΠΈ wakeup()
. Ang mga ito ay ipinatupad sa
/*
* 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) /* β¦ */
Ang proseso na tumatawag sleep()
para sa isang partikular na channel, maaaring magising sa ibang pagkakataon ng isa pang proseso, na tatawag wakeup()
para sa parehong channel. writep()
ΠΈ readp()
i-coordinate ang kanilang mga aksyon sa pamamagitan ng mga ipinares na tawag. tandaan mo yan pipe.c
laging priority PPIPE
kapag tinawag sleep()
, sa lahat sleep()
maaaring maputol ng isang senyas.
Ngayon mayroon kaming lahat upang maunawaan ang pag-andar 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);
}
Maaaring mas madali mong basahin ang function na ito mula sa ibaba hanggang sa itaas. Ang sangay na "basahin at ibalik" ay karaniwang ginagamit kapag mayroong ilang data sa pipeline. Sa kasong ito, ginagamit namin f_offset
basahin, at pagkatapos ay i-update ang halaga ng katumbas na offset.
Sa mga kasunod na pagbabasa, magiging walang laman ang pipeline kung umabot na ang read offset i_size1
sa inode. Ni-reset namin ang posisyon sa 0 at sinusubukang gisingin ang anumang proseso na gustong sumulat sa pipeline. Alam natin na kapag puno na ang conveyor, writep()
matulog sa ip+1
. At ngayong walang laman ang pipeline, maaari natin itong gisingin para ipagpatuloy ang ikot ng pagsulat nito.
Kung walang babasahin, eh readp()
maaaring magtakda ng bandila IREAD
at matutulog na ip+2
. Alam natin kung ano ang magigising sa kanya writep()
kapag nagsusulat ito ng ilang data sa pipeline.
Mga komento sa u
Β» maaari naming ituring ang mga ito tulad ng mga regular na function ng I/O na kumukuha ng file, posisyon, buffer sa memorya, at bilangin ang bilang ng mga byte na babasahin o isusulat.
/*
* 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;
/* β¦ */
Kung tungkol sa "konserbatibo" na pagharang, kung gayon readp()
ΠΈ writep()
i-lock ang mga inode hanggang sa matapos o makakuha ng resulta (i.e. call wakeup
). plock()
ΠΈ prele()
gumana nang simple: gamit ang ibang hanay ng mga tawag sleep
ΠΈ wakeup
hayaan kaming gisingin ang anumang proseso na nangangailangan ng lock na kakalabas lang namin:
/*
* 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);
}
}
Noong una hindi ko maintindihan kung bakit readp()
hindi nagiging sanhi ng prele(ip)
bago ang tawag wakeup(ip+1)
. Ang unang bagay writep()
tawag sa loop nito, ito plock(ip)
, na nagreresulta sa isang deadlock kung readp()
ay hindi pa naalis ang block nito, kaya dapat kahit papaano ay gumana nang tama ang code. Kung titingnan mo wakeup()
, nagiging malinaw na minarkahan lamang nito ang proseso ng pagtulog bilang handa na para sa pagpapatupad, upang sa hinaharap sched()
talagang inilunsad ito. Kaya readp()
sanhi wakeup()
, ina-unlock, itinatakda IREAD
at mga tawag sleep(ip+2)
- lahat ng ito dati writep()
restart ang cycle.
Kinukumpleto nito ang paglalarawan ng mga pipeline sa ikaanim na edisyon. Simpleng code, malalayong implikasyon.
Xv6, isang simpleng kernel na katulad ng Unix
Upang lumikha ng isang nucleus
Ang code ay naglalaman ng isang malinaw at maalalahaning pagpapatupad 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()
nagtatakda ng estado ng lahat ng natitirang pagpapatupad, na kinabibilangan ng mga function piperead()
, pipewrite()
ΠΈ pipeclose()
. Ang aktwal na tawag sa sistema sys_pipe
ay isang wrapper na ipinatupad sa
Linux 0.01
Mahahanap mo ang source code para sa Linux 0.01. Magiging nakapagtuturo na pag-aralan ang pagpapatupad ng mga pipeline sa kanyang fs
/pipe.c
. Dito, ginagamit ang inode upang kumatawan sa pipeline, ngunit ang pipeline mismo ay nakasulat sa modernong C. Kung na-hack mo ang iyong paraan sa pamamagitan ng code ng ikaanim na edisyon, hindi ka magkakaroon ng anumang problema dito. Ito ang hitsura ng function 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;
}
Kahit na hindi tinitingnan ang mga kahulugan ng istruktura, maaari mong malaman kung paano ginagamit ang bilang ng sanggunian ng inode upang suriin kung ang isang write operation ay nagreresulta sa SIGPIPE
. Bilang karagdagan sa byte-by-byte na gawain, ang function na ito ay madaling ihambing sa mga ideya sa itaas. Kahit logic sleep_on
/wake_up
mukhang hindi alien.
Mga Makabagong Linux Kernel, FreeBSD, NetBSD, OpenBSD
Mabilis akong nagpunta sa ilang modernong kernels. Wala sa kanila ang mayroon nang disk-based na pagpapatupad (hindi nakakagulat). Ang Linux ay may sariling pagpapatupad. At bagama't ang tatlong modernong BSD kernels ay naglalaman ng mga pagpapatupad batay sa code na isinulat ni John Dyson, sa paglipas ng mga taon sila ay naging masyadong naiiba sa isa't isa.
Upang basahin fs
/pipe.c
(sa Linux) o sys
/kern
/sys_pipe.c
(sa *BSD), kailangan ng tunay na dedikasyon. Ang pagganap at suporta para sa mga tampok tulad ng vector at asynchronous na I/O ay mahalaga sa code ngayon. At ang mga detalye ng paglalaan ng memorya, mga kandado, at pagsasaayos ng kernel ay lubos na nag-iiba. Hindi ito ang kailangan ng mga unibersidad para sa isang panimulang kurso sa mga operating system.
Sa anumang kaso, kawili-wili para sa akin na mahukay ang ilang mga lumang pattern (halimbawa, pagbuo SIGPIPE
at bumalik EPIPE
kapag sumusulat sa isang saradong pipeline) sa lahat ng ito, napaka-iba, modernong kernels. Malamang na hindi ako makakakita ng isang PDP-11 na computer nang live, ngunit marami pa ring matututunan mula sa code na isinulat ilang taon bago ako isinilang.
Isinulat ni Divi Kapoor noong 2011, ang artikulong "
Pinagmulan: www.habr.com