مرحبا بالجميع!
بينما يستعد
ليونيد لأول
درس مفتوح له في دورة
مسؤول لينوكس ، فإننا نواصل الحديث عن تحميل نواة لينكس.
دعنا نذهب!
فهم كيفية عمل النظام بدون أعطال - الاستعداد لإصلاح الأعطال الحتمية
أقدم نكتة في مجال المصدر المفتوح هي عبارة أن "الكود يوثق نفسه". أظهرت التجربة أن قراءة شفرة المصدر مثل الاستماع إلى توقعات الطقس: سيظل الأشخاص الأذكياء يخرجون وينظرون إلى السماء. فيما يلي نصائح للتحقق من تمهيد نظام Linux وفحصه باستخدام أدوات التصحيح المألوفة. تحليل عملية التمهيد لنظام يعمل بشكل جيد يعد المستخدمين والمطورين لحل حالات الفشل الحتمية.
من ناحية ، عملية التحميل بسيطة بشكل مدهش. تعمل نواة نظام التشغيل (النواة) بسلاسل واحدة ومتزامنة على نواة واحدة (نواة) ، والتي قد تبدو مفهومة حتى للعقل البشري المثير للشفقة. ولكن كيف تبدأ نواة نظام التشغيل؟ ما هي الوظائف التي يقوم بها initrd (
قرص RAM للتهيئة
الأولية ) ومحمل الإقلاع؟ وانتظر ، لماذا يتم تشغيل LED على منفذ Ethernet دائمًا؟

تابع القراءة للحصول على إجابات لهذه الأسئلة وبعض الأسئلة الأخرى ؛ الكود للعروض التوضيحية والتمارين الموصوفة متاح أيضًا على
GitHub .
بدء التشغيل: حالة إيقاف التشغيلتنبيه على الشبكة المحليةحالة OFF تعني أن النظام ليس لديه طاقة ، أليس كذلك؟ البساطة الظاهرة خادعة. على سبيل المثال ، يتم تشغيل Ethernet LED حتى في هذه الحالة ، لأن wake-on-LAN (WOL ، تنبيه على [إشارة من] الشبكة المحلية) قيد التشغيل في نظامك. تأكد من كتابة:
$# sudo ethtool <interface name>
بدلاً من ذلك ، يمكن أن يكون ، على سبيل المثال ، eth0 (يوجد ethtool في حزم Linux بنفس الاسم). إذا أظهرت "Wake-on" في الإخراج g ، يمكن للمضيفين البعيدين تمهيد النظام عن طريق إرسال
MagicPacket . إذا كنت لا ترغب في تشغيل النظام الخاص بك عن بُعد وإعطاء هذه الفرصة للآخرين ، قم بتعطيل WOL في قائمة BIOS النظام ، أو باستخدام:
$# sudo ethtool -s <interface name> wol d
يمكن أن يكون المعالج الذي يستجيب لـ MagicPacket
وحدة تحكم إدارة اللوحة الأساسية (BMC) أو جزءًا من واجهة الشبكة.
Intel Management Engine و Platform Controller Hub و Minixإن BMC ليس المتحكم الدقيق الوحيد (MCU) الذي يمكنه "الاستماع" إلى نظام مغلق بشكل افتراضي. تحتوي أنظمة X86_64 على حزمة برامج Intel Management Engine (IME) لإدارة الأنظمة عن بُعد. تحتوي مجموعة كبيرة من الأجهزة ، من الخوادم إلى أجهزة الكمبيوتر المحمولة ، على تكنولوجيا
تحتوي على ميزات مثل KVM Remote Control أو Intel Capability Licence Service. وفقًا
لأداة Inte l
الخاصة ، فإن
IME لديه ثغرات أمنية لم يتم إصلاحها. الأخبار السيئة هي أن تعطيل IME أمر صعب. أنشأ Trammell Hudson
مشروع me_cleaner ، الذي يمحو بعض مكونات IME الأكثر فظاعة ، مثل خادم الويب المضمن ، ولكن في نفس الوقت هناك احتمال أن يؤدي استخدام المشروع إلى تحويل النظام الذي يتم تشغيله فيه إلى لبنة.
يعتمد برنامج IME الثابت وبرنامج وضع إدارة النظام (SMM) الذي يتبعه عند التمهيد على
نظام التشغيل Minix ويتم تشغيله على معالج منصة وحدة تحكم النظام الأساسي المنفصل ، وليس وحدة المعالجة المركزية الرئيسية للنظام. ثم تطلق SMM برنامج واجهة البرامج الثابتة العالمية الموسعة (UEFI) على المعالج الرئيسي ، والذي تمت
كتابته أكثر من مرة . أطلقت مجموعة Coreboot مشروعًا طموحًا للغاية
غير قابل للتوسيع للبرامج
الثابتة (NERF) في Google ، والذي يهدف إلى استبدال ليس فقط UEFI ، ولكن أيضًا المكونات المبكرة لمساحة مستخدم Linux ، مثل systemd. في غضون ذلك ، نحن في انتظار النتائج ، يمكن لمستخدمي Linux شراء أجهزة الكمبيوتر المحمولة من Purism أو System76 أو Dell ، والتي
تم تعطيل IME عليها ، بالإضافة إلى ذلك ، يمكننا أن نأمل في أجهزة الكمبيوتر المحمولة التي تحتوي على
معالج ARM 64 بت .
لوادر
ماذا تفعل البرامج الثابتة القابلة للتشغيل إلى جانب تشغيل برامج التجسس المشتبه فيها؟ تتمثل مهمة برنامج bootloader في تزويد المعالج الذي تم تشغيله للتو بالموارد اللازمة لتشغيل نظام تشغيل للأغراض العامة مثل Linux. أثناء التشغيل ، لا توجد ذاكرة افتراضية فقط ، ولكن أيضًا DRAM حتى لحظة رفع وحدة التحكم الخاصة بها. ثم يقوم محمل الإقلاع بتشغيل مصادر الطاقة ومسح الحافلات والواجهات للعثور على صورة النواة ونظام ملفات الجذر. تدعم برامج تحميل التشغيل الشائعة ، مثل U-Boot و GRUB ، كلاً من الواجهات الشائعة مثل USB و PCI و NFS ، بالإضافة إلى الأجهزة المضمنة الأخرى المتخصصة ، مثل NOR- و NAND-flash. تتفاعل أجهزة التحميل أيضًا مع أجهزة الأمان ، مثل
الوحدة النمطية للنظام الأساسي الموثوق به (TPM) ، لإنشاء سلسلة ثقة من بداية التنزيل.
تشغيل مُحمِّل U-boot في وضع الحماية على خادم الإنشاء.يتم دعم أداة تحميل
التمهيد U-Boot الشائعة
من قبل أنظمة من Raspberry Pi إلى أجهزة Nintendo ولوحات السيارات وأجهزة Chromebook. لا يوجد سجل نظام ، وإذا حدث خطأ ما ، فقد لا يكون هناك إخراج وحدة التحكم. لتسهيل التصحيح ، يوفر فريق U-Boot وضع حماية لاختبار التصحيحات على مضيف البناء أو حتى في نظام التكامل المستمر. على نظام يحتوي على أدوات تطوير مشتركة مثل Git و GNU Compiler Collection (GCC) مثبتة ، من السهل فهم وضع الحماية U-Boot.
$# git clone git://git.denx.de/u-boot; cd u-boot $# make ARCH=sandbox defconfig $# make; ./u-boot => printenv => help
هذا كل شيء: لقد أطلقت U-Boot على x86_64 ويمكنك اختبار الميزات الصعبة ، على سبيل المثال ، إعادة تقسيم
أجهزة التخزين الوهمية ، ومعالجة المفاتيح الخاصة المستندة إلى TPM ، وأجهزة توصيل USB. يمكن أن يكون وضع الحماية U-Boot في مرحلة واحدة داخل مصحح أخطاء GDB. التطوير باستخدام وضع الحماية أسرع بعشر مرات من الاختبار عن طريق استبدال محمل الإقلاع على اللوحة ، بالإضافة إلى أنه يمكن استعادة وضع الحماية "الطوب" بالضغط على Ctrl + C.
إطلاق نواةتمهيد النواةعند الانتهاء من مهامه ، سيتحول محمل الإقلاع إلى رمز النواة الذي تم تحميله في الذاكرة الرئيسية ويبدأ في تنفيذه ، ويمرر جميع معلمات سطر الأوامر التي حددها المستخدم. ما هو برنامج النواة؟ يظهر الملف / التمهيد / vmlinuz أن هذا هو bzImage. تحتوي شجرة مصدر Linux على
أداة extract-vmlinux يمكنك استخدامها لاستخراج الملف:
$# scripts/extract-vmlinux /boot/vmlinuz-$(uname -r) > vmlinux $# file vmlinux vmlinux: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, stripped
النواة عبارة عن ملف ثنائي
قابل للتنفيذ وقابل للربط (ELF) ، مثل برامج مساحة مستخدم Linux. هذا يعني أنه يمكننا استخدام أوامر binutils مثل readelf لتعلمها. قارن ، على سبيل المثال ، الاستنتاجات التالية:
$# readelf -S /bin/date $# readelf -S vmlinux
قائمة الأقسام في الملفات الثنائية متشابهة في معظمها.
لذا ، يجب أن تطلق النواة ثنائيات ELF Linux أخرى ... ولكن كيف تعمل برامج مساحة المستخدم؟ في الوظيفة
main()
، أليس كذلك؟ ليس بالفعل.
قبل تشغيل الوظيفة
main()
، تحتاج البرامج إلى سياق تنفيذ ، بما في ذلك ذاكرة كومة (كومة) وذاكرة مكدس (مكدس) ، بالإضافة إلى واصفات الملفات لـ
stdio
و
stdout
و
stderr
. تحصل برامج مساحة المستخدم على هذه الموارد من المكتبة القياسية (
glibc
لمعظم أنظمة Linux). خذ بعين الاعتبار ما يلي:
$# file /bin/date /bin/date: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=14e8563676febeb06d701dbee35d225c5a8e565a, stripped
تحتوي ثنائيات ELF على مترجم ، تمامًا مثل نصوص Bash و Python. ولكن لا يلزم تحديده من خلال
#!
كما هو الحال في البرامج النصية ، لأن ELF هو تنسيق Linux أصلي. يقوم مترجم ELF بتزويد الملف الثنائي بكافة الموارد اللازمة عن طريق استدعاء
_start()
، وهي وظيفة متاحة في حزمة مصدر
glibc
، والتي يمكن تعلمها من خلال
GDB . النواة ، من الواضح ، ليس لها مترجم ، ويجب أن تمد نفسها بشكل مستقل ، ولكن كيف؟
تقدم دراسة بدء نواة باستخدام GDB إجابة على هذا السؤال. للبدء ، قم بتثبيت حزمة تصحيح أخطاء kernel ، والتي تحتوي على الإصدار غير المصقول من
vmlinux
، على سبيل المثال ،
apt-get install linux-image-amd64-dbg
. أو قم بتجميع النواة الخاصة بك وتثبيتها من مصدر ما ، على سبيل المثال ، باتباع الإرشادات من دليل
Debian Kernel الممتاز.
gdb vmlinux
متبوعًا
info files
مقطع ELF
init.text
. أشر إلى بداية تنفيذ البرنامج في
init.text
l *(address)
، حيث العنوان هو البداية السداسية العشرية لـ
init.text
. سيشير GDB إلى أن x86_64 kernel يتم تشغيله في
arch/x86/kernel/head_64.S
، حيث نجد دالة
start_cpu0()
والرمز الذي ينشئ المكدس ويفك ضغط zImage قبل استدعاء
x86_64 start_kernel()
. نوى ARM 32 بت لها نفس
arch/arm/kernel/head.S. start_kernel()
arch/arm/kernel/head.S. start_kernel()
مستقل عن البنية ، لذلك توجد الوظيفة في kernel
init/main.c
يمكننا أن نقول أن
start_kernel()
هي دالة لينكس
main()
حقيقية
main()
.
من start_kernel () إلى PID 1بيان أجهزة Kernel: جداول ACPI وأشجار الجهازعند التمهيد ، تحتاج النواة إلى معلومات حول الأجهزة بالإضافة إلى نوع المعالج الذي تم تجميعها من أجله. يتم استكمال التعليمات الموجودة في الرمز ببيانات التكوين ، والتي يتم تخزينها بشكل منفصل. هناك طريقتان رئيسيتان لتخزين البيانات:
أشجار الجهاز
وجداول ACPI . من هذه الملفات ، تكتشف النواة المعدات التي يجب تشغيلها في كل تمهيد.
بالنسبة للأجهزة المضمنة ، تعد شجرة الأجهزة (DU) بيانًا للمعدات المثبتة. DU هو ملف يتم تجميعه في نفس وقت مصدر kernel وعادة ما يكون موجودًا في / boot مع
vmlinux
. لمعرفة ما يوجد في شجرة الجهاز الثنائي على جهاز ARM ، ما عليك سوى استخدام الأمر
strings
من حزمة binutils في الملف الذي يتوافق اسمه مع
/boot/*.dtb
، لأن
dtb
يعني الملف الثنائي لشجرة الجهاز (Device-Tree Binary). يمكنك تغيير وحدة التحكم عن بُعد من خلال تعديل الملفات الشبيهة بـ JSON التي تتكون منها وإعادة تشغيل برنامج التحويل البرمجي الخاص dtc المتوفر مع مصدر kernel. DU هو ملف ثابت يتم تمرير مساره عادةً إلى kernel بواسطة برامج تحميل التمهيد في سطر الأوامر ، ولكن في السنوات الأخيرة تمت إضافة
تراكب شجرة جهاز حيث يمكن للنواة تحميل أجزاء إضافية ديناميكيًا استجابة لأحداث التوصيل السريع بعد التحميل.
تستخدم عائلة x86 والعديد من أجهزة ARM64 على مستوى الأعمال آلية التكوين المتقدم وواجهة الطاقة (
ACPI) البديلة. على عكس جهاز التحكم عن بعد ، يتم تخزين معلومات ACPI في نظام الملفات الظاهري
/sys/firmware/acpi/tables
، والتي يتم إنشاؤها بواسطة النواة عند بدء التشغيل عن طريق الوصول إلى ROM الداخلية. لقراءة جداول ACPI ، استخدم الأمر
acpica-tools
حزمة
acpica-tools
. هنا مثال:
جداول ACPI على أجهزة الكمبيوتر المحمولة Lenovo جاهزة لنظام التشغيل Windows 2001.نعم ، نظام Linux جاهز لنظام التشغيل Windows 2001 إذا كنت تريد تثبيته. يحتوي ACPI على كل من الأساليب والبيانات ، على النقيض من جهاز التحكم عن بعد ، الذي يشبه إلى حد كبير لغة وصف الأجهزة. تظل أساليب ACPI نشطة بعد التمهيد. على سبيل المثال ، إذا قمت بتشغيل الأمر acpi_listen (من حزمة apcid) ، ثم قمت بإغلاق وفتح غطاء الكمبيوتر المحمول ، فسترى أن وظيفة ACPI استمرت في العمل طوال هذا الوقت.
من الممكن
إعادة الكتابة المؤقتة والديناميكية
لجداول ACPI ، ولكن التغيير الدائم يتطلب التفاعل مع قائمة BIOS عند التمهيد أو وميض ROM. بدلاً من هذه التعقيدات ، ربما يجب عليك فقط
تثبيت coreboot ، وهو بديل للبرامج الثابتة مفتوحة المصدر.
من start_kernel () إلى مساحة المستخدم
المدونة في
init/main.c
سهلة القراءة بشكل مدهش ، ومن الغريب أنها لا تزال ترتدي حقوق الطبع والنشر الأصلية لـ Linus Torvalds من 1991-1992. وجدت خطوط في
dmesg | head
ينشأ
dmesg | head
نظام التشغيل بشكل أساسي من ملف المصدر هذا. يتم تسجيل وحدة المعالجة المركزية الأولى من قبل النظام ، وتتم تهيئة هياكل البيانات العالمية ، واحدة تلو الأخرى يتم رفع المجدول ومعالجات المقاطعة (IRQs) والمؤقتات ووحدة التحكم. جميع الطوابع الزمنية قبل تشغيل
timekeeping_init()
صفر. هذا الجزء من تهيئة kernel متزامن ، أي أن التنفيذ يحدث في خيط واحد فقط. لا يتم تنفيذ الوظائف حتى يتم الانتهاء من آخرها وإعادتها. ونتيجة لذلك ، سيكون إخراج
dmesg
قابلاً للتكرار بالكامل حتى بين النظامين ، طالما أن لديهم نفس جهاز التحكم عن بعد أو جداول ACPI. يعمل Linux أيضًا مثل نظام التشغيل في الوقت الفعلي (RTOS) الذي يعمل على MCU ، مثل QNX أو VxWorks. يتم تخزين هذا الموقف في
rest_init()
، والتي يتم استدعاؤها بواسطة
start_kernel()
في لحظة اكتمالها.
وصف موجز لعملية تمهيد النواة المبكرة
يُنشئ
rest_init()
المتواضع الاسم مؤشر ترابط جديد يقوم بتشغيل
kernel_init()
، والذي بدوره يستدعي
do_initcalls()
. يمكن للمستخدمين مراقبة عمل
initcalls
عن طريق إضافة
initcalls_debug
إلى سطر أوامر kernel. نتيجة لذلك ، ستحصل على كيان
dmesg
كل مرة تقوم فيها بتشغيل وظيفة
initcall
. يمر
initcalls
بسبعة مستويات متتالية: في وقت مبكر ، الأساسية ، postcore ، القوس ، subys ، fs ، الجهاز والمتأخر. أكثر ما يمكن ملاحظته من عمليات
initcalls
للمستخدمين هو تحديد وتركيب الأجهزة الطرفية للمعالج: الحافلات والشبكات والتخزين والشاشات وما إلى ذلك ، مصحوبة بتحميل وحدات النواة الخاصة بهم.
rest_init()
أيضًا مؤشر ترابط ثانٍ في معالج التمهيد ، والذي يبدأ بتشغيل
cpu_idle()
بينما يقوم المجدول بتوزيع عمله.
kernel_init()
أيضًا بإعداد
معالجة متعددة متماثلة (SMP). في النواة الحديثة ، يمكنك العثور على هذه اللحظة في إخراج dmesg عن طريق سطر "إحضار وحدات المعالجة المركزية الثانوية ...". يقوم SMP بعد ذلك بجعل وحدة المعالجة المركزية ساخنة ، مما يعني أنه يدير دورة حياته باستخدام جهاز حالة يشبه بشكل مشروط تلك المستخدمة في أجهزة مثل شرائح ذاكرة USB ذات الاستشعار التلقائي. غالبًا ما يقوم نظام إدارة الطاقة kernel بإغلاق النوى الفردية (النوى) وإيقاظها حسب الحاجة حتى يتم استدعاء نفس رمز وحدة المعالجة المركزية بشكل متكرر على جهاز غير مشغول. ألق نظرة على كيفية استدعاء نظام إدارة الطاقة
offcputime.py
المعالجة المركزية الساخنة باستخدام
أداة BCC تسمى
offcputime.py
.
لاحظ أن الكود في
init/main.c
كاد أن ينتهي من التنفيذ عند
smp_init()
. أكمل معالج التمهيد معظم التهيئة لمرة واحدة ، والتي لا تحتاج النوى الأخرى إلى تكرارها. ومع ذلك ، يجب إنشاء مؤشرات الترابط لكل قلب من أجل التحكم في المقاطعات (IRQs) ، وطاولة العمل ، والمؤقتات ، وأحداث الطاقة في كل مركز. على سبيل المثال ، انظر إلى خيوط المعالج التي تخدم برامج العمل والعلامات باستخدام الأمر
ps -o psr.
$\
حيث يعني حقل PSR "المعالج". يجب أن يكون لكل قلب مؤقتات خاصة به ومعالجات hotplug.
وأخيرًا ، كيف يتم إطلاق مساحة المستخدم؟ نحو النهاية ،
kernel_init()
عن
initrd
يمكن أن يبدأ عملية
init
نيابة عنه. إذا لم يكن الأمر كذلك ، فإن النواة تنفذ
init
بمفرده. ثم لماذا قد تكون هناك حاجة إلى
initrd
؟
مساحة المستخدم المبكرة: من طلب initrd؟بالإضافة إلى شجرة الجهاز ، فإن مسار التهيئة الآخر للملف ، الذي يوفره kernel عند التمهيد بشكل اختياري ، ينتمي إلى
initrd
. غالبًا ما يقع
initrd
في / التمهيد مع ملف bzImage vmlinuz على x86 ، أو مع صورة مشابهة وشجرة جهاز لـ ARM. يمكن الاطلاع على قائمة محتويات
intrd
باستخدام أداة
lsinitramfs
، والتي هي جزء من
initramfs-tools-core
. تحتوي صورة توزيع initrd على الحد الأدنى من الأدلة
/bin
و
/sbin
و
/etc
، بالإضافة إلى وحدات kernel والملفات الموجودة في
/scripts
. يجب أن يبدو كل شيء مألوفًا إلى حد ما ، نظرًا لأن
initrd
يشبه في الغالب نظام ملفات الجذر الجذري المبسط في Linux. هذا التشابه مضلل بعض الشيء ، نظرًا لأن جميع الملفات التنفيذية تقريبًا في
/bin
و
/sbin
داخل ramdisk هي روابط
رمزية إلى ثنائي BusyBox ، مما يجعل
مجلدات / bin و / sbin أصغر 10 مرات من
glibc
.
لماذا تحاول إنشاء
initrd
إذا كان الشيء الوحيد الذي يفعله هو تحميل بعض الوحدات وتشغيل
init
على نظام ملفات جذر عادي؟ ضع في اعتبارك نظام ملفات جذر مشفر. قد يعتمد فك التشفير على تحميل وحدة kernel المخزنة في
/lib/modules
لنظام الملفات الجذر ... وكما هو متوقع ، في
initrd
. يمكن تجميع وحدة التشفير بشكل ثابت في النواة ، ولا يتم تحميلها من ملف ، ولكن هناك عدة أسباب لرفض ذلك. على سبيل المثال ، قد يجعل التجميع الثابت للنواة مع الوحدات حجمها كبيرًا جدًا بحيث لا يتناسب مع التخزين المتاح ، أو قد ينتهك التجميع الثابت شروط ترخيص البرنامج. من غير المستغرب ، يمكن أيضًا تمثيل برامج تشغيل التخزين والشبكات و HIDs (أجهزة الإدخال البشري) في
initrd
- بشكل أساسي أي رمز ليس جزءًا مطلوبًا من kernel المطلوب لتحميل نظام الملفات الجذر. في البداية أيضًا ، يمكن للمستخدمين تخزين
رمز ACPI الخاص بهم للجداول .
المرح مع قذيفة الإنقاذ و initrd مخصص.initrd
أيضًا رائع لاختبار أنظمة الملفات وأجهزة التخزين. ضع أدوات الاختبار في
initrd
وقم بتشغيل الاختبارات من الذاكرة ، وليس من كائن الاختبار.
أخيرًا ، عند تشغيل
init
، يعمل النظام! نظرًا لأن المعالجات الثانوية تعمل بالفعل ، أصبحت الماكينة مخلوقًا غير متزامن ، مقسم إلى صفحات ، لا يمكن التنبؤ به وعالي الأداء نعرفه ونحبه جميعًا. في الواقع ، يشير
ps -o pid,psr,comm -p
إلى أن عملية
ps -o pid,psr,comm -p
مساحة المستخدم لم تعد تعمل على معالج التمهيد.
الملخصتبدو عملية تمهيد Linux محظورة ، نظرًا لكمية البرامج المتأثرة ، حتى على جهاز مضمن بسيط. من ناحية أخرى ، فإن عملية التمهيد بسيطة للغاية ، حيث لا يوجد تعقيد مفرط ناتج عن ازدحام المهام المتعددة ، RCU وظروف السباق. مع الانتباه فقط إلى kernel و PID 1 ، يمكن للمرء أن يتجاهل العمل الرائع الذي تقوم به لوادر التمهيد والمعالجات الإضافية لإعداد النظام الأساسي لإطلاق kernel. النواة تختلف بالتأكيد عن برامج Linux الأخرى ، ولكن استخدام الأدوات للعمل مع ثنائيات ELF الأخرى سيساعد على فهم هيكلها بشكل أفضل. سوف تستعد دراسة عملية التمهيد القابلة للتطبيق لحالات التعطل المستقبلية.
النهاية
نحن في انتظار تعليقاتكم وسؤالكم ، كالمعتاد ، إما هنا أو في
درسنا المفتوح حيث سيتم إسقاط ليونيد.