Dosya sistemleri için Rust
(lwn.net)- 2024 LSFMM+BPF Summit’te Linux dosya sistemlerinde Rust’ın nasıl uygulanabileceği tartışıldı; Aralık 2023’teki RFC’den sonra yayımlanan ikinci RFC yaması tartışmanın merkezinde yer aldı
- Rust-for-Linux tarafı, dosya sistemi API’sinin gereksinimlerini Rust tip sistemi içine taşıyarak derleme zamanında hata yakalamayı, kaynak temizliğini otomatikleştirmeyi ve bellekle ilgili açıklıkları azaltmayı hedefliyor
iget_locked()örneği, C çağıranlarının elle yaptığı null kontrolü, inode durumunu ayırt etme ve hata işleme gibi işleri Rust’takiget_or_create_inode()fonksiyonunun tipler ve otomatik temizlik ile zorunlu kılmaya çalıştığı yönü gösteriyor- Dave Chinner, Christian Brauner, James Bottomley, Ted Ts'o gibi isimler C API ile Rust API adlarının uyuşmaması, API senkronizasyonu, nesne yaşam döngüsü farkları ve 50’den fazla dosya sistemi dikkate alındığında bakım yükü konusunda endişelerini dile getirdi
- Gerilimin özü, Rust soyutlamalarının avantajlarından çok, C kodu değişmeye devam ederken Rust bağlamalarını ve soyutlamalarını buna uydurmanın zahmetini kimin üstleneceği konusunda düğümleniyor
LSFMM+BPF’te düzenlenen dosya sistemi Rust oturumu
- 2024 Linux Storage, Filesystem, Memory Management, and BPF Summit kapsamında Wedson Almeida Filho ve Kent Overstreet, Linux dosya sistemlerinde Rust kullanımını ele aldı
- Almeida, Aralık 2023’te dosya sistemleri için Rust soyutlamalarına yönelik bir RFC yama seti göndermişti ve bu yaklaşım etrafında görüş ayrılıkları vardı
- Oturumun yapıldığı mayıs ortasındaki aynı gün Almeida, ikinci RFC yama sürümünü yayımlayarak bunu Rust ile ilgili diğer başlıklarla birlikte tartışmak istedi
Rust-for-Linux’un dosya sistemi soyutlamalarındaki hedefi
- Önerilen dosya sistemi soyutlamaları, Rust-for-Linux project’in izlediği yönü yansıtıyor
- Temel fikir, dosya sistemi API’sinin gereksinimlerini daha fazla Rust tip sistemi ile ifade ederek hataları derleme zamanında yakalamak
- C kodunda kolayca sağlanması zor olan işleri de otomatikleştirmeyi amaçlıyor
- Örnek: kaynak temizliği
- Amaç, dosya sistemi geliştirme deneyimini daha üretken hale getirmek, derleyicinin bulabileceği sorunları ayıklamak için harcanan zamanı azaltmak ve bellekle ilgili açıklıkları düşürmek
- Overstreet, bcachefs’te iki hafta süren hata izleme süreçlerini çok fazla yaşadığını ve Rust’ın C’den daha fazlasını sunduğunu düşünüyor
- Rust tanımsız davranışı ortadan kaldırıyor
- Kodun içinde neler olduğunu görebilmeye yönelik imkânlar sunuyor
- Rust kodunun doğruluğu kanıtlanabilir hale geldiğinde, özellik geliştirmeyi engelleyen hataların çok daha azalacağını düşünüyor
iget_locked() etrafında tip sistemi örneği
- Almeida, slaytlarında mevcut çekirdekteki
iget_locked()fonksiyonunun karmaşık gereksinimleri olduğunu örnek gösterdi - C tarafındaki çağıranlar çeşitli koşulları kendileri ele almak zorunda
- Dönüş değerinin null olup olmadığını kontrol etmeleri gerekiyor
- Dönen
struct inode’un yeni bir inode mu yoksa mevcut bir inode mu olduğunu kontrol etmeleri gerekiyor - Yeni bir inode ise kullanılmadan önce ilklendirilmesi gerekiyor
- İlklendirme başarısız olursa
iget_failed()çağrılmalı
- Al Viro, Almeida’nın slaytındaki
iget_locked()çağıranı gereksinimlerinin bir kısmına katılmadı ve ayrıntılı davranışlar üzerine tartışma sürdü - Overstreet, bu tür kuralların Rust tipleri ve soyutlamaları içine kapsüllenmesi halinde derleyicinin doğru işlemeyi zorunlu kılabileceğini düşünüyor
- Almeida’nın Rust tarafında sunduğu karşılık gelen fonksiyon
get_or_create_inode()idi- C’de olduğu gibi başarısızlık durumu kontrol edilmeli
- Başarı durumunda çağıran, normal referans sayımlı bir inode ya da yeni bir inode alır
- Normal inode’da, nesne artık referans edilmediğinde referans sayacı otomatik olarak azaltılır
- Yeni inode ilklendirilmezse
iget_failed()’a karşılık gelen işlem otomatik çağrılır - Yeni inode bir kez ilklendirildiğinde normal inode’a dönüşür ve sonrasında referans sayacının otomatik azaltılması uygulanır
- Bu davranışlar tip sistemi ile zorunlu kılınır
- Viro, bu kısıtların gerçek kaynak kodunda nerede tanımlanacağını sorguladı
- Almeida, Viro ve diğer dosya sistemi geliştiricilerinden kısıtları anlayıp ardından bunları zorunlu kılacak tipleri ve soyutlamaları oluşturmak istediğini söyledi
C API ile Rust API arasındaki kopukluk
- Dave Chinner, C API ile Rust API’nin adları farklı olursa mevcut geliştiricilerin C koduna bakıp Rust’taki karşılık gelen çağrıyı anlamasının zor olacağını düşündü
- Aynı adlar kullanılmazsa mevcut geliştirici topluluğu için tamamen yabancı bir API’ye dönüşebileceği endişesi de dile getirildi
- C kodu değiştiğinde Rust kodunun da onu takip etmesi gerektiğinden, bu işi kimin üstleneceği bir sorun olarak kalıyor
- Almeida bunun tartışılması gereken bir konu olduğunu kabul etti
- Ad değiştirmeye karşı değil
- Ancak
iget_locked()’ın iyi bir ad olduğunu düşünmüyor; bunu daha iyi bir ad bulma fırsatı olarak da görülebileceğini söylüyor
- Viro,
iget_locked()’ın superblock nesnesinin üye fonksiyonu değil bir kütüphane fonksiyonu olduğunu, bu yüzden örnek seçiminin iyi olmadığını düşündü - Almeida,
get_or_create_inode()’un da bir kütüphane fonksiyonuna dönüştürülebileceğini ve örneğin amacının kısıtları tiplere nasıl kodlayacağını göstermek olduğunu söyledi
Genel amaçlı soyutlama ile basit dosya sistemleri odaklı yaklaşım arasında seçim
- Christian Brauner, önce Rust soyutlamalarının tüm çekirdek dosya sistemleri için genel amaçlı bir soyutlama mı, yoksa Rust ile yazılmış daha basit dosya sistemlerinin ihtiyaç duyduğu işlevlere odaklı mı olacağının belirlenmesi gerektiğini düşünüyor
- Uzun vadede
get_or_create_inode()gibi bir fonksiyoniget_locked()’tan çok daha fazla kısıt içerirse sorun çıkabilir - C kodu, özellikle ilk aşamalarda Rust kodundan daha hızlı evrilebilir; bu yüzden iki API’nin sürekli senkronize tutulması gerekir
- Overstreet, Rust soyutlamaları eklenirken refactoring ve temizlik işlerinin birlikte yapılıp yapılmayacağının kilit konu olduğunu ve bunun gerekli olduğuna güçlü biçimde inandığını söyledi
- James Bottomley, nesne yaşam döngüsünün Rust API’de kodlandığını, ancak C’de buna karşılık gelen bir ifade olmadığını düşünüyor
- Bir tarafta nesne yaşam döngüsü değişirse diğer tarafta hata oluşabilir
- Chinner, inode nesnesinin yaşam döngüsünün bazen dosya sistemine göre değiştiğini söyledi
- API’ye tek bir yaşam döngüsü anlayışı yerleştirilirse bu fonksiyonlar bazı dosya sistemlerinde çalışmayabilir
- Almeida, örneğin yalnızca bugün
iget_locked()çağıran ve bundan fayda görebilecek dosya sistemlerinde kullanılacağını söyledi- Rust geliştiricileri dosya sistemlerini şu an yaptıkları yöntemi değiştirmeye zorlamak istemiyor
“Zahmeti kim üstlenecek?”
- Ted Ts'o, herkesi Rust adlı “dine” döndürmeye yönelik bir girişim varmış gibi göründüğünü, ancak Linux’ta 50’den fazla dosya sistemi bulunduğunu ve bunların hemen dönüştürülmeyeceğini söyledi
- C kodu iyileştirilmeye devam edecek; bu değişiklik Rust bağlamalarını bozarsa, bu bağlamalara bağlı dosya sistemleri de bozulabilir
- Ts'o, şimdilik Rust bağlamalarının ikinci sınıf vatandaş olduğunu ve bozuk Rust bağlamalarının tüm dosya sistemi topluluğunun değil Rust-for-Linux geliştiricilerinin sorunu olduğunu düşünüyor
- Rust bağlamalarının geliştirilmesi ile C kodunun evrimini paralel yürütürken, çok miktarda anlamı tip sistemine taşıma yaklaşımının iyi mi kötü mü olduğunun 1-2 yıl içinde ortaya çıkacağını düşünüyor
- Ts'o’ya göre büyük değişim nihayetinde zahmetin dağıtımı meselesi
- C API’yi değiştiren bir geliştirici, etkilenen C kodunu düzeltse bile Rust bilmediği için Rust bağlamasını düzeltmeyeceğini söyleyebilir
- Almeida, C API’yi sabitlemeye çalışmadığını; dosya sistemi geliştiricileri API’nin anlamını açıklarsa bunu Rust’a kodlamak istediğini söyledi
- Bottomley, bağlamalara ne kadar çok anlam kodlanırsa, senkronizasyon açısından o kadar kırılgan hale gelebileceğini düşünüyor
- Almeida, API değişirse API kullanıcılarının güncellenmesi gerektiğinin diğer kullanıcılardan farklı olmadığını söyledi
Metotlara, fonksiyonlara ve tiplere ne konulacak?
- Viro,
iget_locked()yerine önerilen seçeneğin metotlara dayanmasını yeniden sorun etti- Metot kullanıldığında argümanların açıkça belirtilmediğini düşünüyor
- Overstreet, metotlara yönelik rahatsızlığın kalıtıma aşırı dayanan C++ gibi dillerden kaynaklandığını düşünüyor
- Rust’ın böyle yapmadığını ve Rust’taki metotların büyük ölçüde sözdizimsel bir unsur olduğunu söyledi
- Jan Kara, inode’un kendisine eşlik eden davranış ile
iget_locked()fonksiyonunun doğasında bulunan davranışı ayırdı- inode’da referans sayacı ve bunun işlenmesi gibi davranışlar bulunur
iget_locked()fonksiyonunun içinde ise ayrı bir davranış vardır
- Overstreet ve Almeida, her iki kısmın da tipe kodlandığını ama ayrı tutulduğunu; inode tipini kullanan diğer fonksiyonların farklı özelliklere sahip dönüş değerleri olabileceğini söyledi
- Viro, VFS’de inode’un neden mevcut şekilde davrandığını açıkladı ve küçük başlayıp yönü görme konusunda hemfikir oldu
- Overstreet, bu örneğin karmaşık olduğu için başlangıç noktası olarak iyi olmayabileceğini söyledi; Viro ise “hayır, öyle değil” diye yanıt verdi ve oturum sona erdi
1 yorum
Hacker News yorumları
Her dosya sisteminin inode yaşam döngüsünü özel olarak yönettiği söylenirken, aynı yaşam döngüsü yönetim işlevlerinin kullanılıp yalnızca semantiğin değişmesi anlaşılır gelmiyor
Aynı işlevin uygulama ayrıntılarına göre farklı şekilde kullanılması gerekiyorsa bu, bir soyutlama katmanının tam tersi gibi görünüyor
inode yaşam döngüsü dosya sistemine özgüyse, dosya sistemine özgü işlevlerle yönetilmeli
Bu tür bilgileri toplarken, bu soruların en baştan ortaya çıkmamasını sağlayacak yeniden düzenleme noktaları açığa çıkabilir; bu da iyi bir şey
Dosya sistemine özgü davranışları ele alırken çekirdek tarafında tutarlı bir arayüz sağlayan VFS katmanına genel bir bakış
inode yaşam döngüsü, tartışmayı başlatmak için seçilmiş ilk örneklerden biri olabilir
Derleyici geçici referanslar konusunda yardımcı olsa da, dosya sistemi yine de bağlantı sayısını diskte saklamak zorunda
iget_locked()bunlardan belirli bir kalıpTüm dosya sistemleri bu yöntemi kullanmaz; duruma göre hiç kullanmayabilirler de
Örneğin FAT, inode numarasını kendisi ürettiği ve FAT konumundan inode’a giden kendi eşlemesini tuttuğu için bunu kullanmaz
procgibi inode nesnelerini önbelleğe almayan dosya sistemleri de varinode nesnesinin kendisi nereden gelirse gelsin durum akışı aynı göründüğünden, tüketici açısından
inodekullanım biçimi değişmezDeğişen şey, dosya sistemi katmanının inode nesnesini nasıl oluşturduğu ve içeride nasıl ele aldığıdır
Soruyu yanlış soruyor olabilir miyiz diye düşünüyorum
Rust’ın C’yi daha kolay çağıracak şekilde değişmesi mi gerekiyor?
Rust’ı biraz denedim ama hobi geliştiricisi olarak C ile nasıl birlikte çalışması gerektiği hâlâ bana net değil
Buna karşılık C++ veya Objective C’de doğru header’ı include edip fonksiyonu çağırmak yeterli
Swift, Objective C dosyalarını dahil edebiliyor ve oradan C’yi çağırabiliyor
Bu durumda çekirdek geliştiricilerinin dile uyum sağlamasını beklemek yerine, Rust dilinin biraz daha esnek hale gelmesi gerekmez mi diye düşünüyorum
Harici fonksiyonu bildirip çağırmak yeterli
Örneğin Rust kitabındaki https://doc.rust-lang.org/book/ch19-01-unsafe-rust.html#usin... bölümünde gösterildiği gibi
extern "C"ile bildirebilirsinizKarmaşık bir kütüphanede tüm bildirimleri elle yazmak istemiyorsanız, C header dosyasından
externbildirimlerini otomatik üreten bindgen gibi araçlar kullanabilirsiniz: https://github.com/rust-lang/rust-bindgenbindgen gibi bir şeyin Rust’a dahil edilip üçüncü taraf bağımlılığı veya
build.rsyapılandırması olmadan kullanılabilmesi iyi olur denebilir, ama bu yazının özü bu değilSorun düşük seviyeli binding’ler değil, Rust’a uygun yüksek seviyeli wrapper’lar; herhangi bir C kodundan bu tür wrapper’ları otomatik üretecek genel bir araç olamaz
Yazı, çekirdek dosya sistemi sürücüleri gibi şeyleri Rust ile gerçekten nasıl uygulayabileceğini bulmaya çalışıyor
Çekirdek içindeki Rust kodunun kaçınılmaz olarak C arayüzlerini tüketmesi de önemli
Aklınızdaki kullanım senaryosu için bindgen oldukça iyi uyuyor: https://github.com/rust-lang/rust-bindgen
Rust’tan çağırmak için
extern "C" fn foo() -> Tbildirip,#[link]niteliğiyle veyabuild.rsüzerinden link bayraklarını geçirmek yeterlibindgencrate’iyle binding’leri önceden üretebilir ya dabuild.rsiçinde oluşturupinclude!()ile dahil edebilirsinizGenellikle yalnızca üretilmiş binding’leri içeren bir
-syscrate’i oluşturulur ve asıl kod bu sys crate’indeki binding’leri normal şekildeuseederC++ ve Objective C’de de yalnızca doğru header’ı include etmek değil, kütüphaneye link vermek gerekir
İki yönlü çağrı mümkün, ancak C, Rust’ın ifade edebildiği şeyleri ifade edemiyorsa, iki tarafın ortak kullanması gereken API tasarımı üzerinde önemli etkiler doğar
C fonksiyonunu
extern "C"olarak bildirip çağırmak yeterliGenellikle
unsafegerekir ve referansları ham pointer’lara dönüştürmek ya da cast etmek gerekir, ancak sözdizimi kendi başına basitC header dosyalarını tarayıp bildirim oluşturan araçlar da var; bindgen en yaygın kullanılanı
Bu yazıdaki mesele dilin kendisinden çok Rust’ın nasıl kullanılacağıyla ilgili
Rust-for-Linux geliştiricileri, Rust’ın özellikleri ve tip sistemiyle API çağrılarının semantiğini kodlayarak bunları daha güvenli ve hataya daha az açık hale getirmek istiyor
C tarafındaki insanlar ise bunun C API’sinin davranışını ve semantiğini geliştirmeyi zorlaştırmasından endişe ediyor
Çünkü C API değiştiğinde Rust API’sinin de düzeltilmesi gerekecek ve bu işi üstlenmek istemiyorlar
Daha kolay kabul edilebilecek alternatif, Rust API’sine semantik kodlamak için Rust özelliklerini ve tip sistemini daha az kullanmak
Böylece C API değiştiğinde Rust API’sini güncellemek mekanik ve basit hale gelir; ama Rust-for-Linux, Rust özellikleriyle daha iyi ve güvenli bir API oluşturamayacaksa bu işin anlamı ne, sorusu doğar
Yine de dili yeterince anlamadığını kabul ederken bu konuda kesin konuşmak biraz garip
Bu Rust API’nin C API’sini saran bir şey mi, yoksa yeniden uygulama mı olduğunu Linux dosya sistemini iyi bilmediğim için net anlayamadım
Yeniden uygulama ya da ayrı bir API ise, C API ile adları aynen korumak zaman geçtikçe daha fazla kafa karışıklığı yaratacak gibi görünüyor
Başta konuya aşina geliştiricilerin daha hızlı anlamasına yardımcı olsa bile bunun böyle olduğunu düşünüyorum
iget_locked()karşılığı olan şeyi göstermişti ve adıget_or_create_inode()idiCevap yeniden uygulama gibi duruyor ve aynı adları kullanmama yönünde görünüyor
Bu tür tartışmaların genelde nasıl ilerlediğini ve değişimin ölçeğini düşününce, bu tartışma şaşırtıcı derecede saygılı
Bu thread’deki olumsuz havaya katılmıyorum
İlgili kişilerin temel acı noktalarını laf kalabalığı yapmadan net aktarmış olması bakımından oldukça iyimserim
Gerçek tartışma, güçlü görüşleri olan tuhaf tipler arasındaki programlama dili tartışmalarına yakışır şekilde sert, dağınık ve bolca laf sokmalı olmuş olmalı
Bu özeti yazan Jake Edge, bunları ayıklayıp yalnızca özü yazmakta çok iyi görünüyor
lwn.net sayfasının altındaki bazı yorumlar oldukça kaba
Katkıda bulunduğunuz bir açık kaynak projesinde Science advances one funeral at a time gibi bir yorum aldığınızı hayal edin
Linux çekirdeğinde daha fazla seçeneğin olması her zaman faydalıdır
Ancak Rust her şeyin cevabı olmayabilir
Rust, güvenli bir programlama modelini garanti etmek için elinden geleni yapar, ama o modelin de sınırları vardır
Bellek sorunuysa Rust kullan, eşzamanlılık sorunuysa Rust’a geç gibi görünebilir; ancak
unsafeblokları olmadan C’nin yaptığı her şeyi yapamazsınızRust bu sorunlara yeni bir bakış açısı kazandırabilir, ama eksiksiz bir çözüm değildir
C, özellikle çekirdekte kullanılan C, örtük kuralların tamamını herkesin eksiksiz bilmesi sorumluluğunu tek tek kişilere yüklüyor
Bu ölçeklenebilir değil
Aynı veri yapılarını kullanan çekirdek geliştiricileri aynı odada toplandıklarında bile bu kurallar üzerinde tam olarak uzlaşamadılar
Rust, bilinmesi gereken kuralları görünür kılmakta ve başka biri kurallara uyumu garanti edebiliyorsa bunu benim sorunum olmaktan çıkarmakta güçlü
Bazen sonuç daha az optimal olabilir; ancak Linux çekirdeğinde de daha az optimal varsayılanların doğru olduğu birçok durum var ve daha iyi performans için altı tuhaf kural daha öğrenmeye vakti olanlara
unsafeçıkış kapısı sağlamak yeterliunsafeblokları kullanılabilirSadece gerektiğinde kullanılmaları gerekir
Etki alanı çok sınırlı bir
unsafebloğu kullanmak, kodun geri kalanından elde edilen tüm garantilerin ortadan kalktığı anlamına gelmezunsafekod gerektiği doğruAma
unsafekullanmak gerekiyor diye Rust’ın uygun olmadığı sonucu bir yanılgıdırRust’ın güvenli/güvensiz ayrımı, kodun hangi bölümünün güvensiz olduğunu açıkça işaretleyerek denetimi küçük bir bölüme odaklamayı ve o bölüm doğruysa geri kalanının çalışacağına güvenebilmeyi amaçlar
Dosya sistemi kodunda mutlaka
unsafeolması için iyi bir neden var mı?Muhtemelen birkaç yerde gereken çok küçük bir alt kümedir
İçeride ne olup bittiği sezgisel değil
Toplantı notlarına bakınca çekirdek içindeki Rust ek karmaşıklık maliyeti gibi görünüyor
Bir işletim sistemini sıfırdan yeniden yazıyor olsaydınız dilin gücünden tam olarak yararlanabilirdiniz
Ama zaten devasa bir kod tabanının yanına eklendiğinde, burada görüldüğü gibi ek sorunlar doğuyor
Asahi Linux’un Rust GPU sürücüsünün bir ayda yapıldığını anlatan bloga bakabilirsiniz
Google’da
tales of the m1 gpuaratabilirsiniz; yazarın Hacker News hakkında çok olumsuz düşünceleri varİsterseniz bağlantıdan okuyabilirsiniz: https://asahilinux.org/2022/11/tales-of-the-m1-gpu/
Genel olarak uygulanabilir olup olmayacağını görmek için önümüzdeki birkaç yıla bakmak gerekecek
Bu maliyet, geleceği ve ilerlemeyi kabullenmek için gerekli görülecek
Neden bilinmeyen hatalar ve tavizlerle dolu dev bir akıma atılmak yerine güvenli bir alt kümeyle sınırlı kalınmadığını merak ediyorum
Zaten çok büyük diye yeniliği durdurup süresiz bakım moduna geçilmesi gerektiği anlamına gelmez
Gerçek hayattaki vergiler gibi, bir taraftaki maliyet başka bir sorunu dengelemek için kullanılıyorsa net kayıp olmayabilir
Sonuca bağlanmamış tek bir tartışmaya bakıp hiçbir sorunu dengeleyemeyeceğini söylemek, argüman olarak zayıf görünüyor
C API ile Rust API’nin adlarının uyuşmaması yüzünden C koduna bakarak eşdeğer Rust çağrısını anlayamama kısmı, eski adlandırma gelenekleriyle bir mücadele gibi görünüyor
Aynı adı koruyup alternatif bir ad istendiğinde yeni adın eski adı sarmalamasını sağlayarak bunun iyi çözüldüğü durumlar oldu
Yine de adlandırma zor
Diğer ikisi eşzamanlılık ve off-by-one hatalarıdır