- 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; };
- Örnek: aygıt davranışları için bir yapı
- Farklı aygıtlar (
netdev,diskgibi) aynı API'yi kullanır, ancak gerçekleştirimleri farklıdırnetdev.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; };
- Servis yapısı:
- 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,nextile 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
- Arayüz
Dosya soyutlaması
- Linux'taki file_operations yapısı, “her şey dosyadır” felsefesini uygular
- Örnek: https://elixir.bootlin.com/linux/v6.15/source/include/linux/fs.h
struct file_operations { struct module *owner; loff_t (*llseek)(struct file *, loff_t, int); ssize_t (*read)(struct file *, char __user *, size_t, loff_t *); ssize_t (*write)(struct file *, const char __user *, size_t, loff_t *); ... };
- Örnek: https://elixir.bootlin.com/linux/v6.15/source/include/linux/fs.h
- 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
Hacker News görüşü
NULLolup olmadığını basitçe kontrol etmek yeterlidirvoidiş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ıvoidiş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ş olurthiskullanımından hoşlanmadığını söylüyor. Sonuçtathisörneği zaten sürekli geçiriliyor ve açıkthis, 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üyorthiskullanımının zorunlu olmamasının en büyük hatalardan biri olduğunu düşünüyorobject->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 gereklimFoo,m_Foo,foo_gibi adlandırma kuralları kullandığını söylüyor.foo_,this->fooifadesinden daha kısa olduğu için tercih ediliyor. Elbette C++'tathisaçıkça da yazılabilirthis, kodu daha kısa hale getiriyor ve gerçek metotlar kullanıldığında her işlevdestructönekini tekrar etmeye gerek kalmıyor. Örneğinmystruct_dosmth(s);yerines->dosmth();daha doğal görünüyorstructüzerinden cast etmek, alan kalıtımını beklenenden daha doğal hale getiriyorthisişaretçisi alan işlevler bulunur.struct file_operationsörneği isethisişaretçisi almayan işlev işaretçileri içerdiğinden, bunu gerçek bir vtable saymak zorthing->vtable->foo(thing, ...)yerinefoo(thing, ...)şeklinde kullanıma izin verildiği söyleniyor