من محمل الإقلاع إلى النواةإذا قرأت
المقالات السابقة ، فأنت تعرف هوايتي الجديدة للبرمجة منخفضة المستوى. لقد كتبت العديد من المقالات حول برمجة التجميع لـ
x86_64
Linux وفي نفس الوقت بدأت الغوص في شفرة المصدر لـ Linux kernel.
أنا مهتم جدًا بفهم كيفية عمل الأشياء منخفضة المستوى: كيف تعمل البرامج على جهاز الكمبيوتر الخاص بي ، وكيف توجد في الذاكرة ، وكيف تدير النواة العمليات والذاكرة ، وكيف تعمل حزمة الشبكة على مستوى منخفض ، وأكثر من ذلك بكثير. لذا ، قررت كتابة سلسلة أخرى من المقالات حول نواة لينكس
لبنية x86_64 .
يرجى ملاحظة أنني لست مطورًا محترفًا لـ kernel ولا أكتب رمز kernel في العمل. هذه مجرد هواية أنا فقط أحب الأشياء ذات المستوى المنخفض ومن المثير للاهتمام الخوض فيها. لذلك ، إذا لاحظت أي ارتباك أو أسئلة / تعليقات تظهر ، اتصل بي
على تويتر ، عن طريق
البريد أو مجرد إنشاء
تذكرة . سأكون ممتنا.
يتم نشر جميع المقالات في
مستودع GitHub ، وإذا كان هناك خطأ ما في لغتي الإنجليزية أو محتوى المقالة ، فلا تتردد في إرسال طلب سحب.
يرجى ملاحظة أن هذه ليست وثائق رسمية ، ولكن ببساطة التدريب وتبادل المعرفة.المعرفة المطلوبة- فهم كود C
- فهم كود المجمع (بناء جملة AT&T)
على أي حال ، إذا كنت قد بدأت للتو في تعلم مثل هذه الأدوات ، فسأحاول شرح شيء ما في هذه المقالات والمقالات اللاحقة. حسنًا ، مع انتهاء المقدمة ، حان الوقت للتعمق في نواة لينكس والأشياء ذات المستوى المنخفض.
لقد بدأت في كتابة هذا الكتاب في أيام Linux 3.18 kernel ، وقد تغير الكثير منذ ذلك الحين. إذا كانت هناك تغييرات ، فسأقوم بتحديث المقالات وفقًا لذلك.
زر الطاقة السحري ، ما هي الخطوة التالية؟
على الرغم من أن هذه مقالات حول نواة لينكس ، إلا أننا لم نصل إليها بعد - على الأقل في هذا القسم. بمجرد الضغط على زر الطاقة السحري على الكمبيوتر المحمول أو الكمبيوتر المكتبي ، فإنه يبدأ في العمل. ترسل اللوحة الأم إشارة إلى
مزود الطاقة . بعد تلقي الإشارة ، تزود الكمبيوتر بالكمية اللازمة من الكهرباء. بمجرد أن تتلقى اللوحة الأم إشارة
"Power OK" ، فإنها تحاول بدء تشغيل وحدة المعالجة المركزية. يفرغ كل البيانات المتبقية في سجلاته ويحدد القيم المحددة مسبقًا لكل منها.
يجب أن تحتوي المعالجات
80386 والإصدارات الأحدث على القيم التالية في تسجيلات CPU بعد إعادة التشغيل:
IP 0xfff0
محدد CS 0xf000
قاعدة CS 0xffff0000
يبدأ المعالج في العمل في
الوضع الحقيقي . دعنا نعود قليلاً ونحاول فهم
تقسيم الذاكرة في هذا الوضع. الوضع الحقيقي مدعوم على جميع المعالجات المتوافقة مع x86: من
8086 إلى معالجات Intel 64 بت الحديثة. يستخدم المعالج 8086 ناقل عنوان 20 بت ، أي أنه يمكن أن يعمل مع مساحة عنوان
0-0xFFFFF
أو
1
. ولكن لديها سجلات 16 بت فقط مع عنوان أقصى
2^16-1
أو
0xffff
(64 كيلوبايت).
يلزم
تقسيم الذاكرة لاستخدام مساحة العنوان المتاحة بالكامل. تنقسم الذاكرة بالكامل إلى أجزاء صغيرة بحجم ثابت يبلغ
65536
بايت (64 كيلوبايت). نظرًا لأنه باستخدام سجلات 16 بت ، لا يمكننا الوصول إلى الذاكرة التي تزيد عن 64 كيلوبايت ، فقد تم تطوير طريقة بديلة.
يتكون العنوان من جزأين: 1) محدد قطعة بعنوان أساسي. 2) الإزاحة من العنوان الأساسي. في الوضع الحقيقي ، يكون العنوان الأساسي
* 16
المقطع
* 16
. وبالتالي ، للحصول على العنوان الفعلي في الذاكرة ، تحتاج إلى ضرب جزء من محدد المقطع في 16 وإضافة الإزاحة إليه:
= * 16 +
على سبيل المثال ، إذا كان سجل
CS:IP
يحتوي على القيمة
0x2000:0x0010
، فسيكون العنوان الفعلي المقابل على النحو التالي:
>>> hex((0x2000 << 4) + 0x0010) '0x20010'
ولكن إذا أخذت محدد الجزء الأكبر والإزاحة
0xffff:0xffff
، فستحصل على العنوان:
>>> hex((0xffff << 4) + 0xffff) '0x10ffef'
أي
65520
بايت بعد أول ميغا بايت. نظرًا لأن ميغابايت واحد فقط متاح في الوضع الحقيقي ، يصبح
0x00ffef
مع تعطيل
خط A20 .
حسنًا ، نحن الآن نعرف القليل عن الوضع الحقيقي والذاكرة التي تعالج في هذا الوضع. دعنا نعود إلى مناقشة قيم التسجيل بعد إعادة التعيين.
يتكون سجل
CS
من جزأين: محدد مقطع مرئي وعنوان أساسي مخفي. على الرغم من أن العنوان الأساسي يتكون عادة بضرب قيمة محدد المقطع في 16 ، أثناء إعادة تعيين الجهاز ، فإن محدد المقطع في سجل CS هو
0xf000
، والعنوان الأساسي هو
0xffff0000
. يستخدم المعالج هذا العنوان الأساسي الخاص حتى تتغير CS.
يتكون عنوان البداية بإضافة العنوان الأساسي إلى القيمة في سجل EIP:
>>> 0xffff0000 + 0xfff0 '0xfffffff0'
نحصل على
0xfffffff0
، وهو 16 بايت أقل من 4 غيغابايت. تسمى هذه النقطة
متجه إعادة التعيين . هذا هو الموقع في الذاكرة حيث تنتظر وحدة المعالجة المركزية تنفيذ التعليمات الأولى بعد إعادة التعيين: عملية
انتقال (
jmp ) ، والتي تشير عادةً إلى نقطة إدخال BIOS. على سبيل المثال ، إذا
src/cpu/x86/16bit/reset16.inc
نظرة على شفرة المصدر لـ
coreboot (
src/cpu/x86/16bit/reset16.inc
) ، فسوف نرى:
.section ".reset", "ax", %progbits .code16 .globl _start _start: .byte 0xe9 .int _start16bit - ( . + 2 ) ...
هنا نرى كود
jmp
(العملية
opcode ) ، أي
0xe9
، وعنوان الوجهة
_start16bit - ( . + 2)
.
نرى أيضًا أن قسم
reset
هو 16 بايت ، ويتم
0xfffff0
للتشغيل من العنوان
0xfffff0
(
src/cpu/x86/16bit/reset16.ld
):
SECTIONS { _bogus = ASSERT(_start16bit >= 0xffff0000, "_start16bit too low. Please report."); _ROMTOP = 0xfffffff0; . = _ROMTOP; .reset . : { *(.reset); . = 15; BYTE(0x00); } }
يبدأ BIOS الآن ؛ بعد تهيئة جهاز BIOS والتحقق منه ، تحتاج إلى العثور على جهاز التمهيد. يتم حفظ ترتيب التمهيد في تكوين BIOS. عند محاولة التمهيد من القرص الصلب ، يحاول BIOS العثور على قطاع التمهيد. على الأقراص
المقسمة MBR ، يتم تخزين قطاع التمهيد في أول 446 بايت من القطاع الأول ، حيث يكون كل قطاع 512 بايت.
0x55
من القطاع الأول هما
0x55
و
0xaa
. يظهرون BIOS أنه جهاز تمهيد.
على سبيل المثال:
; ; : Intel x86 ; [BITS 16] boot: mov al, '!' mov ah, 0x0e mov bh, 0x00 mov bl, 0x07 int 0x10 jmp $ times 510-($-$$) db 0 db 0x55 db 0xaa
نجمع وندير:
nasm -f bin boot.nasm && qemu-system-x86_64 boot
يتلقى
QEMU أمرًا لاستخدام ثنائي
boot
الذي أنشأناه للتو كصورة قرص. نظرًا لأن الملف الثنائي الذي تم إنشاؤه أعلاه يلبي متطلبات قطاع التمهيد (بدءًا من
0x7c00
وينتهي بالتسلسل السحري) ، سيأخذ QEMU في الاعتبار الثنائي باعتباره سجل التمهيد الرئيسي (MBR) لصورة القرص.
سترى:

في هذا المثال ، نرى أن الكود يعمل في الوضع الحقيقي 16 بت ويبدأ من العنوان
0x7c00
في الذاكرة. بعد البدء ، يتسبب في مقاطعة
0x10 ، والتي ببساطة طباعة حرف
!
؛ يملأ 510 بايت المتبقية
0xaa
و
0x55
.
يمكنك مشاهدة ملف التفريغ الثنائي باستخدام الأداة
objdump
:
nasm -f bin boot.nasm
objdump -D -b binary -mi386 -Maddr16,data16,intel boot
بالطبع ، في قطاع التمهيد الحقيقي ، هناك رمز لمواصلة عملية التمهيد وجدول التقسيم بدلاً من مجموعة من الأصفار وعلامة تعجب :). من هذه اللحظة ، ينقل BIOS التحكم إلى أداة تحميل التشغيل.
ملاحظة : كما هو موضح أعلاه ، فإن وحدة المعالجة المركزية في الوضع الحقيقي ؛ حيث يكون حساب العنوان الفعلي في الذاكرة كما يلي:
= * 16 +
لدينا فقط تسجيلات للأغراض العامة ذات 16 بت ، والقيمة القصوى لسجل 16 بت هي
0xffff
، لذلك عند القيم الأكبر ستكون النتيجة:
>>> hex((0xffff * 16) + 0xffff) '0x10ffef'
حيث
0x10ffef
هو
1 + 64 - 16
. يحتوي
المعالج 8086 (
المعالج الأول مع الوضع الحقيقي) على سطر عنوان 20 بت. نظرًا لأن
2^20 = 1048576
، فإن الذاكرة الفعلية المتوفرة هي 1 ميجابايت.
بشكل عام ، عنونة الذاكرة في الوضع الحقيقي هي كما يلي:
0x00000000 - 0x000003FF - جدول ناقلات المقاطعة في الوضع الحقيقي
0x00000400 - 0x000004FF - منطقة بيانات BIOS
0x00000500 - 0x00007BFF - غير مستخدم
0x00007C00 - 0x00007DFF - أداة تحميل التشغيل الخاصة بنا
0x00007E00 - 0x0009FFFF - غير مستخدم
0x000A0000 - 0x000BFFFF - ذاكرة الوصول العشوائي للفيديو (VRAM)
0x000B0000 - 0x000B7777 - ذاكرة فيديو أحادية اللون
0x000B8000 - 0x000BFFFF - ذاكرة الفيديو في وضع الألوان
0x000C0000 - 0x000C7FFF - BIOS ROM للفيديو
0x000C8000 - 0x000EFFFF - منطقة الظل (ظل BIOS)
0x000F0000 - 0x000FFFFF - BIOS النظام
في بداية المقال ، كتب أن التعليمات الأولى للمعالج تقع في
0xFFFFFFF0
، وهو أكثر بكثير من
0xFFFFF
(1 ميجابايت). كيف يمكن لوحدة المعالجة المركزية الوصول إلى هذا العنوان في الوضع الحقيقي؟ الإجابة في وثائق
لب القالب :
0xFFFE_0000 - 0xFFFF_FFFF: 128 ROM
في بداية التنفيذ ، BIOS ليس في ذاكرة الوصول العشوائي ، ولكن في ROM.
محمل الإقلاع
يمكن تحميل نواة لينكس مع محمل إقلاع مختلف ، مثل
GRUB 2 و
syslinux . يحتوي kernel على بروتوكول تمهيد يحدد متطلبات أداة تحميل التشغيل لتنفيذ دعم Linux. في هذا المثال ، نعمل مع GRUB 2.
استمرارًا لعملية التمهيد ، حدد BIOS جهاز التمهيد ونقل التحكم إلى قطاع التمهيد ، ويبدأ التنفيذ بـ
boot.img . نظرًا لصغر حجمها ، يعد هذا رمزًا بسيطًا للغاية. يحتوي على مؤشر للانتقال إلى الصورة الرئيسية لـ GRUB 2. يبدأ بـ
diskboot.img وعادة ما يتم تخزينه مباشرة بعد القطاع الأول في مساحة غير مستخدمة قبل القسم الأول. يحمّل الرمز أعلاه في الذاكرة بقية الصورة التي تحتوي على نواة GRUB 2 وبرامج تشغيل معالجة أنظمة الملفات. بعد ذلك ، يتم
تنفيذ وظيفة
grub_main .
تقوم وظيفة
grub_main
بتهيئة وحدة التحكم ، وإرجاع العنوان الأساسي للوحدات النمطية ،
grub_main
الجهاز الجذر ، وتحميل / توزيع ملف تكوين grub ، وتحميل الوحدات النمطية ، إلخ. في نهاية التنفيذ ، يضع اليرقة في الوضع العادي. وظيفة
grub_normal_execute
(من ملف المصدر
grub-core/normal/main.c
) تكمل آخر الاستعدادات وتعرض قائمة لاختيار نظام التشغيل. عند تحديد أحد عناصر قائمة grub ، يتم
grub_menu_execute_entry
وظيفة
grub_menu_execute_entry
، والتي تنفذ أمر
boot
grub وتحميل نظام التشغيل المحدد.
كما هو موضح في بروتوكول تمهيد kernel ، يجب أن يقوم برنامج تحميل التمهيد بقراءة وملء بعض حقول رأس تثبيت kernel ، والتي تبدأ عند الإزاحة
0x01f1
من رمز تثبيت kernel. يشار إلى هذا الإزاحة في
البرنامج النصي linker .
قوس رأس kernel
/ x86 / boot / header.S يبدأ بـ:
.globl hdr hdr: setup_sects: .byte 0 root_flags: .word ROOT_RDONLY syssize: .long 0 ram_size: .word 0 vid_mode: .word SVGA_MODE root_dev: .word 0 boot_flag: .word 0xAA55
يجب أن يملأ محمل الإقلاع هذا والعناوين الأخرى (التي يتم وضع علامة عليها فقط كنوع
write
في بروتوكول تمهيد Linux ، كما في هذا المثال) بقيم يتم تلقيها من سطر الأوامر أو محسوبة في وقت التمهيد. لن نتطرق الآن إلى الأوصاف والتفسيرات لجميع حقول العناوين. سنناقش لاحقًا كيفية استخدام النواة لها. للحصول على وصف لجميع الحقول ، راجع
بروتوكول التنزيل .
كما ترى في بروتوكول تمهيد kernel ، سيتم عرض الذاكرة كما يلي:
| وضع النواة المحمي |
100000 + ------------------------ +
| تعيين الإدخال / الإخراج |
0A0000 + ------------------------ +
| الاحتياطي لـ BIOS | اترك أكبر قدر ممكن من الحرية
~ ~
| سطر الأوامر | (قد يكون أيضًا أقل من X + 10000)
X + 10000 + ------------------------ +
| تكديس / كومة | لاستخدام كود وضع kernel الحقيقي
X + 08000 + ------------------------ +
| تركيب نواة | رمز الوضع الحقيقي لـ Kernel
| قطاع تمهيد النواة | قطاع التمهيد kernel القديم
X + ------------------------ +
| محمل | <- نقطة إدخال 0x7C00 قطاع التمهيد
001000 + ------------------------ +
| الاحتياطي لـ MBR / BIOS |
000800 + ------------------------ +
| تستخدم عادة MBR |
000600 + ------------------------ +
| تستخدم BIOS فقط |
000000 + ------------------------ +
لذلك ، عندما ينقل اللودر التحكم إلى النواة ، يبدأ بالعنوان:
X + sizeof (KernelBootSector) + 1
حيث
X
هو عنوان قطاع تمهيد kernel. في حالتنا ،
X
هو
0x10000
، كما هو موضح في تفريغ الذاكرة:

قام برنامج bootloader بنقل نواة Linux إلى الذاكرة ، وملء حقول الرأس ، ثم نقلها إلى عنوان الذاكرة المطابق. يمكننا الآن الانتقال مباشرة إلى رمز تثبيت kernel.
بداية مرحلة تركيب النواة
أخيرا نحن في صميم! على الرغم من أنها ليست قيد التشغيل من الناحية الفنية. أولاً ، يحتاج جزء تثبيت kernel إلى تكوين شيء ما ، بما في ذلك برنامج فك الضغط وبعض الأشياء في إدارة الذاكرة. بعد كل هذا ، ستقوم بتفريغ النواة الحقيقية وتذهب إليها. يبدأ التثبيت في
arch / x86 / boot / header.S بحرف
_start .
للوهلة الأولى ، قد يبدو هذا غريبًا بعض الشيء ، نظرًا لوجود عدة تعليمات أمامه. ولكن منذ فترة طويلة ، كان لدى نواة لينكس محمل إقلاع خاص بها. الآن إذا ركضت ، على سبيل المثال ،
qemu-system-x86_64 vmlinuz-3.18-generic
سترى:

في الواقع ، يبدأ ملف
header.S
السحري
MZ (انظر لقطة شاشة التفريغ أعلاه) ونص رسالة الخطأ ورأس
PE :
#ifdef CONFIG_EFI_STUB # "MZ", MS-DOS header .byte 0x4d .byte 0x5a #endif ... ... ... pe_header: .ascii "PE" .word 0
هناك حاجة لتحميل نظام تشغيل بدعم
UEFI . سننظر في جهازها في الفصول التالية.
نقطة الدخول الفعلية لتثبيت النواة:
يعرف محمل الإقلاع (grub2 وغيره) هذه النقطة (الإزاحة
0x200
من
MZ
) ويذهب إليها مباشرة ، على الرغم من أن
header.S
يبدأ من قسم
.bstext
، حيث يوجد نص رسالة الخطأ:
// // arch/x86/boot/setup.ld // . = 0; // current position .bstext : { *(.bstext) } // put .bstext section to position 0 .bsdata : { *(.bsdata) }
نقطة إدخال تثبيت Kernel:
.globl _start _start: .byte 0xeb .byte start_of_setup-1f 1:
هنا نرى رمز العملية
jmp
(
0xeb
) ، الذي يذهب إلى النقطة
start_of_setup-1f
. في
Nf
، على سبيل المثال ، يشير
2f
إلى التسمية المحلية
2:
في حالتنا ، هذه هي التسمية
1
، والتي تكون موجودة مباشرة بعد النقل ، وتحتوي على ما تبقى من رأس الإعداد. مباشرة بعد رأس التثبيت ، نرى قسم
.entrytext
، الذي يبدأ
start_of_setup
.
هذا هو الرمز الأول الذي تم تنفيذه بالفعل (بخلاف تعليمات القفزة السابقة ، بالطبع). بعد أن يتلقى جزء من تثبيت kernel التحكم من اللودر ، توجد تعليمات
jmp
الأولى عند الإزاحة
0x200
من بداية وضع kernel الحقيقي ، أي بعد 512 بايت الأولى. يمكن ملاحظة ذلك في كل من بروتوكول تمهيد kernel Linux وفي التعليمات البرمجية المصدر لـ grub2:
segment = grub_linux_real_target >> 4; state.gs = state.fs = state.es = state.ds = state.ss = segment; state.cs = segment + 0x20;
في حالتنا ، يتم تشغيل النواة على العنوان
0x10000
. هذا يعني أنه بعد بدء تثبيت kernel ، سيكون للسجلات المقطع القيم التالية:
gs = fs = es = ds = ss = 0x10000
cs = 0x10200
بعد الذهاب إلى
start_of_setup
يجب أن تقوم النواة بما يلي:
دعونا نرى كيف يتم تنفيذ ذلك.
محاذاة حالة القطعة
بادئ ذي بدء ، تتحقق النواة من أن المقطع
ds
و
es
يشير إلى نفس العنوان. ثم يمسح علم الاتجاه باستخدام
cld
:
movw %ds, %ax movw %ax, %es cld
كما كتبت سابقًا ، يقوم grub2 افتراضيًا بتحميل رمز تثبيت kernel عند
0x10000
، و
cs
عند
0x10200
، لأن التنفيذ لا يبدأ من بداية الملف ، ولكن من الانتقال هنا:
_start: .byte 0xeb .byte start_of_setup-1f
هذا هو إزاحة
512
بايت من
4d 5a . من الضروري أيضًا محاذاة
cs
من
0x10200
إلى
0x10000
، مثل جميع سجلات المقطع الأخرى. بعد ذلك قم بتثبيت المكدس:
pushw %ds pushw $6f lretw
تدفع هذه التعليمات قيمة
ds
إلى المكدس ، متبوعة بعنوان التسمية
6 وتعليمات
lretw
، التي تقوم بتحميل عنوان التسمية
6
في سجل
عداد الأوامر وتحميل
cs
بالقيمة
ds
. بعد ذلك ، سيكون للقيم
ds
و
cs
نفس القيم.
إعداد المكدس
كل هذا الرمز تقريبًا جزء من عملية إعداد بيئة C في الوضع الحقيقي. الخطوة التالية هي التحقق من قيمة تسجيل
ss
وإنشاء المكدس الصحيح إذا كانت قيمة
ss
غير صحيحة:
movw %ss, %dx cmpw %ax, %dx movw %sp, %dx je 2f
يمكن أن يؤدي هذا إلى ثلاثة سيناريوهات مختلفة:
ss
قيمة صالحة 0x1000
(كما هو الحال مع جميع السجلات الأخرى باستثناء cs
)ss
قيمة غير صالحة وتم CAN_USE_HEAP
علامة CAN_USE_HEAP
(انظر أدناه)ss
قيمة غير صالحة ولم CAN_USE_HEAP
تعيين علامة CAN_USE_HEAP
(انظر أدناه)
ضع في اعتبارك جميع السيناريوهات بالترتيب:
ss
قيمة صالحة ( 0x1000
). في هذه الحالة ، ننتقل إلى التسمية 2:
2: andw $~3, %dx jnz 3f movw $0xfffc, %dx 3: movw %ax, %ss movzwl %dx, %esp sti
نقوم هنا بضبط محاذاة تسجيل
dx
(الذي يحتوي على قيمة
sp
المحددة بواسطة المُحمل) إلى
4
بايت والتحقق من الصفر. إذا كان صفرًا ، فإننا نضع القيمة
0xfffc
dx
(عنوان محاذي
4
بايت قبل الحجم الأقصى
0xfffc
64 كيلوبايت). إذا لم يكن يساوي الصفر ، فإننا نواصل استخدام قيمة
sp
المحددة بواسطة أداة
0xf7f4
(
0xf7f4
في حالتنا). ثم نضع قيمة
ax
في
ss
، مما يحفظ عنوان المقطع الصحيح
0x1000
sp
الصحيح. الآن لدينا المكدس الصحيح:

- في السيناريو الثاني ،
ss != ds
. أولاً نضع القيمة _end (عنوان نهاية رمز التثبيت) في dx
loadflags
حقل الرأس ، باستخدام تعليمات testb
للتحقق مما إذا كان يمكن استخدام الكومة. loadflags هو رأس قناع البت الذي يتم تعريفه على النحو التالي:
#define LOADED_HIGH (1<<0) #define QUIET_FLAG (1<<5) #define KEEP_SEGMENTS (1<<6) #define CAN_USE_HEAP (1<<7)
وكما هو موضح في بروتوكول التمهيد:
: loadflags
.
7 (): CAN_USE_HEAP
1, ,
heap_end_ptr . ,
.
إذا تم
CAN_USE_HEAP
بت
CAN_USE_HEAP
، فإننا في
dx
نقوم بتعيين القيمة
heap_end_ptr
(التي تشير إلى
_end
) ونضيف
STACK_SIZE
إليها (الحد الأدنى لحجم المكدس هو
1024
بايت). بعد ذلك ، انتقل إلى التسمية
2
(كما في الحالة السابقة) وقم بعمل المكدس الصحيح.

- إذا لم
CAN_USE_HEAP
تعيين CAN_USE_HEAP
سوى استخدام الحد الأدنى من المكدس من _end
إلى _end + STACK_SIZE
:

إعداد BSS
هناك حاجة إلى خطوتين إضافيتين قبل الانتقال إلى رمز C الرئيسي: هذا هو إعداد
منطقة BSS والتحقق من التوقيع "السحري". التحقق من التوقيع أولا:
cmpl $0x5a5aaa55, setup_sig jne setup_bad
تقارن التعليمات ببساطة
setup_sig بالرقم السحري 0x5a5aaa55. إذا لم تكن متساوية ، يتم الإبلاغ عن خطأ فادح.
إذا كان الرقم السحري مطابقًا ولدينا مجموعة من سجلات المقطع الصحيحة ومكدسًا ، فكل ما تبقى هو تكوين قسم BSS قبل المتابعة إلى رمز C.
يتم استخدام قسم BSS لتخزين البيانات غير المهيأة بشكل ثابت. يتحقق Linux بعناية من إعادة تعيين منطقة الذاكرة هذه:
movw $__bss_start, %di movw $_end+3, %cx xorl %eax, %eax subw %di, %cx shrw $2, %cx rep; stosl
أولاً ،
يتم نقل عنوان بدء
__bss_start إلى
di
. ثم يتم نقل العنوان
_end + 3
(+3 للمحاذاة بـ 4 بايت) إلى
cx
. يتم مسح سجل
eax
(باستخدام تعليمات
xor
) ، ويتم حساب حجم قسم bss (
cx-di
) ، ويتم وضعه في
cx
. ثم يتم تقسيم
cx
إلى أربعة (حجم "الكلمة") ويتم استخدام تعليمات
stosl
، وتخزين القيمة
(صفر) في العنوان مشيرًا إلى
di
، وزيادة
di
تلقائيًا بمقدار أربعة وتكرار ذلك حتى يصل
إلى الصفر). التأثير الصافي لهذا الرمز هو كتابة الأصفار لجميع الكلمات في الذاكرة من
__bss_start
إلى
_end
:

انتقل إلى الصفحة الرئيسية
هذا كل شيء: لدينا مكدس و BSS ، لذلك يمكنك الانتقال إلى الوظيفة
main()
C:
calll main
تقع الوظيفة
main()
في
arch / x86 / boot / main.c. سنتحدث عنها في الجزء التالي.
الخلاصة
هذا هو نهاية الجزء الأول عن جهاز Linux kernel.
إذا كان لديك أسئلة أو اقتراحات، يرجى الاتصال بي على تويتر ، على البريد الإلكتروني أو مجرد إنشاء تذكرة . في الجزء القادم سوف نرى رمز الأول في C، والتي تتم أثناء تثبيت نواة لينكس، وتنفيذ البرامج الفرعية الذاكرة، مثل memset
، memcpy
، earlyprintk
والتنفيذ المبكر وتهيئة وحدة، وأكثر من ذلك.المراجع