1 पॉइंट द्वारा GN⁺ 2024-07-16 | 1 टिप्पणियां | WhatsApp पर शेयर करें
  • 512-बाइट बूट सेक्टर से शुरू करके x86_64 CPU को 16-बिट real mode से 64-बिट long mode तक ले जाने वाले न्यूनतम बूटलोडर flow को चरण-दर-चरण बनाया गया
  • BIOS interrupts से output पहले verify करने के बाद nasm, ld, objcopy, QEMU को जोड़कर जांचा गया कि boot image सच में execute हो रही है या नहीं
  • बूट सेक्टर की capacity limit के कारण इसे stage 1/stage 2 में बांटा गया, और protected mode में जाने से पहले BIOS int 0x13 से अगला code disk से पढ़ा गया
  • 32-बिट protected mode से BIOS routines इस्तेमाल नहीं किए जा सकते, इसलिए GDT, flat segmentation, VGA buffer में direct output जैसी hardware initialization की जरूरत होती है
  • 64-बिट long mode में प्रवेश के लिए page tables, PAE, EFER.LME, cr0.PG, 64-बिट के लिए GDT—सब सही से set होने चाहिए, और इसके बाद freestanding C code को kernel की तरह call किया जा सकता है

BIOS से शुरू होने वाला 16-बिट environment

  • reset के बाद x86 CPU real mode में होता है, और default operand size 16-बिट होती है
  • real mode segmentation से 20-बिट address space बनाता है, और अधिकतम 1MB memory handle कर सकता है
  • BIOS जो पहला code execute करता है वह disk के बूट सेक्टर में होता है
    • BIOS ऐसे disk को ढूंढता है जिसका पहला sector 0xaa55 magic number पर खत्म होता हो
    • उस sector को memory address 0x7c00 पर load करता है
  • BIOS से मिलने वाली जगह सिर्फ 512 bytes होती है, इसलिए यह code बाकी bootloader को load करने वाले bootstrap role पर focus करता है
  • BIOS routines केवल real mode में रहते हुए ही इस्तेमाल की जा सकती हैं

जरूरी चीजें और build environment

  • follow करने के लिए Intel 64 and IA-32 Architectures Software Developer’s Manual, assembler, और QEMU चाहिए
  • example assembler nasm है, और यह मानकर आगे बढ़ा गया है कि आपको x86 assembly और nasm syntax पता है
  • x86_64 CPU न हो तब भी QEMU में x86 CPU emulate करके run किया जा सकता है
  • 32-बिट mode तक की basic सामग्री के लिए Writing a Simple Operating System — from Scratch को reference बनाया गया

बूट सेक्टर बनाना और execution verify करना

  • पहला बूट सेक्टर BIOS routine से "Hello, world!" output करता है, फिर hlt और loop से रुक जाता है
  • string output के लिए BIOS video services call int 0x10 और ah = 0x0e का इस्तेमाल होता है
  • Makefile nasm से object बनाता है, linker script से link करता है, और फिर objcopy -O binary से raw boot image generate करता है
  • make boot QEMU से image run करता है
    • qemu-system-x86_64 -no-reboot -drive file=$<,format=raw,index=0,media=disk
  • linker script यह set करती है कि बूट सेक्टर 0x7c00 के आधार पर रखा जाए
    • MEMORY में boot_sector का origin 0x7c00 है, length 512 है
    • .bootsign section 0x7c00 + 510 position पर 0x55, 0xaa जोड़ता है
  • बूट सेक्टर assembly के अंदर offset और magic number को सीधे handle किया जा सकता है, लेकिन यहां linker script यह role निभाती है

stage 1 द्वारा stage 2 को disk से पढ़ना

  • stage 1 वह बूट सेक्टर code है जिसे BIOS load करता है, और उसका लक्ष्य stage 2 को memory में load करना है
  • stage 2 में 16-बिट real mode से 32-बिट protected mode में जाने वाला code होता है
  • protected mode में जाने के बाद BIOS routines इस्तेमाल नहीं की जा सकतीं, इसलिए disk sector reading transition से पहले खत्म करनी होगी
  • disk access के लिए BIOS disk services int 0x13 का इस्तेमाल होता है
    • ah = 0x42 BIOS extended read feature है
    • dl = 0x80 drive number है
    • disk address packet में पढ़े जाने वाले sectors की संख्या, target address, और starting sector होते हैं
  • example READ_SECTORS_NUM equ 64 से 64 sectors पढ़ता है
    • बूट सेक्टर sector 0 है, इसलिए stage 2 को sector 1 से पढ़ा जाता है
    • target address BOOT_LOAD_ADDR + SECTOR_SIZE, यानी 0x7c00 + 512 है
  • code में requested से कम sectors पढ़े जाने की स्थिति accept करने वाली temporary handling बची हुई है
  • stage 2 शुरुआत में real mode के लिए print_string copy करके "Hello from stage 2" output करता है, और verify करता है कि stage 1 से jump सही से आगे बढ़ा या नहीं

32-बिट protected mode में switch करना

  • protected mode में जाने के लिए पहले Global Descriptor Table(GDT) define करनी होती है
  • protected mode में मूल रूप से segmentation memory protection के लिए इस्तेमाल होती है
  • 64-बिट long mode में paging की जरूरत होती है, लेकिन उससे पहले के step यानी protected mode entry के लिए segmentation setting पहले चाहिए
  • example GDT Intel manual के flat model को follow करता है
    • code segment और data segment रखता है
    • दोनों segments पूरे linear address space पर map होते हैं
    • long mode तक जाने के intermediate step के रूप में सबसे सरल model इस्तेमाल किया गया है
  • GDT memory में रखा contiguous structure है
    • शुरुआत में invalid translation पकड़ने के लिए null descriptor होता है
    • उसके बाद code segment descriptor और data segment descriptor आते हैं
  • transition इस sequence में होता है
    • cli से interrupts disable किए जाते हैं
    • lgdt [gdt32_pseudo_descriptor] से GDTR में GDT address और length load की जाती है
    • cr0.PE, यानी cr0 के bit 0 को set करके protected mode on किया जाता है
    • far jump से instruction pipeline खाली होती है और cs नए code segment से update होता है
  • protected mode में entry के बाद पुराने segment values का अब कोई मतलब नहीं रहता, इसलिए ds, ss, es, fs, gs को नए data segment selector पर set किया जाता है
  • सारी settings के बाद interrupts दोबारा on करने के लिए extra work चाहिए

BIOS के बिना screen पर output करना

  • protected mode में BIOS routines अब call नहीं की जा सकतीं
  • string output VGA buffer में directly लिखने के तरीके में बदल जाता है
  • print_string32 0xb8000 address पर character और color byte लिखता है
    • color value 0xf है
    • हर character cell 2 bytes इस्तेमाल करता है
  • यह output function बहुत simple है, इसलिए message हमेशा screen के top-left में दिखता है

long mode entry के लिए page tables

  • Intel documentation का IA-32e mode AMD64 manual के long mode के बराबर है
  • long mode में switch करने के लिए CPU protected mode में होना चाहिए, और paging भी enabled होनी चाहिए
  • paging concept के लिए Introduction to Paging और OSTEP को reference किया गया
  • PAE enabled long mode में 4-level page table इस्तेमाल होता है
  • build_page_table दिए गए address पर 4-level page table बनाता है
    • page size 0x1000 है
    • हर page table का size 0x1000 है
    • entry count 512 है
    • पहले चारों tables को 0 से initialize करके सभी entries को not present state में रखा जाता है
    • PML4 → PDP → PD → page table की first entries को link किया जाता है
    • सबसे निचली page table layer में 512 entries set की जाती हैं

64-बिट के लिए GDT और long mode transition sequence

  • paging virtual address space और permission management संभालती है, लेकिन long mode में भी GDT की जरूरत होती है
  • 64-बिट के लिए GDT भी flat model follow करती है, और protected mode वाली GDT से लगभग समान है
  • फर्क long mode से जुड़े bit settings में है
    • code segment का 64-bit code segment flag set किया जाता है
    • यह flag set होने पर default operation size bit 0 होना चाहिए
  • long mode transition इस flow में आगे बढ़ता है
    • 0x1000 address पर 4-level page table बनाई जाती है
    • cr3 में PML4 table address डाला जाता है
    • cr4 का bit 5 set करके PAE enabled किया जाता है
    • 0xc0000080 MSR पढ़कर EFER.LME, यानी bit 8 set किया जाता है
    • cr0 के bit 31 यानी PG flag को set करके paging enabled की जाती है
    • 64-बिट के लिए GDT को lgdt से load किया जाता है
    • 64-बिट code segment में far jump करके 64-बिट mode में entry होती है
  • paging on करने के ठीक बाद state IA-32e compatibility mode होती है, और 64-बिट segment flag set वाली GDT पर jump करते हुए 64-बिट mode में switch होता है
  • success confirmation message VGA buffer के जरिए screen के top-left में दिखता है

freestanding C code call करना

  • 64-बिट long mode तक पहुंचने पर freestanding C code call किया जा सकता है
  • kernel.c VGA buffer 0xb8000 साफ करता है और "Hello from C" output करता है
  • assembly side का start_long_mode 64-बिट के लिए string output के बाद extern _start_kernel declare करता है और _start_kernel call करता है
  • linker script memory area को तीन parts में divide करती है
    • boot_sector: 0x7c00, length 512
    • stage2: 0x7e00, length 512
    • kernel: 0x8000, length 0x10000
  • .text, .data, .rodata, .bss sections kernel area में रखे जाते हैं
  • Makefile assembly और C दोनों build करने के लिए बदला गया है
    • C compiler gcc है
    • मुख्य CFLAGS हैं -std=c99 -ffreestanding -m64 -mno-red-zone -fno-builtin -nostdinc -Wall -Wextra
  • पूरा example code download link पर उपलब्ध है

1 टिप्पणियां

 
GN⁺ 2024-07-16
Hacker News की राय
  • protected mode से गुज़रे बिना, कहीं कम code के साथ सीधे long mode में जाया जा सकता है: https://wiki.osdev.org/Entering_Long_Mode_Directly
    इस तरीके से बने एक छोटे 64-bit kernel के लिए bootloader था, और disk से kernel पढ़ने और VESA mode set करने वाला code शामिल करने पर भी वह boot sector में आराम से फिट हो जाता था। 2-stage loader की भी ज़रूरत नहीं थी

    • जिज्ञासा है कि यह सब 512 bytes के अंदर कैसे फिट होता है। kernel को disk पर कहीं भी normal file की तरह रखने वाला कोई असली file system तो शायद नहीं होगा, और सिर्फ file fragmentation handle करना ही 512 bytes से काफी ऊपर चला जाएगा
    • बस https://limine-bootloader.org/ इस्तेमाल करें, यह कहीं ज़्यादा सरल हो जाता है। real mode को छूने की ज़रूरत नहीं, SMP में भी यही बात है, kernel को higher-half mapping के साथ automatically load करता है और aarch64 व riscv64 पर भी चलता है
    • बात सही है, लेकिन partition table समेत modern AHCI controllers और SATA support करना हो तो bootloader के लिए जगह और घट जाती है, इसलिए optimization की ज़रूरत पड़ती है। इस case में loader के लिए पूरे 510 bytes नहीं मिलते, बल्कि काफी कम मिलते हैं, और valid partition entries भी भरनी हों तो table के अंदर के bytes भी इस्तेमाल नहीं कर सकते, जिससे काम और मुश्किल हो जाता है
      असल modern hard disk इस्तेमाल करनी हो तो MBR के बजाय GPT देखना बेहतर है। partition table की सीमाओं से आगे बढ़े बिना 2TB से बड़े disks भी handle किए जा सकते हैं। UEFI ऐसी समस्याएँ खत्म कर देता है और बिना ज्यादा परेशानी के सही disk layout इस्तेमाल करने देता है
      64-bit mode में जाने के लिए protected mode ज़रूरी नहीं है। हालांकि BIOS का इस्तेमाल न करना बेहतर है। यह messy है और काम को सिर्फ और झंझट भरा बनाता है
      UEFI को EDK2 या GnuEFI के साथ इस्तेमाल करना बेहतर तरीका है, और दोनों को implement करना काफी आसान व सुविधाजनक है। UEFI की शुरुआती concepts की आदत पड़ने में थोड़ा समय लगता है, लेकिन GitHub पर example projects देखने से structure आसानी से समझ आ जाता है। EDK में .dec और .inf files वगैरह कुछ खास अच्छी नहीं हैं, और GnuEFI में features खोजने के लिए header files पढ़नी पड़ती हैं, लेकिन यह अस्पष्ट specs वाले BIOS interface से कहीं बेहतर है। real hardware पर तो यह assume भी नहीं कर सकते कि int 0x10, int 0x15 आदि ठीक से मौजूद होंगे
      UEFI systems में एक stable minimum base assume किया जा सकता है, और hardware या platform features को भी sane तरीके से enumerate किया जा सकता है। साथ ही UEFI platform को पहले ही काफी configure कर चुका होता है, इसलिए OS loader component में बहुत initialization करने की ज़रूरत नहीं रहती; सीधे OS, drivers और kernel design के हिसाब से components load कर सकते हैं। memory map लें, EFI file system access करें और ज़रूरी चीज़ें पढ़ लें
    • पता नहीं था कि ऐसा संभव है। अगर लक्ष्य सिर्फ long mode में जाना है, तो शुरू में protected mode से होकर क्यों जाना पड़ता है, यह सोचने वाली बात है
  • 80286 में Machine Status Word (MSW) नाम का 16-bit register है, और 80386 ने इसे 32-bit register CR0 तक extend किया। बाद में 64-bit long mode ने EFER MSR जोड़ा और CR0 को 64-bit तक extend किया, लेकिन आज भी CR0 में सिर्फ 11 bits इस्तेमाल होते हैं और EFER में भी केवल 8 active bits हैं
    जिज्ञासा है कि Intel/AMD ने मौजूदा registers के spare bits का इस्तेमाल करने के बजाय दो बार नया selection क्यों किया: https://wiki.osdev.org/CPU_Registers_x86-64#CR0

    • शायद वजह backward compatibility को और robust बनाना रही होगी। software reserved bits की values के बारे में assumption कर सकता है या उनमें values लिख सकता है। ऐसे hardware registers में bit allocation काफी arbitrary होता है, और higher bits इस्तेमाल करने की कोई खास cost भी नहीं है
    • एक शब्द में जवाब दें तो शायद bureaucracy होगा। बड़े organizations कुल मिलाकर खासकर अच्छे फैसले लेने में बहुत अच्छे नहीं होते, और कई बे-सिर-पैर choices भी पैदा हो जाती हैं
      CR1 और CR5~CR7 अब भी reserved हैं, फिर भी CR8 आया—इसकी वजह भी कुछ ऐसी ही लगती है
  • इस लेख में सबसे बेवजह जटिल दिखने वाली चीज़ Makefile और linker script है। NASM flat binary output support करता है, पर शायद उन्हें वह बहुत “hacky” लगा

    • व्यक्तिगत रूप से मुझे flat NASM की तुलना में linker script कहीं अधिक पढ़ने और reason करने में आसान लगती है। खासकर जब source files कई हों
    • बिल्कुल सही बात है। बाद में Makefile और linker scripts अहम headache बन जाते हैं, लेकिन अगर flat binary बनानी है, तो बस flat binary बनाइए। बेवजह फुलाने की ज़रूरत नहीं
      पहले मेरे OS में इसका मज़ाक उड़ाने के लिए make.sh नाम की file थी। अब हम ‘file format’ जैसी fancy चीज़ें इस्तेमाल कर रहे हैं, इसलिए -fbin और --oformat=binary बस क्षणभंगुर यादें बनकर रह गए हैं। data C files और code C files को अलग करके binary में dump करने और फिर वहाँ से कोई monster assemble करने की बहुत कोशिश की, लेकिन linking और loading बहुत कठिन हो गया। बस ELF या PE इस्तेमाल करना बेहतर है, और असल में वे formats शायद यही काम करने के लिए हैं
  • शानदार और अच्छा exercise लगता है, लेकिन यह useful है या नहीं, पता नहीं। सोचता हूँ कि running configuration देखने या बदलने के लिए Fisher-Price खिलौने जैसी UX भी है क्या
    booting mini-me mode, single-user mode, recovery mode से उड़ान भरती हुई state तक जाने की प्रक्रिया है
    Xenix/DOS के समय से Microsoft products के साथ Unix इस्तेमाल करता आया हूँ, लगभग 40 साल हो गए होंगे। इतने समय में कितनी प्रगति हुई है, यह सवाल है
    Linux भी Swedish version, यानी first release से इस्तेमाल किया है, और GNU 0.1 भी आज़माया है
    Xenix को Unix कहने के लिए माफ़ी। Xenix release के तुरंत बाद से decline तक, पहले ही past की चीज़ बनना चाहता एक me-too किस्म का mess था
    Microsoft products release नहीं करता, बल्कि ग्राहकों के ऊपर cat litter box खाली करने वाली company जैसा है। हालिया examples Copilot और 22H2 हैं
    F1 car, pencil, pocket calculator कैसे विकसित हुए हैं, यह देखें तो सोचता हूँ कि हम usable ideal के कितने करीब हैं
    यह भी सवाल है कि bootloader static kernel mode क्यों नहीं है। पहले ऐसा था, और हाल ही में किसी ने propose किया कि फिर से ऐसा होना चाहिए—मैं भी सहमत था

  • https://wiki.osdev.org/A20_Line

  • CPU को सही मोड में स्विच करने के लिए ज़रूरी सारे कदम देखकर हैरानी होती है, क्योंकि वे सब अनावश्यक लगते हैं। ज़्यादातर प्रक्रियाएँ backward compatibility की वजह से ज़रूरी लगती हैं
    सोचता हूँ कि क्या Intel शुरू से ही सही मोड में शुरू करने के लिए कोई flag या instruction नहीं दे सकता था, या फिर backward compatibility हटा नहीं सकता था
    याद है कि ARM64 में भी कुछ ऐसी ही समस्याएँ थीं। सोचता हूँ कि क्या कोई ऐसा CPU है जिसे शुरू से 64-bit के लिए डिज़ाइन किया गया हो, जिसे backward compatibility की ज़रूरत न हो और जो default रूप से वांछित state में चला जाए। लगता है Itanium का लक्ष्य या design कुछ ऐसा ही था क्या

    • Intel द्वारा प्रस्तावित X86S का उद्देश्य यही है

      X86S is a legacy-reduced-OS ISA that removes outdated execution modes and operating system ISA.
      The presence of the X86S ISA is enumerated by a single, main CPUID feature LEGACY_REDUCED_ISA in CPUID 7.1.ECX[2] which implies all the ISA removals described in this document. A new, 64-bit “start-up” interprocessor interrupt (SIPI) has a separate CPUID feature flag.
      [0] https://cdrdv2.intel.com/v1/dl/getContent/776648 [PDF सावधान]

    • Intel ने 80376 के साथ यह कोशिश की थी, लेकिन बात बनी नहीं: https://en.wikipedia.org/wiki/Intel_80376
      Itanium, यानी Itanic के साथ भी यही हुआ
      backward compatibility ही ARM, MIPS, RISC-V वगैरह के बजाय x86 चुनने की मुख्य वजह है। अफ़सोस कि Intel और AMD के कुछ लोग यह ठीक से नहीं समझते लगते
    • UEFI पहले से मौजूद है। partition के किसी folder में Windows-जैसी binary रख दें, तो वह 64-bit mode के hosting environment में चलती है। बेशक, ऐसे काम को संभालने वाले bootloader भी अनगिनत हैं
    • arm64 में क्या समस्या है, समझ नहीं आ रहा
  • शानदार project है। अगर यहाँ UEFI समर्थक यह सोच रहे हैं कि नया bootloader तरीका बनाने की ज़रूरत ही क्यों पड़ी, तो लगता है वे यह वजह मिस कर रहे हैं कि लोग ऐसा काम क्यों करते हैं
    लेखक ने अंत में जो लिखा, “अगर आपने यहाँ तक follow किया है तो कमाल है”, वह सचमुच सही है—वाकई कमाल

  • सोचता हूँ UEFI आए कितना समय हो गया। काश long mode के साथ BIOS को भी retire कर दिया गया होता

    • BIOS पहले से ही deprecation की राह पर है। नए motherboard में यह capability मूलतः UEFI के ज़रिए emulate की जा रही है, और इसे expand भी नहीं किया जा रहा
      deprecation का मतलब यह नहीं कि वह delete हो गया है, बल्कि यह कि हटाने के लक्ष्य से उसे अब update या develop नहीं किया जा रहा
  • सोचता हूँ कि यह boot procedure EFI/UEFI पर भी काम करता है या नहीं। अगर करता है, तो यह भी जानना चाहूँगा कि UEFI supervisor real mode, protected mode और long mode transitions को emulate करता है, या उन्हें असली hardware पर execute करता है

    • नहीं। UEFI firmware, UEFI bootloader को legacy BIOS environment यानी real-address mode से पूरी तरह अलग environment देता है। आधुनिक systems का UEFI firmware सीधे 64-bit long mode में चला जाता है, और flat memory model GDT तथा identity-mapped paging भी set कर देता है
      hobby OS के लिए UEFI bootloader बनाने की प्रक्रिया यहाँ लिखी है: https://0xc0ffee.netlify.app/osdev/05-bootloader-p1.html
  • सोचता हूँ कि क्या ARM पर यह ज़्यादा सरल है

    • इस मायने में सरल है कि हर board manufacturer अपनी मर्ज़ी से कुछ भी कर सकता है। board manufacturer के लिए सरल, लेकिन बाकी सभी के लिए भयावह रूप से जटिल
    • सही। bootloader अब भी जटिल है, लेकिन ज़रूरी legacy setup कम है। हालांकि BIOS के बजाय UEFI को target करें तो x86 पर भी यह काफ़ी सरल हो जाता है
    • पक्का नहीं कह सकता और उम्मीद भी नहीं करूँगा। अभी RISC-V में गहराई से काम कर रहा हूँ, और वहाँ उम्मीद दिखती है