Memanggil dup() tidak membuat posisi stream yang independen. Descriptor yang dikembalikan mengacu pada open file description yang sama dengan descriptor sumber, sehingga seek melalui salah satu descriptor mengubah offset yang terlihat melalui keduanya. Pada saat yang sama, state lokal descriptor seperti flag close-on-exec tetap melekat pada masing-masing descriptor secara terpisah.
Pemisahan ini mudah terlewat karena kedua jenis state dimanipulasi melalui file descriptor berupa integer. Integer tersebut hanyalah referensi lokal milik proses. Beberapa descriptor dapat menunjuk ke satu open file description, dan objek bersama itulah yang membawa state yang memengaruhi read, write, seek, serta perubahan status flag.
Nomor descriptor bukan open state
Pada sistem POSIX, open() yang berhasil membuat sebuah open file description dan mengembalikan file descriptor yang mengacu padanya. Open file description menyimpan file offset saat ini dan file status flags. Descriptor sendiri merupakan entry dalam descriptor table milik proses.
Pemanggilan open() kedua terhadap pathname yang sama biasanya membuat open file description lain. Karena itu, dua hasil open tersebut dapat memiliki offset independen meskipun keduanya pada akhirnya mengacu pada file yang sama.
Duplikasi memiliki semantik berbeda:
int a = open("events.log", O_RDONLY);
int b = dup(a);Setelah dup(a) berhasil, a dan b adalah nomor descriptor berbeda yang mengacu pada satu open file description. Jika offset awal a adalah nol, read melalui a memajukan offset bersama. Read berikutnya melalui b dimulai dari posisi yang sudah maju tersebut.
Ini bukan properti pathname atau inode semata. Dua pemanggilan independen open("events.log", O_RDONLY) membuat open file description terpisah dan karena itu offset yang terpisah. Sharing muncul dari hubungan referensi yang dibuat melalui duplikasi, inheritance, atau interface lain yang mempertahankan open file description yang sama.
Seek melalui satu alias menggeser posisi alias lainnya
File offset merupakan milik open file description. lseek() mengubah offset tersebut, bukan menyimpan posisi di dalam entry descriptor table.
Urutan singkat berikut memperlihatkan keterkaitannya:
int a = open("data.bin", O_RDONLY);
int b = dup(a);
lseek(b, 4096, SEEK_SET);
char buf[128];
read(a, buf, sizeof buf);Dengan asumsi semua pemanggilan berhasil dan file dapat di-seek, read(a, ...) dimulai pada offset 4096. Operasi itu kemudian memajukan offset bersama sebesar jumlah byte yang dibaca.
Keterkaitan ini penting ketika kode menganggap descriptor hasil duplikasi sebagai batas isolasi. Memberikan duplicate kepada komponen lain tidak memberinya cursor independen. Seek atau operasi I/O biasa yang mengubah posisi dapat mengubah posisi yang digunakan pemegang descriptor asli.
Cursor independen membutuhkan open file description yang independen, umumnya dihasilkan oleh open() lain jika target dan model akses memungkinkan. Meski demikian, pathname race dan semantik replacement dapat menjadi relevan jika reopening berdasarkan nama dilakukan setelah open awal. Pilihan antara duplication dan reopening karena itu juga merupakan pilihan tentang identitas objek dan namespace lookup.
File status flag mengikuti description yang dibagi
State yang dibagi lebih luas daripada offset. File status flags yang diperoleh dengan fcntl(fd, F_GETFL) merupakan milik open file description. Flag yang dapat diubah dengan F_SETFL, seperti O_APPEND atau O_NONBLOCK ketika berlaku, akibatnya terlihat melalui semua descriptor yang mengacu pada description yang sama.
Jika kode mengaktifkan O_APPEND melalui salah satu duplicate, write melalui duplicate lain menggunakan status append yang sama. Perubahan itu tidak terbatas pada descriptor integer yang dipakai dalam pemanggilan fcntl().
Perilaku tersebut dapat melintasi batas abstraksi. Library yang menerima duplicate mungkin tampak memiliki handle sendiri, tetapi mengubah file status flag yang mutable dapat memengaruhi I/O di tempat lain melalui alias lain. Duplikasi descriptor lebih mudah mengisolasi referensi lifetime daripada mengisolasi I/O state.
Efek persis sebuah status flag tetap bergantung pada tipe objek dan kontrak platform. O_NONBLOCK, misalnya, memiliki perilaku bermakna pada interface yang mendefinisikan operasi nonblocking. Sharing flag tidak berarti setiap objek underlying bereaksi dengan cara yang sama.
Descriptor flag tetap lokal pada masing-masing descriptor
File descriptor flags berada pada lapisan berbeda. FD_CLOEXEC, yang dimanipulasi melalui F_GETFD dan F_SETFD, terkait dengan descriptor tertentu, bukan dengan open file description yang dibagi.
Perbedaan ini memungkinkan dua alias memiliki perilaku inheritance yang berbeda saat exec sambil tetap berbagi offset dan status flag yang sama.
dup() biasa mengembalikan duplicate dengan flag close-on-exec dalam keadaan clear. Interface seperti F_DUPFD_CLOEXEC dan, pada Linux, dup3() dengan O_CLOEXEC dapat membuat duplicate dengan close-on-exec aktif secara atomik.
Bentuk atomik penting pada program multithreaded yang membuat descriptor sementara thread lain dapat mengeksekusi program image baru. Membuat descriptor lalu mengatur FD_CLOEXEC dalam operasi berikutnya meninggalkan interval ketika descriptor sudah ada tetapi belum memiliki flag yang dimaksud. Interface yang menerapkan close-on-exec saat descriptor dibuat menghilangkan interval tersebut.
Pemisahannya dapat diringkas sebagai dua lapisan:
descriptor a ----+
+---- open file description ---- file
descriptor b ----+ |
+-- file offset
+-- file status flags
descriptor a: descriptor flags
descriptor b: descriptor flagsDescriptor-local flags tidak menjadi shared hanya karena kedua panah mengarah ke open file description yang sama.
Fork mempertahankan hubungan sharing yang sama
Descriptor sharing tidak terbatas pada dup(). Setelah fork(), child mewarisi descriptor yang mengacu pada open file description yang sama dengan descriptor terkait di parent.
Untuk regular file yang seekable, parent dan child karena itu dapat memengaruhi offset bersama. Jika satu proses melakukan seek, proses lain melihat posisi hasilnya melalui alias yang diwarisi. Read dan write yang memajukan offset juga bekerja terhadap state open file description yang sama.
Ini tidak berarti parent dan child berbagi descriptor table. Setiap proses memiliki descriptor sendiri, dan salah satunya dapat melakukan close atau mengubah descriptor-local flags tanpa langsung mengubah entry descriptor table milik proses lain. Objek yang dibagi berada satu lapisan di bawah table tersebut.
Lifetime open file description juga mengikuti referensi, bukan satu nomor descriptor. Menutup satu alias menghapus referensi tersebut. Open file description yang mendasarinya tetap aktif selama masih ada descriptor lain yang mengacu padanya.
Model referensi ini terlihat pada interface di luar I/O file biasa. Pada Linux, registration epoll terikat pada kombinasi descriptor dan open file description underlying dengan cara yang membuat duplicate descriptor relevan terhadap lifetime registration event. Menutup satu descriptor tidak selalu menghilangkan open description underlying selama alias lain masih ada.
Menutup satu alias tidak mencabut alias lainnya
close(a) yang berhasil membuat descriptor a tidak valid di proses tersebut. Operasi itu tidak membuat b tidak valid ketika b adalah duplicate yang mengacu pada open file description yang sama.
Hal ini berguna untuk ownership transfer yang disengaja. Proses dapat menduplikasi descriptor, meneruskan atau mempertahankan satu referensi, lalu menutup referensi lainnya. Open file description bersama tetap ada sampai lifetime referensinya berakhir sesuai aturan sistem operasi.
Hal ini juga membatasi arti close sebagai mekanisme pembatalan. Jika bagian lain dari proses, child process, atau penerima descriptor passing memegang alias, menutup descriptor lokal tidak mencabut alias tersebut. Ownership resource karena itu tidak dapat disimpulkan hanya dari satu nomor descriptor.
Prinsip yang sama berlaku untuk penghapusan pathname. Open file description mengacu pada objek yang sudah dibuka secara independen dari perubahan pathname berikutnya. Menghapus atau mengganti pathname tidak mengarahkan ulang descriptor yang sudah terbuka ke file baru. Duplikasi descriptor mempertahankan referensi ke objek yang sudah dibuka tersebut, bukan mengulang pathname resolution.
Shared offset adalah coordination state
Shared offset dapat berguna ketika beberapa actor sengaja mengonsumsi satu posisi stream berurutan. Namun ia juga dapat menjadi koordinasi tersembunyi ketika actor tersebut diharapkan bekerja secara independen.
I/O yang tidak bergantung pada posisi menghindari perubahan shared file offset. Interface seperti POSIX pread() dan pwrite() bekerja pada offset eksplisit dan tidak memperbarui current offset milik open file description. Karena itu, interface tersebut dapat mengurangi gangguan cursor ketika objek dan operasinya sesuai.
Hal itu tidak membuat descriptor hasil duplikasi menjadi handle yang sepenuhnya independen. File status flags dan objek underlying yang dibuka tetap dibagi pada lapisan open-file-description, dan concurrent I/O memiliki aturan ordering serta atomicity tambahan yang bergantung pada operasi dan platform.
Batas praktisnya bersifat struktural: duplication membuat nama lain untuk open state yang sudah ada. open() terpisah membuat open state baru. Keduanya dapat menunjuk ke file yang sama sambil membawa semantik sharing yang berbeda, dan kode yang menganggap keduanya dapat dipertukarkan dapat tanpa sengaja mengaitkan posisi cursor, append mode, nonblocking mode, lifetime, atau perilaku process inheritance.
Descriptor integer sendiri tidak memperlihatkan topologi tersebut. Penalaran yang benar bergantung pada lapisan yang memiliki setiap state: descriptor-table entry, open file description, atau underlying file object.