36 poin oleh GN⁺ 2025-08-29 | 1 komentar | Bagikan ke WhatsApp
  • Pola desain berorientasi objek memungkinkan penerapan polimorfisme dan modularitas bahkan di kernel yang ditulis dalam bahasa C, sehingga desain sistem menjadi lebih fleksibel
  • Dengan menggunakan vtable (tabel fungsi virtual), antarmuka perangkat dan layanan dapat distandardisasi, serta perubahan dinamis saat runtime mendukung beragam perilaku
  • Layanan kernel dan scheduler menyediakan antarmuka yang konsisten seperti mulai, berhenti, dan mulai ulang melalui vtable, sambil mengenkapsulasi detail implementasi
  • Dikombinasikan dengan modul kernel, pendekatan ini mendukung pemuatan driver secara dinamis dan memungkinkan sistem diperluas tanpa kompilasi ulang
  • Pendekatan ini memberi fleksibilitas dan kebebasan bereksperimen, tetapi kelemahannya adalah sintaks yang kompleks dan kerepotan akibat pengiriman objek secara eksplisit

Kebebasan dalam pengembangan OS dan pola berorientasi objek

  • Mengembangkan OS sendiri memungkinkan eksperimen bebas tanpa batasan kolaborasi atau tuntutan aplikasi nyata
    • Bebas dari kerentanan keamanan, pemeliharaan kode, dan beban rilis
    • Ini menjadi daya tarik pengembangan OS, karena memungkinkan eksplorasi pola pemrograman yang tidak standar
  • Artikel LWN “Object-oriented design patterns in the kernel” memperkenalkan contoh bagaimana kernel Linux menerapkan prinsip berorientasi objek dalam C
    • Polimorfisme diterapkan melalui struktur yang berisi pointer fungsi
    • Melalui enkapsulasi, modularitas, dan ekstensibilitas, keunggulan berorientasi objek dapat dimanfaatkan bahkan di kernel tingkat rendah

Konsep dasar vtable

  • vtable adalah struktur yang berisi pointer fungsi dan mendefinisikan antarmuka sebuah objek
    • Contoh: struktur untuk operasi perangkat
      struct device_ops {  
          void (*start)(void);  
          void (*stop)(void);  
      };  
      struct device {  
          const char *name;  
          const struct device_ops *ops;  
      };  
      
  • Perangkat yang berbeda (misalnya netdev, disk) menggunakan API yang sama, tetapi implementasinya berbeda
    • netdev.ops->start() memanggil operasi perangkat jaringan, sedangkan disk.ops->start() memanggil operasi perangkat disk
  • Perubahan saat runtime: vtable dapat diganti secara dinamis untuk mengubah perilaku tanpa mengubah kode pemanggil
    • Dengan sinkronisasi yang tepat, ini memungkinkan evolusi perilaku dinamis yang rapi

Contoh penerapan di OS

Manajemen layanan

  • Layanan kernel (manajer jaringan, worker pool, server jendela, dan sebagainya) dapat dikelola dengan antarmuka yang konsisten
    • Struktur layanan:
      struct service_ops {  
          void (*start)(void);  
          void (*stop)(void);  
          void (*restart)(void);  
      };  
      struct service {  
          pid_t pid;  
          const struct service_ops *ops;  
      };  
      
  • Setiap layanan mengimplementasikan perilaku uniknya sendiri, tetapi mulai/berhenti/mulai ulang dapat dijalankan dari terminal dengan cara yang terstandarisasi
  • Keterikatan antara kode dan layanan berkurang, sehingga pengelolaan menjadi lebih sederhana

Scheduler

  • Scheduler dapat mendukung berbagai strategi seperti round robin, shortest job first, FIFO, dan penjadwalan prioritas
    • Antarmukanya disederhanakan menjadi yield, block, add, dan next
    • Didefinisikan lewat vtable sehingga kebijakan penjadwalan dapat diganti saat runtime
    • Seluruh kebijakan dapat diubah tanpa memodifikasi bagian kernel lainnya

Abstraksi file

  • Struktur file_operations di Linux mewujudkan filosofi “segala sesuatu adalah file”
  • Socket, perangkat, dan file teks semuanya menyediakan antarmuka read/write yang sama
  • Kode ruang pengguna dapat bekerja secara konsisten tanpa perlu mengetahui detail implementasinya

Integrasi dengan modul kernel

  • Modul kernel mendukung pemuatan driver atau hook secara dinamis melalui penggantian vtable
    • Seperti modul Linux, kernel dapat diperluas tanpa kompilasi ulang atau reboot
    • Saat menambahkan fitur baru, cukup memperbarui vtable pada struktur yang sudah ada

Kekurangan

  • Kompleksitas sintaks:
    • Objek harus dikirim secara eksplisit seperti object->ops->start(object)
    • Lebih merepotkan dibanding pengiriman implisit di C++
    • Signature fungsi juga lebih panjang:
      static void object_start(struct object* this) {  
          this->id = ...  
      }  
      
  • Kelebihan: pengiriman eksplisit membuat dependensi fungsi jelas, dan keterikatan antara objek serta perilaku menjadi transparan
    • Ini adalah tradeoff yang tepat antara kompleksitas dan kejelasan dalam kode kernel

Implikasi

  • vtable menyediakan cara sederhana untuk mengurangi kompleksitas sambil mempertahankan fleksibilitas
    • Penggantian perilaku saat runtime, pemeliharaan antarmuka yang konsisten, dan kemudahan menambahkan fitur baru
  • Menunjukkan cara baru untuk menerapkan desain berorientasi objek dalam bahasa C, sekaligus menekankan keseruan eksperimental pengembangan OS
  • Referensi tambahan: proyek xine (https://xine.sourceforge.net/hackersguide#id324430) memperkenalkan cara mengelola variabel privat dengan vtable
  • Pengembangan OS adalah ajang eksperimen kreatif, dan pola berorientasi objek terbukti menjadi alat yang kuat bahkan pada sistem tingkat rendah

1 komentar

 
GN⁺ 2025-08-29
Komentar Hacker News
  • Membahas tulisan yang menyebut bahwa meskipun kernel Linux ditulis dalam C, ia tetap mengadopsi prinsip-prinsip berorientasi objek, misalnya dengan memanfaatkan function pointer di dalam struct untuk mengimplementasikan polimorfisme. Teknik seperti ini sudah ada jauh sebelum pemrograman berorientasi objek, dan disebut sebagai 'abstract data type (ADT)' atau abstraksi data. Perbedaan inti antara ADT dan OOP adalah, pada ADT implementasi fungsi bisa dihilangkan, sedangkan pada OOP implementasi selalu dibutuhkan. Jika di OOP diperlukan fungsi opsional, harus dibuat kelas tambahan untuk tiap fungsi opsional, lalu setiap kali mengimplementasikannya harus ikut diwariskan lewat multiple inheritance dan saat runtime perlu diperiksa apakah objek tersebut merupakan instance dari kelas tambahan itu. Sebaliknya, pada ADT cukup memeriksa apakah function pointer bernilai NULL
    • Dalam Smalltalk dan Objective-C, cara OOP yang tradisional adalah cukup memeriksa saat runtime apakah suatu objek bisa merespons sebuah message. Sangat disayangkan esensi OOP menjadi terdistorsi akibat pola desain C++ dan Java yang terlalu berpusat pada kelas
    • Sebagian besar setuju, dan menyebut bahwa pola seperti ini juga dipakai di C, sementara dalam OOP tradisional pendekatan yang umum adalah menaruh implementasi default atau stub di basisnya. Dalam OOP modern atau bahasa berorientasi konsep, ada juga cara dengan melakukan cast ke interface yang hanya memakai subset dari API yang diperlukan. Go adalah contoh yang baik
    • Mengenai klaim bahwa teknik ini lebih dulu ada daripada pemrograman berorientasi objek, ada yang lebih suka menyebut OOP sebagai formalisasi dari pola dan paradigma yang sudah ada sebelumnya
    • Di sebagian besar bahasa OOP seperti Java dan C#, sekarang lambda juga bisa dipakai sehingga implementasinya bisa sama seperti di C. Lambda pada dasarnya hanyalah function pointer, jadi bisa langsung ditugaskan ke variabel instance. (Ada juga kisah lama yang lucu bahwa Java butuh lebih dari 10 tahun untuk mengadopsi lambda, dan Sun Microsystems dulu bahkan sampai menggugat Microsoft terkait upaya menambahkan lambda ke Java)
    • Pewarisan bukanlah keharusan. Cukup gunakan pola komposisi (composite). Python juga mirip karena harus mengoper pointer self/this/object secara eksplisit, sehingga menyerupai abstraksi data gaya C
  • Beberapa tahun lalu Peterpaul pernah mengembangkan sistem berorientasi objek ringan yang nyaman dipakai di atas C (repo). Tidak perlu mengoper objek secara eksplisit, dan meski dokumentasinya kurang, ada test suite lengkap (tes1, tes2)
    • Jika penasaran seperti apa bentuknya tanpa syntactic sugar dari carbon, bisa dilihat di sini. Tampaknya tidak mendukung polimorfisme parametrik
    • Ada yang merasa Vala juga merupakan percobaan yang cocok untuk ceruk ini
  • Saya tidak terlalu paham bagian ini, tetapi sepertinya OP melakukan hal yang berbeda dari yang dilakukan para pengembang kernel. Jika membaca tulisan yang ditautkan OP, vtable berisi type function pointer, tetapi OP memberi kesan menggunakan void pointer. Selain itu, manfaat utama yang disebut dalam tulisan pengembang kernel adalah penghematan memori dengan hanya menaruh satu vtable pointer per instance struct, alih-alih beberapa function pointer pada tiap instance. Jadi penghematan memori adalah poin utamanya, sedangkan OP menggunakan vtable ini sebagai lapisan indirection untuk mengganti method saat runtime dan mengimplementasikan polimorfisme. Pola ini berbeda dari yang dibicarakan pengembang kernel
    • Yang dimaksud OP bukan void pointer, melainkan void (tanpa argumen, tanpa nilai balik). Vtable memang dipakai untuk mengimplementasikan polimorfisme. Kalau tidak ada polimorfisme, vtable sendiri tidak dipakai, jadi memori malah lebih hemat
  • Menanggapi pendapat bahwa harus mengoper objek secara eksplisit setiap kali itu merepotkan, ada yang justru tidak suka penggunaan this yang implisit. Sebenarnya instance this memang terus-menerus dioper, dan this yang eksplisit membuat jelas apakah suatu variabel milik instance, global, atau berasal dari tempat lain
    • Ada yang menganggap salah satu kesalahan besar dalam sintaks OOP C++ (dan Java) adalah tidak mewajibkan penulisan this saat mereferensikan anggota instance
    • Penulis tampaknya menyoroti bahwa pada object->ops->start(object) objek harus disebut dua kali: sekali untuk resolusi vtable, dan sekali lagi untuk mengoper objek ke implementasi fungsi C
    • Untuk memperjelas kepemilikan variabel, biasanya dipakai konvensi penamaan anggota seperti mFoo, m_Foo, foo_, dan sebagainya. foo_ lebih disukai karena lebih ringkas daripada this->foo. Tentu saja, di C++ kita juga bisa menulis this secara eksplisit
    • This yang implisit membuat penulisan kode lebih ringkas, dan jika benar-benar memakai method maka tidak perlu mengulang prefiks struct di setiap fungsi. Misalnya, mystruct_dosmth(s); terasa kurang alami dibanding s->dosmth();
    • Dengan memanfaatkan makro, ini juga bisa ditangani dengan lebih cerdas
  • Saya pertama kali mempelajari pola seperti ini di materi presentasi Tmux (materi) dalam konteks C. Saya juga punya tulisan yang merangkum konsep ini (artikel perintah berorientasi objek tmux)
  • Saat kuliah, saya pernah mengimplementasikan pendekatan seperti ini di beberapa proyek kecil. Menyenangkan bisa menghadirkan nuansa mirip OOP di C, tetapi kalau tidak hati-hati masalahnya bisa cepat membesar
  • Perlu dicatat bahwa ini adalah pola yang memanfaatkan interface (yakni vtable, tabel function pointer), bukan keseluruhan objek. Fitur-fitur berorientasi objek lain seperti kelas, pewarisan, dan sebagainya justru mahal biayanya dan sering sulit diikuti
    • Pada akhirnya pewarisan hanyalah bentuk komposisi dari vtable. Kelas pun pada dasarnya hanya gabungan antara vtable dan variabel dalam scope
    • Di C, jika melakukan cast lewat struct anggota pertama, pewarisan field terasa cukup alami
    • Vtable biasanya berisi fungsi yang menerima pointer this. Contoh struct file_operations berisi function pointer yang tidak menerima pointer this, sehingga sulit disebut benar-benar sebagai vtable
  • Ada yang membuat wrapper inline untuk fungsi vtable sehingga alih-alih thing->vtable->foo(thing, ...), bisa ditulis foo(thing, ...)
  • Selalu penasaran mengapa pola seperti ini tidak dimasukkan ke standar C yang baru. Jelas banyak orang terus-menerus mengimplementasikan pola yang sama
    • Jika ditambahkan syntactic sugar, maka harus ada sekaligus cara penggunaan yang diizinkan secara resmi dan fallback yang terasa seperti ada yang kurang. Salah satu kelebihan C adalah tidak menyembunyikan kompleksitas dinamis. Saat dynamic dispatch terjadi, hal itu selalu jelas. Banyak bahasa memang sudah menyediakan formalisasi seperti ini, tetapi keunggulan khas C adalah kompleksitasnya tetap terlihat. Karena itu, orang hanya akan memakainya saat benar-benar membutuhkan dynamic dispatch. Selain itu, sintaksnya sendiri juga tidak sulit
    • Mungkin di pihak High C Compiler pernah ada upaya ke arah ini sampai tingkat tertentu
  • Ini nasihat keras yang lahir dari pengalaman: jangan pernah memakai pola ini. Saya pernah menderita saat harus memelihara basis kode besar yang disusun seperti ini. Keterbacaannya buruk sekali, compiler tidak bisa mengoptimalkan pemanggilan lewat pointer, dan tooling sama sekali tidak mendukung. Sintaksnya juga canggung, dan engineer baru harus benar-benar memahami isi dalam compiler C++ hanya untuk bisa membaca kodenya. Yang paling penting, dibanding manfaat meragukan dari memasukkan OOP, dalam jangka panjang pola ini bisa merusak maintainability. Kalau memang perlu, pakai saja C++
    • Menanggapi pertanyaan tentang bagian mana yang terasa seperti mimpi buruk, ada yang berpendapat justru minimnya syntactic sugar membuat jelas apakah suatu pemanggilan fungsi adalah dynamic dispatch, sehingga lebih mudah dibaca. Karena itu pola ini bisa dibatasi hanya pada tempat yang benar-benar membutuhkan dynamic dispatch. Selain itu, pernah ada tulisan blog yang menyebut kode dinamis di C lebih mudah dioptimalkan karena jumlah function pointer-nya lebih sedikit. Ini bukan soal harus mereplikasi compiler C++ secara penuh; cukup memahami esensi OOP maka implementasi seperti ini akan terasa alami. Terakhir, terhadap argumen 'jangan ubah C menjadi C++ yang jelek', justru alasan memilih pendekatan ini adalah karena terasa lebih khas C dan memudahkan menyisipkan dinamika hanya di bagian yang diinginkan.