Bu makale, Unix çekirdeğinde boru hatlarının uygulanmasını açıklamaktadır. " başlıklı yeni bir makale beni biraz hayal kırıklığına uğrattı.
Ne bahsediyoruz?
Ardışık hatlar, "Unix'teki muhtemelen en önemli icattır" - Unix'in altında yatan küçük programları bir araya getirme felsefesinin ve tanıdık komut satırı sloganının belirleyici bir özelliği:
$ echo hello | wc -c
6
Bu işlevsellik, çekirdek tarafından sağlanan sistem çağrısına bağlıdır pipe
, dokümantasyon sayfalarında açıklanan
İşlem hatları, süreçler arası iletişim için tek yönlü bir kanal sağlar. İşlem hattının bir girişi (yazma ucu) ve bir çıkışı (okuma ucu) vardır. Boru hattının girişine yazılan veriler çıkışta okunabilir.
Ardışık düzen çağrılarak oluşturulur
pipe(2)
, iki dosya tanıtıcı döndürür: biri ardışık düzenin girişini, ikincisi ise çıktıyı ifade eder.
Yukarıdaki komutun izleme çıktısı, bir ardışık düzenin oluşturulmasını ve bir işlemden diğerine veri akışını gösterir:
$ 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
Üst süreç çağrıları pipe()
ekli dosya tanımlayıcılarını almak için. Bir alt süreç, bir tanımlayıcıya yazar ve başka bir süreç, başka bir tanımlayıcıdan aynı verileri okur. Kabuk, stdin ve stdout ile eşleşmesi için 2 ve 3 tanımlayıcılarını dup4 ile "yeniden adlandırır".
Ardışık hatlar olmadan, kabuğun bir işlemin çıktısını bir dosyaya yazması ve dosyadaki verileri okumak için onu başka bir işleme aktarması gerekir. Sonuç olarak, daha fazla kaynak ve disk alanı israf etmiş oluruz. Ancak, işlem hatları geçici dosyalardan kaçınmaktan daha fazlası için iyidir:
Bir işlem boş bir ardışık düzenden okumaya çalışırsa, o zaman
read(2)
veriler mevcut olana kadar engelleyecektir. Bir işlem tam bir boru hattına yazmaya çalışırsa, o zamanwrite(2)
yazmayı tamamlamak için ardışık düzenden yeterli veri okunana kadar engelleyecektir.
POSIX gereksinimi gibi, bu da önemli bir özelliktir: boru hattına yazma PIPE_BUF
baytların (en az 512) atomik olması gerekir, böylece süreçler, normal dosyaların (bu tür garantiler sağlamayan) yapamayacağı şekilde ardışık düzen aracılığıyla birbirleriyle iletişim kurabilir.
Normal bir dosyayla, bir süreç tüm çıktısını ona yazabilir ve onu başka bir sürece aktarabilir. Veya süreçler, bir yazma veya okumanın tamamlanması hakkında birbirlerini bilgilendirmek için harici bir sinyal mekanizması (bir semafor gibi) kullanarak sert bir paralel modda çalışabilir. Konveyörler bizi tüm bu zahmetten kurtarıyor.
Ne arıyoruz?
Bir konveyörün nasıl çalışabileceğini hayal etmenizi kolaylaştırmak için parmaklarımla açıklayacağım. Bellekte bir arabellek ve bazı durumlar ayırmanız gerekecek. Arabelleğe veri eklemek ve çıkarmak için işlevlere ihtiyacınız olacak. Dosya tanıtıcılarda okuma ve yazma işlemleri sırasında işlevleri çağırmak için bazı olanaklara ihtiyacınız olacaktır. Ve yukarıda açıklanan özel davranışı uygulamak için kilitlere ihtiyaç vardır.
Belirsiz zihinsel modelimizi doğrulamak veya çürütmek için parlak lamba ışığı altında çekirdeğin kaynak kodunu sorgulamaya artık hazırız. Ancak beklenmedik durumlara karşı her zaman hazırlıklı olun.
Nereye bakıyoruz?
Ünlü kitabın benim kopyamın nerede olduğunu bilmiyorum.
TUHS arşivlerinde dolaşmak bir müzeyi gezmek gibidir. Ortak tarihimize bakabiliriz ve tüm bu materyali eski kasetlerden ve çıktılardan parça parça kurtarmak için yıllarca verilen çabaya saygı duyuyorum. Ve hala kayıp olan bu parçaların kesinlikle farkındayım.
Boru hatlarının antik tarihi hakkındaki merakımızı giderdikten sonra, karşılaştırma için modern çekirdeklere bakabiliriz.
Bu arada, pipe
tablodaki sistem çağrı numarası 42'dir sysent[]
. Tesadüf?
Geleneksel Unix çekirdekleri (1970–1974)
hiçbir iz bulamadım pipe(2)
ne de
TUHS iddia ediyor
Unix'in üçüncü baskısı, birleştiricide yazılmış bir çekirdeğe sahip son sürümdü, ama aynı zamanda işlem hatlarına sahip ilk sürümdü. 1973'te üçüncü baskıyı geliştirmek için çalışmalar yapılıyordu, çekirdek C'de yeniden yazıldı ve böylece Unix'in dördüncü baskısı doğdu.
Bir okuyucu, Doug McIlroy'un "programları bir bahçe hortumu gibi bağlama" fikrini önerdiği bir belgenin tarandığını buldu.
Brian Kernighan'ın kitabında
Unix ortaya çıktığında, eşyordamlara olan tutkum, işletim sistemi yazarı Ken Thompson'dan bazı işlemlere yazılan verilerin yalnızca aygıta değil, aynı zamanda başka bir işlemin çıkışına da gitmesine izin vermesini istememe neden oldu. Ken bunun mümkün olduğunu düşündü. Ancak bir minimalist olarak, her sistem özelliğinin önemli bir rol oynamasını istedi. İşlemler arasında doğrudan yazma, bir ara dosyaya yazmaya göre gerçekten büyük bir avantaj mı? Ve ancak akılda kalıcı "boru hattı" adıyla ve süreçlerin etkileşiminin sözdiziminin bir açıklamasıyla belirli bir teklif yaptığımda, Ken sonunda "Yapacağım!"
Ve yaptım. Önemli bir akşam, Ken çekirdeği ve kabuğu değiştirdi, girdileri nasıl kabul ettiklerini (bir işlem hattından gelebilir) standart hale getirmek için birkaç standart programı düzeltti ve dosya adlarını değiştirdi. Ertesi gün, uygulamalarda boru hatları çok yaygın olarak kullanılmaya başlandı. Haftanın sonunda, sekreterler belgeleri kelime işlemcilerden yazıcıya göndermek için kullandılar. Bir süre sonra Ken, boru hatlarının kullanımını o zamandan beri kullanılan daha temiz kurallarla sarmak için orijinal API'yi ve sözdizimini değiştirdi.
Ne yazık ki, üçüncü sürüm Unix çekirdeğinin kaynak kodu kayboldu. Ve C ile yazılmış çekirdek kaynak koduna sahip olmamıza rağmen
Şunun için dokümantasyon metnimiz var: pipe(2)
her iki sürümden, böylece belgeleri arayarak başlayabilirsiniz pipe(2)
birleştiricide yazılır ve yalnızca bir dosya tanıtıcı döndürür, ancak zaten beklenen temel işlevselliği sağlar:
Sistem çağrısı boru boru hattı adı verilen bir G/Ç mekanizması oluşturur. Döndürülen dosya tanımlayıcı, okuma ve yazma işlemleri için kullanılabilir. Ardışık düzene bir şey yazıldığında, 504 bayta kadar veriyi arabelleğe alır ve ardından yazma işlemi askıya alınır. Ardışık düzenden okunurken arabelleğe alınan veriler alınır.
Ertesi yıl, çekirdek C'de yeniden yazıldı ve pipe(fildes)
"
Sistem çağrısı boru boru hattı adı verilen bir G/Ç mekanizması oluşturur. Döndürülen dosya tanıtıcıları, okuma ve yazma işlemlerinde kullanılabilir. Ardışık düzene bir şey yazıldığında, r1'de döndürülen tanımlayıcı (resp. fildes[1]) kullanılır, 4096 bayta kadar veri arabelleğe alınır ve ardından yazma işlemi askıya alınır. Ardışık düzenden okurken, r0'a döndürülen tanımlayıcı (resp. fildes[0]) verileri alır.
Bir ardışık düzen tanımlandıktan sonra, iki (veya daha fazla) etkileşimli sürecin (sonraki çağrılarla yaratıldığı) varsayılır. çatal) çağrıları kullanarak işlem hattından veri iletir okumak и yazmak.
Kabuk, bir ardışık düzen aracılığıyla bağlanan doğrusal bir süreç dizisini tanımlamak için bir sözdizimine sahiptir.
Yalnızca bir ucu olan (tüm yazma dosyası tanımlayıcıları kapalı) boş bir ardışık düzenden (arabelleğe alınmış veri içermeyen) okuma çağrıları "dosyanın sonu"nu döndürür. Benzer bir durumdaki yazma çağrıları yoksayılır.
en erken
Unix Altıncı Baskı (1975)
Unix kaynak kodunu okumaya başlama
Uzun yıllar kitap Aslanlar Bell Labs dışında Unix çekirdeğiyle ilgili tek belgeydi. Altıncı baskı lisansı öğretmenlerin kaynak kodunu kullanmasına izin verse de, yedinci baskı lisansı bu olasılığı dışladı, bu nedenle kitap daktiloyla yazılmış yasa dışı kopyalarla dağıtıldı.
Bugün, kapağı fotokopi makinesindeki öğrencileri tasvir eden kitabın yeniden basılmış bir kopyasını satın alabilirsiniz. Ve (TUHS projesini başlatan) Warren Toomey sayesinde, indirebilirsiniz
15 yılı aşkın bir süre önce, içinde sağlanan kaynak kodunun bir kopyasını yazdım. Aslanlarçünkü bilinmeyen sayıda başka kopyadan kopyamın kalitesini beğenmedim. TUHS henüz yoktu ve eski kaynaklara erişimim yoktu. Ancak 1988'de, bir PDP9 bilgisayarından yedeği olan 11 parçalı eski bir kaset buldum. İşe yarayıp yaramadığını bilmek zordu, ancak dosyaların çoğunun 1979 olarak işaretlendiği bozulmamış bir /usr/src/ ağacı vardı ve o zaman bile eski görünüyordu. Yedinci baskıydı ya da bir PWB türeviydi, diye düşündüm.
Bulguyu temel aldım ve kaynakları altıncı baskının durumuna göre manuel olarak düzenledim. Kodun bir kısmı aynı kaldı, modern belirteci += eskimiş =+ olarak değiştirerek, bir kısmı biraz düzenlenmeliydi. Bir şey basitçe silindi ve bir şeyin tamamen yeniden yazılması gerekiyordu, ancak çok fazla değil.
Ve bugün TUHS'de çevrimiçi olarak altıncı baskısının kaynak kodunu okuyabiliyoruz.
Bu arada, ilk bakışta Kernighan ve Ritchie döneminden önceki C kodunun ana özelliği, kısa. Sitemdeki nispeten dar bir görüntüleme alanına sığdırmak için kapsamlı düzenleme yapmadan kod parçacıkları ekleyemem.
Erken
/*
* 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
Tampon boyutu dördüncü baskıdan bu yana değişmedi. Ancak burada, herhangi bir kamuya açık belge olmaksızın, ardışık düzenlerin bir zamanlar dosyaları yedek depolama olarak kullandığını görüyoruz!
LARG dosyalarına gelince, bunlar şuna karşılık gelir:
İşte gerçek sistem çağrı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;
}
Yorum, burada neler olduğunu açıkça açıklıyor. Ancak kodu anlamak o kadar kolay değil, kısmen "R0
и R1
sistem çağrısı parametreleri ve dönüş değerleri iletilir.
ile deneyelim
pipe()
nedeniyle R0
и R1
okuma ve yazma için dosya tanımlayıcı numaralarını döndürür. falloc()
bir dosya yapısına bir işaretçi döndürür, ancak aynı zamanda aracılığıyla "döndürür" u.u_ar0[R0]
ve bir dosya tanıtıcı. Yani, kod depolanır r
okumak için dosya tanıtıcısı ve doğrudan dosyadan yazmak için bir tanımlayıcı atar. u.u_ar0[R0]
ikinci aramadan sonra falloc()
.
Bayrak FPIPE
işlem hattını oluştururken ayarladığımız , işlevin davranışını kontrol eder
/*
* 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);
}
/* … */
}
Daha sonra fonksiyon readp()
в pipe.c
boru hattından veri okur. Ancak, uygulamanın başından itibaren izlenmesi daha iyidir. writep()
. Yine, argüman geçiş kuralının doğası gereği kod daha karmaşık hale geldi, ancak bazı ayrıntılar atlanabilir.
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;
}
Boru hattı girişine bayt yazmak istiyoruz u.u_count
. İlk önce inode'u kilitlememiz gerekiyor (aşağıya bakın plock
/prele
).
Ardından inode referans sayısını kontrol ederiz. Boru hattının her iki ucu da açık kaldığı sürece sayaç 2 olmalıdır. rp->f_inode
), dolayısıyla sayaç 2'den küçükse, bu, okuma işleminin ardışık düzenin sonunu kapattığı anlamına gelmelidir. Başka bir deyişle, kapalı bir ardışık düzene yazmaya çalışıyoruz ki bu bir hatadır. İlk hata kodu EPIPE
ve sinyal SIGPIPE
Unix'in altıncı baskısında yayınlandı.
Ancak konveyör açık olsa bile dolu olabilir. Bu durumda, başka bir işlemin boru hattından okuyacağı ve içinde yeterince yer açacağı umuduyla kilidi serbest bırakıyoruz ve uykuya geçiyoruz. Uyandığımızda başa dönüyoruz, kilidi tekrar kapatıyoruz ve yeni bir yazma döngüsü başlatıyoruz.
Ardışık düzende yeterli boş alan varsa, kullanarak ona veri yazarız. i_size1
inode'a (boş bir ardışık düzen ile 0'a eşit olabilir) halihazırda içerdiği verilerin sonunu gösterir. Yazmak için yeterli alan varsa, boru hattını şuradan doldurabiliriz: i_size1
karşı PIPESIZ
. Ardından kilidi serbest bırakır ve boru hattından okumayı bekleyen herhangi bir işlemi uyandırmaya çalışırız. İhtiyacımız olan kadar bayt yazıp yazamadığımızı görmek için başa dönüyoruz. Değilse, yeni bir kayıt döngüsü başlatırız.
Genellikle parametre i_mode
inode izinleri depolamak için kullanılır r
, w
и x
. Ancak işlem hatları söz konusu olduğunda, bitleri kullanarak bazı işlemlerin bir yazma veya okuma beklediğini bildiririz. IREAD
и IWRITE
sırasıyla. İşlem bayrağı ayarlar ve çağırır sleep()
ve gelecekte başka bir işlemin çağrılması bekleniyor wakeup()
.
Gerçek sihir olur sleep()
и wakeup()
. Onlar uygulanır
/*
* 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ğıran süreç sleep()
belirli bir kanal için, daha sonra arayacak olan başka bir işlem tarafından uyandırılabilir. wakeup()
aynı kanal için writep()
и readp()
bu tür eşleştirilmiş aramalar yoluyla eylemlerini koordine edin. dikkat pipe.c
her zaman öncelik ver PPIPE
çağrıldığında sleep()
, Böylece hepsi sleep()
bir sinyalle kesilebilir.
Şimdi işlevi anlamak için her şeye sahibiz 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 işlevi aşağıdan yukarıya okumayı daha kolay bulabilirsiniz. "Oku ve geri dön" dalı genellikle ardışık düzende bazı veriler olduğunda kullanılır. Bu durumda, kullanırız f_offset
okuyun ve ardından karşılık gelen ofsetin değerini güncelleyin.
Sonraki okumalarda, okuma ofseti ulaştıysa boru hattı boş olacaktır. i_size1
düğümde. Konumu 0'a sıfırlıyoruz ve boru hattına yazmak isteyen herhangi bir işlemi uyandırmaya çalışıyoruz. Biliyoruz ki konveyör dolduğunda, writep()
uyuyakalmak ip+1
. Ve artık işlem hattı boş olduğuna göre, yazma döngüsüne devam etmek için onu uyandırabiliriz.
Okuyacak bir şey yoksa, o zaman readp()
bir bayrak ayarlayabilir IREAD
ve üzerinde uyuyakalmak ip+2
. Onu neyin uyandıracağını biliyoruz. writep()
boru hattına bazı veriler yazdığında.
İle ilgili yorumlar u
» bunları bellekte bir dosya, konum, arabellek alan ve okunacak veya yazılacak bayt sayısını sayan normal G/Ç işlevleri gibi ele alabiliriz.
/*
* 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;
/* … */
"İhtiyatlı" engellemeye gelince, o zaman readp()
и writep()
düğümleri bitene veya bir sonuç alana kadar kilitleyin (ör. wakeup
). plock()
и prele()
basitçe çalışın: farklı bir arama seti kullanma sleep
и wakeup
az önce serbest bıraktığımız kilide ihtiyaç duyan herhangi bir işlemi uyandırmamıza izin 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);
}
}
İlk başta nedenini anlayamadım readp()
neden olmaz prele(ip)
aramadan önce wakeup(ip+1)
. İlk şey writep()
döngüsündeki çağrılar, bu plock(ip)
, eğer bir kilitlenme ile sonuçlanır readp()
henüz bloğunu kaldırmadı, bu yüzden kod bir şekilde doğru çalışıyor olmalı. eğer bakarsan wakeup()
, yalnızca uyku sürecini yürütmeye hazır olarak işaretlediği anlaşılır, böylece gelecekte sched()
gerçekten başlattı. Bu yüzden readp()
nedenleri wakeup()
, kilidini açar, ayarlar IREAD
ve aramalar sleep(ip+2)
- tüm bunlar daha önce writep()
döngüyü yeniden başlatır.
Bu, altıncı baskıdaki boru hatlarının açıklamasını tamamlar. Basit kod, geniş kapsamlı çıkarımlar.
Xv6, Unix benzeri basit bir çekirdek
Bir çekirdek oluşturmak için
Kod, açık ve düşünceli bir uygulama içeriyor 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()
işlevleri içeren uygulamanın geri kalanının durumunu ayarlar piperead()
, pipewrite()
и pipeclose()
. Gerçek sistem çağrısı sys_pipe
uygulanan bir sarmalayıcıdır
Linux 0.01
Linux 0.01 için kaynak kodunu bulabilirsiniz. Boru hatlarının uygulanmasını onun araştırmalarında incelemek öğretici olacaktır. fs
/pipe.c
. Burada, boru hattını temsil etmek için bir inode kullanılır, ancak boru hattının kendisi modern C'de yazılmıştır. Altıncı baskı kodunu hacklediyseniz, burada herhangi bir sorun yaşamayacaksınız. Fonksiyon böyle görünüyor 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;
}
Yapı tanımlarına bakmadan bile, bir yazma işleminin sonuçlanıp sonuçlanmadığını kontrol etmek için inode referans sayısının nasıl kullanıldığını anlayabilirsiniz. SIGPIPE
. Bayt bayt çalışmaya ek olarak, bu işlevi yukarıdaki fikirlerle karşılaştırmak kolaydır. Hatta mantık sleep_on
/wake_up
çok yabancı görünmüyor.
Modern Linux Çekirdekleri, FreeBSD, NetBSD, OpenBSD
Hemen bazı modern çekirdeklerin üzerinden geçtim. Hiçbirinin zaten disk tabanlı bir uygulaması yok (şaşırtıcı değil). Linux'un kendi uygulaması vardır. Ve üç modern BSD çekirdeği, John Dyson tarafından yazılan koda dayalı uygulamalar içermesine rağmen, yıllar içinde birbirlerinden çok farklı hale geldiler.
Okumak fs
/pipe.c
(Linux'ta) veya sys
/kern
/sys_pipe.c
(*BSD'de), gerçek özveri gerektirir. Vektör ve eşzamansız G/Ç gibi özellikler için performans ve destek, günümüzde kodda önemlidir. Ve bellek tahsisi, kilitler ve çekirdek yapılandırmasının ayrıntıları büyük ölçüde değişir. Üniversitelerin işletim sistemlerine giriş dersi için ihtiyacı olan şey bu değil.
Her halükarda, birkaç eski modeli ortaya çıkarmak benim için ilginçti (örneğin, SIGPIPE
ve dönüş EPIPE
kapalı bir boru hattına yazarken) tüm bu, çok farklı, modern çekirdeklerde. Muhtemelen bir PDP-11 bilgisayarını asla canlı görmeyeceğim, ancak ben doğmadan birkaç yıl önce yazılan koddan öğrenilecek çok şey var.
Divi Kapoor tarafından 2011 yılında yazılan "
Kaynak: habr.com