36 puan yazan GN⁺ 2025-08-29 | 1 yorum | WhatsApp'ta paylaş
  • Nesne yönelimli tasarım kalıpları, C diliyle yazılmış çekirdeklerde de çok biçimliliği ve modülerliği hayata geçirerek esnek sistem tasarımını mümkün kılar
  • vtable (sanal fonksiyon tablosu) kullanılarak aygıt ve servis arayüzleri standartlaştırılır; çalışma zamanında dinamik değişiklik ile farklı davranışlar desteklenir
  • Çekirdek servisleri ve zamanlayıcı, vtable üzerinden başlatma, durdurma, yeniden başlatma gibi tutarlı arayüzler sunarken gerçekleştirim ayrıntılarını kapsüller
  • Çekirdek modülleri ile birleştirildiğinde dinamik sürücü yüklemeyi destekler; sistemi yeniden derlemeden genişletmek mümkün olur
  • Bu yaklaşım esneklik ve deneysel özgürlük sağlar; ancak karmaşık söz dizimi ve nesnenin açıkça geçirilmesinden doğan ayrıntılı yapı dezavantaj olabilir

OS geliştirmede özgürlük ve nesne yönelimli kalıplar

  • Kendi OS geliştirme süreciniz, iş birliği ya da gerçek uygulama kısıtları olmadan özgürce deney yapmanıza olanak tanır
    • Güvenlik açıkları, kod bakımı ve sürüm çıkarma yükünden bağımsız olursunuz
    • Bu da OS geliştirmenin cazibelerinden biridir; standart dışı programlama kalıplarını keşfetmeye imkan verir
  • LWN makalesi “Object-oriented design patterns in the kernel”, Linux çekirdeğinin C ile nesne yönelimli ilkeleri nasıl uyguladığına dair örnekler sunar
    • Fonksiyon işaretçileri içeren yapılarla çok biçimlilik uygulanır
    • Kapsülleme, modülerlik ve genişletilebilirlik sayesinde düşük seviyeli çekirdekte bile nesne yönelimli yaklaşımın avantajları kullanılabilir

vtable'ın temel kavramı

  • vtable, fonksiyon işaretçileri içeren ve nesnenin arayüzünü tanımlayan bir yapıdır
    • Örnek: aygıt davranışları için bir yapı
      struct device_ops {  
          void (*start)(void);  
          void (*stop)(void);  
      };  
      struct device {  
          const char *name;  
          const struct device_ops *ops;  
      };  
      
  • Farklı aygıtlar (netdev, disk gibi) aynı API'yi kullanır, ancak gerçekleştirimleri farklıdır
    • netdev.ops->start() ağ aygıtını, disk.ops->start() ise disk aygıtı davranışını çağırır
  • Çalışma zamanında değişim: vtable dinamik olarak değiştirilerek çağıran kodu değiştirmeden davranış değiştirilebilir
    • Uygun senkronizasyon ile temiz bir dinamik davranış evrimi sağlanır

OS içindeki uygulama örnekleri

Servis yönetimi

  • Çekirdek servisleri (ağ yöneticisi, worker pool, pencere sunucusu vb.) tutarlı bir arayüzle yönetilebilir
    • Servis yapısı:
      struct service_ops {  
          void (*start)(void);  
          void (*stop)(void);  
          void (*restart)(void);  
      };  
      struct service {  
          pid_t pid;  
          const struct service_ops *ops;  
      };  
      
  • Her servis kendi özgün davranışını uygular, ancak terminalden başlatma/durdurma/yeniden başlatma işlemleri standart bir biçimde yürütülür
  • Kod ile servisler arasındaki bağlılık azalır, yönetim sadeleşir

Zamanlayıcı

  • Zamanlayıcı, round-robin, en kısa iş önce, FIFO, öncelik tabanlı zamanlama gibi çeşitli stratejileri destekler
    • Arayüz yield, block, add, next ile sadeleştirilir
    • vtable ile tanımlanarak zamanlama politikası çalışma zamanında değiştirilebilir
    • Çekirdeğin geri kalanını değiştirmeden genel politika değiştirilebilir

Dosya soyutlaması

  • Linux'taki file_operations yapısı, “her şey dosyadır” felsefesini uygular
  • Soketler, aygıtlar ve metin dosyaları aynı read/write arayüzünü sunar
  • Kullanıcı alanı kodunun gerçekleştirim ayrıntılarını bilmesine gerek kalmadan tutarlı şekilde çalışması sağlanır

Çekirdek modülleriyle birleşim

  • Çekirdek modülleri, vtable değişimi üzerinden dinamik sürücü veya hook yüklemeyi destekler
    • Linux modüllerinde olduğu gibi, yeniden derleme ya da yeniden başlatma olmadan çekirdek genişletilebilir
    • Yeni işlev eklenirken yalnızca mevcut yapının vtable'ı güncellenir

Dezavantajlar

  • Söz dizimi karmaşıklığı:
    • object->ops->start(object) gibi kullanımlarda nesnenin açıkça geçirilmesi gerekir
    • C++'taki örtük geçişe kıyasla daha ayrıntılıdır
    • Fonksiyon imzaları da uzundur:
      static void object_start(struct object* this) {  
          this->id = ...  
      }  
      
  • Avantajı: açık geçiş sayesinde fonksiyon bağımlılıkları nettir; nesne ile davranış arasındaki bağlılık şeffaflaşır
    • Çekirdek kodunda karmaşıklık ile açıklık arasında uygun bir tradeoff sunar

Çıkarımlar

  • vtable, esnekliği korurken karmaşıklığı azaltmak için basit bir yöntem sunar
    • Çalışma zamanında davranış değiştirme, tutarlı arayüzü koruma ve yeni işlev eklemeyi kolaylaştırır
  • C dilinde nesne yönelimli tasarımın uygulanmasına yeni bir yaklaşım sunar ve OS geliştirmenin deneysel eğlencesini vurgular
  • Ek kaynak: xine projesi (https://xine.sourceforge.net/hackersguide#id324430), vtable ile özel değişkenlerin nasıl yönetilebileceğini gösterir
  • OS geliştirme, yaratıcı deneyler için bir alandır; nesne yönelimli kalıpların düşük seviyeli sistemlerde de güçlü bir araç olduğunu kanıtlar

1 yorum

 
GN⁺ 2025-08-29
Hacker News görüşü
  • Linux çekirdeğinin C ile yazılmış olmasına rağmen, yapıların içinde işlev işaretçileri kullanarak çok biçimliliği uygulamak gibi nesne yönelimli ilkeleri benimsediğini anlatan yazı tartışılıyor. Bu tür teknikler nesne yönelimli programlamadan çok daha eskidir ve buna "soyut veri tipi (ADT)" ya da veri soyutlama denir. ADT ile OOP arasındaki temel fark, ADT'de işlev uygulamasının atlanabilmesi, OOP'de ise her zaman bir uygulamanın gerekmesidir. OOP'de isteğe bağlı işlevler gerekiyorsa, her isteğe bağlı işlev için ek bir sınıf oluşturmak ve bunları uygularken çoklu kalıtımla birlikte devralmak, çalışma zamanında da nesnenin bu ek sınıfın bir örneği olup olmadığını kontrol etmek gibi zahmetler doğar. Buna karşılık ADT'de işlev işaretçisinin NULL olup olmadığını basitçe kontrol etmek yeterlidir
    • Smalltalk ve Objective-C'de bir nesnenin çalışma zamanında bir mesaja yanıt verip veremediğini basitçe kontrol etmek, geleneksel OOP yaklaşımıdır. OOP'nin C++ ve Java'nın aşırı sınıf merkezli tasarım kalıpları yüzünden özünün bozulmuş olması üzücü
    • Buna büyük ölçüde katılıyor ve C'de de bu tür kalıpların kullanıldığını, geleneksel OOP'de ise taban sınıfa varsayılan ya da stub uygulamalar koymanın yaygın bir yaklaşım olduğunu belirtiyor. Daha modern OOP ya da kavram odaklı dillerde, gerekli API'nin yalnızca bir alt kümesini kullanan bir arayüze cast etme yolu da var. Go dili buna iyi bir örnek
    • Bu tekniklerin nesne yönelimli programlamadan önce geldiği iddiasına karşılık, OOP'yi daha çok mevcut kalıp ve paradigmaların resmileştirilmesi olarak tanımlamayı tercih ettiğini söylüyor
    • Java, C# gibi çoğu OOP dilinde artık lambda kullanılabildiği için C'dekiyle aynı şekilde uygulanabileceği söyleniyor. Lambdalar işlev işaretçisinden ibaret olduğundan doğrudan örnek değişkenlerine atanabiliyor. (Java'nın lambda eklemesinin 10 yıldan fazla sürmesi ve Sun Microsystems'ın geçmişte Microsoft'a karşı Java'ya lambda ekleme girişimleri yüzünden dava açmış olması da komik bir eski anekdot olarak anılıyor)
    • Kalıtım zorunlu değil. Composite kalıbı kullanılabilir. Python da benzer şekilde self/this/object işaretçisinin açıkça geçirilmesini gerektirdiğinden, C tarzı veri soyutlamasına benziyor
  • Birkaç yıl önce Peterpaul, C üzerinde rahat kullanılabilen hafif bir nesne yönelimli sistem geliştirmişti (repo). Nesneleri açıkça geçirmek gerekmiyor; belgeleri zayıf ama tam bir test paketi var (test1, test2)
    • Carbon'ın söz dizimsel şekerleri olmadan bunun nasıl göründüğünü merak ediyorsanız buradan bakabilirsiniz. Parametrik çok biçimlilik desteklemiyor gibi görünüyor
    • Vala'nın da bu niş alana uygun bir deneme yaptığı düşünülüyor
  • Bu kısmı çok iyi bilmediğini ama OP'nin çekirdek geliştiricilerinin yaptığından farklı bir şey yapıyor gibi göründüğünü söylüyor. OP'nin verdiği bağlantıdaki yazıya bakılırsa vtable içinde tür işlev işaretçileri var, ama OP daha çok void işaretçileri kullanıyor izlenimi veriyor. Ayrıca çekirdek geliştiricisinin yazısında belirtilen ana fayda, her yapı örneğinde birden fazla işlev işaretçisi tutmak yerine yalnızca tek bir vtable işaretçisi tutarak bellekten tasarruf etmek. Yani asıl nokta bellek tasarrufu; OP ise bu vtable'ı çalışma zamanında metot değiştirme ve çok biçimlilik için bir dolaylama katmanı olarak kullanıyor. Bu kalıp, çekirdek geliştiricisinin anlattığından farklı
    • OP void işaretçisi değil, voidu argümansız ve dönüş değeri olmayan işlev anlamında kullanıyor. Vtable çok biçimliliği uygulamak için kullanılır. Çok biçimlilik yoksa zaten vtable da kullanılmaz; bu durumda bellekten daha da fazla tasarruf edilmiş olur
  • Nesneyi her seferinde açıkça geçirmenin rahatsız edici olduğu görüşüne karşılık, kendisi örtük this kullanımından hoşlanmadığını söylüyor. Sonuçta this örneği zaten sürekli geçiriliyor ve açık this, bir değişkenin örneğe mi ait olduğu, yoksa global ya da başka bir yerden mi geldiği konusundaki karışıklığı önlüyor
    • C++ (ve Java) OOP söz diziminde örnek üyelerine başvururken this kullanımının zorunlu olmamasının en büyük hatalardan biri olduğunu düşünüyor
    • Yazarın, aşağıdaki object->ops->start(object) ifadesinde nesnenin iki kez açıkça belirtilmesi gerektiğine dikkat çektiğini düşünüyor. Biri vtable çözümlemesi için, diğeri de C işlev uygulamasına nesneyi geçirmek için gerekli
    • Değişkenin aidiyetini netleştirmek için üye değişkenlerde mFoo, m_Foo, foo_ gibi adlandırma kuralları kullandığını söylüyor. foo_, this->foo ifadesinden daha kısa olduğu için tercih ediliyor. Elbette C++'ta this açıkça da yazılabilir
    • Örtük this, kodu daha kısa hale getiriyor ve gerçek metotlar kullanıldığında her işlevde struct önekini tekrar etmeye gerek kalmıyor. Örneğin mystruct_dosmth(s); yerine s->dosmth(); daha doğal görünüyor
    • Makrolar kullanılarak bu biraz daha akıllıca ele alınabilir
  • Tmux sunumunda (sunum) C'de bu kalıbı ilk kez öğrendiğini söylüyor. Bu kavramla ilgili kendi yazdığı bir metin de var (tmux nesne yönelimli komutlar yazısı)
  • Üniversite yıllarında birkaç küçük projede bu yaklaşımı uyguladığını söylüyor. C'de OOP benzeri bir his vermesi eğlenceliydi ama dikkat edilmezse sorunlar hızla büyüyebiliyor
  • Bunun tüm nesnenin değil, arayüzün yani vtable'ın, işlev işaretçisi tablosunun kullanıldığı bir kalıp olduğuna dikkat çekiliyor. Sınıflar, kalıtım gibi diğer nesne yönelimli özellikler ise tersine maliyetli ve izlemesi zor olabiliyor
    • Kalıtım sonuçta vtable'ın bileşik bir biçimidir. Sınıf denilen şey de sadece vtable ile kapsam değişkenlerinin birleşimidir
    • C'de ilk üye olarak bir struct üzerinden cast etmek, alan kalıtımını beklenenden daha doğal hale getiriyor
    • Vtable'da genellikle this işaretçisi alan işlevler bulunur. struct file_operations örneği ise this işaretçisi almayan işlev işaretçileri içerdiğinden, bunu gerçek bir vtable saymak zor
  • Vtable işlevleri için satır içi sarmalayıcılar yazıp thing->vtable->foo(thing, ...) yerine foo(thing, ...) şeklinde kullanıma izin verildiği söyleniyor
  • Bu kalıbın neden yeni C standardına dahil edilmediğini hep merak ettiğini söylüyor. Belli ki pek çok kişi aynı kalıbı tekrar tekrar uyguluyor
    • Söz dizimsel şeker eklendiğinde, resmen izin verilen bir kullanım biçimiyle birlikte bir şey eksikmiş gibi duran bir fallback'in de bulunması gerekir. C'nin avantajı, dinamik karmaşıklığı gizlememesidir. Dinamik dispatch olduğunda bu her zaman açıktır. Pek çok dil zaten bunu resmi olarak sunuyor ama C'nin kendine özgü artısı, karmaşıklığın görünür kalmasıdır. Bu yüzden gerçekten yalnızca dinamik dispatch gerektiğinde kullanılır. Ayrıca söz dizimi de o kadar zor değildir
    • Muhtemelen High C Compiler tarafında bu yöne kısmen bir deneme yapılmıştı
  • Bu kalıbı kesinlikle kullanmamak gerektiğini söyleyen, güçlü deneyime dayalı bir uyarı da var. Böyle yapılandırılmış büyük bir kod tabanını sürdürmenin tam bir kâbus olduğunu söylüyor. Okunabilirlik korkunç, derleyici işaretçi üzerinden yapılan çağrıları optimize edemiyor ve araç desteği hiç yok. Söz dizimi de garip; yeni başlayanların kodu okuyabilmesi için adeta C++ derleyicisinin iç işleyişine hâkim olması gerekiyor. En önemlisi de, OOP eklemenin şüpheli faydalarına karşılık, uzun vadede bakım maliyetini bozabiliyor. Gerçekten gerekliyse doğrudan C++ kullanmak gerektiğini savunuyor
    • Tam olarak hangi kısmın kâbus olduğu sorulunca, söz dizimsel şekerin az olmasının aksine bir işlev çağrısının dinamik dispatch olup olmadığını açıkça gösterdiği için okunabilirliği artırdığını düşündüğünü söylüyor. Bu yüzden yalnızca gerçekten gereken yerlerde sınırlı biçimde kullanılabilir. Ayrıca C'deki dinamik kodun, işlev işaretçisi sayısı az olduğu için optimize edilmesinin daha kolay olduğunu anlatan bir blog yazısı gördüğünü de ekliyor. Burada kastedilen, C++ derleyicisini birebir yeniden uygulamak değil; yalnızca OOP'nin özünü anlamanın bunu doğal biçimde kurmayı mümkün kıldığı. Son olarak, "C'yi kaba bir C++'a çevirmeyin" iddiasına karşı da bunun aslında C'ye daha özgü bir yol olduğunu ve istenen yere uygun miktarda dinamizm eklemeyi kolaylaştırdığını savunuyor.