Ez a cikk a folyamatok megvalósítását írja le a Unix kernelben. Kissé csalódott voltam, hogy egy nemrégiben megjelent cikk a következő címmel jelent meg:
Miről beszélünk?
A csővezetékek, „valószínűleg a legfontosabb találmány a Unixban”, meghatározó jellemzői a kis programok összekapcsolásának alapjául szolgáló Unix filozófiának, valamint ismerős jel a parancssorban:
$ echo hello | wc -c
6
Ez a funkció a kernel által biztosított rendszerhívástól függ pipe
, amely leírása a dokumentációs oldalakon található
A csővezetékek egyirányú csatornát biztosítanak a folyamatok közötti kommunikációhoz. A csővezetéknek van bemenete (írási vége) és kimenete (olvasási vége). A csővezeték bemenetére írt adatok a kimeneten olvashatók.
A folyamat a hívás segítségével jön létre
pipe(2)
, amely két fájlleírót ad vissza: az egyik a folyamat bemenetére, a másik a kimenetre vonatkozik.
A fenti parancs nyomkövetési kimenete mutatja a folyamat létrehozását és az azon keresztüli adatáramlást egyik folyamatból a másikba:
$ 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
A szülő folyamat hív pipe()
csatolt fájlleírók beszerzéséhez. Egy utódfolyamat az egyik kezelőhöz ír, egy másik folyamat pedig egy másik kezelőből olvassa be ugyanazokat az adatokat. A shell a dup2-t használja a 3. és 4. leíró átnevezéséhez, hogy megfeleljen az stdin és az stdout.
Csövek nélkül a parancsértelmezőnek az egyik folyamat eredményét egy fájlba kellene írnia, és át kellene adnia egy másik folyamatnak, hogy kiolvassa az adatokat a fájlból. Ennek eredményeként több erőforrást és lemezterületet pazarolnánk el. A csővezetékek azonban nem csak azért jók, mert lehetővé teszik az ideiglenes fájlok használatának elkerülését:
Ha egy folyamat egy üres csővezetékből próbál olvasni, akkor
read(2)
blokkolja, amíg az adatok elérhetővé nem válnak. Ha egy folyamat egy teljes folyamatra próbál írni, akkorwrite(2)
blokkolja, amíg elegendő adatot nem olvasott ki a folyamatból az írás végrehajtásához.
A POSIX-követelményhez hasonlóan ez is egy fontos tulajdonság: írás a folyamatba ig PIPE_BUF
bájtoknak (legalább 512) atominak kell lenniük, hogy a folyamatok úgy kommunikálhassanak egymással a csővezetéken keresztül, ahogy a normál fájlok (amelyek nem nyújtanak ilyen garanciákat) nem.
Normál fájl használatakor egy folyamat az összes kimenetét ráírhatja, és továbbadhatja egy másik folyamatnak. Vagy a folyamatok rendkívül párhuzamos üzemmódban is működhetnek, külső jelzőmechanizmus (például szemafor) segítségével értesítik egymást, ha az írás vagy olvasás befejeződött. A szállítószalagok megmentenek minket ettől a sok gondtól.
Mit keresünk?
Egyszerűen elmagyarázom, hogy könnyebben el tudja képzelni, hogyan működik egy szállítószalag. Le kell foglalnia egy puffert és néhány állapotot a memóriában. Funkciókra lesz szüksége adatok hozzáadásához és eltávolításához a pufferből. Szüksége lesz bizonyos eszközökre a függvények meghívásához a fájlleírók olvasási és írási műveletei során. A fent leírt speciális viselkedés megvalósításához pedig zárakra lesz szükség.
Most készen állunk arra, hogy erős lámpafény mellett lekérdezzük a kernel forráskódját, hogy megerősítsük vagy megcáfoljuk homályos mentális modellünket. De mindig készülj fel a váratlanra.
Hol keresünk?
Nem tudom, hol van a híres könyvem példánya "
A TUHS archívumában való barangolás olyan, mint egy múzeum látogatása. Megtekinthetjük közös történelmünket, és tisztelettel fejezem ki azt a sok éves erőfeszítést, hogy ezt az anyagot apránként visszanyerjük a régi szalagokról és nyomatokról. És nagyon tisztában vagyok azokkal a töredékekkel, amelyek még mindig hiányoznak.
Miután kielégítettük a szállítószalagok ókori története iránti kíváncsiságunkat, összehasonlításképpen a modern kerneleket tekinthetjük meg.
By the way, pipe
a 42-es rendszerhívás a táblázatban sysent[]
. Véletlen egybeesés?
Hagyományos Unix kernelek (1970-1974)
Nem találtam nyomokat pipe(2)
sem benne
A TUHS ezt állítja
A Unix 1973rd Edition volt az utolsó assembly nyelven írt kernellel rendelkező verzió, de egyben az első csővezetékes verzió is. XNUMX folyamán dolgoztak a harmadik kiadás fejlesztésén, a kernelt átírták C nyelven, így megjelent a Unix negyedik kiadása.
Az egyik olvasó talált egy beszkennelt dokumentumot, amelyben Doug McIlroy a „programok kerti tömlőként való összekapcsolásának” ötletét javasolta.
Brian Kernighan könyvében
Amikor megjelent a Unix, a korutinok iránti rajongásom arra késztetett, hogy megkérjem az operációs rendszer szerzőjét, Ken Thompsont, engedje meg, hogy egy folyamatba írt adatok ne csak az eszközre kerüljenek, hanem egy másik folyamathoz is. Ken úgy döntött, hogy lehetséges. Minimalistaként azonban azt akarta, hogy minden rendszerfunkció jelentős szerepet kapjon. Valóban nagy előny a folyamatok közötti közvetlen írás a köztes fájlba való írással szemben? Ken végül csak akkor kiáltott fel, amikor konkrét javaslatot tettem a fülbemászó „pipeline” névvel és a folyamatok közötti interakció szintaxisának leírásával: „Megcsinálom!”
És meg is tette. Egy végzetes estén Ken megváltoztatta a rendszermagot és a shellt, több szabványos programot javított, hogy szabványosítsa a bemenetek fogadását (amelyek csővezetékről származhatnak), és megváltoztatta a fájlneveket is. A következő napon a csővezetékeket nagyon széles körben kezdték használni az alkalmazásokban. A hét végére a titkárok a szövegszerkesztőkből dokumentumokat küldtek a nyomtatóba. Kicsit később Ken lecserélte az eredeti API-t és szintaxist a csővezetékek használatának tisztább konvencióira, amelyeket azóta is használnak.
Sajnos a harmadik kiadású Unix kernel forráskódja elveszett. És bár a kernel forráskódja C-ben van írva
Szöveges dokumentációval rendelkezünk pipe(2)
mindkét kiadásból, így a dokumentációban való kereséssel kezdheti pipe(2)
assembly nyelven íródott, és csak egy fájlleírót ad vissza, de már biztosítja az elvárt alapfunkciókat:
Rendszerhívás cső létrehoz egy bemeneti/kimeneti mechanizmust, amelyet pipeline-nek neveznek. A visszaadott fájlleíró használható olvasási és írási műveletekhez. Amikor valamit a folyamatba írunk, legfeljebb 504 bájtnyi adat pufferelődik, majd az írási folyamat felfüggesztésre kerül. A csővezetékből történő olvasáskor a pufferelt adatok eltávolításra kerülnek.
A következő évben a kernelt átírták C nyelven, és pipe(fildes)
"
Rendszerhívás cső létrehoz egy bemeneti/kimeneti mechanizmust, amelyet pipeline-nek neveznek. A visszaadott fájlleírók olvasási és írási műveletekben használhatók. Amikor valamit írunk a folyamatba, akkor az r1-ben visszaadott kezelő (ill. fildes[1]) kerül felhasználásra, 4096 bájtnyi adatra pufferelve, majd az írási folyamat felfüggesztésre kerül. A csővezetékből történő olvasáskor az r0-ra visszaadott kezelő (illetve fildes[0]) veszi az adatokat.
Feltételezzük, hogy a folyamat definiálása után két (vagy több) kommunikációs folyamat (amelyeket a következő hívások hoznak létre villa) hívások segítségével továbbítja az adatokat a csővezetékről olvas и ír.
A héjnak van egy szintaxisa folyamatok lineáris tömbjének definiálására, amelyeket egy csővezeték köt össze.
Egy üres (pufferelt adatokat nem tartalmazó) csővezetékből történő olvasási hívások, amelyeknek csak az egyik vége van (az írási fájlleírók zárva vannak), a „fájl vége” értéket adják vissza. A hasonló helyzetben történő írásra vonatkozó felhívásokat figyelmen kívül hagyjuk.
Legkorábban
A Unix hatodik kiadása (1975)
Kezdjük el olvasni a Unix forráskódot
Sok éven át a könyv Nevezetességek volt az egyetlen dokumentum a Unix kernelről, amely a Bell Labson kívül elérhető. Bár a hatodik kiadású licenc lehetővé tette a tanárok számára a forráskód használatát, a hetedik kiadású licenc kizárta ezt a lehetőséget, így a könyvet illegális géppel írt példányok formájában terjesztették.
Ma már megvásárolható egy utánnyomás a könyvből, melynek borítóján a diákok másológépnél láthatók. És hála Warren Toomeynek (aki elindította a TUHS projektet) letöltheti
Több mint 15 évvel ezelőtt begépeltem a megadott forráskód másolatát Nevezetességek, mert ismeretlen számú más példánytól nem tetszett a másolatom minősége. A TUHS még nem létezett, és nem fértem hozzá a régi forrásokhoz. De 1988-ban találtam egy régi 9 sávos szalagot, amely egy PDP11 számítógépről készült biztonsági másolatot tartalmazott. Nehéz volt megállapítani, hogy működik-e, de volt egy sértetlen /usr/src/ fa, amelyben a legtöbb fájl 1979-es évszámmal volt ellátva, ami még akkor is ősinek tűnt. Ez volt a hetedik kiadás vagy annak származéka, a PWB, ahogy hittem.
A leletet vettem alapul, és manuálisan szerkesztettem a forrásokat a hatodik kiadásig. A kód egy része ugyanaz maradt, de néhányat kissé szerkeszteni kellett, a modern += tokent az elavult =+-ra cserélve. Néhány dolgot egyszerűen töröltek, néhányat pedig teljesen át kellett írni, de nem túl sokat.
Ma pedig online olvashatjuk a TUHS-en a hatodik kiadás forráskódját
Mellesleg, első pillantásra a Kernighan és Ritchie időszaka előtti C-kód fő jellemzője az tömörség. Nem gyakran fordul elő, hogy részletes szerkesztés nélkül tudok kódrészleteket beszúrni, hogy elférjen a webhelyem viszonylag szűk megjelenítési területe.
Korai
/*
* 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
A puffer mérete nem változott a negyedik kiadás óta. De itt látjuk, minden nyilvános dokumentáció nélkül, hogy a csővezetékek egykor fájlokat használtak biztonsági mentési tárolóként!
Ami a LARG fájlokat illeti, ezek megfelelnek a
Itt az igazi rendszerhívás 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;
}
A komment egyértelműen leírja, mi folyik itt. De a kód megértése nem olyan egyszerű, részben az út miatt."R0
и R1
rendszerhívási paraméterek és visszatérési értékek kerülnek átadásra.
Próbáljuk meg
pipe()
keresztül kell mennie R0
и R1
visszaadja a fájlleíró számokat olvasáshoz és íráshoz. falloc()
mutatót ad vissza a fájlszerkezetre, de "visszaadja" a via u.u_ar0[R0]
és egy fájlleíró. Vagyis a kód elmentődik r
fájlleíró az olvasáshoz, és hozzárendel egy fájlleírót a közvetlen íráshoz u.u_ar0[R0]
a második hívás után falloc()
.
zászló FPIPE
, amelyet a folyamat létrehozásakor állítunk be, szabályozza a függvény viselkedését
/*
* 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);
}
/* … */
}
Aztán a függvény readp()
в pipe.c
adatokat olvas be a csővezetékből. De jobb, ha nyomon követi a megvalósítást, kezdve writep()
. A kód ismét összetettebbé vált az argumentumok átadásának konvenciói miatt, de néhány részlet elhagyható.
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;
}
Bájtokat akarunk írni a csővezeték bemenetére u.u_count
. Először zárolnunk kell az inódot (lásd alább plock
/prele
).
Ezután ellenőrizzük az inode referenciaszámlálót. Amíg a csővezeték mindkét vége nyitva marad, a számlálónak egyenlőnek kell lennie 2-vel. Egy linket tartunk (a rp->f_inode
), tehát ha a számláló 2-nél kisebb, az azt jelenti, hogy az olvasási folyamat lezárta a csővezeték végét. Más szóval, egy zárt csővezetékre próbálunk írni, és ez hiba. Első hibakód EPIPE
és jelezze SIGPIPE
a Unix hatodik kiadásában jelent meg.
De még akkor is, ha a szállítószalag nyitva van, tele lehet. Ebben az esetben feloldjuk a zárat és aludni megyünk abban a reményben, hogy egy másik folyamat kiolvas a csővezetékből, és elegendő helyet szabadít fel benne. Miután felébredtünk, visszatérünk az elejére, ismét leteszzük a zárat és új felvételi ciklusba kezdünk.
Ha van elég szabad hely a folyamatban, akkor a segítségével adatokat írunk rá i_size1
az inode-nál (ha a folyamat üres, akkor egyenlő lehet 0-val) jelzi a már benne lévő adatok végét. Ha van elegendő felvételi hely, akkor a csővezetéket meg tudjuk tölteni i_size1
a PIPESIZ
. Ezután feloldjuk a zárat, és megpróbálunk felébreszteni minden olyan folyamatot, amely arra vár, hogy olvassa a folyamatot. Visszamegyünk az elejére, hogy megnézzük, képesek vagyunk-e annyi bájtot írni, amennyire szükségünk van. Ha nem sikerül, akkor új felvételi ciklust kezdünk.
Általában a paraméter i_mode
Az inode az engedélyek tárolására szolgál r
, w
и x
. A csővezetékek esetében azonban bitek segítségével jelezzük, hogy valamilyen folyamat írásra vagy olvasásra vár IREAD
и IWRITE
illetőleg. A folyamat beállítja a zászlót és hív sleep()
, és várhatóan a jövőben valamilyen más folyamat fog okozni wakeup()
.
Az igazi varázslat benne történik sleep()
и wakeup()
. ben valósulnak meg
/*
* 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) /* … */
A folyamat, ami okozza sleep()
egy adott csatorna esetében később egy másik folyamat felébresztheti, ami azt okozza wakeup()
ugyanarra a csatornára. writep()
и readp()
összehangolni tevékenységeiket ilyen páros hívásokon keresztül. vegye figyelembe, hogy pipe.c
mindig elsőbbséget ad PPIPE
amikor hívják sleep()
, Szóval ennyi sleep()
jelzés megszakíthatja.
Most már minden megvan a funkció megértéséhez 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);
}
Lehet, hogy könnyebben elolvashatja ezt a funkciót alulról felfelé. Az "olvasás és visszaküldés" ágat általában akkor használják, ha van néhány adat a folyamatban. Ebben az esetben használjuk f_offset
leolvasását, majd frissítse a megfelelő eltolás értékét.
A következő olvasásoknál a folyamat üres lesz, ha az olvasási eltolás elérte i_size1
inode-nál. Visszaállítjuk a pozíciót 0-ra, és megpróbálunk felébreszteni minden olyan folyamatot, amely írni akar a folyamatba. Tudjuk, hogy amikor a szállítószalag megtelik, writep()
elalszik tovább ip+1
. És most, hogy a folyamat üres, felébreszthetjük az írási ciklus folytatásához.
Ha nincs mit olvasni, akkor readp()
zászlót állíthat fel IREAD
és aludj tovább ip+2
. Tudjuk, mi ébreszti fel writep()
, amikor néhány adatot ír a folyamatba.
Megjegyzések a u
"Kezelhetjük őket normál I/O függvényekként, amelyek egy fájlt, egy pozíciót, egy puffert vesznek fel a memóriában, és megszámolják az olvasandó vagy írandó bájtok számát.
/*
* 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;
/* … */
Ami tehát a „konzervatív” blokkolást illeti readp()
и writep()
blokkolja az inodot, amíg be nem fejezik a munkájukat vagy eredményt nem kapnak (vagyis hívják wakeup
). plock()
и prele()
egyszerűen működik: más híváskészlettel sleep
и wakeup
lehetővé teszi számunkra, hogy felébresszünk minden olyan folyamatot, amelyhez az imént feloldott zárra van szükség:
/*
* 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);
}
}
Először nem értettem, miért readp()
nem okoz prele(ip)
a hívás előtt wakeup(ip+1)
. Az első dolog az writep()
ciklusában okozza ezt plock(ip)
, ami holtponthoz vezet, ha readp()
még nem távolítottam el a blokkolásomat, így valahogy a kódnak megfelelően kell működnie. Ha megnézed wakeup()
, akkor világossá válik, hogy csak az alvási folyamatot jelöli meg végrehajtásra késznek, így a jövőben sched()
tényleg elindította. Így readp()
okai wakeup()
, eltávolítja a zárat, beállítja IREAD
és hív sleep(ip+2)
- mindezt korábban writep()
folytatja a ciklust.
Ezzel befejeződik a szállítószalagok leírása a hatodik kiadásban. Egyszerű kód, messzemenő következmények.
Xv6, egy egyszerű Unix-szerű kernel
A kernel létrehozásához
A kód világos és átgondolt megvalósítást tartalmaz 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()
beállítja a többi megvalósítás állapotát, amely magában foglalja a funkciókat is piperead()
, pipewrite()
и pipeclose()
. Valós rendszerhívás sys_pipe
-ban implementált burkoló
Linux 0.01
Linux 0.01 forráskód található. Tanulságos lesz tanulmányozni a csővezetékek megvalósítását az övében fs
/pipe.c
. Ez egy inode-ot használ a folyamat ábrázolására, de maga a folyamat a modern C-ben van írva. Ha végigdolgoztad a 6. kiadás kódját, itt nem lesz gond. Így néz ki a függvény 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;
}
A struktúra definíciók megtekintése nélkül is kitalálhatja, hogy az inode hivatkozási szám hogyan használható annak ellenőrzésére, hogy egy írási művelet eredményeként SIGPIPE
. Amellett, hogy bájtonként működik, ez a funkció könnyen összehasonlítható a fent leírt ötletekkel. Még a logika is sleep_on
/wake_up
nem tűnik olyan idegennek.
Modern Linux kernelek, FreeBSD, NetBSD, OpenBSD
Gyorsan átfutottam néhány modern kernelen. Egyiknek sincs már lemezmegvalósítása (nem meglepő). A Linuxnak megvan a maga megvalósítása. Bár a három modern BSD kernel tartalmaz olyan implementációkat, amelyek John Dyson által írt kódon alapulnak, az évek során túlságosan különböztek egymástól.
Olvasni fs
/pipe.c
(Linuxon) ill sys
/kern
/sys_pipe.c
(*BSD-n), valódi odaadást igényel. A mai kód a teljesítményről és az olyan funkciók támogatásáról szól, mint a vektoros és az aszinkron I/O. A memóriafoglalás, a zárolások és a kernelkonfiguráció részletei pedig nagyon eltérőek. A főiskoláknak nem erre van szükségük egy bevezető operációs rendszer-tanfolyamhoz.
Mindenesetre érdekelt néhány régi minta előásása (például generálás SIGPIPE
és vissza EPIPE
amikor zárt csővezetékre írunk) mindezekben a különböző modern kernelekben. Valószínűleg soha nem fogok látni PDP-11 számítógépet a valóságban, de még mindig sokat kell tanulnom abból a kódból, amelyet évekkel a születésem előtt írtak.
Divi Kapoor 2011-ben írt cikke:
Forrás: will.com