أوّلُ نواةٍ تُقلِعُ في QEMU
اقرأْ هذا أوّلًا — صدقُ هذه الصفحةِ يعتمدُ عليه
هذا الدرسُ يقودُك إلى كائنِ ELF٣٢ مُنتَجٍ فعلًا — وقد أُنتِجَ وفُحِصَت ترويستُه أثناءَ كتابةِ الصفحة. أمّا الربطُ والإقلاعُ فلم يُنفَّذا هنا: لا ld.lld ولا nasm ولا qemu-system-i386 في بيئةِ الكتابة —
$ qemu-system-x86_64 --version
qemu-system-x86_64: command not foundفالخطوتانِ ٤ و٥ غيرُ مُتحقَّقٍ منهما ومُعلَّمتانِ كذلك في موضعِهما. والمستودعُ لا يحوي نواةً ولا مُحمِّلَ إقلاع، فالنواةُ ههنا تُشحَنُ في الصفحةِ نفسِها.
ما ستملكُه في نهايةِ الخطوةِ الثالثة: ملفُّ نواة.o — كائنُ ELF٣٢ لـi686 يُصدِّرُ الرمزَ kmain، يكتبُ على شاشةِ VGA النصّيّةِ ثمّ يُسبِتُ المعالج. ما عليكَ أن تُحضِرَه أنت: كعبَ multiboot وسكربتَ الرابط.
الخطوةُ ١ · اكتُبِ النواة
ملفٌّ واحد، نواة.ص:
# نواةٌ صغيرةٌ تكتبُ على شاشةِ VGA النصّيّةِ ثمّ تُسبِتُ المعالج.
@رمز("kernel_ticks") متغير متطاير نبضات = 0
دالة اكتب_خانة(رقم فهرس، رقم حرف، رقم لون)
متغير خليّة = حرف + (لون * 256)
اكتب_ذاكرة16(0xB8000 + (فهرس * 2)، خليّة)
نهاية
@رمز("kmain") دالة لا_ترجع نواة()
اكتب_خانة(0، 83، 0x0A)
اكتب_خانة(1، 65، 0x0A)
اكتب_خانة(2، 68، 0x0A)
نبضات = نبضات + 1
بينما (صحيح)
أسبت_المعالج()
نهاية
نهايةأربعُ لبناتٍ تستحقُّ الوقوفَ عندها:
@رمز("kmain")يمنحُ الدالّةَ رمزَ رابطٍ ASCII مُصدَّرًا بدلَ الاسمِ الداخليِّ المُشوَّه. بدونَه لا يجدُ كعبُ الإقلاعِ ما ينادي.دالة لا_ترجع— المُعدِّلُ يلي «دالة» قبلَ الاسم. ⚠️ والصيغةُ اللاحقةُدالة نواة() لا_ترجعتُترجَمُ بلا شكوى ولا تُنتِجُnoreturn(مقيسٌ بمقارنةِ الـIR) — فالتزِمِ الترتيبَ المُعلَن.متطايرلصيقةٌ لاحقةٌ تجعلُ كلَّ قراءةٍ وكتابةٍ للمخزنِvolatile.أسبت_المعالج()تُخفَضُ إلىhlt— لا نداءَ مكتبةٍ ولا حلقةَ انتظار.
الخطوةُ ٢ · ترجِمْ وتحقّقْ من الـIR
$ sad-build نواة.ص --حرّ --بلا-رئيسية \
--هدف=i686-unknown-elf --أظهر-llvm -o نواة.llلا مخرَجَ — والرمزُ صفر. وفي الـIR:
@kernel_ticks = global i64 0
; Function Attrs: noreturn
define void @kmain() #0 {
entry:
...
store volatile i64 %addtmp, ptr @kernel_ticks, align 4
br label %while_body_1
while_body_1:
call void asm sideeffect "hlt", ""()
br label %while_body_1
}kmain بالاسمِ المطلوب · noreturn حاضرة · store volatile · وhlt تجميعًا مضمَّنًا. وكتابةُ الشاشةِ تظهرُ في الدالّةِ المساعِدة:
%addtmp5 = add i64 753664, %shltmp4 ; 753664 = 0xB8000
%mem.ptr = inttoptr i64 %addtmp5 to ptr
store volatile i16 %0, ptr %mem.ptr, align 2--بلا-رئيسية قِيسَ بلا أثرٍ مرصود
العَلَمُ مُعلَنٌ في language-truth/cli_flags.yaml (flag.no_main) ويقبلُه المترجّم (العَلَمُ المجهولُ يُرفَض). لكنّ ترجمةَ هذا البرنامجِ بالعلَمِ وبدونِه أعطت ملفَّي IR متطابقَينِ بصمةً (sha256 نفسُه، ١٤٨٢ سطرًا لكلٍّ)، و@main و@__sad_main باقيانِ في الحالتَين. فلا تعتمدْ عليه لإسقاطِ نقطةِ الدخولِ الافتراضيّة؛ اعتمِدْ على سكربتِ الرابطِ في تعيينِ ENTRY. وهذا مقيسٌ على هذا البرنامجِ بعينِه، لا دعوى عامّةً عن العَلَم.
الخطوةُ ٣ · أنتِجْ كائنَ ELF٣٢
$ sad-build نواة.ص --حرّ --بلا-رئيسية --هدف=i686-unknown-elf -c -o نواة.oوالتحقّقُ من الترويسةِ مباشرةً (بلا objdump، فقد لا يكونُ عندك):
$ python -c "d=open('نواة.o','rb').read(20); print(d[:4], d[4], d[16], d[18])"
b'\x7fELF' 1 1 3الحقولُ بالترتيب: التوقيعُ · class=1 ⇒ ELF٣٢ · type=1 ⇒ كائنٌ قابلٌ للنقل · machine=3 ⇒ EM_386. هذا هو الناتجُ الذي يُبرهِنُ عليه هذا الدرس.
وللمعدنِ ٦٤-بت
--هدف=x86_64-unknown-none-elf يُنتِجُ ELF٦٤/EM_X86_64 (class 2، machine 62) — وقد قِيسَ كذلك. وهو الهدفُ الذي يقبلُ معالجاتِ المقاطعات، بخلافِ i686.
الخطوةُ ٤ · اربِطْ — ⚠️ غيرُ مُتحقَّقٍ منها هنا
نواةُ multiboot تلزمُها ترويسةٌ سحريّةٌ ومكدَّسٌ ابتدائيّ، وكلاهما خارجَ ما تُعبِّرُ عنه ص اليومَ (لا وسمَ مقطعٍ ولا بيانٌ ثنائيٌّ خام). فأحضِرْ كعبًا:
; إقلاع.s — كعبُ multiboot1، يُجمَّعُ بـ`nasm -f elf32`
section .multiboot
dd 0x1BADB002
dd 0x00000000
dd -(0x1BADB002 + 0x00000000)
section .bss
مكدّس_قاع: resb 16384
مكدّس_رأس:
section .text
global _start
extern kmain
_start:
mov esp, مكدّس_رأس
call kmain
.توقّف:
hlt
jmp .توقّف/* رابط.ld */
ENTRY(_start)
SECTIONS {
. = 1M;
.text : { *(.multiboot) *(.text) }
.rodata : { *(.rodata) }
.data : { *(.data) }
.bss : { *(.bss) *(COMMON) }
_kernel_end = .;
}nasm -f elf32 إقلاع.s -o إقلاع.o
ld.lld -m elf_i386 -T رابط.ld إقلاع.o نواة.o -o نواة.elf⚠️ لم أُنفِّذْ هذا الأمر. لا مُجمِّعَ ولا رابطَ في بيئةِ الكتابة. الكعبُ والسكربتُ نمطانِ قياسيّانِ لـmultiboot1، والذي قِيسَ أنّ
نواة.oكائنُEM_386يُصدِّرُkmain— أي أنّه يوافقُextern kmainأعلاه. ويقبلُ المترجّمُ--سكربت-الرابط=رابط.ldعلَمًا صالحًا إن آثرتَ أن يتولّى هو الربط.
الخطوةُ ٥ · أقلِعْ — ⚠️ غيرُ مُتحقَّقٍ منها هنا
qemu-system-i386 -kernel نواة.elfالمتوقَّعُ: SAD بلونٍ أخضرَ في أعلى يسارِ الشاشةِ ثمّ إسباتٌ دائم.
⚠️ لم أُشغِّلْ QEMU — غيرُ مثبَّتٍ في هذه البيئة، والأمرُ في أعلى الصفحة يُثبِتُ ذلك. فإن لم تظهرِ الحروفُ عندَك فابدأْ بكعبِ الإقلاعِ لا بالنواة:
نواة.oنفسُه مقيسٌ سليمَ الترويسةِ والرمز.
إذا تعثَّرتَ
| العَرَض | السبب المرجَّح |
|---|---|
[SEM019] … غير متاحة في الوضع الحرّ | مدمَجةٌ مستضافة — اقرأْ لماذا |
warning: الوضع الحرّ بلا «--هدف» | نسيتَ --هدف فوقعتَ على ثالوثِ المضيف |
[SEM038] … تخصّ عائلة x86 | اكتب_منفذ أو نظيرتُها على هدفٍ غيرِ x86 |
| رابطٌ يشكو من رمزٍ عربيّ | نسيتَ @رمز("...") على الدالّةِ المُصدَّرة |
| النواةُ صامتةٌ ولا خطأ | راجِعْ ملاحظةَ BSP: جسرا الإخراجِ والوقتِ كعبانِ خارجَ x86 |
ثمّ ماذا
اقرأْ ما الذي يمنعُه الوضعُ الحرُّ ولماذا لتفهمَ ما جرى، ثمّ MMIO والمنافذ لتُخرِجَ على المنفذِ التسلسليّ، ثمّ معالجاتِ المقاطعات لتُدخِلَ المؤقّتَ في الحلقة.