Fomos: Rust ile geliştirilen deneysel bir işletim sistemi
(github.com/Ruddle)- Fomos, Rust ile geliştirilen deneysel bir işletim sistemidir; Non-Unix OS fikirlerini ve exo-kernel deseninin zorluklarını anlamaya yönelik bir projedir
- Grafik çıktısı, dinamik bellek ayırma, uygulamaları eşzamanlı yükleme ve çalıştırma, Virtio fare ve klavye desteği ile işbirlikçi zamanlamayı sunar
- Uygulamalar,
pub extern "C" fn _start(ctx: &mut Context) -> i32biçimindeki tek bir fonksiyon olarak ele alınır; standart kütüphane olmadan, işletim sistemi işlevleriContextüzerinden sağlanır Context, günlükleme, PID, framebuffer,calloc,cdalloc,store, girdi gibi işlev ve durumları içeren bir yapıdır; yeni özellikler sona eklenerek eski uygulamalarla uyumluluk korunur- Sistem çağrıları yoktur; uygulamalar denetimi
returnile işletim sistemine geri verir ve ardındanstartfonksiyonu yeniden çağrılan işbirlikçi yürütme modeli kullanılır - Uygulama durumu
Context.storeiçinde saklanabilir ve çekirdek döngüsü, uygulama listesini dolaşarak her uygulama için_start(Context::new(...))çağrısı yapan basitleştirilmiş bir yapıdadır - Tüm işlevler ve yan etkiler
Contextüzerinden iletildiği için,Contextiçindeki fonksiyonların değiştirilmesi veya sarmalanmasıyla sandboxing, ölçümleme ve hata ayıklama kurgulanabilir - Şu anda güvenlik uygulanmış değil; uygulamalar diğer uygulamaların RAM'ini görebiliyor ve bağlam değişimi ile uygulama başına sanal bellek yığını olmadan veri güvenliği sağlama planı bulunuyor
- Eksik başlıklar arasında kalıcı depolama, GPU desteği, ağ iletişimi ve uygulamalar arası veri ile işlev paylaşımı için soyutlamalar yer alıyor; Virgl üzerinde ise çalışılıyor
- Derleme
./build.shile yapılır;rust nightly,gccve Virgl ile SDL bayrakları etkinqemugerekebilir
1 yorum
Hacker News yorumları
Hoşuma gitmeyen nokta şu: preemptive sistemlerde
while (true)sistemi yavaşlatabilir ama işbirlikçi sistemlerde denetimi geri vermediği anda makine fiilen dururGüvenlik açısından da böyle bir sistemde hizmet engelleme saldırıları çok kolaylaşır ve bir uygulamadaki tek bir hata tüm sisteme yayılabilir
İşletim sistemi geliştiricisi değilim; yanılıyorsam düzeltilmek isterim
İşletim sistemlerinin işbirlikçi çoklu görevden vazgeçmesinin nedeni, tüm “kontrolden çıkan kaynak kullanımı” sorunlarını sonsuza dek çözmüş olmaları değil; UI iş parçacığının bloklanması veya kazara oluşan sonsuz döngü gibi basit uygulama düzeyi hataların tüm sistem durumunu bozmasıydı
Rastgele programları çalıştırmak üzere tasarlanmış bir sistemde bu oldukça ölümcül
Uygulamaların işbirlikçi biçimde zamanlanmasına izin verirken
while(true){}ifadesinin sistemi sonsuza kadar ele geçirmesini de engelleyebilirsinizÖrneğin uygulama başına zaman sınırı koyabilir ya da daha deneysel olarak döngüleri algılayıp cömert bir zaman sınırı tanımlayabilirsiniz
Programcı için ilgisiz programlar arasındaki yalıtımın en yüksek, ilgili programlar arasındaki yalıtımın en düşük olması rahattır; kullanıcı içinse hesaplama kaynaklarının güçlü biçimde yalıtılması, depolama veya izinler gibi şeylerin ise daha az kapalı olması rahattır
Pratikte işbirlikçiden çok preemptive’e daha yakın, ama biraz işbirlikçi olan karmaşık bir zamanlama gerekir
Linux’ta da fork bombası gibi kötü amaçlı programların, özellikle swap açıksa sistemi durma noktasına getirmesi zor değildir; preemptive scheduler olsa bile bir program sistem iş parçacıklarının %99’unu kaplarsa fiilen çoğunlukla o program çalışır
Zamanlama bir spektrumdur ve mevcut işletim sistemleri preemptive olsa da bir ölçüde işbirlikçidir
Uygulama denetimi bırakıp bırakmayacağına karar verebilir
Tersine, işletim sistemi işbirlikçi bırakılıp kaynak kullanım eşikleri veya zamanlayıcı kesmesi oluştuğunda nadiren hata modunda bağlam değiştirerek preemptive’e geçebilir, uygulamayı sonlandırdıktan sonra tekrar işbirlikçi moda dönebilir
Buna iyimser işbirlikçi, kötümser preemptive denebilir
while(true)döngüsü tek çekirdekli sistemi çökertir ama çok çekirdekli sistemde bu her zaman böyle değildirBugün kullandığımız işletim sistemlerinin temel yapısının oluştuğu dönemle bugün arasında ödünleşimler değişmiş olabilir
Özellikle “Fomos’ta uygulama sadece bir fonksiyondur” kısmını sevdim
Unix veya Windows çalıştırılabilir dosyaları bağımsız bir fonksiyona kıyasla çok karmaşık; bu şekilde yazılmış bir çekirdeğin ne kadar harika olacağını hayal etmek bile zor
Smalltalk/Squeak’in de böyle olup olmadığını merak ediyorum; yazarın dosya sistemi, görev yöneticisi, güvenli bellek yığını ve kaynak paylaşımına kadar devam etmesini isterim
Elbette asgari kavram kanıtı gereksinimi olarak DOOM çalıştırmak da lazım
Lisp makinesi işletim sistemi daha yakın; başlangıçta nesne sistemi olmadan bağımsız fonksiyonlar birbirini çağırıyordu, sonra argüman sınıflarına özelleşmiş jenerik fonksiyonlara dönüştü
Tasarımcılar, diğer işletim sistemlerinin bugün atladıkları şeylere neden ihtiyaç duymaya başladığını henüz keşfetmemiş gibi görünüyor
int main() { … }fonksiyonu olduğu diğer işletim sistemlerinden ne farkı var merak ediyorumRust ile yapılmış başka bir örnek: https://github.com/hermit-os/hermit-rs
Fikir fena değil ama eski öğelerle uyumlu kalmak için Context yapısına sürekli yeni fonksiyonlar eklemek geriye dönük uyumluluk cehennemine giden yol
Eski veya terk edilmiş öğeleri Context yapısından kaldıramayacak şekilde kendinizi kilitlemiş olursunuz
Daha iyi yöntem, işletim sistemiyle uygulama arasına semantik sürümleme getirmek gibi görünüyor
Uygulama hangi işletim sistemi sürümüne göre derlendiğini veya hangisine bağımlı olduğunu beyan ederse, işletim sistemi uyumluluğu kontrol edip buna uygun Context yapısı sürümünü geçirebilir
Geriye dönük uyumluluk sorunlarının çoğu kalır ama çekirdek içinde ana/alt sürümlere göre birden çok yapı tutarak Context yapısını temiz tutabilirsiniz
İyi fikir ama çalışma zamanı arayüzünün tek olmasındaki sadelik de hoşuma gidiyor
İşletim sistemi zaten tüm sürümleri ele almak zorundaysa, gelecekteki uygulamalar padding kullanarak bunu “temiz” hissettirebilir
struct Context{ padding: [u8;256], // old stuff ctx: ContextV42 }Yine de bunu yazınca biraz yanlış hissettirdi
Uygulamanın kendi sürümünü beyan etmesi, ELF gibi çalıştırılabilir dosya biçimlerinin zaten çözdüğü bir sorun gibi geliyor; bu yüzden alternatifleri deniyorum
“Nasıl sleep edilir veya asenkron beklenir? Sadece return etmek yeterli” kısmı biraz garip
io_uringtarzı asenkron G/Ç harika olurdu, ama bu model bunu dışlıyor gibi göründüğü için yeterli performans elde etmek zor olabilirAsenkronu desteklememek de garip; çünkü doğal duraklama noktalarına bağlanabilir
Ancak bunu yapmak için uygulama durumunu diske açıkça kaydedip yükleyen tasarımdan büyük ölçüde vazgeçmek gerekecek gibi ve maliyeti yüksek görünüyor
Ağ tarafında da benzer nedenlerle, en azından verimli biçimde yapmak zorlaşabilir diye düşünüyorum
Bu fonksiyon, rastgele durumu parametre olarak alan olay döngüsünün uç noktası gibi görünüyor; dolayısıyla olay döngüsünün yaptığı iş büyük ölçüde genelleştirilebilir
Ancak dil düzeyindeki coroutine ve async desteğinden vazgeçilmiş olur
Verilen örnek fazla yapay
Preemptive bir işletim sisteminde uygulamalar genelde iş parçacığı deadlock'u ya da sonsuz döngü gibi, bütünü kooperatif hâle getirmeyen şekillerde takılır
Ayrıca preemptive bir sistem, bir uygulama çok fazla iş parçacığı ya da dosya oluşturursa veya çok fazla bellek kullanırsa, fiilen kooperatif hâle gelmeden çok önce onu sonlandırabilir
Bizim sistemimiz sadece daha hoşgörülü
Üstelik “öncülleri kabul ederseniz sandboxing bedava” derken bir yandan da “herhangi bir uygulama başka bir uygulamanın RAM'ini kolayca kontrol edebilir ve bu çözmesi zor bir problem” deniyorsa, sandboxing bedava değildir
Yine de güzel bir fikir ve yazarın başarılı olmasını isterim
Bu problemin çözümü, fonksiyonlar; yani uygulamayı saran ve uygulamanın kendi Context'i gibi davranan closure'lar oluşturmak olabilir
Bir uygulama başka bir uygulamayı açabilse, ya da bir uygulama başka bir uygulama için işletim sistemi olabilse nasıl olurdu diye düşünüyorum
Sağlam sandboxing için düzgün tasarlanmış bir sistem tüm kaynaklara sınır koyar ve sınıra ulaşıldığında isteği reddeder
Fomos'un process ile çalıştırılabilir dosyayı nasıl ayırdığını merak ediyorum
Linux'ta process; argv/envp işaretçileri, stack, heap, sinyal maskesi, dosya handle tablosu, sinyal handler'ları, çalıştırılabilir bellek içeren sanal adres alanı ve uid, gid gibi kernel içi verilerden oluşur
Çalıştırılabilir dosya ise
execvesistem çağrısı sırasında loader'ın o adres alanını doldurmasına yetecek bitleri içeren dosyadırÇalıştırılabilir dosya olmadan da
clone3veyaforkile process oluşturabilirsiniz; kernel ELF kullanır ve kullanıcı alanının çoğu GLIBC'nin RTLD loader'ını kullanır, ancak belirli bir çalıştırılabilir dosya biçiminden process oluşturmak için ikisi de şart değildirPosition-independent code içermeyen statik linklenmiş çalıştırılabilir dosya, assembler açısından “sadece bir fonksiyon”a daha yakındır; ancak ASLR olmadan runtime sembolleri çözümlerseniz, bağımlı fonksiyon adresleri bilindiğinde buffer overflow saldırılarına açık hâle gelir
glibc'nin kusurlarına ve Posix process modeline alternatifler istiyorum, ama Unix çalıştırılabilir dosya karmaşıklığının önemli bir kısmının özsel olduğunu düşünüyorum
Runtime sembol çözümleme zor ama yararlı; keyfi interpreter'lara izin vermek can sıkıcı olsa da Linux'un Windows ve MacOS'tan güçlü olduğu bir nokta; kernel arayüzünü kararlı sistem çağrılarıyla sunmak da Linux'un güçlü tarafı
Mac'te Mach-O, Windows'ta PE, Linux'ta ELF gibi; oysa çalıştırma/linkleme biçimleri için çeşitli bir ekosisteme sahip olmamak için bir neden yok
Kod yükleme modeli çok basit olan bir işletim sistemi böyle deneyler için iyi bir yer
Kararlı ABI Linux'a özgü bir şey değil ve böyle bir kararın faydası da oldukça şüpheli; ama sürücü desteği harika ve bunu inkâr etmek zor
Oldukça ilginç
Güvenilmeyen kooperatif uygulamalarla belli bir ölçüde güvenlik ve emniyetin nasıl sağlanabileceğini bilmiyorum
Herhangi bir uygulama CPU'yu süresiz olarak elinde tutup kernel'ı ve diğer uygulamaları durdurabilir
Preemptive scheduling kullanan işletim sistemleri kullanmamızın nedeni, hatalı çalışan bir uygulamayı sistemin geri kalanını bozmadan durdurabilmemizdir
Preemptive multitasking kullanıyordu ama bellek korumasını zorlamıyordu; bunun yerine derleyiciyi bir sistem servisi olarak konumlandırıp yalnızca sistem derleyicisinin oluşturduğu ve imzaladığı çalıştırılabilir dosyaların çalışmasına izin veriyordu sanırım
Derleyici build zamanında bellek korumasını garanti ettiği için sistem çağrıları ve process'ler arası iletişim çok ucuz hâle gelmişti
Biraz daha gelişmiş bir derleyici, gerekli yerlere
yieldçağrıları ekleyerek kooperatif multitaskingi de benzer şekilde zorunlu kılabilirGenel durma problemi çözülemez, ama statik analizle sonlandığı veya kontrolü devrettiği kanıtlanabilen program sınıfları yine de vardır
Sadece kanıtlanamayan programları ayrı ele almak gerekir; watchdog timer ile hatalı çalışan programlar otomatik olarak durdurulabilir
Güvenilmeyen kod “ana” imajda çalıştırılmaz, atılabilir bir VM'de çalıştırılır
Burada da hypervisor kullanarak aynı modeli uygulamak mümkün olabilir; ama yalnızca Smalltalk sistemi kullanan kimse yok ve belli bir altyapı gerekiyor
Tamamen yeniden tasarlamadan, yani mevcut işletim sistemlerinin zaten yaptığı işleri fiilen baştan yapmadan bu işletim sisteminde güvenliğin uygulanıp uygulanamayacağını merak ediyorum
Aynı donanımda çalışan uygulamaların güvenliğini zorlamanın iki yolunu biliyorum
Biri çalışma zamanında süreçleri sanal bellekle yalıtmak, diğeri ise yükleme anında yükleyicinin kodun rastgele bellek erişimi yapıp yapmadığını doğrulaması
İkincisi genelde JVM ya da Smalltalk gibi, işaretçi aritmetiği olmayan sınırlı bir komut kümesinin bytecode’larına izin veren bir sanal makineyle zorlanır
Fomos’un yazarı bağlam geçişi ve bellek yalıtımı vb. istemiyor, Rust derleyicisi de bytecode üretmiyor; başka bir yol var mı?
Anladığım kadarıyla sertifikalı derleyicide
unsafeyasağı gibi kurallar zorunlu kılınıyor; dolayısıyla burada kaynak kod fiilen bytecode’un karşılığı oluyorİlk bakışta Midori’ye çok benziyor, ama uygulama ayrıntıları epey farklı
Theseus’ta sürücüler ve uygulamalar vb. ELF nesneleri; hepsi tek bir yürütülebilir dosyaya, yani çekirdeğe dinamik olarak bağlanıyor ve sıcak yükseltme gibi ilginç teknikler de var
https://github.com/theseus-os/Theseus
https://www.theseus-os.com/
Çalışma zamanında bir segmentasyon hatası oluşursa, çağıranın o sayfaya erişip çağrı yapma yetkisi olup olmadığını güvenlik belirteci gibi bir şeyle kontrol etmek gibi
Pratikte ne kadar kullanışlı olur bilmiyorum
İşbirlikli çoklu görev olsa bile, bugün çekirdek sayısının çok fazla olması nedeniyle Classic MacOS dönemindekiyle aynı olmayacağını düşünüyorum
İşbirliği yapmayan bir iki süreç, tüm sistemi mutlaka rehin almayabilir
Bir fonksiyon hatalı çalışıp geri dönmüyorsa, sistem tüm çekirdekleri tükettiğinde onu sonlandırmak da mümkün olabilir
İşbirlikli çoklu görev mutlaka kötü performans anlamına gelmez
Zaman paylaşımlı çalışma başlangıçta devasa tek bir CPU’yu birden çok kullanıcı arasında paylaştırmanın yoluydu; ama artık tek kullanıcılı çok çekirdekli CPU’lar yaygın, bu yüzden çekirdekleri kullanmanın başka yollarını düşünme zamanı çoktan geldi
Bu projenin var olması gerçekten heyecan verici
Bu modelde bağlam geçişi olmadığı için performansın aksine daha iyi olma ihtimali var
Bu yüzden Linux’un time slice değerini 10 saniye gibi saçma bir değere çıkarınca ne olacağını merak ediyorum
Güvenlik planını daha ayrıntılı duymak isterim
Genel olarak bu tür deneylerin, işletim sistemlerinin greenfield tasarım ile geliştirilebileceğini gösterdiğini düşünüyorum
Biraz Mirage OS’yi hatırlatıyor: https://mirage.io/