• आधुनिक Hello World प्रोग्राम के पीछे छिपी abstraction की दुनिया की पड़ताल

    • यह लेख C में लिखे गए Hello World प्रोग्राम के बारे में है। C, interpreter/compiler/JIT वाले उन high-level languages में सबसे निचले स्तर के करीब है, जहाँ प्रोग्राम के वास्तव में चलने से पहले यह सोचने की ज़रूरत नहीं पड़ती कि भाषा अंदर क्या कर रही है।
    • मूल रूप से इसे इस तरह लिखने की कोशिश की गई थी कि coding background वाला कोई भी व्यक्ति समझ सके, लेकिन कम से कम C या assembly का ज्ञान हो तो मदद मिलेगी.
  • Hello World प्रोग्राम की शुरुआत

    • लगभग हर कोई Hello World प्रोग्राम से परिचित होगा। Python में शायद आपने पहला प्रोग्राम print('Hello World!') जैसा ही लिखा होगा।
    • इस लेख में हम C programming language में लिखे Hello World को देखेंगे। C में interpreter को बुलाकर प्रोग्राम नहीं चलाया जा सकता। पहले compiler चलाकर उसे machine code में बदलना पड़ता है, जिसे computer processor सीधे चला सके।
  • हमारे प्रोग्राम का विश्लेषण

    • compiled program file का विश्लेषण करने पर पता चलता है कि यह ELF executable file है और x86-64 instruction set architecture के लिए है.
    • ELF executable file, Linux में Windows की .exe file के बराबर है।
    • x86-64 वह CPU architecture है जो 1981 में IBM PC के आने के बाद से PC में इस्तेमाल होता आया है।
    • इस file में machine code शामिल है, जो एकमात्र भाषा है जिसे CPU समझ सकता है।
  • assembly code का विश्लेषण

    • हम प्रोग्राम के शुरुआती address यानी entry point को ढूँढकर assembly code का विश्लेषण करते हैं।
    • assembly language, machine code का वह रूप है जिसे इंसान पढ़ सके।
    • compiler (सटीक रूप से linker) द्वारा अपने-आप जोड़ा गया initialization code दिखता है, और __libc_start_main function को call करते हुए देखा जा सकता है।
    • लेकिन यह code हमारे प्रोग्राम में defined नहीं है, बल्कि कहीं और मौजूद है।
  • C standard library

    • __libc_start_main function हमारे system की standard C library libc.so.6 में defined है।
    • C standard library, routines और functions का ऐसा संग्रह है जिसका उपयोग हमारे कंप्यूटर के लगभग सभी programs करते हैं।
    • C library initialization का काम करती है और फिर हमारे द्वारा लिखे गए main() function को call करती है। main() return होने पर यह हमारे दिए गए exit code के साथ प्रोग्राम बंद कर देती है।
  • main() function का विश्लेषण

    • main() function में stack frame सेट किया जाता है, Hello World string का address function call के argument के रूप में रखा जाता है, और फिर puts() function को call किया जाता है।
    • puts() असल में printf() को call करने के लिए लिखे गए को compiler optimization के कारण बदला गया है। printf() जटिल है, लेकिन puts() सिर्फ बिना format वाली string को output करता है, इसलिए यह अधिक सरल है।
  • Hello World string

    • string का रूप "Hello World!" के बाद NULL terminator आने जैसा है।
    • C में string के साथ लंबाई की जानकारी नहीं होती, इसलिए string के अंत को NULL terminator से दिखाया जाता है। अगर NULL terminator न हो, तो प्रोग्राम अनधिकृत memory पढ़ते-पढ़ते Segmentation Fault से क्रैश हो सकता है।
    • compiler optimization की वजह से printf() में इस्तेमाल किया गया newline (\n) हटा दिया गया। puts() string output करने के बाद अपने-आप newline जोड़ देता है।
  • puts() function

    • puts() function फिर से standard library के अंदर मौजूद code को call करता है।
    • glibc के code को देखें तो _IO_puts -> _IO_new_file_xsputn क्रम में call होते दिखाई देते हैं, लेकिन code जटिल होने के कारण इसे समझाना कठिन है।
    • musl libc के मामले में यह थोड़ा सरल है। puts -> fputs -> fwrite -> __fwritex -> __stdio_write -> syscall क्रम में call होता है।
  • system call

    • C library कितनी भी बड़ी क्यों न हो, वह hardware से सीधे बात नहीं कर सकती। यह काम केवल kernel कर सकता है।
    • इसलिए puts() call का अंततः मतलब होता है OS से कुछ करने का अनुरोध करना। यहाँ इसका मतलब output stream में string लिखना है।
    • musl libc writev नाम का system call इस्तेमाल करता है, जो कई buffers को एक साथ लिखने देता है।
    • system call registers में parameters सेट करके और syscall instruction चलाकर किया जाता है। इसके बाद control kernel को चला जाता है, और kernel parameters पढ़कर system call पूरा करता है।
  • kernel

    • Linux kernel को system call द्वारा माँगी गई कार्रवाई करनी होती है। write system call kernel को किसी file system के खुले file या stream में लिखने का निर्देश देता है।
    • write तीन parameters लेता है: किस file descriptor पर लिखना है, कौन-सा buffer लिखना है, और कितने bytes लिखने हैं।
    • वास्तव में कहाँ लिखा जाएगा, यह स्थिति पर निर्भर करता है। Terminal emulator हो तो यह virtual terminal (pty) के रूप में दिखेगा, remote login हो तो sshd तक जाएगा, physical terminal हो तो serial-USB adapter तक जाएगा। Frame buffer console हो तो kernel text render करके display पर दिखा देगा।
  • निष्कर्ष

    • आधुनिक software systems hardware के ऊपर बहुत जटिल और परिष्कृत तरीके से काम करते हैं, इसलिए कंप्यूटर के किसी छोटे-से काम को पूरी तरह समझने की कोशिश करना अपने-आप में बहुत कठिन है।
    • सब कुछ समझाने के लिए बहुत-सी बातों को छोड़ना ही पड़ा।
    • Hello World संदेश भेजना भी इस समय कंप्यूटर पर चल रहे अनगिनत system calls और programs में से सिर्फ एक है।

GN⁺ की राय

  • यह लेख दिखाता है कि computing system की हर layer abstraction के ज़रिए अपने नीचे की layer की जटिलता छिपाती है, ताकि developers आसानी से applications बना सकें।
  • दूसरी ओर, यह एहसास भी कराता है कि application की एक पंक्ति चलने के लिए नीचे कितनी चीज़ें होती हैं, और debugging इतनी कठिन क्यों होती है।
  • मेरा मानना है कि हर programmer को कम-से-कम उस system layer तक अच्छी समझ होनी चाहिए जो उसकी रोज़मर्रा की language के नीचे काम करती है। पूरी चीज़ जानना ज़रूरी नहीं, लेकिन यह जानना महत्वपूर्ण है कि abstract की गई चीज़ें वास्तव में कैसे काम करती हैं।
  • चाहे आप high-level language का उपयोग करें, memory layout, stack और heap, system call जैसी system programming concepts का अध्ययन debugging और performance optimization में बहुत मदद करेगा।
  • Application developers को शायद compiler या C library को सीधे छूने की ज़रूरत कम पड़े, लेकिन यह समझना कि आपका लिखा प्रोग्राम अंततः system का उपयोग कैसे करता है, अच्छा programmer बनने के लिए बेहद ज़रूरी है।

अभी कोई टिप्पणी नहीं है.

अभी कोई टिप्पणी नहीं है.