Бұл мақалада Unix ядросында конвейерлерді іске асыру сипатталған. «Жақында жарияланған мақала мені біраз ренжітті.
Сіз не туралы айтып отырсыз?
Құбырлар - «Unix-тегі ең маңызды өнертабыс» - Unix-тің шағын бағдарламаларды біріктіру философиясының айқындаушы ерекшелігі және таныс командалық ұран:
$ echo hello | wc -c
6
Бұл функция ядромен қамтамасыз етілген жүйелік шақыруға байланысты pipe
, ол құжаттама беттерінде сипатталған
Құбырлар процесс аралық байланыс үшін бір жақты арнаны қамтамасыз етеді. Құбырдың кірісі (жазудың соңы) және шығысы (оқылатыны) бар. Құбырдың кірісіне жазылған деректерді шығыста оқуға болады.
Құбыр шақыру арқылы жасалады
pipe(2)
, ол екі файл дескрипторын қайтарады: біреуі құбырдың кірісіне, екіншісі шығысқа сілтеме жасайды.
Жоғарыда келтірілген пәрменнің бақылау шығысы конвейердің жасалуын және ол арқылы бір процесстен екіншісіне деректер ағынын көрсетеді:
$ 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
Ата-аналық процесс шақырады pipe()
тіркелген файл дескрипторларын алу үшін. Бір еншілес процесс бір дескрипторға жазады, ал басқа процесс басқа дескриптордан бірдей деректерді оқиды. Қабық stdin және stdout сәйкестендіру үшін dup2 бар 3 және 4 дескрипторларының "атын өзгертеді".
Құбырларсыз қабық бір процестің нәтижесін файлға жазып, файлдағы деректерді оқу үшін оны басқа процеске жіберуі керек еді. Нәтижесінде біз көбірек ресурстар мен дискілік кеңістікті босқа кетіретін едік. Дегенмен, құбырлар уақытша файлдарды болдырмау үшін ғана жақсы:
Егер процесс бос құбырдан оқуға тырысса, онда
read(2)
деректер қолжетімді болғанша блоктайды. Егер процесс толық конвейерге жазуға тырысса, ондаwrite(2)
жазуды аяқтау үшін конвейерден жеткілікті деректер оқылғанша блоктайды.
POSIX талабы сияқты, бұл маңызды қасиет: құбырға дейін жазу PIPE_BUF
байт (кемінде 512) атомдық болуы керек, осылайша процестер бір-бірімен конвейер арқылы қалыпты файлдар (мұндай кепілдіктерді қамтамасыз етпейтін) байланыса алмайды.
Кәдімгі файлдың көмегімен процесс оған өзінің барлық шығысын жазып, оны басқа процесске бере алады. Немесе процестер жазу немесе оқудың аяқталуы туралы бір-бірін хабарлау үшін сыртқы сигнал беру механизмін (семафор сияқты) пайдалана отырып, қатты параллель режимде жұмыс істей алады. Конвейерлер бізді осы қиындықтардан құтқарады.
Біз не іздейміз?
Конвейердің қалай жұмыс істейтінін елестетуді жеңілдету үшін мен саусақтарыммен түсіндіремін. Жадта буфер мен кейбір күйді бөлу қажет болады. Буферден деректерді қосу және жою үшін сізге функциялар қажет болады. Файл дескрипторларында оқу және жазу операциялары кезінде функцияларды шақыру үшін сізге кейбір мүмкіндіктер қажет болады. Ал құлыптар жоғарыда сипатталған ерекше мінез-құлықты жүзеге асыру үшін қажет.
Енді біз анық емес психикалық модельімізді растау немесе жоққа шығару үшін жарық шамның астында ядроның бастапқы кодын сұрауға дайынбыз. Бірақ әрқашан күтпеген жағдайға дайын болыңыз.
Біз қайда қарап тұрмыз?
Әйгілі кітабымның қайда жатқанын білмеймін.
TUHS мұрағаттарын аралау мұражайға бару сияқты. Біз ортақ тарихымызды қарастыра аламыз және мен ескі кассеталар мен басып шығарылған материалдардан осы материалдың барлығын аздап қалпына келтіруге жұмсалған көп жылғы күш-жігерді құрметтеймін. Мен әлі де жетіспейтін фрагменттерді жақсы білемін.
Құбырлардың көне тарихына деген қызығушылығымызды қанағаттандыра отырып, біз салыстыру үшін заманауи ядроларды қарастыра аламыз.
Айтпақшы, pipe
кестедегі 42 жүйелік қоңырау нөмірі болып табылады sysent[]
. Кездейсоқ па?
Дәстүрлі Unix ядролары (1970–1974)
Мен ешқандай із таппадым pipe(2)
ішінде де
Бұл туралы TUHS мәлімдейді
Unix-тің үшінші шығарылымы ассемблерде жазылған ядросы бар соңғы нұсқасы, сонымен қатар конвейерлері бар бірінші нұсқасы болды. 1973 жылы үшінші басылымды жетілдіру жұмыстары жүргізілді, ядро Си тілінде қайта жазылды, осылайша Unix-тің төртінші басылымы дүниеге келді.
Бір оқырман Даг Макилрой «бағдарламаларды бақша шлангісі сияқты қосу» идеясын ұсынған құжаттың сканерін тапты.
Брайан Керниганның кітабында
Unix пайда болған кезде, менің корутиндерге деген құмарлығым мені ОЖ авторы Кен Томпсоннан қандай да бір процесске жазылған деректердің құрылғыға ғана емес, сонымен қатар басқа процеске шығуына да рұқсат беруін өтінді. Кен бұл мүмкін деп ойлады. Дегенмен, минималист ретінде ол әрбір жүйелік мүмкіндіктің маңызды рөл атқарғанын қалады. Процестер арасында тікелей жазу шынымен аралық файлға жазудан үлкен артықшылық па? Мен «құбыр» деген қызықты атаумен және процестердің өзара әрекеттесу синтаксисінің сипаттамасымен нақты ұсыныс жасағанда ғана, Кен ақыры: «Мен мұны істеймін!» деп айқайлады.
Және жасады. Бір тағдырлы кеште Кен ядро мен қабықты өзгертті, енгізуді қалай қабылдайтынын стандарттау үшін бірнеше стандартты бағдарламаларды бекітті (ол құбырдан келуі мүмкін) және файл атауларын өзгертті. Келесі күні құбырлар қосымшаларда өте кең қолданылды. Аптаның соңына қарай хатшылар оларды мәтіндік процессорлардан принтерге құжаттарды жіберу үшін пайдаланды. Біраз уақыттан кейін Кен түпнұсқа API және құбырларды пайдалануды содан бері қолданылып келе жатқан таза конвенциялармен орау үшін синтаксисті ауыстырды.
Өкінішке орай, Unix ядросының үшінші шығарылымының бастапқы коды жоғалды. Бізде ядроның бастапқы коды C тілінде жазылғанымен
Бізде құжаттама мәтіні бар pipe(2)
екі шығарылымнан да, сондықтан құжаттаманы іздеуден бастауға болады pipe(2)
ассемблерде жазылған және тек бір файл дескрипторын қайтарады, бірақ күтілетін негізгі функционалдылықты қамтамасыз етеді:
Жүйелік қоңырау Құбыр конвейер деп аталатын енгізу/шығару механизмін жасайды. Қайтарылған файл дескрипторын оқу және жазу әрекеттері үшін пайдалануға болады. Құбырға бірдеңе жазылғанда, ол 504 байтқа дейін деректерді буферлейді, содан кейін жазу процесі тоқтатылады. Құбырдан оқу кезінде буферленген деректер алынады.
Келесі жылы ядро C тілінде қайта жазылды және pipe(fildes)
«:
Жүйелік қоңырау Құбыр конвейер деп аталатын енгізу/шығару механизмін жасайды. Қайтарылған файл дескрипторларын оқу және жазу операцияларында пайдалануға болады. Құбырға бірдеңе жазылғанда, r1-де қайтарылған дескриптор (респ. fildes[1]) пайдаланылады, 4096 байт деректерге дейін буферленеді, содан кейін жазу процесі тоқтатылады. Конвейерден оқу кезінде r0-ге қайтарылған дескриптор (респ. файлдар[0]) деректерді қабылдайды.
Құбыр анықталғаннан кейін екі (немесе одан да көп) өзара әрекеттесетін процестер (кейінгі шақырулар арқылы жасалған) деп болжанады. Шанышқы) қоңырауларды пайдаланып құбырдан деректерді жібереді оқу и жазу.
Қабықта құбыр арқылы қосылған процестердің сызықтық массивін анықтауға арналған синтаксис бар.
Тек бір ұшы бар (барлық жазу файлы дескрипторлары жабық) бос конвейерден (буферленген деректері жоқ) оқуға шақырулар "файлдың соңын" қайтарады. Ұқсас жағдайдағы жазу қоңыраулары еленбейді.
Ең ерте
Unix алтыншы басылымы (1975)
Unix бастапқы кодын оқуды бастау
Көп жылдар бойы кітап Lions Bell Labs-тан тыс қол жетімді Unix ядросындағы жалғыз құжат болды. Алтыншы басылым лицензиясы мұғалімдерге өзінің бастапқы кодын пайдалануға мүмкіндік бергенімен, жетінші басылым лицензиясы бұл мүмкіндікті жоққа шығарды, сондықтан кітап заңсыз машинкамен басылған көшірмелермен таратылды.
Бүгін сіз мұқабасында көшіргіштегі студенттер бейнеленген кітаптың қайта басылған көшірмесін сатып ала аласыз. Ал Уоррен Тумидің (TUHS жобасын бастаған) арқасында жүктеп алуға болады
15 жылдан астам уақыт бұрын мен берілген бастапқы кодтың көшірмесін енгіздім Lionsсебебі белгісіз сандағы басқа көшірмелердегі көшірменің сапасы маған ұнамады. TUHS әлі болған жоқ және мен ескі дереккөздерге қол жеткізе алмадым. Бірақ 1988 жылы мен PDP9 компьютерінен сақтық көшірмесі бар 11 жолдан тұратын ескі таспаны таптым. Оның жұмыс істеп тұрғанын білу қиын болды, бірақ файлдардың көпшілігінде 1979 деп белгіленген, сол кезде де көне болып көрінетін бұзылмаған /usr/src/ ағашы болды. Бұл жетінші басылым немесе PWB туындысы болды деп ойладым.
Мен табуды негізге алып, дереккөздерді алтыншы басылым күйіне дейін қолмен өңдедім. Кодтың бір бөлігі өзгеріссіз қалды, бір бөлігін аздап өңдеуге тура келді, қазіргі заманғы += белгісін ескірген =+ етіп өзгертті. Бірдеңе жай ғана жойылды және бірдеңені толығымен қайта жазу керек болды, бірақ тым көп емес.
Ал бүгін біз TUHS-те алтыншы басылымның бастапқы кодын онлайн оқи аламыз
Айтпақшы, бір қарағанда, Керниган мен Ричи кезеңіне дейінгі С-кодының басты ерекшелігі оның қысқалық. Менің сайтымдағы салыстырмалы түрде тар дисплей аймағына сәйкестендіру үшін ауқымды өңдеусіз код үзінділерін кірістіре алатыным жиі бола бермейді.
Басында
/*
* 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
Төртінші басылымнан бері буфер өлшемі өзгерген жоқ. Бірақ бұл жерде біз ешқандай қоғамдық құжаттамасыз, құбырлар бір кездері файлдарды резервтік сақтау ретінде пайдаланғанын көреміз!
LARG файлдарына келетін болсақ, олар сәйкес келеді
Міне, нақты жүйелік қоңырау 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;
}
Түсініктеме мұнда не болып жатқанын анық сипаттайды. Бірақ кодты түсіну оңай емес, ішінара қалай «R0
и R1
жүйелік шақыру параметрлері және қайтару мәндері беріледі.
-мен тырысайық
pipe()
арқылы төленеді R0
и R1
оқу және жазу үшін файл дескрипторының нөмірлерін қайтару. falloc()
файл құрылымына көрсеткішті қайтарады, сонымен қатар арқылы «қайтарады». u.u_ar0[R0]
және файл дескрипторы. Яғни, код сақталады r
оқуға арналған файл дескрипторы және тікелей жазу үшін дескриптор тағайындайды u.u_ar0[R0]
екінші қоңыраудан кейін falloc()
.
Ту FPIPE
, біз құбырды жасау кезінде орнатамыз, функцияның әрекетін басқарады
/*
* 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);
}
/* … */
}
Содан кейін функция readp()
в pipe.c
құбырдан деректерді оқиды. Бірақ іске асыруды бастап бақылаған дұрыс writep()
. Тағы да, код конвенциядан өту аргументінің сипатына байланысты күрделене түсті, бірақ кейбір мәліметтерді өткізіп жіберуге болады.
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;
}
Біз конвейер кірісіне байтты жазғымыз келеді u.u_count
. Алдымен инодты құлыптауымыз керек (төменде қараңыз plock
/prele
).
Содан кейін біз инодтың анықтамалық санын тексереміз. Құбырдың екі шеті де ашық болғанша, есептегіш 2 болуы керек. Біз бір сілтемені (ден бастап) ұстаймыз. rp->f_inode
), сондықтан есептегіш 2-ден аз болса, онда бұл оқу процесі құбырдың ұшын жауып тастағанын білдіруі керек. Басқаша айтқанда, біз жабық құбырға жазуға тырысамыз, бұл қате. Бірінші қате коды EPIPE
және сигнал SIGPIPE
Unix-тің алтыншы басылымында пайда болды.
Бірақ конвейер ашық болса да, ол толып кетуі мүмкін. Бұл жағдайда біз құлыпты босатып, басқа процесс құбырдан оқып, онда жеткілікті орын босатады деген үмітпен ұйықтаймыз. Біз оянғанда, біз басына ораламыз, құлыпты қайтадан іліп, жаңа жазу циклін бастаймыз.
Егер құбырда бос орын жеткілікті болса, біз оған деректерді пайдалана отырып жазамыз i_size1
inode'a (бос құбырмен 0-ге тең болуы мүмкін) құрамында бар деректердің соңына нұсқайды. Жазу үшін жеткілікті орын болса, біз құбырды толтыра аламыз i_size1
қарай PIPESIZ
. Содан кейін біз құлыпты босатып, құбырдан оқуды күтіп тұрған кез келген процесті оятуға тырысамыз. Біз қажет болғанша көп байт жаза алдық па, жоқ па, соны білу үшін басына ораламыз. Олай болмаса, жаңа жазу циклін бастаймыз.
Әдетте параметр i_mode
inode рұқсаттарды сақтау үшін пайдаланылады r
, w
и x
. Бірақ құбырлар жағдайында біз қандай да бір процесс биттерді пайдаланып жазуды немесе оқуды күтіп тұрғанын білдіреміз IREAD
и IWRITE
тиісінше. Процесс жалаушаны және қоңырауларды орнатады sleep()
, және болашақта басқа процесс шақырылады деп күтілуде wakeup()
.
Нағыз сиқыр сонда болады sleep()
и wakeup()
. Олар жылы жүзеге асырылады
/*
* 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) /* … */
Шақыратын процесс sleep()
белгілі бір арна үшін кейінірек шақырылатын басқа процесс оятуы мүмкін wakeup()
сол арна үшін. writep()
и readp()
осындай жұптық қоңыраулар арқылы өз әрекеттерін үйлестіреді. ескертіп қой pipe.c
әрқашан басымдық беріңіз PPIPE
шақырғанда sleep()
, сондықтан барлығы sleep()
сигнал арқылы үзілуі мүмкін.
Енді бізде функцияны түсіну үшін бәрі бар 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);
}
Бұл функцияны төменнен жоғары қарай оқу оңайырақ болуы мүмкін. «Оқу және қайтару» тармағы әдетте құбырда кейбір деректер болған кезде қолданылады. Бұл жағдайда біз пайдаланамыз f_offset
оқыңыз, содан кейін сәйкес ығысу мәнін жаңартыңыз.
Келесі оқуларда, егер оқу ығысуына жеткен болса, құбыр бос болады i_size1
инодта. Біз позицияны 0-ге қалпына келтіреміз және құбырға жазуды қалайтын кез келген процесті оятуға тырысамыз. Біз білеміз, конвейер толған кезде, writep()
ұйықтап қалу ip+1
. Енді құбыр бос болғандықтан, жазу циклін жалғастыру үшін оны оятуға болады.
Егер оқитын ештеңе болмаса, онда readp()
жалауша қоя алады IREAD
және ұйықтап кетіңіз ip+2
. Біз оны не оятатынын білеміз writep()
ол құбырға кейбір деректерді жазғанда.
Пікірлер u
» біз оларды файлды, орынды, жадтағы буферді қабылдайтын және оқу немесе жазу үшін байттардың санын есептейтін әдеттегі енгізу/шығару функциялары ретінде қарастыра аламыз.
/*
* 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;
/* … */
«Консервативті» блоктауға келетін болсақ, онда readp()
и writep()
инодтарды аяқтағанша немесе нәтиже алғанша құлыптаңыз (яғни қоңырау wakeup
). plock()
и prele()
қарапайым жұмыс: басқа қоңыраулар жинағын пайдалану sleep
и wakeup
Жаңа ғана шығарған құлыпты қажет ететін кез келген процесті оятуға мүмкіндік беріңіз:
/*
* 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);
}
}
Басында неге екенін түсіне алмадым readp()
тудырмайды prele(ip)
қоңырау алдында wakeup(ip+1)
. Бірінші нәрсе writep()
оның циклінде шақырады, бұл plock(ip)
, бұл тығырыққа әкеледі, егер readp()
оның блогын әлі жойған жоқ, сондықтан код қандай да бір түрде дұрыс жұмыс істеуі керек. Қарасаңыз wakeup()
, ол тек ұйықтау процесін орындауға дайын деп белгілейтіні белгілі болды, сондықтан болашақта sched()
оны шынымен іске қосты. Сондықтан readp()
себептері wakeup()
, құлпын ашады, орнатады IREAD
және қоңыраулар sleep(ip+2)
- мұның бәрі бұрын writep()
циклді қайта бастайды.
Бұл алтыншы басылымдағы құбырлардың сипаттамасын аяқтайды. Қарапайым код, ауқымды салдары.
Xv6, Unix тәрізді қарапайым ядро
Ядро құру үшін
Кодта нақты және ойластырылған енгізу бар 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()
функцияларын қамтитын барлық қалған іске асырудың күйін белгілейді piperead()
, pipewrite()
и pipeclose()
. Нақты жүйелік қоңырау sys_pipe
іске асырылған орауыш болып табылады
Linux 0.01
Linux 0.01 үшін бастапқы кодты таба аласыз. Оның ішінде құбырлардың жүзеге асырылуын зерделеу ғибратты болмақ fs
/pipe.c
. Мұнда инод құбырды бейнелеу үшін пайдаланылады, бірақ конвейердің өзі қазіргі С тілінде жазылған. Егер сіз алтыншы шығарылым кодын бұзған болсаңыз, мұнда ешқандай қиындық болмайды. Функция осылай көрінеді 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;
}
Құрылымдық анықтамаларды қарамай-ақ, жазу операциясының нәтижесін тексеру үшін инодтың анықтамалық саны қалай пайдаланылатынын анықтауға болады. SIGPIPE
. Байт-байт жұмысынан басқа, бұл функцияны жоғарыдағы идеялармен салыстыру оңай. Тіпті логика sleep_on
/wake_up
соншалықты бөтен болып көрінбейді.
Қазіргі заманғы Linux ядролары, FreeBSD, NetBSD, OpenBSD
Мен кейбір заманауи ядроларды тез аралап шықтым. Олардың ешқайсысында дискіге негізделген іске асыру жоқ (таңқаларлық емес). Linux-тың өзіндік іске асырылуы бар. Үш заманауи BSD ядросында Джон Дайсон жазған кодқа негізделген енгізулер болса да, жылдар өте олар бір-бірінен тым ерекшеленді.
Оқу fs
/pipe.c
(Linux жүйесінде) немесе sys
/kern
/sys_pipe.c
(*BSD-де), бұл нағыз арнауды қажет етеді. Векторлық және асинхронды енгізу/шығару сияқты мүмкіндіктерге өнімділік пен қолдау бүгінгі күні кодта маңызды. Және жадты бөлу, құлыптар және ядро конфигурациясының мәліметтері айтарлықтай өзгереді. Бұл университеттерге операциялық жүйелерге кіріспе курсы үшін қажет емес.
Қалай болғанда да, мен үшін бірнеше ескі үлгілерді ашу қызықты болды (мысалы, генерациялау SIGPIPE
және қайтару EPIPE
жабық құбырға жазғанда) осының барлығында, соншалықты әртүрлі, заманауи ядроларда. Мен PDP-11 компьютерін ешқашан тірі көрмейтін шығармын, бірақ мен туылғанға дейін бірнеше жыл бұрын жазылған кодтан әлі көп нәрсені үйрену керек.
2011 жылы Диви Капур жазған мақала «
Ақпарат көзі: www.habr.com