Bu məqalə Unix nüvəsində boru kəmərlərinin həyata keçirilməsini təsvir edir. “Bu yaxınlarda çıxan məqalə məni bir qədər məyus etdi.
Nədir?
Boru kəmərləri "ehtimal ki, Unix-də ən mühüm ixtiradır" - Unix-in kiçik proqramları bir araya toplamaq fəlsəfəsinin müəyyənedici xüsusiyyəti, eləcə də komanda xəttindəki tanış yazı:
$ echo hello | wc -c
6
Bu funksionallıq kernel tərəfindən təmin edilən sistem çağırışından asılıdır pipe
, bu sənədlərin səhifələrində təsvir edilmişdir
Boru kəmərləri proseslərarası əlaqə üçün birtərəfli kanal təmin edir. Boru kəmərinin girişi (yazma sonu) və çıxışı (oxumaq sonu) var. Boru kəmərinin girişinə yazılan məlumatlar çıxışda oxuna bilər.
Boru kəməri zəng etməklə yaradılır
pipe(2)
, iki fayl deskriptorunu qaytarır: biri boru kəmərinin girişinə, ikincisi çıxışa aiddir.
Yuxarıdakı komandanın iz çıxışı boru kəmərinin yaradılmasını və onun vasitəsilə bir prosesdən digərinə məlumat axınını göstərir:
$ 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
Ana proses çağırır pipe()
əlavə edilmiş fayl deskriptorlarını əldə etmək üçün. Bir uşaq proses bir deskriptora yazır və digər proses eyni məlumatları digər deskriptordan oxuyur. Qabıq stdin və stdout-a uyğunlaşdırmaq üçün 2 və 3-cü deskriptorları dup4 ilə "adlandırır".
Boru kəmərləri olmadan, qabıq bir prosesin çıxışını fayla yazmalı və fayldan məlumatları oxumaq üçün onu başqa bir prosesə ötürməli olardı. Nəticədə biz daha çox resurs və disk sahəsi sərf etmiş olarıq. Bununla belə, boru kəmərləri müvəqqəti fayllardan qaçmaqdan daha çox şey üçün yaxşıdır:
Bir proses boş bir boru kəmərindən oxumağa çalışırsa, o zaman
read(2)
məlumatlar mövcud olana qədər bloklanacaq. Bir proses tam boru xəttinə yazmağa çalışırsa, o zamanwrite(2)
yazmağı tamamlamaq üçün boru kəmərindən kifayət qədər məlumat oxunana qədər bloklanacaq.
POSIX tələbi kimi, bu da mühüm xüsusiyyətdir: boru xəttinə qədər yazmaq PIPE_BUF
bayt (ən azı 512) atomik olmalıdır ki, proseslər boru kəməri vasitəsilə bir-biri ilə normal faylların (belə zəmanətlər təqdim etməyən) edə bilmədiyi şəkildə əlaqə saxlasın.
Adi bir fayl ilə proses bütün çıxışını ona yaza və onu başqa prosesə ötürə bilər. Yaxud proseslər yazının və ya oxunmanın tamamlanması haqqında bir-birini məlumatlandırmaq üçün xarici siqnal mexanizmindən (semafor kimi) istifadə edərək sərt paralel rejimdə işləyə bilər. Konveyerlər bizi bütün bu əngəldən xilas edir.
Biz nə axtarırıq?
Konveyerin necə işlədiyini təsəvvür etməyinizi asanlaşdırmaq üçün barmaqlarımla izah edəcəyəm. Yaddaşda bufer və bəzi dövlətlər ayırmalı olacaqsınız. Buferdən məlumat əlavə etmək və silmək üçün sizə funksiyalar lazımdır. Fayl deskriptorlarında oxumaq və yazma əməliyyatları zamanı funksiyaları çağırmaq üçün sizə bəzi qurğular lazımdır. Və yuxarıda təsvir edilən xüsusi davranışı həyata keçirmək üçün kilidlər lazımdır.
Biz indi qeyri-müəyyən zehni modelimizi təsdiq və ya təkzib etmək üçün parlaq lampa işığı altında nüvənin mənbə kodunu sorğulamağa hazırıq. Ancaq həmişə gözlənilməzliyə hazır olun.
Biz hara baxırıq?
Məşhur kitabın nüsxəsinin harada olduğunu bilmirəm.
TUHS arxivlərində dolaşmaq muzeyi ziyarət etmək kimidir. Biz ortaq tariximizə nəzər sala bilərik və mən köhnə kasetlərdən və çap materiallarından bütün bu materialı yavaş-yavaş bərpa etmək üçün uzun illər çəkdiyim səylərə hörmətlə yanaşıram. Mən hələ də itkin düşmüş fraqmentlərdən xəbərdaram.
Boru kəmərlərinin qədim tarixinə olan marağımızı təmin edərək, müqayisə üçün müasir nüvələrə baxa bilərik.
Yeri gəlmişkən, pipe
Cədvəldəki 42 nömrəli sistem çağırışıdır sysent[]
. Təsadüf?
Ənənəvi Unix nüvələri (1970-1974)
Heç bir iz tapmadım pipe(2)
nə də
TUHS bunu iddia edir
Unix-in üçüncü nəşri assemblerdə yazılmış nüvə ilə son versiya, həm də boru kəmərləri ilə ilk versiya idi. 1973-cü il ərzində üçüncü nəşrin təkmilləşdirilməsi üzərində iş gedirdi, nüvə C-də yenidən yazılır və beləliklə Unix-in dördüncü nəşri yaranır.
Bir oxucu, Doug McIlroy-un "proqramları bağ şlanqı kimi birləşdirmək" ideyasını təklif etdiyi bir sənədin skanını tapdı.
Brayan Kerniqanın kitabında
Unix peyda olanda koroutinlərə olan həvəsim məni OS müəllifi Ken Thompsondan xahiş etdim ki, hansısa prosesə yazılan məlumatların təkcə cihaza deyil, həm də başqa prosesə çıxışına icazə versin. Ken bunun mümkün olduğunu düşünürdü. Bununla belə, minimalist kimi o, hər bir sistem xüsusiyyətinin əhəmiyyətli rol oynamasını istəyirdi. Proseslər arasında birbaşa yazı, ara fayla yazmaqdan daha böyük üstünlükdürmü? Və yalnız mən cəlbedici "boru kəməri" adı və proseslərin qarşılıqlı sintaksisinin təsviri ilə xüsusi bir təklif edəndə, Ken nəhayət qışqırdı: "Mən bunu edəcəm!".
Və etdi. Taleyüklü axşamların birində Ken nüvəni və qabığı dəyişdirdi, girişi necə qəbul etdiyini standartlaşdırmaq üçün bir neçə standart proqramı düzəltdi (bu boru kəmərindən gələ bilər) və fayl adlarını dəyişdirdi. Ertəsi gün boru kəmərləri tətbiqlərdə çox geniş istifadə edildi. Həftənin sonuna qədər katiblər onlardan sənədləri mətn prosessorlarından printerə göndərmək üçün istifadə etdilər. Bir qədər sonra, Ken o vaxtdan bəri istifadə olunan daha təmiz konvensiyalarla boru kəmərlərinin istifadəsini əhatə etmək üçün orijinal API və sintaksisi əvəz etdi.
Təəssüf ki, üçüncü nəşr Unix nüvəsinin mənbə kodu itdi. Baxmayaraq ki, nüvənin mənbə kodu C dilində yazılmışdır
üçün sənədləşmə mətnimiz var pipe(2)
hər iki buraxılışdan, beləliklə, sənədləri axtararaq başlaya bilərsiniz pipe(2)
assemblerdə yazılır və yalnız bir fayl deskriptorunu qaytarır, lakin artıq gözlənilən əsas funksionallığı təmin edir:
Sistem zəngi boru boru kəməri adlanan I/O mexanizmi yaradır. Qaytarılan fayl deskriptoru oxumaq və yazma əməliyyatları üçün istifadə edilə bilər. Boru kəmərinə bir şey yazıldıqda, o, 504 bayta qədər məlumatı bufer edir, bundan sonra yazı prosesi dayandırılır. Boru kəmərindən oxuyarkən tamponlanmış məlumatlar alınır.
Gələn il nüvə C-də yenidən yazılmışdı və pipe(fildes)
'
Sistem zəngi boru boru kəməri adlanan I/O mexanizmi yaradır. Qaytarılan fayl deskriptorları oxuma və yazma əməliyyatlarında istifadə edilə bilər. Boru kəmərinə nəsə yazıldıqda, r1-də qaytarılmış deskriptordan (cavab. fildes[1]) istifadə olunur, 4096 bayta qədər məlumat buferlənir, bundan sonra yazma prosesi dayandırılır. Boru kəmərindən oxuyarkən r0-a qayıdan deskriptor (cavab. fildes[0]) məlumatları qəbul edir.
Güman edilir ki, boru kəməri müəyyən edildikdən sonra iki (və ya daha çox) qarşılıqlı əlaqədə olan proseslər (sonrakı çağırışlarla yaradılmışdır) yaba) zənglərdən istifadə edərək boru kəmərindən məlumatları ötürəcək oxumaq и yazmaq.
Qabıqda boru kəməri ilə birləşdirilən proseslərin xətti massivini təyin etmək üçün sintaksis var.
Yalnız bir ucu olan (bütün yazı faylı deskriptorları bağlıdır) boş boru kəmərindən oxumağa çağırışlar "faylın sonu"nu qaytarır. Bənzər bir vəziyyətdə yazılan zənglərə məhəl qoyulmur.
Ən erkən
Unix Altıncı Nəşr (1975)
Unix mənbə kodunu oxumağa başlayır
Uzun illər kitab Lions Bell Laboratoriyasından kənarda mövcud olan Unix ləpəsində yeganə sənəd idi. Altıncı nəşr lisenziyası müəllimlərə öz mənbə kodundan istifadə etməyə icazə versə də, yeddinci nəşr lisenziyası bu imkanı istisna edirdi, buna görə də kitab qeyri-qanuni olaraq maşınla yazılmış nüsxələrdə paylandı.
Bu gün siz, üz qabığında surətçıxarma maşınında tələbələri təsvir edən kitabın təkrar nəşrini ala bilərsiniz. Və Warren Toomey (TUHS layihəsini başlatan) sayəsində yükləyə bilərsiniz
15 ildən çox əvvəl mən təqdim olunan mənbə kodunun surətini daxil etdim Lionsçünki naməlum sayda başqa nüsxələrdən olan surətimin keyfiyyətini bəyənmədim. TUHS hələ mövcud deyildi və mənim köhnə mənbələrə çıxışım yox idi. Lakin 1988-ci ildə mən PDP9 kompüterindən ehtiyat nüsxəsi olan 11 trekdən ibarət köhnə lent tapdım. İşləyib-işləmədiyini bilmək çətin idi, lakin bütöv bir /usr/src/ ağacı var idi ki, orada faylların çoxu 1979-cu il kimi qeyd olunurdu və o zaman da qədim görünürdü. Düşündüm ki, bu, yeddinci nəşr və ya PWB törəməsi idi.
Mən tapıntını əsas götürdüm və mənbələri altıncı nəşrin vəziyyətinə əl ilə redaktə etdim. Kodun bir hissəsi olduğu kimi qaldı, bir hissəsi müasir += işarəsini köhnəlmiş =+ ilə dəyişdirərək bir az redaktə edilməli idi. Bir şey sadəcə silindi və bir şey tamamilə yenidən yazılmalı idi, amma çox deyil.
Və bu gün biz TUHS-də altıncı nəşrin mənbə kodunu onlayn oxuya bilərik
Yeri gəlmişkən, ilk baxışdan Kerniqan və Ritçi dövründən əvvəlki C kodunun əsas xüsusiyyəti onun qısalıq. Saytımdakı nisbətən dar ekran sahəsinə sığdırmaq üçün geniş redaktə etmədən kod fraqmentlərini daxil edə bilməyim çox vaxt olmur.
Başlanğıcda
/*
* 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
Bufer ölçüsü dördüncü nəşrdən bəri dəyişməyib. Ancaq burada heç bir ictimai sənəd olmadan görürük ki, boru kəmərləri bir vaxtlar faylları ehtiyat yaddaş kimi istifadə edirdi!
LARG fayllarına gəldikdə, onlar uyğun gəlir
Budur əsl sistem çağırışı 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;
}
Şərh burada baş verənləri açıq şəkildə təsvir edir. Amma kodu başa düşmək o qədər də asan deyil, qismən ona görə ki, "R0
и R1
sistem çağırış parametrləri və qaytarma dəyərləri ötürülür.
ilə cəhd edək
pipe()
vasitəsilə R0
и R1
oxumaq və yazmaq üçün fayl deskriptor nömrələrini qaytarın. falloc()
fayl strukturuna göstərici qaytarır, həm də vasitəsilə "qaytarır" u.u_ar0[R0]
və fayl deskriptoru. Yəni kod saxlanılır r
oxumaq üçün fayl deskriptoru və birbaşa ondan yazmaq üçün deskriptor təyin edir u.u_ar0[R0]
ikinci zəngdən sonra falloc()
.
Bayraq FPIPE
boru kəmərini yaratarkən təyin etdiyimiz , funksiyanın davranışına nəzarət edir
/*
* 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);
}
/* … */
}
Sonra funksiya readp()
в pipe.c
boru kəmərindən məlumatları oxuyur. Amma daha yaxşı olar ki, həyata keçirilməsini ondan başlayaraq izləyəsiniz writep()
. Yenə də kod konvensiyadan keçən arqumentin təbiətinə görə daha mürəkkəbləşdi, lakin bəzi detallar buraxıla bilər.
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;
}
Biz boru xəttinin girişinə bayt yazmaq istəyirik u.u_count
. Əvvəlcə inodu bağlamalıyıq (aşağıya baxın plock
/prele
).
Sonra inode istinad sayını yoxlayırıq. Boru kəmərinin hər iki ucu açıq qaldıqca, sayğac 2 olmalıdır. Biz bir linkdən (dan rp->f_inode
), belə ki, sayğac 2-dən azdırsa, bu, oxu prosesinin boru kəmərinin sonunu bağladığını bildirməlidir. Başqa sözlə, biz qapalı boru xəttinə yazmağa çalışırıq, bu, səhvdir. İlk səhv kodu EPIPE
və siqnal SIGPIPE
Unix-in altıncı nəşrində çıxdı.
Amma konveyer açıq olsa belə, dolu ola bilər. Bu vəziyyətdə, kilidi buraxırıq və başqa bir prosesin boru kəmərindən oxuyacağı və içərisində kifayət qədər yer açacağı ümidi ilə yuxuya gedirik. Yuxudan oyandıqdan sonra başlanğıca qayıdırıq, kilidi yenidən asırıq və yeni yazma dövrünə başlayırıq.
Boru kəmərində kifayət qədər boş yer varsa, istifadə edərək ona məlumat yazırıq i_size1
inode'a (boş boru kəməri ilə 0-a bərabər ola bilər) artıq ehtiva etdiyi məlumatın sonuna işarə edir. Yazmaq üçün kifayət qədər yer varsa, boru kəmərini doldura bilərik i_size1
üzrə PIPESIZ
. Sonra kilidi buraxırıq və boru kəmərindən oxunmasını gözləyən hər hansı bir prosesi oyatmağa çalışırıq. Lazım olan qədər bayt yaza bildiyimizi görmək üçün başlanğıca qayıdırıq. Əgər yoxsa, o zaman yeni qeyd dövrünə başlayırıq.
Adətən parametr i_mode
inode icazələri saxlamaq üçün istifadə olunur r
, w
и x
. Ancaq boru kəmərləri vəziyyətində, bəzi prosesin bitlərdən istifadə edərək yazmağı və ya oxumağı gözlədiyini bildiririk IREAD
и IWRITE
müvafiq olaraq. Proses bayrağı və zəngləri təyin edir sleep()
, və gələcəkdə başqa bir prosesin çağırılacağı gözlənilir wakeup()
.
Əsl sehr içində baş verir sleep()
и wakeup()
. Onlar həyata keçirilir
/*
* 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) /* … */
Zəng edən proses sleep()
müəyyən bir kanal üçün, daha sonra zəng edəcək başqa bir proses tərəfindən oyandırıla bilər wakeup()
eyni kanal üçün. writep()
и readp()
belə qoşalaşmış zənglər vasitəsilə öz hərəkətlərini əlaqələndirirlər. qeyd edin ki pipe.c
həmişə prioritet PPIPE
çağıranda sleep()
, belə ki, hamısı sleep()
siqnalla kəsilə bilər.
İndi funksiyanı başa düşmək üçün hər şeyimiz var 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);
}
Bu funksiyanı aşağıdan yuxarı oxumaq daha asan ola bilər. "Oxumaq və qaytarmaq" bölməsi adətən boru kəmərində bəzi məlumatlar olduqda istifadə olunur. Bu vəziyyətdə istifadə edirik f_offset
oxuyun və sonra müvafiq ofsetin dəyərini yeniləyin.
Sonrakı oxunuşlarda oxunuş ofsetinə çatdıqda boru kəməri boş olacaq i_size1
inode. Mövqeyi 0-a qaytarırıq və boru xəttinə yazmaq istəyən hər hansı bir prosesi oyatmağa çalışırıq. Biz bilirik ki, konveyer dolu olduqda, writep()
yatmaq ip+1
. İndi boru kəməri boşdur, biz onu oyandıraraq yazma dövrünü davam etdirə bilərik.
Oxumağa bir şey yoxdursa, deməli readp()
bayraq qoya bilər IREAD
və yuxuya get ip+2
. Onu nəyin oyatacağını bilirik writep()
boru kəmərinə bəzi məlumatları yazdıqda.
Şərhlər u
» biz onlara fayl, mövqe, yaddaşda bufer götürən və oxumaq və ya yazmaq üçün baytların sayını hesablayan adi I/O funksiyaları kimi davrana bilərik.
/*
* 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;
/* … */
O ki qaldı “mühafizəkar” bloklamaya, o zaman readp()
и writep()
bitənə və ya nəticə əldə edənə qədər inodları kilidləyin (yəni zəng edin wakeup
). plock()
и prele()
sadə işləyin: fərqli zənglər dəstindən istifadə edin sleep
и wakeup
İndi buraxdığımız kilidə ehtiyac duyan hər hansı bir prosesi oyatmağa icazə verin:
/*
* 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);
}
}
Əvvəlcə səbəbini başa düşə bilmədim readp()
səbəb olmur prele(ip)
zəngdən əvvəl wakeup(ip+1)
. İlk şey writep()
öz döngəsində çağırır, bu plock(ip)
, əgər dalana səbəb olur readp()
hələ blokunu silməyib, ona görə də kod birtəhər düzgün işləməlidir. Baxsanız wakeup()
, aydın olur ki, o, yalnız yuxu prosesini icraya hazır olaraq qeyd edir, belə ki, gələcəkdə sched()
həqiqətən işə saldı. Belə ki readp()
səbəbləri wakeup()
, kilidləri açır, təyin edir IREAD
və zənglər sleep(ip+2)
- bütün bunlar əvvəl writep()
dövrü yenidən başlayır.
Bu, altıncı nəşrdə boru kəmərlərinin təsvirini tamamlayır. Sadə kod, geniş təsirlər.
Xv6, Unix-ə bənzər sadə nüvədir
Nüvə yaratmaq üçün
Kod aydın və düşünülmüş icradan ibarətdir 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()
funksiyaları ehtiva edən icranın qalan bütün hissələrinin vəziyyətini təyin edir piperead()
, pipewrite()
и pipeclose()
. Həqiqi sistem çağırışı sys_pipe
-də həyata keçirilən sarğıdır
Linux 0.01
Linux 0.01 üçün mənbə kodunu tapa bilərsiniz. Onun ərazisində boru kəmərlərinin həyata keçirilməsini öyrənmək ibrətamiz olacaqdır fs
/pipe.c
. Burada boru kəmərini təmsil etmək üçün inode istifadə olunur, lakin boru kəmərinin özü müasir C dilində yazılmışdır. Əgər altıncı nəşr kodunu sındırmısınızsa, burada heç bir probleminiz olmayacaq. Funksiya belə görünür 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;
}
Struktur təriflərinə baxmadan belə, yazma əməliyyatının nəticə verib-vermədiyini yoxlamaq üçün inode istinad sayının necə istifadə olunduğunu anlaya bilərsiniz. SIGPIPE
. Bayt-bayt işinə əlavə olaraq, bu funksiyanı yuxarıdakı fikirlərlə müqayisə etmək asandır. Hətta məntiq sleep_on
/wake_up
o qədər də yad görünmür.
Müasir Linux nüvələri, FreeBSD, NetBSD, OpenBSD
Mən tez bəzi müasir ləpələri nəzərdən keçirdim. Onların heç birinin artıq disk əsaslı tətbiqi yoxdur (təəccüblü deyil). Linux-un öz tətbiqi var. Üç müasir BSD nüvəsi Con Dayson tərəfindən yazılmış koda əsaslanan tətbiqləri ehtiva etsə də, illər keçdikcə onlar bir-birindən çox fərqli oldular.
Oxumaq fs
/pipe.c
(Linux-da) və ya sys
/kern
/sys_pipe.c
(*BSD-də), bu, əsl fədakarlıq tələb edir. Performans və vektor və asinxron I/O kimi funksiyalar üçün dəstək bu gün kodda vacibdir. Yaddaşın ayrılması, kilidlər və nüvə konfiqurasiyasının təfərrüatları çox fərqlidir. Bu, universitetlərə əməliyyat sistemləri ilə bağlı giriş kursu üçün lazım olan şey deyil.
Hər halda, mənim üçün bir neçə köhnə nümunələri (məsələn, generating SIGPIPE
və qayıt EPIPE
qapalı boru xəttinə yazarkən) bütün bu, çox fərqli, müasir nüvələrdə. Mən yəqin ki, heç vaxt PDP-11 kompüterini canlı görməyəcəyəm, amma mən doğulmamışdan bir neçə il əvvəl yazılmış koddan öyrənməli olduğum çox şey var.
Divi Kapur tərəfindən 2011-ci ildə yazılmış məqalə "
Mənbə: www.habr.com