64-बिट मोड के लिए BIOS बूटलोडर लिखना
(thasso.xyz)- 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
0xaa55magic number पर खत्म होता हो - उस sector को memory address
0x7c00पर load करता है
- BIOS ऐसे disk को ढूंढता है जिसका पहला sector
- 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का इस्तेमाल होता है Makefilenasm से object बनाता है, linker script से link करता है, और फिरobjcopy -O binaryसे raw boot image generate करता हैmake bootQEMU से image run करता हैqemu-system-x86_64 -no-reboot -drive file=$<,format=raw,index=0,media=disk
- linker script यह set करती है कि बूट सेक्टर
0x7c00के आधार पर रखा जाएMEMORYमेंboot_sectorका origin0x7c00है, length512है.bootsignsection0x7c00 + 510position पर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 = 0x42BIOS extended read feature हैdl = 0x80drive 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_stringcopy करके"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_string320xb8000address पर character और color byte लिखता है- color value
0xfहै - हर character cell 2 bytes इस्तेमाल करता है
- color value
- यह 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 की जाती हैं
- page size
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 में आगे बढ़ता है
0x1000address पर 4-level page table बनाई जाती हैcr3में PML4 table address डाला जाता हैcr4का bit 5 set करके PAE enabled किया जाता है0xc0000080MSR पढ़करEFER.LME, यानी bit 8 set किया जाता हैcr0के bit 31 यानीPGflag को 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.cVGA buffer0xb8000साफ करता है और"Hello from C"output करता है- assembly side का
start_long_mode64-बिट के लिए string output के बादextern _start_kerneldeclare करता है और_start_kernelcall करता है - linker script memory area को तीन parts में divide करती है
boot_sector:0x7c00, length512stage2:0x7e00, length512kernel:0x8000, length0x10000
.text,.data,.rodata,.bsssections kernel area में रखे जाते हैंMakefileassembly और C दोनों build करने के लिए बदला गया है- C compiler
gccहै - मुख्य CFLAGS हैं
-std=c99 -ffreestanding -m64 -mno-red-zone -fno-builtin -nostdinc -Wall -Wextra
- C compiler
- पूरा example code download link पर उपलब्ध है
1 टिप्पणियां
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 की भी ज़रूरत नहीं थी
असल 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 करें और ज़रूरी चीज़ें पढ़ लें
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
CR1 और CR5~CR7 अब भी reserved हैं, फिर भी CR8 आया—इसकी वजह भी कुछ ऐसी ही लगती है
इस लेख में सबसे बेवजह जटिल दिखने वाली चीज़ Makefile और linker script है। NASM flat binary output support करता है, पर शायद उन्हें वह बहुत “hacky” लगा
पहले मेरे 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 कुछ ऐसा ही था क्या
Itanium, यानी Itanic के साथ भी यही हुआ
backward compatibility ही ARM, MIPS, RISC-V वगैरह के बजाय x86 चुनने की मुख्य वजह है। अफ़सोस कि Intel और AMD के कुछ लोग यह ठीक से नहीं समझते लगते
शानदार project है। अगर यहाँ UEFI समर्थक यह सोच रहे हैं कि नया bootloader तरीका बनाने की ज़रूरत ही क्यों पड़ी, तो लगता है वे यह वजह मिस कर रहे हैं कि लोग ऐसा काम क्यों करते हैं
लेखक ने अंत में जो लिखा, “अगर आपने यहाँ तक follow किया है तो कमाल है”, वह सचमुच सही है—वाकई कमाल
सोचता हूँ UEFI आए कितना समय हो गया। काश long mode के साथ BIOS को भी retire कर दिया गया होता
deprecation का मतलब यह नहीं कि वह delete हो गया है, बल्कि यह कि हटाने के लक्ष्य से उसे अब update या develop नहीं किया जा रहा
सोचता हूँ कि यह boot procedure EFI/UEFI पर भी काम करता है या नहीं। अगर करता है, तो यह भी जानना चाहूँगा कि UEFI supervisor real mode, protected mode और long mode transitions को emulate करता है, या उन्हें असली hardware पर execute करता है
hobby OS के लिए UEFI bootloader बनाने की प्रक्रिया यहाँ लिखी है: https://0xc0ffee.netlify.app/osdev/05-bootloader-p1.html
सोचता हूँ कि क्या ARM पर यह ज़्यादा सरल है