36 पॉइंट द्वारा GN⁺ 2025-08-29 | 1 टिप्पणियां | WhatsApp पर शेयर करें
  • Object-oriented design patterns C भाषा में लिखे गए kernel में भी polymorphism और modularity लागू करके लचीला system design संभव बनाते हैं
  • vtable (virtual function table) का उपयोग करके device और service interfaces को standardize किया जा सकता है, और runtime में dynamic बदलाव के जरिए अलग-अलग behaviors को support किया जा सकता है
  • Kernel services और scheduler vtable के जरिए start, stop, restart जैसे consistent interfaces देते हैं और implementation details को encapsulate करते हैं
  • Kernel modules के साथ मिलकर यह dynamic driver loading को support करता है, जिससे recompilation के बिना system को extend किया जा सकता है
  • यह approach flexibility और experimental freedom देता है, लेकिन complex syntax और explicit object passing के कारण कुछ verbosity इसकी कमी है

OS development में आज़ादी और object-oriented patterns

  • अपना खुद का OS development collaboration या real-world application की पाबंदियों के बिना आज़ाद experimentation की सुविधा देता है
    • security vulnerabilities, code maintenance, और release burden से अपेक्षाकृत मुक्ति
    • यही OS development का आकर्षण है, जहाँ non-standard programming patterns को explore किया जा सकता है
  • LWN लेख “Object-oriented design patterns in the kernel” में Linux kernel द्वारा C में object-oriented principles लागू करने के उदाहरण दिखाए गए हैं
    • function pointers वाले structs के जरिए polymorphism लागू किया जाता है
    • encapsulation, modularity, और extensibility के जरिए low-level kernel में भी object-oriented फ़ायदों का उपयोग होता है

vtable की बुनियादी अवधारणा

  • vtable function pointers वाला एक struct होता है, जो object का interface define करता है
    • उदाहरण: device behavior के लिए एक struct
      struct device_ops {  
          void (*start)(void);  
          void (*stop)(void);  
      };  
      struct device {  
          const char *name;  
          const struct device_ops *ops;  
      };  
      
  • अलग-अलग devices (जैसे netdev, disk) एक ही API का उपयोग करते हैं, लेकिन implementation अलग होता है
    • netdev.ops->start() network device behavior को call करता है, जबकि disk.ops->start() disk device behavior को
  • Runtime बदलाव: vtable को dynamically बदलकर caller code बदले बिना behavior बदला जा सकता है
    • सही synchronization के साथ यह dynamic behavior evolution को साफ़ तरीके से संभव बनाता है

OS में उपयोग के उदाहरण

Service management

  • Kernel services (networking manager, worker pool, window server आदि) को consistent interface से manage किया जा सकता है
    • service struct:
      struct service_ops {  
          void (*start)(void);  
          void (*stop)(void);  
          void (*restart)(void);  
      };  
      struct service {  
          pid_t pid;  
          const struct service_ops *ops;  
      };  
      
  • हर service अपना अलग behavior implement करती है, लेकिन terminal से start/stop/restart standard तरीके से चलाए जा सकते हैं
  • code और services के बीच coupling कम होता है, और management आसान हो जाता है

Scheduler

  • Scheduler round-robin, shortest-job-first, FIFO, priority scheduling जैसी कई strategies को support कर सकता है
    • interface को yield, block, add, next तक सरल बनाया जा सकता है
    • इसे vtable से define करके runtime में scheduling policy बदली जा सकती है
    • kernel के बाकी हिस्सों में बदलाव किए बिना पूरी policy बदली जा सकती है

File abstraction

  • Linux का file_operations struct “everything is a file” दर्शन को लागू करता है
    • उदाहरण: 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 *);  
          ...  
      };  
      
  • sockets, devices, और text files सभी एक जैसा read/write interface देते हैं
  • user-space code को implementation details जाने बिना consistent तरीके से काम करने की सुविधा मिलती है

Kernel modules के साथ एकीकरण

  • Kernel modules vtable replacement के जरिए dynamic drivers या hooks की loading को support करते हैं
    • Linux modules की तरह, kernel को recompile या reboot किए बिना extend किया जा सकता है
    • नया feature जोड़ते समय मौजूदा struct का vtable ही update करना होता है

कमियाँ

  • Syntax की जटिलता:
    • object->ops->start(object) की तरह object को explicitly pass करना पड़ता है
    • C++ की implicit passing की तुलना में यह अधिक verbose है
    • function signatures भी लंबे हो सकते हैं:
      static void object_start(struct object* this) {  
          this->id = ...  
      }  
      
  • फायदे: explicit passing से function dependencies साफ़ रहती हैं, और object व behavior के बीच coupling पारदर्शी रहती है
    • kernel code में complexity और clarity के बीच यह एक उपयोगी tradeoff है

निहितार्थ

  • vtable flexibility बनाए रखते हुए complexity कम करने का एक सरल तरीका देता है
    • runtime behavior replacement, consistent interface, और नए features जोड़ना आसान होता है
  • यह C भाषा में object-oriented design लागू करने का एक नया तरीका दिखाता है और OS development के experimental मज़े को रेखांकित करता है
  • अतिरिक्त सामग्री: xine project (https://xine.sourceforge.net/hackersguide#id324430) vtable के जरिए private variables manage करने का तरीका दिखाता है
  • OS development creative experimentation का मंच है, और यह साबित करता है कि object-oriented patterns low-level systems में भी शक्तिशाली tools हैं

1 टिप्पणियां

 
GN⁺ 2025-08-29
Hacker News राय
  • यह उस लेख पर चर्चा है जिसमें कहा गया है कि Linux kernel भले ही C में लिखा गया हो, फिर भी वह structs में function pointers का उपयोग करके polymorphism लागू करने जैसी object-oriented सिद्धांतों को अपनाता है। इस तरह की तकनीकें object-oriented programming से बहुत पहले से मौजूद थीं, और इन्हें 'abstract data type(ADT)' या data abstraction कहा जाता है। ADT और OOP के बीच मुख्य अंतर यह है कि ADT में function implementation को छोड़ा जा सकता है, लेकिन OOP में हमेशा implementation चाहिए होता है। OOP में अगर optional functions चाहिए हों, तो हर optional function के लिए अलग class बनानी पड़ती है, और उसे लागू करते समय multiple inheritance के ज़रिए साथ inherit करना पड़ता है और runtime पर यह जाँचना पड़ता है कि object उस अतिरिक्त class का instance है या नहीं। इसके उलट ADT में बस यह देखना होता है कि function pointer NULL है या नहीं
    • Smalltalk और Objective-C में runtime पर यह आसानी से जाँचा जाता है कि object किसी message का जवाब दे सकता है या नहीं; यही पारंपरिक OOP तरीका है। अफ़सोस यह है कि C++ और Java के अत्यधिक class-केंद्रित design patterns के कारण OOP का मूल स्वरूप कुछ बिगड़ गया
    • ज़्यादातर बातों से सहमति है, और C में भी यह pattern इस्तेमाल होता है, लेकिन पारंपरिक OOP में base में default या stub implementation रखना आम तरीका है। आधुनिक OOP या concept-oriented भाषाओं में ज़रूरी API के subset का उपयोग करने वाले interface में cast करने का तरीका भी है। Go इसका अच्छा उदाहरण है
    • इस दावे पर कि ये तकनीकें object-oriented programming से पहले की हैं, कहना बेहतर होगा कि OOP ने दरअसल पहले से मौजूद patterns और paradigms को formalize किया
    • Java, C# जैसी ज़्यादातर OOP भाषाओं में अब lambda का उपयोग किया जा सकता है, इसलिए C की तरह ही इसे लागू करना संभव है। Lambda आख़िरकार function pointer ही है, इसलिए उसे सीधे instance variable में assign किया जा सकता है। (Java में lambda आने में 10 साल से ज़्यादा लगे, और एक समय Sun Microsystems ने Java में lambda जोड़ने की कोशिश को लेकर Microsoft पर मुकदमा भी किया था—एक मज़ेदार पुराना क़िस्सा)
    • Inheritance अनिवार्य नहीं है। Composite pattern इस्तेमाल किया जा सकता है। Python भी कुछ हद तक ऐसा ही है क्योंकि उसमें self/this/object pointer को स्पष्ट रूप से पास करना पड़ता है, इसलिए वह C-style data abstraction से मिलता-जुलता है
  • कुछ साल पहले Peterpaul ने C के ऊपर आराम से इस्तेमाल की जा सकने वाली एक lightweight object-oriented system बनाई थी (repo)। इसमें object को explicit तौर पर पास करने की ज़रूरत नहीं होती, और documentation कम है लेकिन पूरा test suite मौजूद है (test1, test2)
    • अगर यह देखना हो कि carbon की syntactic sugar के बिना यह कैसा दिखता है, तो यहाँ देखा जा सकता है। लगता है कि parametric polymorphism supported नहीं है
    • लगता है कि Vala भी इस niche क्षेत्र में एक उपयुक्त कोशिश कर रहा है
  • मुझे इस हिस्से की पूरी जानकारी नहीं है, लेकिन लगता है कि OP kernel developers से अलग कुछ कर रहा है। OP के लिंक किए गए लेख को पढ़ने पर vtable में typed function pointers हैं, लेकिन OP शायद void pointers का उपयोग कर रहा है। साथ ही kernel developer की पोस्ट में बताया गया मुख्य फ़ायदा यह है कि हर struct instance में कई function pointers रखने के बजाय सिर्फ़ एक vtable pointer रखकर memory बचाई जाती है। यानी memory saving मुख्य बिंदु है, जबकि OP इस vtable का उपयोग runtime में methods बदलने और polymorphism लागू करने के लिए indirect layer की तरह कर रहा है। यह pattern kernel developer की बात से अलग है
    • OP ने void pointer नहीं बल्कि void (कोई argument नहीं, return value नहीं) कहा था। vtable का उपयोग polymorphism लागू करने के लिए ही होता है। अगर polymorphism ही नहीं है तो vtable की ज़रूरत नहीं, यानी memory और भी बचती है
  • इस राय के जवाब में कि हर बार object को explicit तौर पर पास करना असुविधाजनक है, मेरा उल्टा मानना है कि implicit this मुझे पसंद नहीं। असल में आप this instance को हर समय पास ही कर रहे होते हैं, और explicit this होने से यह भ्रम नहीं रहता कि कोई variable instance का है, global है या कहीं और से आया है
    • मुझे लगता है कि C++ (और Java) की OOP syntax में instance members को refer करते समय this को अनिवार्य न रखना बड़ी ग़लतियों में से एक है
    • मेरा ख़याल है कि लेखक नीचे वाले object->ops->start(object) जैसे रूप में object को दो बार लिखने वाली बात की ओर इशारा कर रहा है। एक बार vtable resolve करने के लिए, और एक बार C function implementation को object देने के लिए
    • variable ownership स्पष्ट करने के लिए लोग member variables के लिए अक्सर mFoo, m_Foo, foo_ जैसी naming conventions का उपयोग करते हैं। foo_, this->foo से छोटा है इसलिए पसंद आता है। बेशक C++ में this को explicit रूप से लिखा भी जा सकता है
    • implicit this से coding ज़्यादा संक्षिप्त हो जाती है, और असली methods का उपयोग करने पर हर function में struct prefix दोहराने की ज़रूरत नहीं रहती। जैसे mystruct_dosmth(s); की जगह s->dosmth(); ज़्यादा स्वाभाविक लगता है
    • macros का उपयोग करके इसे थोड़ा और स्मार्ट तरीके से भी किया जा सकता है
  • मैंने C में यह pattern पहली बार Tmux की प्रस्तुति सामग्री में सीखा था (slides)। इस concept पर मेरी अपनी एक लिखी हुई पोस्ट भी है (tmux object-oriented commands post)
  • विश्वविद्यालय के दिनों में मैंने कुछ छोटे projects में यह तरीका लागू किया था। C में OOP जैसा अहसास लाना मज़ेदार था, लेकिन सावधानी न बरती जाए तो चीज़ें बहुत जल्दी बेकाबू हो सकती हैं
  • ध्यान रहे कि यह पूरे object का नहीं बल्कि interface (यानी vtable, function pointer table) का उपयोग करने वाला pattern है। Class, inheritance जैसी दूसरी object-oriented सुविधाएँ अक्सर महँगी पड़ती हैं और उनका पालन करना भी कठिन होता है
    • inheritance आख़िरकार vtable की composition ही है। Class भी मूलतः vtable और scoped variables का संयोजन भर है
    • C में struct को पहले member के रूप में cast किया जाए तो field inheritance काफ़ी स्वाभाविक लगता है
    • vtable में आमतौर पर ऐसे functions होते हैं जो this pointer लेते हैं। struct file_operations का उदाहरण this pointer लेने वाले function pointers नहीं रखता, इसलिए उसे सच्चे अर्थ में vtable कहना कठिन है
  • vtable functions के लिए inline wrappers बनाए जा सकते हैं ताकि thing->vtable->foo(thing, ...) की जगह foo(thing, ...) जैसा लिखा जा सके
  • मैं हमेशा सोचता रहा हूँ कि यह pattern नए C standard में क्यों शामिल नहीं किया गया। साफ़ है कि बहुत लोग बार-बार यही pattern ख़ुद लागू कर रहे हैं
    • अगर syntactic sugar जोड़ी जाए, तो एक आधिकारिक अनुमत तरीका और उसके साथ कुछ-कुछ अधूरा-सा fallback दोनों रखने पड़ेंगे। C की एक खूबी यह है कि वह dynamic complexity को छिपाता नहीं है। जब dynamic dispatch होता है, तो वह हमेशा स्पष्ट दिखता है। बहुत-सी भाषाएँ पहले ही इसका औपचारिक रूप दे चुकी हैं, लेकिन C की खास ताकत यह है कि complexity दिखाई देती है। इसलिए इसका उपयोग तभी होता है जब सच में dynamic dispatch चाहिए हो। और syntax भी इतना कठिन नहीं है
    • शायद High C Compiler की तरफ़ कुछ हद तक इस दिशा में कोशिश की गई थी
  • बहुत कड़े अनुभव से निकली सलाह है: इस pattern का इस्तेमाल बिल्कुल न करें। इस संरचना में लिखे गए बड़े codebase को maintain करना दुःस्वप्न था। Readability भयानक होती है, compiler pointer-आधारित calls को optimize नहीं कर पाता, tooling का कोई सहारा नहीं मिलता। Syntax अजीब है, और नए लोगों को code पढ़ने के लिए C++ compiler internals तक समझने पड़ते हैं। सबसे बड़ी बात, OOP लाने के संदिग्ध लाभों की तुलना में यह लंबे समय में maintainability को बर्बाद कर सकता है। अगर सच में ज़रूरत है, तो सीधा C++ इस्तेमाल करना चाहिए
    • जब पूछा गया कि ख़ास तौर पर दुःस्वप्न किस बात में था, तो जवाब यह था कि कम syntactic sugar होने से उलटे यह ज़्यादा साफ़ दिखता है कि कोई function call dynamic dispatch है या नहीं, इसलिए readability बेहतर हो सकती है। इस वजह से इसे सिर्फ़ वहीं सीमित रूप में इस्तेमाल किया जा सकता है जहाँ dynamic dispatch सचमुच चाहिए। और C के dynamic code पर ऐसा एक blog post भी देखा था जिसमें कहा गया कि कम function pointers होने से optimization आसान होती है। मुद्दा यह नहीं कि C++ compiler को ज्यों का त्यों दोबारा बनाया जाए, बल्कि इतना है कि OOP का मूल समझने पर इसे स्वाभाविक रूप से लागू किया जा सकता है। आख़िर में, 'C को घटिया C++ मत बनाओ' वाली बात के जवाब में कहा गया कि यह उलटे C-शैली का ही तरीका है, और जहाँ ज़रूरत हो वहाँ ठीक-ठीक dynamic व्यवहार जोड़ पाना ही इसे चुनने की वजह है.