Artikel ini menjelaskan implementasi pipeline di kernel Unix. Saya agak kecewa karena artikel terbaru berjudul "
Apa yang kita bicarakan?
Pipeline adalah "mungkin penemuan paling penting di Unix" - fitur yang menentukan filosofi dasar Unix dalam menyusun program kecil, dan slogan baris perintah yang sudah dikenal:
$ echo hello | wc -c
6
Fungsionalitas ini bergantung pada panggilan sistem yang disediakan kernel pipe
, yang dijelaskan di halaman dokumentasi
Pipeline menyediakan saluran satu arah untuk komunikasi antar proses. Pipa memiliki input (akhir tulis) dan output (akhir baca). Data yang ditulis ke input pipa dapat dibaca di output.
Pipa dibuat dengan menelepon
pipe(2)
, yang mengembalikan dua deskriptor file: satu merujuk ke input pipa, yang kedua ke output.
Output jejak dari perintah di atas menunjukkan pembuatan pipa dan aliran data melaluinya dari satu proses ke proses lainnya:
$ 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
Panggilan proses induk pipe()
untuk mendapatkan deskriptor file terlampir. Satu proses anak menulis ke satu deskriptor dan proses lain membaca data yang sama dari deskriptor lain. Shell "mengganti nama" deskriptor 2 dan 3 dengan dup4 agar sesuai dengan stdin dan stdout.
Tanpa pipa, shell harus menulis output dari satu proses ke file dan menyalurkannya ke proses lain untuk membaca data dari file. Akibatnya, kami akan membuang lebih banyak sumber daya dan ruang disk. Namun, pipeline lebih dari sekadar menghindari file sementara:
Jika suatu proses mencoba membaca dari pipa kosong, maka
read(2)
akan memblokir sampai data tersedia. Jika suatu proses mencoba untuk menulis ke pipa penuh, makawrite(2)
akan memblokir sampai data yang cukup telah dibaca dari pipa untuk menyelesaikan penulisan.
Seperti persyaratan POSIX, ini adalah properti penting: menulis ke pipa hingga PIPE_BUF
byte (setidaknya 512) harus atom sehingga proses dapat berkomunikasi satu sama lain melalui pipa dengan cara yang tidak bisa dilakukan oleh file normal (yang tidak memberikan jaminan seperti itu).
Dengan file biasa, suatu proses dapat menulis semua keluarannya ke sana dan meneruskannya ke proses lain. Atau proses dapat beroperasi dalam mode paralel keras, menggunakan mekanisme pensinyalan eksternal (seperti semaphore) untuk saling menginformasikan tentang penyelesaian penulisan atau pembacaan. Konveyor menyelamatkan kita dari semua kerumitan ini.
Apa yang kita cari?
Saya akan menjelaskan dengan jari saya untuk memudahkan Anda membayangkan cara kerja konveyor. Anda perlu mengalokasikan buffer dan beberapa status di memori. Anda memerlukan fungsi untuk menambah dan menghapus data dari buffer. Anda memerlukan beberapa fasilitas untuk memanggil fungsi selama operasi baca dan tulis pada deskriptor file. Dan kunci diperlukan untuk mengimplementasikan perilaku khusus yang dijelaskan di atas.
Kami sekarang siap untuk menginterogasi kode sumber kernel di bawah cahaya lampu terang untuk mengkonfirmasi atau menyangkal model mental kami yang tidak jelas. Tapi selalu bersiaplah untuk hal yang tak terduga.
Di mana kita mencari?
Saya tidak tahu di mana salinan buku terkenal saya berada.
Menelusuri arsip TUHS seperti mengunjungi museum. Kita dapat melihat sejarah bersama kita dan saya menghargai upaya bertahun-tahun untuk memulihkan semua materi ini sedikit demi sedikit dari kaset dan cetakan lama. Dan saya sangat sadar akan fragmen-fragmen yang masih hilang itu.
Setelah memuaskan keingintahuan kita tentang sejarah pipa kuno, kita dapat melihat inti modern sebagai perbandingan.
Kebetulan, pipe
adalah system call nomor 42 dalam tabel sysent[]
. Kebetulan?
Kernel Unix Tradisional (1970β1974)
Saya tidak menemukan jejak apapun pipe(2)
tidak di
TUHS mengklaim itu
Edisi ketiga Unix adalah versi terakhir dengan kernel yang ditulis dalam assembler, tetapi juga versi pertama dengan saluran pipa. Selama tahun 1973, pekerjaan sedang dilakukan untuk meningkatkan edisi ketiga, kernel ditulis ulang dalam C, dan dengan demikian lahirlah Unix edisi keempat.
Seorang pembaca menemukan pindaian dokumen di mana Doug McIlroy mengusulkan gagasan "menghubungkan program seperti selang taman".
Dalam buku Brian Kernighan
Ketika Unix muncul, kecintaan saya pada coroutine membuat saya meminta penulis OS, Ken Thompson, untuk mengizinkan data yang ditulis ke beberapa proses agar tidak hanya masuk ke perangkat, tetapi juga keluar ke proses lain. Ken pikir itu mungkin. Namun, sebagai minimalis, dia ingin setiap fitur sistem memainkan peran penting. Apakah penulisan langsung antar proses benar-benar merupakan keuntungan besar dibandingkan menulis ke file perantara? Dan hanya ketika saya membuat proposal khusus dengan nama menarik "pipeline" dan deskripsi sintaks interaksi proses, Ken akhirnya berseru: "Saya akan melakukannya!".
Dan berhasil. Suatu malam yang menentukan, Ken mengubah kernel dan shell, memperbaiki beberapa program standar untuk membakukan cara mereka menerima input (yang mungkin berasal dari pipa), dan mengubah nama file. Keesokan harinya, pipa sangat banyak digunakan dalam aplikasi. Pada akhir minggu, sekretaris menggunakannya untuk mengirim dokumen dari pengolah kata ke printer. Beberapa saat kemudian, Ken mengganti API dan sintaks asli untuk membungkus penggunaan pipa dengan konvensi yang lebih bersih yang telah digunakan sejak saat itu.
Sayangnya, kode sumber untuk kernel Unix edisi ketiga telah hilang. Dan meskipun kami memiliki kode sumber kernel yang ditulis dalam C
Kami memiliki teks dokumentasi untuk pipe(2)
dari kedua rilis, sehingga Anda dapat memulai dengan menelusuri dokumentasi pipe(2)
ditulis dalam assembler dan mengembalikan hanya satu deskriptor file, tetapi sudah menyediakan fungsionalitas inti yang diharapkan:
Panggilan sistem pipa menciptakan mekanisme I/O yang disebut pipeline. Deskriptor file yang dikembalikan dapat digunakan untuk operasi baca dan tulis. Ketika sesuatu ditulis ke dalam pipa, ia menyimpan hingga 504 byte data, setelah itu proses penulisan dihentikan. Saat membaca dari pipa, data buffer diambil.
Pada tahun berikutnya, kernel telah ditulis ulang dalam bahasa C, dan pipe(fildes)
Β»:
Panggilan sistem pipa menciptakan mekanisme I/O yang disebut pipeline. Deskriptor file yang dikembalikan dapat digunakan dalam operasi baca dan tulis. Ketika sesuatu ditulis ke pipa, deskriptor yang dikembalikan dalam r1 (resp.fildes[1]) digunakan, di-buffer hingga 4096 byte data, setelah itu proses penulisan ditangguhkan. Saat membaca dari pipa, deskriptor kembali ke r0 (resp.fildes[0]) mengambil data.
Diasumsikan bahwa setelah sebuah pipa didefinisikan, dua (atau lebih) proses yang berinteraksi (diciptakan oleh pemanggilan berikutnya garpu) akan meneruskan data dari pipeline menggunakan panggilan Baca baca ΠΈ menulis.
Shell memiliki sintaks untuk mendefinisikan array linier dari proses yang terhubung melalui pipa.
Panggilan untuk membaca dari pipa kosong (tidak berisi data buffer) yang hanya memiliki satu ujung (semua deskriptor file tulis ditutup) mengembalikan "akhir file". Panggilan tulis dalam situasi serupa diabaikan.
Paling awal
Unix Edisi Keenam (1975)
Mulai membaca kode sumber Unix
Selama bertahun-tahun buku itu Lions adalah satu-satunya dokumen tentang kernel Unix yang tersedia di luar Bell Labs. Meskipun lisensi edisi keenam mengizinkan guru untuk menggunakan kode sumbernya, lisensi edisi ketujuh mengecualikan kemungkinan ini, sehingga buku tersebut didistribusikan dalam salinan yang diketik secara ilegal.
Hari ini Anda dapat membeli salinan cetak ulang buku tersebut, yang sampulnya menggambarkan siswa di mesin fotokopi. Dan terima kasih kepada Warren Toomey (yang memulai proyek TUHS), Anda dapat mengunduh
Lebih dari 15 tahun yang lalu, saya mengetikkan salinan kode sumber yang disediakan Lionskarena saya tidak suka kualitas salinan saya dari salinan lain yang tidak diketahui jumlahnya. TUHS belum ada, dan saya tidak memiliki akses ke sumber lama. Tetapi pada tahun 1988 saya menemukan kaset lama dengan 9 trek yang memiliki cadangan dari komputer PDP11. Sulit untuk mengetahui apakah itu berhasil, tetapi ada pohon /usr/src/ utuh di mana sebagian besar file bertanda 1979, yang bahkan terlihat kuno. Itu edisi ketujuh, atau turunan PWB, pikirku.
Saya mengambil temuan itu sebagai dasar dan mengedit sumber secara manual ke edisi keenam. Sebagian kode tetap sama, sebagian harus sedikit diedit, mengubah token modern += menjadi =+ yang sudah usang. Sesuatu dihapus begitu saja, dan sesuatu harus ditulis ulang sepenuhnya, tetapi tidak terlalu banyak.
Dan hari ini kita bisa membaca secara online di TUHS kode sumber edisi keenam
Omong-omong, sekilas fitur utama kode-C sebelum periode Kernighan dan Ritchie adalah keringkasan. Jarang saya dapat menyisipkan potongan kode tanpa pengeditan ekstensif agar sesuai dengan area tampilan yang relatif sempit di situs saya.
Awal
/*
* 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
Ukuran buffer tidak berubah sejak edisi keempat. Namun di sini kita melihat, tanpa dokumentasi publik apa pun, bahwa pipeline pernah menggunakan file sebagai penyimpanan cadangan!
Adapun file LARG, mereka sesuai dengan
Inilah panggilan sistem yang sebenarnya 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;
}
Komentar tersebut dengan jelas menggambarkan apa yang terjadi di sini. Tapi tidak semudah itu untuk memahami kodenya, sebagian karena caranya "R0
ΠΈ R1
parameter panggilan sistem dan nilai pengembalian diteruskan.
Mari kita coba dengan
pipe()
jatuh tempo R0
ΠΈ R1
mengembalikan nomor deskriptor file untuk membaca dan menulis. falloc()
mengembalikan pointer ke struktur file, tetapi juga "mengembalikan" melalui u.u_ar0[R0]
dan deskriptor file. Artinya, kode disimpan di r
deskriptor file untuk membaca dan menetapkan deskriptor untuk menulis langsung dari u.u_ar0[R0]
setelah panggilan kedua falloc()
.
Bendera FPIPE
, yang kami setel saat membuat pipeline, mengontrol perilaku fungsi
/*
* 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);
}
/* β¦ */
}
Kemudian fungsinya readp()
Π² pipe.c
membaca data dari pipa. Tetapi lebih baik untuk melacak implementasinya mulai dari writep()
. Sekali lagi, kode menjadi lebih rumit karena sifat dari konvensi penyampaian argumen, tetapi beberapa detail dapat dihilangkan.
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;
}
Kami ingin menulis byte ke input pipa u.u_count
. Pertama kita perlu mengunci inode (lihat di bawah plock
/prele
).
Kemudian kami memeriksa jumlah referensi inode. Selama kedua ujung pipa tetap terbuka, penghitungnya harus 2. Kami berpegang pada satu tautan (dari rp->f_inode
), jadi jika penghitungnya kurang dari 2, ini berarti bahwa proses pembacaan telah menutup ujung salurannya. Dengan kata lain, kami mencoba menulis ke pipa tertutup, yang merupakan kesalahan. Kode kesalahan pertama EPIPE
dan sinyal SIGPIPE
muncul di edisi keenam Unix.
Tetapi meskipun konveyor terbuka, konveyor bisa penuh. Dalam hal ini, kami melepaskan kunci dan tertidur dengan harapan proses lain akan membaca dari pipa dan mengosongkan cukup ruang di dalamnya. Saat kita bangun, kita kembali ke awal, menutup kuncinya lagi dan memulai siklus tulis baru.
Jika ada cukup ruang kosong di dalam pipa, maka kami menulis data menggunakan i_size1
inode'a (dengan pipa kosong bisa sama dengan 0) menunjuk ke akhir data yang sudah dikandungnya. Jika ada cukup ruang untuk menulis, kita dapat mengisi saluran pipa i_size1
untuk PIPESIZ
. Kemudian kami melepaskan kunci dan mencoba membangunkan proses apa pun yang menunggu untuk dibaca dari pipa. Kami kembali ke awal untuk melihat apakah kami berhasil menulis byte sebanyak yang kami butuhkan. Jika tidak, maka kami memulai siklus rekaman baru.
Biasanya parameter i_mode
inode digunakan untuk menyimpan izin r
, w
ΠΈ x
. Tetapi dalam kasus pipeline, kami memberi sinyal bahwa beberapa proses sedang menunggu penulisan atau pembacaan menggunakan bit IREAD
ΠΈ IWRITE
masing-masing. Proses menetapkan bendera dan panggilan sleep()
, dan diharapkan di masa mendatang beberapa proses lain akan dipanggil wakeup()
.
Keajaiban nyata terjadi di sleep()
ΠΈ wakeup()
. Mereka diimplementasikan di
/*
* 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) /* β¦ */
Proses yang memanggil sleep()
untuk saluran tertentu, nantinya dapat dibangunkan oleh proses lain, yang akan memanggil wakeup()
untuk saluran yang sama. writep()
ΠΈ readp()
mengoordinasikan tindakan mereka melalui panggilan berpasangan tersebut. perhatikan itu pipe.c
selalu mengutamakan PPIPE
saat dipanggil sleep()
, jadi semua sleep()
dapat terganggu oleh sinyal.
Sekarang kita memiliki segalanya untuk memahami fungsinya 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);
}
Anda mungkin akan lebih mudah membaca fungsi ini dari bawah ke atas. Cabang "baca dan kembalikan" biasanya digunakan ketika ada beberapa data dalam saluran pipa. Dalam hal ini, kami menggunakan f_offset
baca, lalu perbarui nilai offset yang sesuai.
Pada pembacaan selanjutnya, pipeline akan kosong jika pembacaan offset telah tercapai i_size1
di inode. Kami mengatur ulang posisi ke 0 dan mencoba membangunkan proses apa pun yang ingin menulis ke saluran pipa. Kita tahu bahwa ketika konveyor penuh, writep()
tertidur di ip+1
. Dan sekarang pipanya kosong, kita bisa membangunkannya untuk melanjutkan siklus tulisnya.
Jika tidak ada yang bisa dibaca, maka readp()
dapat memasang bendera IREAD
dan tertidur ip+2
. Kita tahu apa yang akan membangunkannya writep()
ketika menulis beberapa data ke pipa.
Komentar di u
Β» kita dapat memperlakukannya seperti fungsi I/O biasa yang mengambil file, posisi, buffer di memori, dan menghitung jumlah byte untuk dibaca atau ditulis.
/*
* 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;
/* β¦ */
Adapun pemblokiran "konservatif", lalu readp()
ΠΈ writep()
mengunci inode sampai selesai atau mendapatkan hasil (mis wakeup
). plock()
ΠΈ prele()
bekerja sederhana: menggunakan serangkaian panggilan yang berbeda sleep
ΠΈ wakeup
izinkan kami untuk membangunkan proses apa pun yang membutuhkan kunci yang baru saja kami lepaskan:
/*
* 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);
}
}
Awalnya saya tidak mengerti mengapa readp()
tidak menyebabkan prele(ip)
sebelum panggilan wakeup(ip+1)
. Hal pertama writep()
panggilan dalam lingkarannya, ini plock(ip)
, yang menghasilkan kebuntuan jika readp()
belum menghapus bloknya, jadi kodenya entah bagaimana harus bekerja dengan benar. Jika Anda melihat wakeup()
, menjadi jelas bahwa itu hanya menandai proses tidur siap untuk dieksekusi, sehingga di masa depan sched()
benar-benar meluncurkannya. Jadi readp()
penyebab wakeup()
, buka kunci, atur IREAD
dan panggilan sleep(ip+2)
- semua ini sebelumnya writep()
memulai kembali siklus.
Ini melengkapi deskripsi saluran pipa di edisi keenam. Kode sederhana, implikasi luas.
Xv6, kernel sederhana mirip Unix
Untuk membuat nukleus
Kode berisi implementasi yang jelas dan bijaksana 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()
mengatur status semua implementasi lainnya, yang mencakup fungsi piperead()
, pipewrite()
ΠΈ pipeclose()
. Panggilan sistem yang sebenarnya sys_pipe
adalah pembungkus yang diimplementasikan di
Linux 0.01
Anda dapat menemukan kode sumber untuk Linux 0.01. Akan sangat bermanfaat untuk mempelajari implementasi jaringan pipa di bukunya fs
/pipe.c
. Di sini, inode digunakan untuk merepresentasikan pipeline, tetapi pipeline itu sendiri ditulis dalam bahasa C modern. Jika Anda telah meretas kode edisi keenam, Anda tidak akan mengalami masalah di sini. Seperti inilah fungsinya 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;
}
Bahkan tanpa melihat definisi struct, Anda dapat mengetahui bagaimana penghitungan referensi inode digunakan untuk memeriksa apakah operasi tulis menghasilkan SIGPIPE
. Selain pekerjaan byte demi byte, fungsi ini mudah dibandingkan dengan ide di atas. Bahkan logika sleep_on
/wake_up
tidak terlihat begitu asing.
Kernel Linux Modern, FreeBSD, NetBSD, OpenBSD
Saya segera membahas beberapa kernel modern. Tak satu pun dari mereka sudah memiliki implementasi berbasis disk (tidak mengherankan). Linux memiliki implementasinya sendiri. Dan meskipun tiga kernel BSD modern berisi implementasi berdasarkan kode yang ditulis oleh John Dyson, selama bertahun-tahun mereka menjadi terlalu berbeda satu sama lain.
Untuk membaca fs
/pipe.c
(di Linux) atau sys
/kern
/sys_pipe.c
(di *BSD), dibutuhkan dedikasi yang nyata. Performa dan dukungan untuk fitur seperti vektor dan I/O asinkron penting dalam kode saat ini. Dan detail alokasi memori, kunci, dan konfigurasi kernel semuanya sangat bervariasi. Ini bukan yang dibutuhkan universitas untuk kursus pengantar sistem operasi.
Bagaimanapun, menarik bagi saya untuk menggali beberapa pola lama (misalnya, generate SIGPIPE
dan kembali EPIPE
saat menulis ke pipa tertutup) di semua kernel modern yang sangat berbeda ini. Saya mungkin tidak akan pernah melihat komputer PDP-11 secara langsung, tetapi masih banyak yang harus dipelajari dari kode yang ditulis beberapa tahun sebelum saya lahir.
Ditulis oleh Divi Kapoor pada tahun 2011, artikel "
Sumber: www.habr.com