
Bu məqalədə Unix kernelində boruların implementasiyası təsvir olunur. Son dövrlərdə "" adlı məqalənin deyil daxili quruluşa dair olduğunu öyrənmək məni bir qədər məyus etdi. Mən maraqlanmağa başlayıb köhnə mənbələrə müraciət etdim.
Nə haqqındadır?
Borular — "bəlkə də Unix-dəki ən vacib ixtira" — Unix-in fəlsəfəsinin əsasını təşkil edən kiçik proqramların birləşməsi prinsipini əks etdirir, həmçinin komanda xəttində tanış ifadəni:
$ echo hello | wc -c
6
Bu funksionallıq kernel tərəfindən təqdim edilən sistem çağırışından asılıdır pipe, bu, sənəddə izah olunur və :
Borular bir istiqamətli proseslərarası ünsiyyət kanalı təmin edir. Borunun bir girişi (yazma ucu) və bir çıxışı (oxu ucu) var. Borunun girişinə yazılan məlumatlar çıxışda oxuna bilər.
Borular çağırışı ilə yaradılır
pipe(2), bu, iki fayl təsvirçisi qaytarır: biri borunun girişinə, digəri isə çıxışına aiddir.
Yuxarıda göstərilən əmrin iz sürmə nəticələri bir prosesi digərinə məlumat axını ilə boru yaratmağı nümayiş etdirir:
$ 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
Valideyn prosesi pipe(), qoşulmuş fayl təsvirçilərini əldə etmək üçün çağırış edir. Bir alt proses bir təsvirçidə yazır, digər proses isə eyni məlumatları digər təsvirçidən oxuyur. Kimi shell dup2 "təsvirçiləri 3 və 4-ü stdin və stdout-a uyğunlaşdırır.
Borular olmasa, shell bir prosesin nəticələrini bir fayla yazmalı və digər prosesi həmin fayldan oxumaq üçün keçirməli olardı. Nəticədə daha çox resurs və disk sahəsi itirərdik. Lakin borular yalnız müvəqqəti fayllardan qaçmağı təmin etmir:
Əgər proses boş borudan oxumağa cəhd edirsə, onda
read(2)məlumatlar mövcud olanadək bloklanacaq. Əgər proses dolu boruya yazmağa cəhd edirsə, ondawrite(2)yazma əməliyyatı üçün kifayət qədər məlumat oxunanadək bloklanacaq.
POSIX tələblərinə uyğun olaraq, bu vacib bir xüsusiyyətdir: boruya yazılması PIPE_BUF bayt (ən azı 512) atomar olmalıdır ki, proseslər bir-biri ilə boru vasitəsilə normal faylların (belə zəmanət verməyən) edə bilmədiyi şəkildə ünsiyyət qura bilsin.
Bir adi fayldan istifadə edildikdə, proses bütün çıxışlarını ora yaza bilər və başqa bir prosesə ötürə bilər. Və ya proseslər siqnal mexanizmi (məsələn, semafor) ilə qeydiyyatın və ya oxumağın başa çatdığı barədə bir-birinə məlumat verərək sərt paralelləşdirmə rejimində fəaliyyət göstərə bilərlər. Borular bizə bütün bu əziyyətlərdən xilas edir.
Nə axtarırıq?
Bunu barmaqlarla izah edəcəyəm ki, konveyerin necə işləyə biləcəyini daha yaxşı təsəvvür edə biləsiniz. Sizə yaddaşda bir buffer və bir vəziyyət ayırmaq lazımdır. Verilənləri buffer-a əlavə etmək və oradan silmək üçün funksiyalar lazımdır. Fayl təsvirçilərində oxumaq və yazmaq əməliyyatları zamanı funksiyaları çağırmaq üçün bir vasitə lazımdır. Və yuxarıda təsvir olunan xüsusi davranışı reallaşdırmaq üçün bloklamalara ehtiyac var.
İndi parlak lampanın altında kernel mənbə kodunu sorğu-sual etməyə hazırıq ki, abstrakt mental modelimizi təsdiq edək və ya təkzib edək. Amma hər zaman gözlənilməzliklərə hazır olun.
Harada axtarırıq?
Mən Unix 6-nın mənbə kodu ilə bağlı olan məşhur kitabımın harada olduğunu bilmirəm, amma-ın sayəsində onlayn axtarmaq mümkündür hələ daha köhnə Unix versiyalarında.
TUHS arxivləri arasında gəzmək bir muzeyə baş çəkməyə bənzəyir. Ümumi tariximizə nəzər sala bilərik və mən bütün bu materialların bit-bit toplanmasında illərlə çəkilən zəhmətə həyəcandan hiss edirəm. Və hələ də olmayan parçaları kəskin şəkildə başa düşürəm.
Konveyerlərin qədim tarixi ilə marağımı təmin etdikdən sonra, müasir kernel-lərə nəzər yetirə bilərik.
Əslində, pipe nömrə 42 sistem çağırışıdır. sysent[]. Təsadüfdür?
Ənənəvi Unix kernel-ləri (1970–1974)
Mən heç bir iz tapa bilmədim pipe(2) nə (yanvar 1970), nə (noyabr 1971), nə də qismən tamamlanmış kodda (iyun 1972).
TUHS iddia edir ki, (fevral 1973) konveyerlərlə ilk versiya olub:
Üçüncü Unix versiyası assembler ilə yazılmış kernel ilə son versiya oldu, amma eyni zamanda konveyerlərlə ilk versiya oldu. 1973-cü ildə üçüncü versiyanı təkmilləşdirmək üçün işlər aparıldı, kernel C dilinə yenidən yazıldı və dördüncü Unix versiyası beləcə meydana çıxdı.
Oxuculardan biri, Doug McIlroy-un «proqramları bağ hortumu prinsipinə əsasən birləşdirmək» fikrini təklif etdiyi bir sənədin skanını tapdı.

Brian Kernighan-ın «» kitabında konveyerlərin yaranma tarixindən də bu sənəd haqqında söz açılır: «... o, 30 il ərzində Bell Labs-dakı ofisimin divarında asılı qalmışdı». Budur , və 2014-cü ildən :
Unix meydana gəldikdə, korutinalara olan marağım məni əməliyyat sisteminin müəllifi Ken Tompsonla əlaqə saxlayıb, hansısa prosesdə yazılmış məlumatların yalnız cihazlara deyil, eyni zamanda digər bir prosesə göndərilməsi imtiyazını istəməyə sövq etdi. Ken bunu mümkün hesab etdi. Lakin minimalist olaraq, o, hər bir sistem funksiyasının əhəmiyyətli bir rol oynamağını istəyirdi. Gerçəkdən də, proseslər arasında birbaşa yazma, ara faylına yazma ilə müqayisədə böyük bir üstünlük təmin edirmi? Təkcə mən "konveyer" adlandırdığım konkret bir təklif irəlilədikdə və proseslərin qarşılıqlı əlaqəsini açıqlayan sintaksis təqdim etdikdə, Ken sonunda: "Bunu edəcəyəm!" dedi.
Və etdi. Bir taleyüklü axşam, Ken nüvəni və qabığı dəyişdi, bir neçə standart proqramı düzəltdi, onların giriş qəbul prosedurlarını standartlaşdırdı (bu prosedurlar konveyerdən gələ bilər) və həmçinin fayl adlarını dəyişdi. Növbəti gün konveyerlər tətbiqlərdə geniş şəkildə istifadəyə verilməyə başlandı. Həftənin sonuna doğru, katiblər onların vasitəsilə mətn redaktorlarından çap etmək üçün sənədləri printerə göndərirdilər. Bir az sonra Ken orijinal API və konveyerlərin istifadə sintaksisini daha təmiz razılaşmalarla əvəz etdi, bu da o vaxtdan bəri istifadə olunur.
Təəssüf ki, üçüncü nəşrin Unix nüvəsinin mənbə kodu itmişdir. Və bizdə C dilində yazılmış nüvə kodu olmasına baxmayaraq , 1973-cü ilin noyabrında çıxmışdır, lakin bu, rəsmi buraxılışdan bir neçə ay əvvəl buraxıldı və konveyerların yerinə yetirilməsini əhatə etmir. Unix-in əfsanəvi funksiyasının mənbə kodunun itməsi, bəlkə də əbədi olaraq, çox təəssüfdür.
Hər iki buraxılış üçün bizdə sənədin mətnidir, buna görə də sənəddə axtarışa başlamaq olar pipe(2) üçüncü nəşr assembler dilində yazılıb və yalnız bir fayl təsvirçisi qaytarır, lakin artıq gözlənilən əsas funksionallığı təmin edir:pipe(2) Sistem çağırışı
bir adlandırma mexanizmi yaradır ki, buna konveyer deyilir. Qaytarılan fayl təsvirçisi oxuma və yazma əməliyyatları üçün istifadə edilə bilər. Konveyerə bir şey yazıldıqda, 504 bayta qədər məlumat yığılır və sonrasını yazma prosesi dayandırılır. Konveyerdən oxuma zamanı yığılmış məlumatlar alınır. pipe Gələn ilə nüvə C dilinə yazıldı və
pipe(2) dördüncü nəşrdə pipe(fildes)pipe(fildes)»:
bir adlandırma mexanizmi yaradır ki, buna konveyer deyilir. Qaytarılan fayl təsvirçisi oxuma və yazma əməliyyatları üçün istifadə edilə bilər. Konveyerə bir şey yazıldıqda, 504 bayta qədər məlumat yığılır və sonrasını yazma prosesi dayandırılır. Konveyerdən oxuma zamanı yığılmış məlumatlar alınır. pipe bir konveyer adlanan giriş-çıxış mexanizmi yaradır. Qayıdan fayl təsvirçiləri oxuma və yazma əməliyyatlarında istifadə oluna bilər. Konveyerə yazıldıqda, r1-də (müvafiq olaraq fildes[1]) qaytarılan təsvirçi istifadə olunur, yəni məlumat 4096 bayta qədər tamponlanır, sonra yazma prosesi dayanır. Konveyerdən oxunanda, r0-da (müvafiq olaraq fildes[0]) qaytarılan təsvirçi məlumatları alır.
Konveyer müəyyən edildikdən sonra iki (və ya daha çox) qarşılıqlı prosesin (sonrakı çağırışlarla yaradılan) məlumat ötürməsi gözlənilir. fork) konveyerdən məlumat ötürmək üçün çağırışların vasitəsilə. read və write.
Komanda xəttində konveyer vasitəsilə birləşdirilmiş xətti proseslərin mənbəyini təyin etmək üçün sintaksis var.
Tamponlanmamış, yalnız bir ucu olan (yazı təsvirçiləri bağlanmış) boş konveyerdən oxuma çağırışları 'fayl sonu' qaytarır. Eyni vəziyyətdə yazma çağırışları nəzərə alınmır.
Ən erkən beşinci Unix versiyasına aiddir Altıncı Unix versiyası (1975)
Unix-in mənbə kodunu oxumağa başlayırıq
altıncı versiyanın Lions onu daha əvvəlki versiyaların mənbələrindən daha asan tapmaq mümkündür: Bir çox illər ərzində kitab
Unix nüvəsi haqqında Bell Labs-dan kənarda mövcud olan yeganə sənəd idi. Altıncı versiyanın lisenziyası müəllimlərə onun mənbə kodunu istifadə etməyə icazə versə də, yeddinci versiyanın lisenziyası bu imkanı istisna etdi, buna görə kitab qeyri-qanuni çaplar şəklində yayıldı. onu daha əvvəlki versiyaların mənbələrindən daha asan tapmaq mümkündür: Bu gün kitabın üz qabığında fotokopiya masasında tələbələrin olduğu bir nüsxəsini satın almaq mümkündür. Warren Toomey (TUHS layihəsini başladan) sayəsində altıncı versiyanın mənbə kodu ilə PDF faylını yükləyə bilərsiz.
. Sizə faylın yaradılması üçün sərf olunan əməyi göstərmək istəyirəm: nümunə mənbə kodunun bir nüsxəsini düzəltmişdim
, çünki mənim bilinməyən sayda digər nüsxələrdən aldığım nüsxənin keyfiyyətindən narazı idim. TUHS yoxdur, köhnə mənbələrə girişim yox idi. Amma 1988-ci ildə mən PDP11 kompüterindən olan rezerv kopyası olan 9 zolaqlı köhnə kaset tapdım. Onun işləyib-işləmədiyini anlamaq çətindi, amma orada 1979-cu ilə işarələnmiş faylların əksəriyyətinin olduğu pozulmamış /usr/src/ ağacı vardı ki, bu da artıq o zaman köhnə görünürdü. Bu yeddinci versiya ya da onun törəməsi PWB idi, mənim düşüncəmə görə. onu daha əvvəlki versiyaların mənbələrindən daha asan tapmaq mümkündür:, потому что мне не нравилось качество моей копии с неизвестного количества других копий. TUHS ещё не существовало, и у меня не было доступа к старым исходникам. Но в 1988-м я нашёл старую ленту с 9 дорожками, на которой была резервная копия из компьютера PDP11. Трудно было понять, работает ли она, но там было неповреждённое дерево /usr/src/, в котором большинство файлов были помечены 1979-м годом, что уже тогда выглядело древностью. Это была седьмая редакция или её производная PWB, как я считал.
Mən tapıntını əsas götürdüm və mənbələri altıncı redaksiyaya qədər əl ilə redaktə etdim. Kodun bir hissəsi eyni qaldı, bir hissəsini müasir token += əvəzinə köhnəlmiş =+ ilə kiçik dəyişikliklər edərək redaktə etməli oldum. Bəzilərini sadəcə sildim, bəzilərini isə tamamilə yenidən yazmalı oldum, lakin çox da olmadı.
Və bu gün biz onlayn olaraq TUHS-da altıncı redaksiyanın mənbə kodunu oxuya bilirik .
Bu arada, ilk baxışda, Kernighan və Ritchie dövründən əvvəlki C kodunun əsas xüsusiyyəti qısa olmasıdır.. Mənim saytıma uyğun olaraq nisbətən dar bir sahəni əhatə edən kod parçalarını geniş redaktə etmədən yeritmək nadir hallarda mümkün olur.
Başlanğıcda izahat verən şərh var (və bəli, orada hələ də var) ):
/*
* 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
Buferin ölçüsü dördüncü redaksiyadan bəri dəyişməyib. Ancaq burada biz heç bir ictimai sənədləşmə olmadan görürük ki, bir zamanlar konveyerlər faylları ehtiyat saxlama kimi istifadə ediblər!
LARG fayllarına gəldikdə, onlar , hansı "böyük ünvanlama" alqoritmi tərəfindən daha böyük fayl sistemlərini dəstəkləmək üçün işlənməsində istifadə olunur. Ken dediyinə görə, onları istifadə etməmək daha yaxşıdır, ona söz verəcəyimdən məmnunam.
Bu, həqiqi sistem çağırışıdı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;
}
Şərhdə burada nə baş verdiyi açıq şəkildə təsvir edilib. Amma kodu başa düşmək asan deyil, qismən "" və registrlər R0 və R1 ilə sistem çağırışlarının parametr və geri qaytarılan dəyərlərin ötürüldüyü şəkildə bağlıdır.
Gəlin ilə diskdə yerləşdirək, və ilə yaddaşda iki . Əgər hər şey mükəmməl keçərsə, həmin faylların iki konveyərin ucları kimi müəyyən edilməsi üçün bayraqlar təyin edəcəyik, onları eyni inode-da qeyd edəcəyik (onun istinad sayğacı 2-yə bərabər olacaq) və inode-u dəyişdirilmiş və istifadə olunan kimi qeyd edəcəyik. Yeni inode-də istinad sayğacını azaltmaq üçün xətalı yollar (error paths) üzrə iput() çağırışlarını qeyd edin. fayl təsvirçiləri oxumaq və yazmaq üçün geri qaytarmalıdır.
pipe() fayl strukturu üçün bir göstərici qaytardığı ilə yanaşı, eyni zamanda " R0 və R1 u.u_ar0[R0] falloc() " və fayl təsvirçisini də "geri qaytarır". Yəni kod oxumaq üçün fayl təsvirçisini saxlayır və yazma üçün təsvirçini sadəcə 2-ci çağırışdan sonra r FPIPE oxumaq üçün fayl təsvirçisini saxlayır və yazma üçün təsvirçini sadəcə , hansı ki, konveyir yaradılarkən təyin etdiyimiz kimi, rdwr() funksiyasının davranışını idarə edir falloc().
--set-exit-code-below-score sys2.c-də, giriş-çıxışın konkret alt proqramlarını çağıran: readp()
/*
* 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);
}
/* … */
}
pipe.c konveyerdən məlumatları oxuyur. Ancaq implementasiyanı daha yaxşı şəkildə yazmaya başlamaq daha yaxşıdır daxilindədir. writep() . Yenidən deyirəm ki, kod argumentlərin ötürülməsi razılaşmasının spesifik xüsusiyyətlərinə görə çətinləşib, amma bəzi detalları keçmək olar. 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 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;
}
Kanalın girişinə baytları yazmaq istəyirik u.u_count. Əvvəlcə indeks təsvirçisini (yuxarıda baxın) kilidləməliyik. plock/prele).
Sonra inode-un istinad sayını yoxlayırıq. Kanalın hər iki ucu açıq olduğu müddətcə, say 2-yə bərabər olmalıdır. Biz bir istinadı saxlayırıq (bu, rp->f_inode), beləliklə, əgər say 2-dən azdırsa, bu, oxuyucu prosesin kanalın ucunu bağladığını göstərməlidir. Başqa sözlə, bağlanmış kanala yazmağa çalışırıq ki, bu da səhvdir. Səhv kodu ilk dəfə EPIPE və siqnal SIGPIPE Unix-un altıncı redaksiyasında meydana gəldi.
Amma kanalda açıq olsa belə, dolu ola bilər. Bu halda, kilidi açırıq və digər prosesin kanaldan oxumaq üçün yuxuya gedirik, görünür ki, orada yetərincə yer açılacaq. Oyananda, yenidən başlayırıq, bir daha kilid salırıq və yeni yazı dövrü başlatırıq.
Əgər kanalda kifayət qədər boş yer varsa, o zaman məlumatları . Parametr inode-da (boş kanalda 0-a bərabər ola bilər) olan i_size1, orada artıq saxlanılan məlumatların sonunu göstərir. Yazmaq üçün kifayət qədər yer varsa, kanalı PIPESIZ inode-da (boş kanalda 0-a bərabər ola bilər) olan i_size1, orada artıq saxlanılan məlumatların sonunu göstərir. Yazmaq üçün kifayət qədər yer varsa, kanalı ile də doldura bilərik. Sonra kilidi açırıq və kanaldan oxumaq üçün gözləyən hər hansı bir prosesi oyatmağa çalışırıq. Yenidən başlayırıq, görürük ki, lazım olan qədər bayt yazmaq mümkün oldumu. Əgər mümkün olmayıbsa, yeni yazı dövrünə başlayırıq.Adətən
i_mode inode-da icazələri saxlamaq üçün istifadə olunur. Ancaq kanallar üçün yazma və oxumaq prosesini gözləmək üçün r, w və xIREAD və və IWRITE şəklində baytlarla siqnal veririk. Proses, müəyyən bir kanal üçün sleep()çağırır, və gözlənilir ki, gələcəkdə başqa bir proses çağıracaq wakeup().
Gerçək sehr sleep() və wakeup()burada baş verir. Onlar dədir, məşhur "Bunu anlamanız gözlənilmir" (You are not expected to understand this) şərhinin qaynağıdır. Xoşbəxtlikdən, kodu başa düşməli deyiliksə, sadəcə bəzi şərhlərə baxırıq.
/*
* 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) /* … */
Müəyyən bir kanaldan istifadə edən proses, daha sonra eyni kanal üçün çağıran digər proses tərəfindən oyadıla bilər. sleep() zəng wakeup() və eyni kanal üçün. writep() və konveyerdən məlumatları oxuyur. Ancaq implementasiyanı daha yaxşı şəkildə yazmaya başlamaq daha yaxşıdır hərəkətlərini belə cüt çağırışlarla koordinasiya edirlər. Diqqət yetirin ki, writep() həmişə prioritet verir PPIPE çağırdıqda sleep(), buna görə də hamısı sleep() sinyal ilə dayandırıla bilər.
İndi bütün bunları başa düşmək üçün hər şeyə sahibik konveyerdən məlumatları oxuyur. Ancaq implementasiyanı daha yaxşı şəkildə yazmaya başlamaq daha yaxşıdır:
readp(fp)
int *fp;
{
register *rp, *ip;
rp = fp;
ip = rp->f_inode;
loop:
/* Çox mühafizəkar kilidləmə. */
plock(ip);
/*
* Əgər baş (oxu) quyruğa (yazı) çatıbsa,
* ikisini də 0-ə sıfırla.
*/
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);
}
}
/*
* Əgər həm oxucu, həm də
* yazıcı aktiv deyilsə, oxumağı
* təmin etmədən qaytar.
*/
prele(ip);
if(ip->i_count i_mode =| IREAD;
sleep(ip+2, PPIPE);
goto loop;
}
/* Oxuyuruq və qaytarırıq */
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);
}
Bəlkə bu funksiyanı aşağıdan yuxarıya oxumaq sizin üçün daha rahat olacaq. "Oxu və qaytar" branchi ümumiyyətlə konveyerdə bəzi məlumatlar olduqda istifadə olunur. Bu vəziyyətdə biz hazırkı başlanğıcdan başlayaraq nə qədər məlumat mövcuddursa oxuyuruq f_offset oxuma, sonra uyğun kaydırma dəyərini yeniləyirik.
Sonrakı oxuma zamanı konveyer boş olacaq, əgər oxuma kaydırması inode-da (boş kanalda 0-a bərabər ola bilər) olan i_size1, orada artıq saxlanılan məlumatların sonunu göstərir. Yazmaq üçün kifayət qədər yer varsa, kanalı inode-un dəyərinə çatdı. Pozisiyamızı 0-a sıfırlayırıq və konveyerə yazmaq istəyən hər hansı bir prosesi oyatmağa çalışırıq. Biz bilirik ki, konveyer dolduqda, writep() ip+1-də yuxuya gedəcək. . İndi konveyer boş olduğundan, onu oyatmağa cəhd edə bilərik ki, yazma dövrünü yenidən bərpa etsin.Əgər oxumaq üçün heç nə yoxdursa,
bir bayraq təyin edə bilər konveyerdən məlumatları oxuyur. Ancaq implementasiyanı daha yaxşı şəkildə yazmaya başlamaq daha yaxşıdır və ip+2-də yuxuya gedə bilər. və Biz bilirik ki, onu oyadacaq , konveyerə hər hansı bir məlumat yazdıqda.readi() və writei() haqqında şərhlər writep()bizə kömək edəcək ki, parametr ötürməklə "
u yerinə onları adi giriş-çıxış funksiyaları kimi idarə edə bilərik, bunlar fayl, mövqe, yaddaşdakı tampon və oxumaq və ya yazmaq üçün bayt sayını hesablayırlar.readi() funksiyasıkonservativ bloklama ilə bağlı
/*
* 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;
/* … */
inode-u işini bitirənə qədər yaxud nəticəni alana qədər kilitləyir (yəni konveyerdən məlumatları oxuyur. Ancaq implementasiyanı daha yaxşı şəkildə yazmaya başlamaq daha yaxşıdır və writep() wakeup plock()). prele() və sadəcə işləyir: digər çağırışlar vasitəsilə yeni bloklamanı açdığımız prosesi oyatmağımıza imkan tanıyır: sleep və plock() İlk həmdəm qala bilmədim ki, niyə
/*
* 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);
}
}
prele(ip) konveyerdən məlumatları oxuyur. Ancaq implementasiyanı daha yaxşı şəkildə yazmaya başlamaq daha yaxşıdır wakeup(ip+1) çağırmadan əvvəl çağırılmır. İlk o, öz dövründə plock(ip) çağırır ki, əgərhələ də blokunu açmamışsa, qarşılıqlı bloklanmaya səbəb olur, bu səbəbdən kod bir şəkildə doğru işləməlidir. Əgər writep() baxsaq, başa düşüləndir ki, o, sadəcə yuxuda olan prosesi icra edilməyə hazır kimi işarələyir ki, gələcəkdə sched()onu həqiqətən işə salsın. Beləliklə, konveyerdən məlumatları oxuyur. Ancaq implementasiyanı daha yaxşı şəkildə yazmaya başlamaq daha yaxşıdır çağırır, wakeup()kilidi açır, təyin edir və konveyerdən məlumatları oxuyur. Ancaq implementasiyanı daha yaxşı şəkildə yazmaya başlamaq daha yaxşıdır sleep(ip+2) çağırır wakeup()— bunların hamısı və dövrünü bərpa etmədən əvvəl. sleep(ip+2)— всё это до того, как writep() возобновляет цикл.
Bu konveyerlerin altıncı nəşrdə təsviri sona çatır. Sadə kod, uzağa gedən nəticələr.
(1979-cu ilin yanvarında) dörd ildən sonra yeni bir əsas buraxılış idi, burada bir çox yeni proqramlar və nüvənin xüsusiyyətləri ortaya çıxdı. Ayrıca, burada tip gətirmə, uniyaların istifadəsi və strukturlara tiplənmiş göstəricilər ilə bağlı əhəmiyyətli dəyişikliklər oldu. Lakin praktik olaraq dəyişməz qaldı. Bu nəşri atlaya bilərik.
Xv6, sadə bir Unix-tipi nüvəsidir
Nüvə yaratmağa altıncı Unix nəşrinə təsir göstərdi, lakin o müasir C dilində yazılmışdır ki, x86 prosessorlarında işlənsin. Kod asan oxunur, başa düşüləndir. Bununla yanaşı, TUHS ilə Unix mənbələri arasındakı fərq, siz onu kompilə edib, modifikasiya edib və PDP 11/70-dən başqa bir şeydə işə sala bilərsiniz. Buna görə bu nüvə universitetlərdə əməliyyat sistemləri üzrə tədris materialı kimi geniş istifadə olunur. Mənbələr .
Kodda başa düşülən və düşünülmüş bir həyata keçirilmə , diskdəki inode əvəzinə yaddaşda tamponla dəstəklənir. Burada yalnız "strukturlaşdırılmış konveyer" anlayışını və aşağıdakı funksiyanı təqdim edirəm 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() bütün qalan həyata keçirmənin vəziyyətini müəyyən edir ki, burada piperead(), pipewrite() və pipeclose(). Real sistem çağırışı sys_pipe sysfile.c-də həyata keçirilən bir sarğıdır. Bütün kodunu oxumağı tövsiyə edirəm. Altıncı nəşrin mənbə səviyyəsində çətinliyi var, lakin oxumaq çox daha asan və xoşdur. Linux 0.01-in mənbə kodunu tapa bilərsiniz. Konveyerlərin implementasiyasını öyrənmək öyrədici olacaq
fs
. Burada konveyeri təqdim etmək üçün inode istifadə olunur, lakin konveyer müasir C dili ilə yazılıb. Altıncı nəşr kodundan keçmisinizsə, burada çətinlik çəkməyəcəksiniz. Funksiya aşağıdakı kimidir write_pipe()/writep()int write_pipe(struct m_inode * inode, char * buf, int count) { char * b=buf;wake_up(&inode->i_wait); if (inode->i_count != 2) { /* oxucular yoxdur */ 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; } Strukturların təriflərinə baxmadan, inode referans sayğacının yazma əməliyyatına səbəb olub-olmadığını başa düşə bilərsiniz:
. Bayt səviyyəsində işləməklə yanaşı, bu funksiyanı yuxarıda təsvir edilən fikirlərlə asanlıqla uyğunlaşdırmaq mümkündür. Hətta
sleep_on SIGPIPEwake_up hətta bu qədər qeyri-adi görünmür./Müasir Linux nüvələri, FreeBSD, NetBSD, OpenBSD не выглядит такой чужеродной.
Современные ядра Linux, FreeBSD, NetBSD, OpenBSD
Mən müasir nüvələrdən bəzilərinə sürətlə baxdım. Heç birində diskdən istifadə edən bir reallaşma artıq yoxdur (təəccüblü deyil). Linux-un öz reallaşması var. Və üç müasir BSD nüvəsi, Jon Dyson tərəfindən yazılan kod əsasında reallaşmalar saxlayır, lakin illər ərzində bir-birindən çox fərqləndilər.
Oxumaq üçün write_pipe()/writep() (Linux-da) və ya sys/kern/sys_pipe.c (BSD-də), həqiqi özünü həsr etmə tələb olunur. Bu gün kodda performans və vektorlar, asinxron giriş-çıxış əməliyyatları kimi funksiyaların dəstəyi vacibdir. Yaddaş ayrılması, kilidlər və nüvənin konfiqurasiyası ilə bağlı detallarla bağlı hər şey çox fərqlidir. Bu, müasir əməliyyat sistemləri üçün universitetlərdə başlanğıc kursu üçün lazım olan bir şey deyil.
Hər halda, mən bir neçə köhnə nümunələri (məsələn, yaradılması SIGPIPE və qaytarılması EPIPE bağlı boruya yazılarkən) bu, belə fərqli müasir nüvələrdə aşkar etməyə maraqlandım. Yəqin ki, mən heç vaxt PDP-11 kompüterini canlı görməyəcəyəm, amma mənim doğulmağımdan bir neçə il əvvəl yazılmış koddan öyrənmək üçün hələ də çox şey var.
2011-ci ildə Divi Kapur tərəfindən yazılmış "" adlı məqalə Linux-da (hələ də) boruların necə işlədiyini izah edir. Və boru modelinin qarşılıqlı əlaqəsini təsvir edir, onun imkanları müvəqqəti fayllardan daha irəlidədir; həmçinin boruların altıncı redaksiyada Unix-də "çox mühafizəkar kilid" modelindən nə qədər irəlilədiyini göstərir.
Mənbə: habr.com
