4 puan yazan GN⁺ 2023-08-31 | 1 yorum | WhatsApp'ta paylaş
  • 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) -> i32 biçimindeki tek bir fonksiyon olarak ele alınır; standart kütüphane olmadan, işletim sistemi işlevleri Context ü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 return ile işletim sistemine geri verir ve ardından start fonksiyonu yeniden çağrılan işbirlikçi yürütme modeli kullanılır
  • Uygulama durumu Context.store iç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, Context iç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.sh ile yapılır; rust nightly, gcc ve Virgl ile SDL bayrakları etkin qemu gerekebilir

1 yorum

 
GN⁺ 2023-08-31
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 durur
    Gü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

    • Doğru ve buna karşı argüman bence konuyu saptırmaya yakın
      İş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
    • Her şeyi ya hep ya hiç diye görmemek gerekir
      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
    • Yazarıyım
      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
    • İşletim sistemi geliştiricisi değilim ama çekirdek sayısının fark yarattığını düşünüyorum
      while(true) döngüsü tek çekirdekli sistemi çökertir ama çok çekirdekli sistemde bu her zaman böyle değildir
      Bugü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
    • Windows 3.1’in işbirlikçi çoklu görev deneyinden sonra dünyanın preemptive çoklu göreve geçmesinin bir nedeni var
  • Ö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

    • Smalltalk’ta bu bağımsız bir fonksiyondan çok bir sınıfın metoduna karşılık gelir; ama imaj içindeki tüm nesnelerin birbirine mesaj gönderdiği, yani birbirlerinin metotlarını çağırdığı anlamında doğru
      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ü
    • “Uygulama sadece bir fonksiyondur” sözü greenfield geliştirmenin laneti gibi geliyor
      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
    • Aslında uygulamanın int main() { … } fonksiyonu olduğu diğer işletim sistemlerinden ne farkı var merak ediyorum
    • Bu, unikernel tanımına uyuyor gibi görünüyor
      Rust ile yapılmış başka bir örnek: https://github.com/hermit-os/hermit-rs
    • Eski usul Classic MacOS çalıştırmayı deneyin
  • 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

    • Yazarıyım
      İ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_uring tarzı asenkron G/Ç harika olurdu, ama bu model bunu dışlıyor gibi göründüğü için yeterli performans elde etmek zor olabilir
    Asenkronu 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

    • Tahminimce asenkron G/Ç, Context içinde G/Ç isteğini güncelleyip geri dönmek ve hazır olduğunda fonksiyonun yeniden çağrılması şeklinde uygulanır
      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

    • Tarayıcı dünyasında açık olan tüm siteler tarayıcı heap belleğini paylaşır ama birbirlerine müdahale etmez
      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
    • O örnek, tüm sistemlerin Windows ve Linux gibi sandboxing konusunda berbat olduğunu varsaydığı için daha da yapay
      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 execve sistem ç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 clone3 veya fork ile 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ğildir
    Position-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ı

    • Biraz daha düşününce, büyük hatalardan biri çalıştırılabilir dosya biçimlerinin homojenleşmesi gibi geliyor
      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
    • Linux'un güçlü yanının kararlı ABI'den çok sürücüler olduğunu düşünürdüm
      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
    • Zircon'ın (Fuchsia) bunu nasıl ele aldığını görüp görmediğini merak ediyorum
      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

    • 2000'lerin ortalarında Microsoft Research'te yalnızca .NET ile yazılmış bir prototip işletim sistemi olduğunu hatırlıyorum
      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ılabilir
      Genel 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
    • Squeak veya Pharo gibi Smalltalk sistemlerinde bir iş parçacığı takılırsa kullanıcı klavye kısayoluyla yürütmeyi keser
      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ı?

    • Theseus, bytecode olmadan Rust ile uygulanmış ikinci yönteme bir örnek
      Anladığım kadarıyla sertifikalı derleyicide unsafe yasağı 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/
    • Tahminimce tüm “programlar” tek bir adres alanını paylaşırken, sanal bellek ile belirli bir anda erişilebilir sayfaların görünürlüğü kısıtlanabilir
      Ç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
    • https://en.wikipedia.org/wiki/Capability-based_addressing ?
  • İş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

    • Ek olarak, “işbirlikli çoklu görev mutlaka kötü performans anlamına gelmez” derken kastım kötü etkileşim performansıydı
      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/