Rust untuk sistem berkas
(lwn.net)- Pada LSFMM+BPF Summit 2024, dibahas cara menerapkan Rust pada sistem berkas Linux, dan patch RFC kedua yang muncul setelah RFC Desember 2023 menjadi pusat diskusi
- Pihak Rust-for-Linux ingin memasukkan kebutuhan API sistem berkas ke dalam sistem tipe Rust untuk mendeteksi kesalahan saat kompilasi, mengotomatiskan pembersihan sumber daya, dan mengurangi kerentanan terkait memori
- Contoh
iget_locked()menunjukkan arah di managet_or_create_inode()di Rust berupaya memaksa pemeriksaan null, pembedaan status inode, dan penanganan kegagalan yang sebelumnya ditangani langsung oleh pemanggil C melalui tipe dan pembersihan otomatis - Dave Chinner, Christian Brauner, James Bottomley, Ted Ts'o, dan lainnya mengkhawatirkan ketidaksesuaian nama antara API C dan Rust, sinkronisasi API, perbedaan siklus hidup objek, serta beban pemeliharaan dengan mempertimbangkan lebih dari 50 sistem berkas
- Inti konfliknya bukan pada keunggulan abstraksi Rust itu sendiri, melainkan siapa yang akan menanggung beban untuk menyesuaikan binding dan abstraksi Rust ketika kode C terus berubah
Sesi Rust untuk sistem berkas di LSFMM+BPF
- Pada Linux Storage, Filesystem, Memory Management, and BPF Summit 2024, Wedson Almeida Filho dan Kent Overstreet membahas cara menggunakan Rust pada sistem berkas Linux
- Almeida mengunggah rangkaian patch RFC abstraksi Rust untuk sistem berkas pada Desember 2023, dan ada perbedaan pendapat seputar pendekatan ini
- Pada hari yang sama di pertengahan Mei saat sesi berlangsung, Almeida mengunggah versi patch RFC kedua untuk didiskusikan bersama topik Rust lainnya
Tujuan abstraksi sistem berkas di Rust-for-Linux
- Abstraksi sistem berkas yang diusulkan mencerminkan arah yang dikejar oleh proyek Rust-for-Linux
- Intinya adalah mengekspresikan lebih banyak kebutuhan API sistem berkas ke dalam sistem tipe Rust agar kesalahan bisa ditangkap pada waktu kompilasi
- Juga ingin mengotomatiskan hal-hal yang sulit disediakan dengan mudah dalam kode C
- Contoh: pembersihan sumber daya
- Tujuannya adalah membuat pengalaman pengembangan sistem berkas lebih produktif, mengurangi waktu debugging untuk masalah yang bisa ditemukan compiler, dan menurunkan kerentanan terkait memori
- Overstreet mengatakan ia terlalu sering mengalami pelacakan bug selama dua minggu di bcachefs, dan menilai Rust menawarkan lebih banyak daripada C
- Rust menghilangkan undefined behavior
- Rust menyediakan kemampuan untuk melihat apa yang terjadi di dalam kode
- Jika kebenaran kode Rust dapat dibuktikan, ia memperkirakan bug yang menghambat pengembangan fitur akan jauh berkurang
Contoh sistem tipe seputar iget_locked()
- Almeida memberi contoh dalam slide bahwa
iget_locked()di kernel saat ini memiliki kebutuhan yang kompleks - Pemanggil C harus menangani sendiri berbagai kondisi
- Harus memeriksa apakah nilai kembalian null
- Harus memeriksa apakah
struct inodeyang dikembalikan adalah inode baru atau inode yang sudah ada - Jika inode baru, harus diinisialisasi sebelum digunakan
- Jika inisialisasi gagal, harus memanggil
iget_failed()
- Al Viro tidak setuju dengan sebagian kebutuhan pemanggil
iget_locked()yang ada di slide Almeida, dan perdebatan berlanjut mengenai detail perilakunya - Overstreet menilai bahwa jika aturan semacam ini dikapsulkan ke dalam tipe dan abstraksi Rust, compiler dapat memaksa penanganan yang benar
- Fungsi padanan di sisi Rust yang diajukan Almeida adalah
get_or_create_inode()- Seperti di C, kegagalan tetap harus diperiksa
- Jika berhasil, pemanggil akan menerima inode dengan reference count biasa atau inode baru
- Untuk inode biasa, reference count akan berkurang secara otomatis saat objek tidak lagi direferensikan
- Untuk inode baru, jika tidak diinisialisasi maka penanganan setara
iget_failed()akan dipanggil secara otomatis - Setelah inode baru diinisialisasi satu kali, ia menjadi inode biasa, dan setelah itu pengurangan reference count otomatis akan berlaku
- Perilaku-perilaku ini dipaksakan oleh sistem tipe
- Viro mempertanyakan di mana tepatnya batasan-batasan ini akan didefinisikan dalam kode sumber nyata
- Almeida menjawab bahwa setelah memahami batasan dari Viro dan pengembang sistem berkas lain, ia ingin membuat tipe dan abstraksi yang memaksakan batasan tersebut
Kesenjangan antara API C dan API Rust
- Dave Chinner menilai bahwa jika nama API C dan API Rust berbeda, pengembang lama akan sulit mengetahui padanan pemanggilan Rust hanya dengan melihat kode C
- Muncul juga kekhawatiran bahwa jika tidak menggunakan nama yang sama, API tersebut bisa menjadi benar-benar asing bagi komunitas pengembang yang ada
- Karena kode C akan berubah, kode Rust juga harus mengikutinya, sehingga pertanyaan tentang siapa yang akan menangani pekerjaan itu tetap ada
- Almeida mengakui bahwa ini adalah hal yang perlu didiskusikan
- Ia tidak menentang penggantian nama
- Namun ia juga tidak menganggap
iget_locked()sebagai nama yang baik, dan mengatakan ini bisa dilihat sebagai kesempatan untuk membuat nama yang lebih baik
- Viro menilai pemilihan contoh kurang tepat karena
iget_locked()bukan metode anggota objek superblock, melainkan fungsi library - Almeida menjawab bahwa
get_or_create_inode()juga bisa diubah menjadi fungsi library, dan contoh itu dimaksudkan untuk menunjukkan cara mengenkodekan batasan ke dalam tipe
Pilihan antara abstraksi umum dan pendekatan yang berpusat pada sistem berkas sederhana
- Christian Brauner menilai bahwa pertama-tama harus diputuskan apakah abstraksi Rust ini adalah abstraksi umum untuk semua sistem berkas kernel, atau berfokus pada fungsi yang diperlukan untuk sistem berkas yang lebih sederhana dan ditulis dalam Rust
- Dalam jangka panjang, jika fungsi seperti
get_or_create_inode()memuat jauh lebih banyak batasan daripadaiget_locked(), hal itu bisa menimbulkan masalah - Kode C, terutama pada awalnya, bisa berevolusi lebih cepat daripada kode Rust, sehingga kedua API harus terus disinkronkan
- Overstreet melihat inti persoalannya adalah apakah penambahan abstraksi Rust akan disertai refactoring dan perapian, dan ia sangat yakin itu perlu
- James Bottomley menilai bahwa siklus hidup objek dienkodekan dalam API Rust, tetapi tidak ada representasi yang setara di C
- Jika siklus hidup objek berubah di satu sisi, bug bisa muncul di sisi lain
- Chinner mengatakan bahwa siklus hidup objek inode kadang berbeda menurut sistem berkas
- Jika satu pemahaman tunggal tentang siklus hidup dimasukkan ke API, fungsi-fungsi itu mungkin tidak akan bekerja pada beberapa sistem berkas
- Almeida menjawab bahwa contoh tersebut hanya akan digunakan pada sistem berkas yang saat ini memanggil
iget_locked()dan bisa memperoleh manfaat darinya- Pengembang Rust tidak sedang berusaha memaksa sistem berkas mengubah cara kerjanya saat ini
“Siapa yang akan menanggung beban”
- Ted Ts'o mengatakan bahwa tampaknya ada upaya untuk mengonversi semua orang ke “agama” Rust, tetapi Linux memiliki lebih dari 50 sistem berkas dan transisi itu tidak akan terjadi seketika
- Kode C akan terus ditingkatkan, dan jika perubahan itu merusak binding Rust, sistem berkas yang bergantung pada binding tersebut juga bisa rusak
- Ts'o menilai bahwa untuk sementara binding Rust adalah warga kelas dua, dan binding Rust yang rusak adalah masalah pengembang Rust-for-Linux, bukan masalah seluruh komunitas sistem berkas
- Ia menilai bahwa dalam 1–2 tahun ke depan akan terlihat apakah pendekatan mengembangkan binding Rust sambil membiarkan evolusi kode C berjalan paralel, serta memasukkan banyak makna ke sistem tipe, adalah pendekatan yang baik atau buruk
- Bagi Ts'o, perubahan besar pada akhirnya adalah soal pembagian beban
- Pengembang yang mengubah API C mungkin akan memperbaiki kode C yang terdampak, tetapi karena tidak memahami Rust, mereka bisa saja mengatakan tidak akan memperbaiki binding Rust
- Almeida menjawab bahwa ia tidak berusaha membekukan API C, melainkan ingin mengenkodekan ke Rust makna yang dijelaskan oleh pengembang sistem berkas tentang API tersebut
- Bottomley menilai bahwa semakin banyak makna dienkodekan ke binding, semakin rentan binding itu dari sudut pandang sinkronisasi
- Almeida menjawab bahwa jika API berubah, pengguna API memang harus diperbarui, sama seperti pengguna lainnya
Apa yang harus dimasukkan ke metode, fungsi, dan tipe
- Viro kembali mempersoalkan bahwa pengganti
iget_locked()bergantung pada metode- Ia menilai bahwa dengan metode, argumen tidak ditentukan secara eksplisit
- Overstreet menilai bahwa keluhan terhadap metode berasal dari bahasa seperti C++ yang terlalu bergantung pada pewarisan
- Rust tidak demikian, dan menurutnya metode di Rust pada dasarnya adalah unsur sintaksis
- Jan Kara membedakan antara perilaku yang mengikuti inode itu sendiri dan perilaku yang melekat pada fungsi
iget_locked()- Inode memiliki perilaku seperti reference count dan penanganannya
- Fungsi
iget_locked()memiliki perilaku terpisah yang melekat padanya
- Overstreet dan Almeida menjawab bahwa kedua bagian itu sama-sama dienkodekan dalam tipe, tetapi tetap dipisahkan, dan fungsi lain yang menggunakan tipe inode dapat memiliki nilai kembalian dengan sifat berbeda
- Viro menjelaskan mengapa inode di VFS bekerja dengan cara seperti sekarang, dan setuju untuk memulai dari hal kecil lalu melihat arah perkembangannya
- Overstreet mengatakan bahwa contoh kali ini mungkin terlalu kompleks sehingga kurang cocok sebagai titik awal, dan Viro menjawab, “tidak, bukan begitu,” menutup sesi tersebut
1 komentar
Opini Hacker News
Sulit dipahami bahwa tiap sistem berkas memiliki siklus hidup inode kustom, tetapi tetap memakai fungsi manajemen siklus hidup yang sama dan hanya semantiknya yang berbeda
Jika fungsi yang sama harus digunakan secara berbeda tergantung detail implementasi, itu terdengar seperti kebalikan dari lapisan abstraksi
Jika siklus hidup inode berbeda untuk tiap sistem berkas, seharusnya dikelola dengan fungsi per sistem berkas
Saat mengumpulkan informasi semacam itu, titik-titik yang bisa direfaktor agar pertanyaan seperti ini tidak muncul sejak awal bisa terlihat, dan itu hal yang baik
Ini adalah ringkasan lapisan VFS yang mempertahankan antarmuka konsisten di sisi kernel sambil menangani perilaku khusus tiap sistem berkas
Siklus hidup inode mungkin hanya contoh awal untuk memulai diskusi
Kompiler membantu referensi sementara, tetapi sistem berkas tetap harus menyimpan jumlah tautan di disk
iget_locked()yang menjadi fokus di sini adalah salah satu pola tertentuTidak semua sistem berkas menggunakan cara itu, dan dalam situasi tertentu juga tidak digunakan
Misalnya, FAT tidak menggunakannya karena membuat nomor inode dan mempertahankan pemetaan sendiri dari lokasi FAT ke inode
Ada juga sistem berkas seperti
procyang tidak menyimpan objek inode di cacheObjek inode itu sendiri tampaknya memiliki alur status yang sama dari mana pun asalnya, jadi dari sudut pandang konsumen, cara menggunakan
inodetidak berubahYang berubah adalah cara lapisan sistem berkas membuat dan menangani objek inode secara internal
Saya bertanya-tanya apakah pertanyaannya justru keliru
Apakah Rust harus diubah agar lebih mudah memanggil C?
Saya sudah sedikit mencoba Rust, tetapi dari sudut pandang developer hobi, masih belum jelas bagaimana harus berinteroperasi dengan C
Sebaliknya, di C++ atau Objective C, cukup menyertakan header yang benar dan memanggil fungsi
Swift bisa menyertakan file Objective C, lalu dari sana bisa memanggil C
Dalam kasus ini, alih-alih mengharapkan developer kernel menyesuaikan diri dengan bahasa, bukankah bahasa Rust sendiri seharusnya menjadi sedikit lebih fleksibel?
Cukup deklarasikan fungsi eksternal lalu panggil
Misalnya, seperti yang ditunjukkan di Rust book https://doc.rust-lang.org/book/ch19-01-unsafe-rust.html#usin..., fungsi bisa dideklarasikan dengan
extern "C"Jika tidak ingin menulis semua deklarasi secara manual untuk library yang kompleks, bisa memakai alat seperti bindgen yang otomatis menghasilkan deklarasi
externdari file header C: https://github.com/rust-lang/rust-bindgenBisa saja berargumen bahwa akan bagus jika sesuatu seperti bindgen disertakan dalam Rust sehingga bisa dipakai tanpa dependensi pihak ketiga atau konfigurasi
build.rs, tetapi inti tulisan ini bukan ituMasalahnya bukan binding level rendah, melainkan wrapper level tinggi yang idiomatis Rust, dan tidak mungkin ada alat umum yang otomatis membuat wrapper seperti itu dari sembarang kode C
Tulisan itu membahas cara benar-benar mengimplementasikan driver filesystem kernel dan semacamnya dengan Rust
Penting juga bahwa kode Rust di dalam kernel niscaya mengonsumsi antarmuka C
Untuk use case yang saya bayangkan, bindgen cukup cocok: https://github.com/rust-lang/rust-bindgen
Untuk memanggil dari Rust, deklarasikan
extern "C" fn foo() -> T, lalu lewatkan flag linking dengan atribut#[link]ataubuild.rsBinding bisa dibuat lebih dulu dengan crate
bindgen, atau dibuat dibuild.rslalu disertakan denganinclude!()Biasanya dibuat crate
-sysyang hanya berisi binding hasil generasi, lalu kode sebenarnya melakukanusepada binding dari crate sys itu seperti biasaDi C++ dan Objective C pun, bukan hanya perlu menyertakan header yang benar, tetapi juga harus melakukan link ke library
Pemanggilan dua arah memang mungkin, tetapi jika C tidak bisa mengekspresikan apa yang dapat diekspresikan Rust, itu berdampak penting pada desain API yang harus dipakai bersama oleh kedua sisi
Deklarasikan fungsi C dengan
extern "C"lalu panggilBiasanya perlu
unsafedan harus mengubah referensi menjadi raw pointer atau melakukan casting, tetapi sintaksnya sendiri sederhanaAda juga alat yang memindai file header C dan membuat deklarasi, dan bindgen adalah yang paling banyak dipakai
Perdebatan dalam tulisan ini lebih dekat ke bagaimana Rust digunakan, bukan bahasa itu sendiri
Developer Rust-for-Linux ingin memakai fitur dan type system Rust untuk mengenkode semantik pemanggilan API agar lebih aman dan lebih minim kesalahan
Orang-orang di sisi C khawatir bahwa hal itu akan membuat perilaku dan semantik C API lebih sulit berevolusi
Karena saat C API berubah, Rust API juga harus diperbaiki, dan mereka tidak ingin menanggung pekerjaan itu
Alternatif yang lebih mudah diterima adalah memakai lebih sedikit fitur dan type system Rust untuk mengenkode semantik dalam Rust API
Dengan begitu, saat C API berubah, pembaruan Rust API menjadi mekanis dan sederhana, tetapi jika Rust-for-Linux tidak bisa menggunakan fitur Rust untuk membuat API yang lebih baik dan aman, muncul pertanyaan apa makna dari pekerjaan ini
Namun agak aneh berbicara secara definitif tentang topik ini sambil mengakui bahwa ia tidak cukup memahami bahasanya
Saya tidak begitu paham filesystem Linux, jadi tidak jelas apakah Rust API ini membungkus C API atau mengimplementasikannya ulang
Jika itu implementasi ulang atau API terpisah, mempertahankan nama yang sama dengan C API tampaknya akan makin membingungkan seiring waktu
Menurut saya begitu, meski pada awalnya mungkin membantu developer yang sudah familier memahami lebih cepat
iget_locked()di Rust, dan namanya adalahget_or_create_inode()Jawabannya tampaknya itu adalah implementasi ulang, dan tidak memakai nama yang sama
Mengingat bagaimana diskusi seperti ini biasanya berjalan dan skala perubahannya, diskusi kali ini secara mengejutkan cukup sopan
Saya tidak setuju dengan nuansa negatif di thread ini
Saya cukup optimistis karena para pihak terkait menyampaikan titik-titik masalah utama dengan jelas tanpa omong kosong
Diskusi sebenarnya kemungkinan panas, berantakan, dan penuh cari-cari celah, layaknya debat bahasa pemrograman di antara para geek yang berpendapat kuat
Jake Edge, yang menulis ringkasan ini, tampaknya sangat hebat dalam menyingkirkan bagian-bagian seperti itu dan hanya menuliskan intinya
Sebagian komentar di bawah halaman lwn.net cukup tidak sopan
Bayangkan saja menerima komentar seperti Science advances one funeral at a time pada proyek open source yang sedang Anda kontribusikan
Selalu menguntungkan jika kernel Linux punya lebih banyak pilihan
Namun Rust mungkin bukan jawaban untuk semuanya
Rust berupaya sebaik mungkin menjamin model pemrograman yang aman, tetapi model itu pun punya batas
Bisa saja terlihat seperti: kalau masalahnya memori, pakai Rust; kalau masalahnya konkurensi, ganti ke Rust. Namun tanpa blok
unsafe, Rust tidak bisa melakukan semua hal yang dilakukan CRust bisa memberi sudut pandang baru terhadap masalah-masalah ini, tetapi bukan solusi yang sepenuhnya tuntas
C, terutama C yang dipakai di kernel, melemparkan tanggung jawab kepada masing-masing orang untuk sepenuhnya memahami semua aturan implisit
Itu tidak bisa diskalakan
Para pengembang kernel yang memakai struktur data yang sama pun, meski berkumpul dalam satu ruangan, tidak bisa sepenuhnya sepakat soal aturan-aturan itu
Rust kuat dalam menampakkan aturan yang perlu diketahui, dan jika orang lain bisa menjamin kepatuhan terhadap aturan tersebut, menjadikannya bukan lagi masalah kita
Kadang hasilnya bisa kurang optimal, tetapi di kernel Linux pun default yang kurang optimal sering kali merupakan pilihan yang tepat; bagi mereka yang punya waktu mempelajari enam aturan aneh tambahan demi performa lebih baik, cukup sediakan jalan keluar berupa
unsafeunsafebisa digunakanHanya saja harus dipakai hanya saat diperlukan
Memakai blok
unsafedengan cakupan dampak yang sangat terbatas tidak berarti semua jaminan yang diperoleh dari sisa kode ikut hilangunsafeNamun anggapan bahwa karena harus memakai
unsafe, Rust jadi tidak cocok, adalah salah pahamPemisahan aman/tidak aman di Rust bertujuan menandai dengan jelas bagian mana dari kode yang tidak aman, sehingga audit dapat difokuskan pada bagian kecil itu, dan jika bagian tersebut benar, sisa kode dapat dipercaya akan berjalan
Apakah ada alasan kuat mengapa kode filesystem harus selalu
unsafe?Mungkin hanya subset yang sangat kecil yang diperlukan di beberapa tempat
Tidak intuitif apa yang sebenarnya terjadi di dalamnya
Jika melihat notulen rapat, Rust di dalam kernel tampak seperti biaya kompleksitas tambahan
Jika menulis ulang sistem operasi dari nol, kekuatan bahasa bisa dimanfaatkan sepenuhnya
Namun jika ditempelkan di samping codebase raksasa yang sudah ada, muncul masalah tambahan seperti yang terlihat di sini
Lihat blog tentang driver GPU Rust Asahi Linux yang dibuat dalam satu bulan
Cukup cari
tales of the m1 gpudi Google, dan penulisnya punya pandangan yang sangat negatif terhadap Hacker NewsKalau mau, bisa dibaca lewat tautan ini: https://asahilinux.org/2022/11/tales-of-the-m1-gpu/
Apakah ini bisa diterapkan secara umum masih harus dilihat dalam beberapa tahun ke depan
Biaya itu akan dianggap perlu demi menerima masa depan dan kemajuan
Saya penasaran mengapa tidak membatasi diri pada subset yang aman, alih-alih terjun ke arus besar yang penuh bug tak dikenal dan kompromi
Fakta bahwa semuanya sudah terlalu besar bukan berarti inovasi harus dihentikan dan masuk ke mode pemeliharaan tanpa batas
Seperti pajak di dunia nyata, jika biaya di satu sisi dipakai untuk mengimbangi masalah lain, itu belum tentu kerugian bersih
Mengatakan bahwa tidak akan ada masalah apa pun yang terimbangi hanya dengan melihat satu diskusi yang belum menghasilkan kesimpulan terasa seperti argumen yang pendek
Bagian tentang nama C API dan Rust API yang tidak selaras sehingga orang tidak bisa mengetahui pemanggilan Rust yang setara hanya dengan melihat kode C tampak seperti pertarungan melawan konvensi penamaan lama
Ada kasus yang berjalan baik dengan mempertahankan nama yang sama, lalu ketika menginginkan nama alternatif, nama baru itu membungkus nama lama
Meski begitu, penamaan memang sulit
Dua lainnya adalah konkurensi dan kesalahan off-by-one