4 poin oleh GN⁺ 2023-08-31 | 1 komentar | Bagikan ke WhatsApp
  • Fomos adalah OS eksperimental yang dibangun dengan Rust, dan merupakan proyek untuk memahami ide OS non-Unix serta tantangan pola exo-kernel
  • Menyediakan output grafis, alokasi dinamis, pemuatan dan eksekusi aplikasi secara bersamaan, dukungan mouse·keyboard Virtio, serta penjadwalan kooperatif
  • Aplikasi diperlakukan sebagai fungsi tunggal berbentuk pub extern "C" fn _start(ctx: &mut Context) -> i32, dengan struktur yang menerima fungsi OS melalui Context tanpa pustaka standar
  • Context adalah struktur yang memuat fungsi dan status seperti log, PID, framebuffer, calloc, cdalloc, store, input, dan lainnya; fitur baru ditambahkan di bagian belakang untuk menjaga kompatibilitas dengan aplikasi lama
  • Tidak ada system call; aplikasi mengembalikan kontrol ke OS dengan return, lalu fungsi start dipanggil lagi setelahnya dalam model eksekusi kooperatif
  • Status aplikasi dapat disimpan di Context.store, dan loop kernel memiliki struktur sederhana yang menelusuri daftar aplikasi lalu memanggil _start(Context::new(...)) untuk masing-masing aplikasi
  • Karena semua fungsi dan efek samping diteruskan melalui Context, penyandian sandbox, instrumentasi, dan debugging dapat dibangun dengan mengganti atau membungkus fungsi-fungsi di Context
  • Saat ini keamanan belum diimplementasikan, dan aplikasi masih dapat memeriksa RAM aplikasi lain; ada rencana untuk menerapkan keamanan data tanpa context switch dan tanpa stack memori virtual per aplikasi
  • Hal yang masih belum ada meliputi penyimpanan permanen, dukungan GPU, jaringan, serta abstraksi untuk berbagi data dan fungsi antar aplikasi; Virgl sedang dalam pengerjaan
  • Build dijalankan dengan ./build.sh, dan mungkin memerlukan rust nightly, gcc, serta qemu dengan flag Virgl dan SDL

1 komentar

 
GN⁺ 2023-08-31
Komentar Hacker News
  • Hal yang tidak saya suka adalah, dalam sistem preemptive, while (true) mungkin bisa membuat sistem melambat, tetapi dalam sistem kooperatif, begitu ia tidak mengembalikan kontrol, mesin pada dasarnya berhenti
    Dari sudut pandang keamanan juga, sistem seperti ini membuat serangan denial-of-service terlalu mudah, dan satu bug di sebuah aplikasi bisa merembet ke seluruh sistem
    Saya bukan pengembang sistem operasi, jadi tolong koreksi kalau saya salah

    • Benar, dan menurut saya bantahannya lebih dekat ke mengaburkan pokok masalah
      Alasan sistem operasi meninggalkan multitasking kooperatif bukan karena mereka menyelesaikan semua masalah “penggunaan sumber daya yang lepas kendali” untuk selamanya, melainkan karena kesalahan sederhana di level aplikasi seperti thread UI yang terblokir atau loop tak berujung yang tidak disengaja bisa merusak keadaan seluruh sistem
      Untuk sistem yang dirancang menjalankan program arbitrer, ini cukup fatal
    • Jangan melihatnya sebagai semua-atau-tidak-sama-sekali
      Kita bisa mengizinkan aplikasi dijadwalkan secara kooperatif sambil tetap mencegah while(true){} menahan sistem tanpa batas
      Misalnya dengan memberi batas waktu per aplikasi, atau secara lebih eksperimental mendeteksi loop dan memberi batas waktu yang longgar
      Bagi programmer, nyaman ketika isolasi antarprogram yang tidak terkait maksimal dan isolasi antarprogram yang terkait minimal, tetapi bagi pengguna, nyaman ketika sumber daya komputasi diisolasi kuat sementara hal seperti penyimpanan atau izin tidak terlalu dibatasi
      Dalam praktiknya, kita membutuhkan penjadwalan kompleks yang lebih dekat ke preemptive daripada kooperatif, tetapi sedikit kooperatif
      Di Linux pun, program berbahaya seperti fork bomb tidak sulit membuat sistem berhenti, terutama jika swap aktif, dan meski ada scheduler preemptive, jika satu program mengambil 99% thread sistem, pada dasarnya sebagian besar yang berjalan adalah program itu
    • Saya penulisnya
      Penjadwalan adalah spektrum, dan sistem operasi saat ini juga preemptive, tetapi sampai batas tertentu juga kooperatif
      Aplikasi bisa memutuskan apakah akan menyerahkan kontrol
      Sebaliknya, sistem operasi bisa dibiarkan kooperatif, tetapi jika ambang penggunaan sumber daya atau interupsi timer terjadi, ia jarang beralih konteks sebagai mode kegagalan menjadi preemptive, menghentikan aplikasi, lalu kembali lagi ke kooperatif
      Ini bisa disebut kooperatif optimistis dan preemptive pesimistis
    • Saya bukan pengembang sistem operasi, tetapi menurut saya jumlah core membuat perbedaan
      Loop while(true) meruntuhkan sistem single-core, tetapi belum tentu begitu pada sistem multicore
      Trade-off sekarang mungkin berbeda dari masa ketika struktur dasar sistem operasi yang kita pakai saat ini dibuat
    • Ada alasan mengapa setelah eksperimen multitasking kooperatif Windows 3.1, dunia beralih ke multitasking preemptive
  • Saya terutama menyukai bagian “di Fomos, aplikasi hanyalah fungsi”
    File executable Unix atau Windows sangat kompleks dibandingkan fungsi mandiri, jadi sulit membayangkan betapa kerennya kernel yang ditulis seperti ini
    Saya penasaran apakah Smalltalk/Squeak juga menggunakan cara seperti ini, dan berharap penulis terus melanjutkannya sampai ke sistem berkas, task manager, stack memori yang aman, dan berbagi sumber daya
    Tentu saja, sebagai syarat proof-of-concept minimal, menjalankan DOOM juga diperlukan

    • Di Smalltalk, itu lebih seperti metode sebuah kelas daripada fungsi mandiri, tetapi benar dalam arti semua objek di dalam image saling mengirim pesan, yaitu memanggil metode satu sama lain
      Sistem operasi mesin Lisp lebih dekat; awalnya fungsi-fungsi mandiri saling memanggil tanpa sistem objek, lalu kemudian menjadi fungsi generik yang dispesialisasi berdasarkan kelas argumen
    • Ungkapan “aplikasi hanyalah fungsi” terdengar seperti kutukan pengembangan greenfield
      Tampaknya para perancang belum menemukan mengapa sistem operasi lain akhirnya membutuhkan hal-hal yang sekarang mereka hilangkan
    • Dalam praktiknya, saya penasaran apa bedanya dengan sistem operasi lain yang aplikasinya adalah fungsi int main() { … }
    • Ini tampaknya sesuai dengan definisi unikernel
      Contoh lain yang dibuat dengan Rust adalah https://github.com/hermit-os/hermit-rs
    • Cukup jalankan Classic MacOS kuno
  • Idenya bagus, tetapi cara terus menambahkan fungsi baru ke struct Context agar kompatibel dengan item lama adalah jalan menuju neraka kompatibilitas mundur
    Itu seperti mengurung diri sendiri sehingga item lama atau yang sudah dibuang tidak bisa dihapus dari struct Context
    Cara yang lebih baik tampaknya adalah memperkenalkan semantic versioning antara sistem operasi dan aplikasi
    Jika aplikasi menyatakan bahwa ia dibangun untuk, atau bergantung pada, versi sistem operasi tertentu, sistem operasi bisa memeriksa kompatibilitas dan meneruskan versi struct Context yang sesuai
    Sebagian besar masalah kompatibilitas mundur tetap ada, tetapi dengan menaruh beberapa struct per versi mayor/minor di dalam kernel, struct Context bisa tetap bersih

    • Saya penulisnya
      Ide bagus, tetapi saya juga suka kesederhanaan karena hanya ada satu runtime interface
      Kalau bagaimanapun sistem operasi harus menangani semua versi, aplikasi masa depan juga bisa menggunakan padding agar terasa “bersih”
      struct Context{ padding: [u8;256], // old stuff ctx: ContextV42 }
      Namun setelah menuliskannya, rasanya agak keliru juga
      Deklarasi versi oleh aplikasi terasa seperti masalah yang sudah diselesaikan format executable seperti ELF, jadi saya sedang mencoba alternatif
  • Bagian “bagaimana melakukan sleep atau menunggu secara async? cukup return” terasa agak aneh
    I/O asinkron ala io_uring akan sangat bagus, tetapi model ini tampaknya mengesampingkan hal seperti itu, sehingga mungkin sulit mencapai performa yang layak
    Tidak mendukung async juga terasa aneh, karena itu bisa terhubung ke titik penghentian yang alami
    Namun untuk melakukannya, sepertinya harus banyak mengorbankan desain yang secara eksplisit menyimpan dan memuat state aplikasi ke disk, dan biayanya tampak besar
    Networking juga, karena alasan serupa, menurut saya bisa menjadi sulit, setidaknya jika ingin efisien

    • Dugaan saya, I/O asinkron akan diimplementasikan dengan memperbarui permintaan I/O di Context lalu mengembalikannya, dan ketika siap, fungsi dipanggil lagi
      Fungsi ini tampak seperti ujung event loop yang menerima state arbitrer sebagai parameter, jadi apa yang dilakukan event loop kemungkinan besar bisa digeneralisasi
      Namun dukungan coroutine dan async di tingkat bahasa harus dikorbankan
  • Contoh yang diberikan terasa terlalu dibuat-buat
    Pada sistem operasi preemptive, aplikasi biasanya berhenti dengan cara yang tidak membuat semuanya menjadi kooperatif, seperti deadlock thread atau infinite loop
    Selain itu, sistem preemptive dapat menghentikan aplikasi jauh sebelum sistem itu pada praktiknya menjadi kooperatif jika aplikasi membuat terlalu banyak thread atau file, atau memakai terlalu banyak memori
    Sistem kami hanya lebih toleran
    Lagi pula, meski mengatakan “sandboxing itu gratis selama Anda menerima premisnya”, mereka juga mengatakan “aplikasi mana pun dapat dengan mudah memeriksa RAM aplikasi lain, dan ini masalah yang sulit dipecahkan”, jadi sandboxing tidak gratis
    Namun tetap saja ini ide yang keren, dan saya berharap penulisnya sukses

    • Di dunia browser, semua situs yang terbuka berbagi memori heap browser, tetapi tidak saling mengganggu
      Solusi untuk masalah ini mungkin adalah membuat fungsi, yakni closure yang membungkus aplikasi, sehingga bertindak seperti Context milik aplikasi itu sendiri
      Saya bertanya-tanya, bagaimana jika aplikasi bisa membuka aplikasi, atau aplikasi bisa menjadi sistem operasi bagi aplikasi lain?
    • Contoh itu bahkan lebih dibuat-buat karena mengasumsikan semua sistem memiliki sandboxing yang buruk seperti Windows dan Linux
      Sistem yang dirancang dengan benar untuk sandboxing yang kuat akan memberi batas pada semua resource, dan menolak permintaan ketika batas tercapai
  • Saya penasaran bagaimana Fomos membedakan proses dan executable file
    Di Linux, proses adalah data internal kernel seperti uid dan gid, serta ruang alamat virtual yang mencakup pointer argv/envp, stack, heap, signal mask, tabel file handle, signal handler, dan memori yang dapat dieksekusi
    Executable file adalah file yang berisi bit-bit yang cukup bagi loader untuk mengisi ruang alamat itu saat system call execve
    Proses bisa dibuat tanpa executable file melalui clone3 atau fork; kernel memakai ELF dan sebagian besar userspace memakai loader RTLD dari GLIBC, tetapi keduanya tidak benar-benar diperlukan untuk membuat proses dengan format executable tertentu
    Executable yang di-link statis tanpa position-independent code dari sudut pandang assembler lebih dekat ke “sekadar fungsi”, tetapi jika simbol runtime di-resolve tanpa ASLR, ia rentan terhadap serangan buffer overflow ketika alamat fungsi dependensi diketahui
    Saya menginginkan alternatif untuk kekurangan glibc dan model proses Posix, tetapi menurut saya sebagian besar kompleksitas executable Unix bersifat esensial
    Resolusi simbol runtime memang sulit tetapi berguna, mengizinkan interpreter arbitrer memang merepotkan tetapi merupakan salah satu keunggulan Linux dibanding Windows dan MacOS, dan menyediakan antarmuka kernel melalui system call yang stabil adalah kekuatan Linux

    • Setelah dipikirkan lagi, salah satu kesalahan besarnya tampaknya adalah homogenisasi format executable
      Mac memakai Mach-O, Windows memakai PE, Linux memakai ELF, dan seterusnya, tetapi tidak ada alasan kita tidak bisa memiliki ekosistem format eksekusi/linking yang beragam
      Sistem operasi dengan model pemuatan kode yang sangat sederhana adalah tempat yang bagus untuk eksperimen semacam itu
    • Saya sempat berpikir kekuatan Linux bukanlah ABI yang stabil, melainkan driver
      ABI yang stabil juga bukan hal yang unik bagi Linux, dan kegunaan keputusan seperti itu pun cukup meragukan, tetapi dukungan driver memang luar biasa dan sulit dibantah
    • Saya penasaran apakah Anda sudah melihat bagaimana Zircon (Fuchsia) menangani ini
      Cukup menarik
  • Saya tidak tahu bagaimana bisa mencapai tingkat keamanan dan keselamatan tertentu dengan aplikasi kooperatif yang tidak tepercaya
    Aplikasi mana pun bisa menahan CPU tanpa batas waktu dan menghentikan kernel serta aplikasi lain
    Alasan kita memakai sistem operasi dengan penjadwalan preemptive adalah karena aplikasi yang bermasalah dapat dihentikan tanpa merusak sisa sistem

    • Saya ingat pada pertengahan 2000-an Microsoft Research punya prototipe sistem operasi yang ditulis sepenuhnya dengan .NET
      Mereka memakai multitasking preemptive tetapi tidak memaksakan proteksi memori; sebagai gantinya, compiler dijadikan layanan sistem sehingga hanya executable yang dibuat dan ditandatangani oleh compiler sistem yang bisa dijalankan
      Karena compiler menjamin proteksi memori pada waktu build, system call dan komunikasi antarproses menjadi sangat murah
      Dengan compiler yang sedikit lebih canggih, multitasking kooperatif juga bisa dipaksakan dengan cara serupa dengan menyisipkan pemanggilan yield di lokasi yang diperlukan
      Halting problem umum memang tidak bisa dipecahkan, tetapi masih ada kelas program yang dapat dibuktikan melalui analisis statis akan berhenti atau melakukan yield
      Hanya program yang tidak dapat dibuktikan yang perlu ditangani secara terpisah, dan program yang bermasalah juga bisa dihentikan otomatis dengan watchdog timer
    • Dalam sistem Smalltalk seperti Squeak atau Pharo, jika thread berhenti, pengguna menghentikan eksekusi dengan pintasan keyboard
      Kode yang tidak tepercaya tidak dijalankan di image “utama”, melainkan di VM sekali pakai
      Di sini pun model yang sama mungkin bisa diterapkan dengan hypervisor, tetapi tidak ada orang yang hanya memakai sistem Smalltalk saja, dan dibutuhkan sejumlah infrastruktur
  • Saya penasaran apakah keamanan bisa diterapkan di sistem operasi ini tanpa perancangan ulang total, dan pada dasarnya tanpa mengulang apa yang sudah dilakukan sistem operasi yang ada
    Saya tahu ada dua cara untuk menegakkan keamanan aplikasi yang berjalan di perangkat keras yang sama
    Salah satunya adalah mengisolasi proses dengan memori virtual saat runtime, dan yang lainnya adalah loader memverifikasi pada saat pemuatan apakah kode melakukan akses memori arbitrer
    Yang terakhir biasanya ditegakkan dengan mesin virtual yang hanya mengizinkan bytecode dari set instruksi terbatas tanpa operasi pointer, seperti JVM atau Smalltalk
    Penulis Fomos tidak menginginkan context switch dan isolasi memori, dan compiler Rust tidak menghasilkan bytecode; apakah ada cara lain?

    • Theseus adalah contoh pendekatan kedua yang diimplementasikan dengan Rust tanpa bytecode
      Sejauh yang saya pahami, karena aturan seperti melarang unsafe ditegakkan oleh compiler tersertifikasi, di sini source code pada dasarnya setara dengan bytecode
      Sekilas terlihat sangat mirip dengan Midori, tetapi detail implementasinya cukup berbeda
      Di Theseus, driver, aplikasi, dan lainnya adalah objek ELF, semuanya di-link secara dinamis menjadi satu berkas executable, yaitu kernel, dan ada juga teknik menarik seperti hot upgrade
      https://github.com/theseus-os/Theseus
      https://www.theseus-os.com/
    • Ini dugaan saja, tetapi semua “program” mungkin berbagi satu ruang alamat, sementara memori virtual dapat membatasi visibilitas halaman yang bisa diakses pada saat tertentu
      Misalnya, saat terjadi segmentation fault pada runtime, sistem memeriksa semacam token keamanan untuk memastikan apakah pemanggil punya hak mengakses dan memanggil halaman tersebut
      Saya tidak tahu seberapa praktis ini dalam kenyataan
    • https://en.wikipedia.org/wiki/Capability-based_addressing ?
  • Meski multitasking kooperatif, sekarang jumlah core sangat banyak, jadi tampaknya tidak sama seperti era Classic MacOS
    Satu atau dua proses yang tidak mau menyerahkan kendali belum tentu menahan seluruh sistem
    Jika sebuah fungsi berperilaku salah dan tidak pernah kembali, sistem mungkin bisa menghentikannya saat semua core sudah terpakai
    Multitasking kooperatif tidak selalu berarti performa buruk
    Time-sharing awalnya adalah cara membagi satu CPU raksasa untuk banyak pengguna, tetapi sekarang CPU multi-core untuk satu pengguna sudah umum, jadi sudah saatnya memikirkan cara lain memanfaatkan core
    Fakta bahwa proyek ini ada benar-benar membuat saya antusias

    • Sebagai tambahan, yang saya maksud dengan “multitasking kooperatif tidak selalu berarti performa buruk” adalah performa interaksi yang buruk
      Dalam model ini tidak ada context switch, jadi justru ada kemungkinan performanya membaik
      Karena itu saya jadi penasaran apa yang akan terjadi jika timeslice Linux dinaikkan ke nilai yang tidak masuk akal seperti 10 detik
  • Saya ingin mendengar rencana keamanannya secara lebih rinci
    Secara umum, saya pikir eksperimen seperti ini menunjukkan bahwa sistem operasi bisa diperbaiki lewat desain greenfield
    Sedikit mengingatkan saya pada Mirage OS: https://mirage.io/