Queue io_uring adalah shared memory yang diubah oleh dua domain eksekusi independen. User space menyiapkan submission entry dan memajukan metadata queue; kernel mengonsumsi submission tersebut lalu memublikasikan completion entry. Layout ring menghilangkan satu batas copy, tetapi pada saat yang sama membuat visibilitas memori menjadi bagian dari kontrak interface.
Assignment biasa pada level source ke tail queue tidak cukup sebagai model publikasi yang portabel. Data entry harus terlihat lebih dulu sebelum nilai tail yang membuat entry tersebut boleh dikonsumsi. Di sisi completion, user space harus mengamati publikasi completion dari kernel sebelum membaca field di completion tersebut. Relasi ordering ini adalah bagian dari correctness, bukan sekadar detail optimasi.
Properti ini membedakan io_uring dari interface di mana system call menjadi titik handoff yang jelas untuk setiap operasi. Shared ring memungkinkan handoff melalui memori, sehingga protokol producer-consumer harus menyatakan ordering yang dibutuhkan secara eksplisit.
Kepemilikan queue terbagi berdasarkan arah
Sebuah instance io_uring mengekspos submission queue dan completion queue melalui mapping yang dibagi dengan kernel. Peran producer dan consumer berlawanan di kedua arah.
Untuk submission, user space adalah producer. Ia mengisi submission queue entry, mencatat indeks pada submission array jika layout menggunakannya, lalu memajukan submission tail. Kernel mengonsumsi dari submission head.
Untuk completion, kernel adalah producer. Kernel menulis completion queue entry dan memajukan completion tail. User space mengonsumsi entry tersebut lalu memajukan completion head.
Pembagian ini dapat diringkas seperti berikut:
submission queue
userspace: entries -> tail
kernel: head ->
completion queue
kernel: entries -> tail
userspace: head ->Nilai head dan tail bukan sekadar counter. Tail di sisi producer memublikasikan write yang terjadi sebelumnya dan mendefinisikan entry baru yang kini terlihat. Head di sisi consumer melepaskan kapasitas setelah entry terkait selesai dikonsumsi.
Queue mask menangani wraparound indeks, tetapi wraparound berbeda dari ordering. Aritmetika yang benar tetap dapat mengekspos entry yang belum terinisialisasi penuh jika publikasi terjadi sebelum isi entry terlihat oleh consumer.
Publikasi membutuhkan edge ordering
Bayangkan sebuah submission entry yang memiliki opcode, file descriptor, offset, alamat buffer, length, dan application data. User space dapat menulis seluruh field itu sebelum membuat entry terlihat melalui tail queue.
Relasi yang dibutuhkan secara konseptual adalah:
write SQE fields
write submission index
|
| release publication
v
advance SQ tail
|
| acquire observation
v
kernel reads published entryOperasi release mencegah update publikasi terlihat seolah-olah terjadi sebelum write yang menginisialisasi entry. Operasi acquire yang sesuai di sisi consumer mencegah read entry berikutnya diperlakukan seolah-olah terjadi sebelum publikasi diamati.
Primitive pastinya merupakan bagian dari implementasi ABI dan bahasa atau library yang digunakan untuk mengaksesnya. Dokumentasi interface Linux menampilkan operasi acquire dan release di sekitar state ring karena akses shared memory biasa tetap harus mengikuti memory model arsitektur. Library seperti liburing membungkus detail ini sehingga caller biasanya menggunakan helper function daripada menulis barrier secara manual.
Ini tidak berarti setiap field membutuhkan full memory fence yang mahal. Kontraknya hanya membutuhkan relasi ordering tertentu antara inisialisasi dan publikasi. Semantik acquire-release mengekspresikan relasi itu lebih sempit daripada global serialization barrier.
System call tidak menghapus kontrak shared memory
Jalur submission umum pada akhirnya memanggil io_uring_enter() untuk memberi tahu kernel bahwa pekerjaan tersedia. Transisi ini dapat membuat persoalan ordering terlihat tidak perlu: jika eksekusi masuk ke kernel, mudah untuk menganggap syscall sebagai seluruh protokol publikasi.
ABI ring lebih luas dari asumsi tersebut. Metadata queue sendiri dibagi, dan mode seperti submission queue polling memungkinkan kernel mengonsumsi submission melalui polling thread. Dengan IORING_SETUP_SQPOLL, user space dapat memublikasikan pekerjaan ke thread tersebut melalui ring dan mungkin menghindari system call selama polling thread masih aktif.
Ketika polling thread tidur, kernel mengekspos kondisi wakeup dan user space mungkin perlu memanggil io_uring_enter() untuk membangunkannya. Urutan yang didokumentasikan tetap mengatur publikasi tail relatif terhadap pemeriksaan wakeup flag. Keputusan wakeup dan relasi publikasi entry adalah dua hal yang berbeda.
Karena itu, correctness tidak boleh bergantung pada adanya syscall insidental setelah setiap entry. Kode yang memanipulasi raw ring harus mengikuti protokol shared memory yang didefinisikan untuk setup mode yang digunakan.
Konsumsi completion memiliki constraint cermin
Completion queue membalik peran producer. Kernel mengisi completion queue entry, termasuk result dan correlation data dari aplikasi, lalu memublikasikan completion baru melalui completion tail.
User space harus mengamati publikasi tersebut sebelum menganggap field CQE siap digunakan. Secara konseptual:
kernel writes CQE fields
|
| release publication
v
kernel advances CQ tail
|
| acquire observation
v
userspace reads CQE fieldsSetelah memproses completion, user space memajukan completion head untuk mengembalikan kapasitas ring. Memajukan head terlalu cepat dapat membuat slot dipakai ulang sementara kode aplikasi masih bergantung pada isi sebelumnya.
Ini menciptakan batas lifetime untuk pointer CQE. Pointer ke mapped completion ring menunjuk storage milik ring, bukan object hasil immutable yang dialokasikan terpisah. Setelah konsumsi diakui dan slot boleh digunakan kembali, menyimpan pointer itu sebagai state aplikasi jangka panjang menjadi tidak aman. Data yang diperlukan setelah batas tersebut harus disalin ke storage milik aplikasi.
Pola yang sama berlaku pada sisi submission. Pointer ke SQE yang diperoleh dari helper library menunjuk storage yang dikelola ring. Window aman untuk mengubahnya berakhir ketika protokol submission memindahkan ownership untuk diproses.
Lifetime request melampaui publikasi SQE
Memublikasikan SQE tidak berarti semua object yang dirujuk SQE boleh langsung dibebaskan.
Sebagian metadata request disalin atau dikonsumsi ketika submission, tetapi buffer yang dipakai untuk read atau write asynchronous dapat tetap relevan selama operasi masih berjalan. Misalnya write buffer harus tetap valid selama kernel masih dapat mengaksesnya sesuai semantik operasi. Completion biasanya menjadi batas ketika aplikasi dapat menganggap operasi selesai dan mereclaim resource yang lifetimenya terikat pada operasi tersebut.
Karena itu SQE dan data yang dirujuk memiliki lifetime berbeda:
SQE storage: prepare -> publish -> ring may reuse
buffer storage: prepare -> submit ---------> completion -> reclaimMenganggap submission sebagai pelepasan ownership universal dapat menciptakan use-after-free meskipun ordering head dan tail sudah benar.
Registered buffer dan fixed file mengubah mekanisme lookup dan registration, tetapi tidak menghapus aturan lifetime resource. Penggunaannya tetap dibatasi oleh state registration, semantik operasi, dan completion.
Banyak producer menambah lapisan sinkronisasi kedua
Aturan ordering yang menghadap kernel tidak otomatis menserialkan beberapa thread aplikasi yang sama-sama menyiapkan submission pada state ring user space yang sama.
Jika beberapa thread mengubah metadata producer queue secara konkuren, mereka memerlukan model koordinasi yang didukung library dan konfigurasi ring. Release store dapat memublikasikan write sebelumnya, tetapi tidak dengan sendirinya mengalokasikan slot SQE unik untuk beberapa producer atau mencegah dua thread merusak bookkeeping bersama.
Ini adalah problem sinkronisasi terpisah:
application threads
| |
+-- coordinate slot ownership
|
v
publish to kernel
|
ABI memory orderingMencampur kedua lapisan ini dapat menghasilkan kode yang memakai acquire-release dengan benar tetapi tetap race antar-thread user space. Sebaliknya, mutex di sekitar bookkeeping producer tidak membuat raw ring boleh mengabaikan aturan publikasi ABI ketika state berpindah ke ownership kernel.
Setup flag dapat menambahkan constraint issuer lain. Constraint tersebut harus diperlakukan sebagai kondisi interface, bukan disimpulkan dari workload tertentu yang kebetulan hanya memakai satu thread submission.
Urutan completion bukan urutan submission
Memory ordering juga tidak menetapkan ordering eksekusi operasi.
Dua SQE dapat dipublikasikan berurutan tetapi operasi dasarnya selesai dalam urutan berbeda jika interface mengizinkan eksekusi independen. Semantik acquire dan release menentukan visibilitas state queue; keduanya tidak mengubah ring menjadi log eksekusi serial.
Jika operasi memerlukan dependency ordering, io_uring menyediakan mekanisme request linking dengan semantik sendiri. Mekanisme tersebut bekerja pada level request I/O dan secara konseptual berbeda dari memory ordering yang digunakan untuk memublikasikan entry queue dengan aman.
Pemisahan ini mencegah kesalahan kategori yang halus. Tail update yang terurut benar membuktikan consumer dapat melihat submission yang sudah terinisialisasi. Itu tidak membuktikan submission A selesai sebelum submission B, storage sudah persisten, atau peer eksternal sudah mengamati salah satu operasi.
Shared ring mengekspos detail arsitektur melalui protokol yang stabil
Field ring yang terlihat adalah ABI, tetapi CPU yang menjalankan kode producer dan consumer dapat memiliki perilaku memory ordering berbeda. Sequence yang tampak bekerja pada mesin dengan ordering kuat dapat gagal sebagai strategi implementasi umum jika mengandalkan properti yang tidak dijamin ABI dan memory model bahasa pemrograman.
Inilah nilai protokol acquire-release yang ditentukan: ia menyatakan relasi yang dibutuhkan tanpa bergantung pada kebetulan perilaku prosesor. Library dapat memetakan protokol tersebut ke compiler intrinsic dan instruksi arsitektur yang sesuai.
Akses raw ring karena itu membawa tanggung jawab lebih besar daripada sekadar mengisi struktur dengan angka yang benar. Correctness mencakup layout, ownership, aritmetika indeks, publication ordering, resource lifetime, dan semantik operasi.
Desain shared memory mengurangi transition dan pergerakan data pada jalur yang didukung, tetapi memindahkan sebagian batas sinkronisasi ke mapped memory. Dalam io_uring, queue bukan hanya transport deskripsi I/O. Queue adalah interface konkuren yang field head dan tail-nya membawa transisi ownership antara user space dan kernel.