splice() dapat memindahkan byte antara dua file descriptor tanpa terlebih dahulu menyalin payload ke buffer userspace, tetapi antarmukanya mengharuskan setidaknya satu endpoint berupa pipe. Syarat ini menjadikan pipe lebih dari sekadar transport perantara. Pipe adalah batas buffer yang terlihat oleh kernel, tempat semantik offset, blocking, kapasitas, dan partial progress operasi tersebut dibentuk.
Hal ini berbeda dari read() yang diikuti write(). Pada urutan tersebut, userspace memiliki array byte perantara dan dapat memeriksa atau mengubahnya. Dengan splice(), payload dapat tetap berada di storage yang dikelola kernel sementara proses mengoordinasikan perpindahan antar-endpoint.
Pipe merupakan bagian dari kontrak antarmuka
System call Linux splice() menerima input descriptor, output descriptor, batas jumlah byte, offset opsional, dan flags. Setidaknya satu descriptor harus merujuk ke pipe. Jika tidak ada yang berupa pipe, pemanggilan gagal dengan EINVAL.
Relay file ke socket karena itu biasanya memiliki dua tahap transfer. Tahap pertama memasukkan byte ke pipe, lalu tahap kedua mengeluarkannya menuju socket.
Pipe memisahkan ingress dari egress. Byte yang diterima pemanggilan pertama memakai kapasitas pipe sampai pemanggilan kedua mengonsumsinya. Transfer pertama yang berhasil tidak berarti destination socket sudah menerima byte yang sama.
Pemisahan tersebut terlihat saat terjadi backpressure. Jika socket belum dapat menerima data tambahan, pipe dapat menyimpan byte yang tertunda. Ketika tidak ada kapasitas bebas, transfer sisi input berikutnya harus menunggu, mengembalikan partial progress, atau gagal dengan EAGAIN pada kondisi nonblocking yang sesuai.
Menghindari userspace copy tidak menghapus buffering
Properti utama splice() adalah transfer data tanpa menyalin payload antara kernel address space dan user address space. Hal ini tidak berarti buffering tidak ada atau setiap endpoint dapat menyerahkan physical page yang sama secara langsung ke endpoint lain.
Linux pipe buffer mendeskripsikan data yang dikelola kernel. Bergantung pada source, destination, filesystem, dan jalur kernel, data dapat direpresentasikan melalui referensi ke page tanpa disalin ke array userspace. Kontrak antarmukanya tetap mengenai operasi transfer, bukan janji universal tentang perpindahan page.
SPLICE_F_MOVE memperlihatkan batas ini. Flag tersebut merupakan permintaan untuk memindahkan page alih-alih menyalinnya ketika memungkinkan, tetapi hanya berupa hint. Perilaku Linux yang terdokumentasi tidak menjadikan page movement sebagai jaminan yang dapat diandalkan caller. Kode yang memakai splice() harus tetap benar ketika kernel melakukan internal copy.
Dengan demikian ada dua klaim berbeda: pemanggilan menghindari jalur payload kernel-ke-userspace-ke-kernel yang biasa, tetapi tidak menjamin setiap byte mencapai tujuan tanpa internal copying sama sekali.
Offset hanya berlaku pada endpoint non-pipe
Pipe memiliki stream semantics, bukan posisi file yang dapat di-seek, sehingga pointer offset yang bersesuaian harus NULL. Memberikan offset untuk pipe menghasilkan error.
Untuk input descriptor non-pipe, offset NULL berarti splice() menggunakan dan memajukan file offset descriptor tersebut. Pointer non-NULL memilih posisi awal eksplisit; nilai yang ditunjuk maju sebesar jumlah byte yang ditransfer sementara file offset descriptor sendiri tidak berubah. Sisi output mengikuti aturan serupa ketika bukan pipe.
Perbedaan ini relevan ketika beberapa operasi berbagi open file description. Menggunakan current descriptor offset mengikuti shared-offset semantics normal dari open file description tersebut. Offset pointer eksplisit memberi transfer posisi sendiri tanpa mengubah descriptor offset.
Argumen offset mengatur pencatatan posisi, bukan penempatan di dalam pipe. Data yang masuk ke pipe diurutkan oleh stream state pipe, sedangkan file positioning tetap melekat pada endpoint non-pipe.
Return value positif tetap berarti partial progress
Jumlah byte yang diminta merupakan batas atas. Pemanggilan yang berhasil dapat mentransfer lebih sedikit byte dan mengembalikan jumlah yang lebih kecil.
Relay berbasis splice() harus mempertahankan disiplin partial progress yang sama seperti antarmuka I/O lain. Jika jumlah yang dikembalikan lebih kecil dari permintaan, sisanya belum ditransfer oleh pemanggilan tersebut. Pada relay dua tahap, tahap kedua juga harus memperhitungkan konsumsi parsial dari pipe.
Hal ini menciptakan dua progress counter terpisah: byte yang sudah masuk ke pipe dan byte yang sudah keluar. Memperlakukan keduanya sebagai satu counter dapat meninggalkan data tertunda atau mengambil input baru sebelum data sebelumnya selesai dikeluarkan.
Return value nol memiliki arti yang bergantung pada endpoint. Untuk input non-pipe seperti regular file, nilai nol dapat menandai akhir input. Ketika input berupa pipe, nol menunjukkan tidak ada data tersisa dan tidak ada writer yang terhubung.
Mode nonblocking memiliki lebih dari satu batas
SPLICE_F_NONBLOCK menerapkan perilaku nonblocking pada operasi pipe yang dilakukan splice(). Dokumentasi juga mencatat bahwa file descriptor lain masih dapat melakukan blocking kecuali descriptor tersebut memiliki state nonblocking yang sesuai.
Satu flag karena itu tidak mengubah semua kemungkinan operasi endpoint menjadi transaksi nonblocking. Transfer pipe ke socket memiliki state pipe sekaligus state socket. Flag tersebut menangani yang pertama; konfigurasi dan semantik endpoint tetap menentukan yang kedua.
Ketika kondisi nonblocking mencegah progress, EAGAIN dapat dikembalikan. Event loop harus mempertahankan byte yang sudah berada di pipe dan melanjutkan dari batas yang benar. Mengulang sisi input seolah tidak ada state dapat merusak bookkeeping walaupun kernel pipe masih menyimpan data sebelumnya.
SPLICE_F_MORE memiliki fungsi berbeda. Ketika output menuju socket, flag ini memberi hint bahwa data tambahan diperkirakan akan datang pada operasi splice berikutnya. Flag tersebut tidak mengubah jumlah byte yang ditransfer menjadi completion guarantee.
tee menduplikasi referensi pipe tanpa mengonsumsi input
Linux tee() memperlihatkan konsekuensi lain dari transfer berbasis pipe. Operasi ini menduplikasi data dari satu pipe ke pipe lain tanpa mengonsumsi data pada source pipe. Byte hasil duplikasi kemudian dapat dikonsumsi secara independen oleh operasi berikutnya.
Kernel dapat mengimplementasikannya dengan menambahkan referensi ke underlying page yang sama, bukan menyalin payload byte. Hal ini memungkinkan fan-out tanpa terlebih dahulu mematerialisasi data di userspace.
Lifetime model ikut berubah. Page yang dirujuk beberapa pipe buffer tidak dapat dianggap dimiliki eksklusif oleh satu consumer. Setiap pipe memiliki consumption state sendiri walaupun buffer entry mereka merujuk data dasar yang sama.
Perbedaannya dengan splice() jelas: splice() mengonsumsi data dari pipe saat memindahkannya, sedangkan tee() mempertahankan isi source pipe dan membuat referensi lain yang terlihat melalui pipe.
Kompatibilitas merupakan properti endpoint
Tidak semua jenis descriptor atau jalur filesystem mendukung setiap arah splice. System call dapat gagal dengan EINVAL ketika filesystem target tidak mendukung splicing, dan kombinasi descriptor yang tidak didukung dapat memiliki batas serupa.
Fallback berbasis capability karena itu menjadi bagian dari desain aplikasi Linux yang portable. Proses dapat memakai splice() ketika endpoint mendukungnya dan mempertahankan jalur buffered read()/write() ketika tidak. Fallback mengubah data path tanpa harus mengubah kontrak byte stream pada level yang lebih tinggi.
Batas serupa berlaku pada append mode. Target file yang dibuka dengan O_APPEND ditolak oleh splice(). Primitive transfer tidak otomatis mewarisi setiap mode write yang diterima write().
Batas optimisasi juga merupakan batas state
splice() sering dibahas sebagai mekanisme copy avoidance, tetapi properti antarmukanya yang lebih penting adalah pipe kernel eksplisit di antara tahap transfer. Pipe memiliki kapasitas terbatas, isi terurut, lifetime writer, lifetime reader, dan blocking state. Properti tersebut tetap terlihat walaupun payload byte tidak pernah masuk ke buffer userspace.
Relay yang benar karena itu melacak ownership dan progress pada batas pipe, bukan memperlakukan operasi sebagai shortcut transparan dari satu file descriptor ke descriptor lain. Copy avoidance mengubah jalur byte, tetapi tidak menghapus flow control, partial completion, pemeriksaan capability endpoint, atau kebutuhan mempertahankan pending state di antara retry.