ما الذي يمنعُه الوضعُ الحرُّ ولماذا
الوضعُ الحرُّ (--حرّ) ليس علَمَ تحسين. هو إعلانٌ بأنّ ما تحتَ برنامجِك ليس نظامَ تشغيل: لا libc، ولا runtime مضمَّن، ولا مُصفِّحَ ذاكرةٍ يُصفِّح .bss عند أوّلِ مسّ. وكلُّ ما في هذه الصفحةِ يتفرّعُ عن تلك الجملةِ الواحدة.
العَلَمُ وحدَه لا يكفي
--حرّ يصفُ غيابَ المكتبة، لا المعماريّةَ ولا حلقةَ الامتياز. والهدفُ يُقالُ بـ--هدف. فإن سكتَّ عنه وقعتَ على ثالوثِ المضيف — وهو ما يشرحُه قسمُ الهدفِ المُستنتَج أدناه.
١ · البوّابة: SEM019 يرفضُ بالاسم
المدمَجةُ المستضافةُ لا تُترَكُ حتّى الربط. هي تُرفَضُ في توليدِ LLVM، قبلَ أن يُنادى رابطٌ أصلًا:
$ sad-build gate.ص --حرّ --هدف=x86_64-unknown-none-elf --أظهر-llvm -o gate.llerror: [SEM019] (AR) الدالة المدمجة 'عشوائي' غير متاحة في الوضع الحرّ (--freestanding)
لأنها تعتمد على مكتبة أو خدمة نظام غائبة (نظام ملفّات/دخل قياسيّ/تحكّم طرفيّة/
عشوائيّة/نوم/إنهاء عمليّة/رياضيّات libm/فرز/وقت/أمن مستضاف)
→ في نواة/وضع حرّ استعمل بدائل مباشرة (منافذ/ذاكرة/مؤقّتات عتاد) بدل دوال
المكتبة القياسية، أو أزل الاستدعاء.
1 error(s) generated.والفرقُ هو كلُّ شيء. بلا هذه البوّابةِ يمضي التوليدُ فيبثُّ نداءَ rand()، ثمّ يُخفِقُ الرابطُ بـundefined symbol: rand — رسالةٌ لا تدلُّ على السطرِ ولا على المدمَجةِ ولا على العلاج. البوّابةُ تُبدِّلُ ذلك بتشخيصٍ يسمّي المدمَجةَ ويقترحُ البديل.
الجدولُ المحروسُ في المترجّمِ يحملُ اليومَ ٥٠ رمزَ عمليّة — يُعَدُّ بأمرِه:
$ awk 'NR>=85' compiler/src/backend/llvm/llvm_codegen_instructions.cpp |
awk '/^ }/{exit} {print}' | grep -c 'case SIROpcode::'
50وأصنافُها: نظامُ ملفّاتٍ · دخلٌ قياسيّ · تحكّمُ طرفيّة · عشوائيّة · نومٌ · إنهاءُ عمليّة · libm · فرزٌ · أمنٌ مستضاف.
لماذا في التوليدِ لا في الطبقةِ الدلاليّة؟
لأنّ الطبقةَ الدلاليّةَ لا تعرفُ الوضع. والبوّابةُ موضوعةٌ قبلَ طبقاتِ التوزيعِ الثلاثِ في emitInstruction كي لا يُقرَأَ إيقافُها «رمزُ عمليّةٍ غيرُ مدعوم» — وهو ما كان يحدثُ حين تسقطُ التعليمةُ عبرَ الطبقاتِ كلِّها.
٢ · ليست قائمةَ منعٍ فحسب — بل قائمةَ سماحٍ كذلك
الجدولُ يمنعُ ما نعرفُه. والحارسُ الثاني يمنعُ ما لا نعرفُه: tests/system/lowlevel_freestanding/test_no_hosted_symbols.py يوكِّدُ أنّ كلَّ declare في وحدةٍ حرّة إمّا تعليمةُ LLVM جوهريّة (llvm.*) أو رمزٌ عرَّفه المستعمِلُ صراحةً — وما عداه رمزٌ مستضافٌ مسرَّب.
والعطبُ الذي وَلَدَ ذلك الحارسَ يستحقُّ أن يُروى: مقدّمةُ نقطةِ الدخولِ كانت تبثُّ setlocale(LC_ALL, ".UTF8") بلا فحصِ الوضع. تعليلُها سليمٌ للمستضاف، لكنّ الوضعَ الحرَّ لا CRT فيه — فكان كلُّ ثنائيٍّ حرٍّ يُخفِقُ ربطُه. ولو كان الحارسُ قائمةَ منعٍ بالاسمِ لأمسكَ setlocale وحدَها وتركَ العائلة.
٣ · ما لا يُمنَع: مساراتٌ حرّةٌ أصيلة
المنعُ ليس الجوابَ الوحيد. ثلاثةُ أصنافٍ لها مسارٌ حرٌّ بديل بدل البوّابة:
| المدمَجة | المستضاف | الحرّ |
|---|---|---|
جذر · مطلق | نداءُ libm | تعليمةُ LLVM جوهريّة (llvm.sqrt · llvm.fabs) |
عائلةُ التأكيد (تأكد · اهلع) | abort() | __sad_panic — تعريفٌ ضعيف تتجاوزُه النواة |
الآن | time() من libc | جسرُ ساعةِ CMOS على المعدن · نداءُ نظامٍ على لينكس |
و__sad_panic ضعيفٌ (weak) عمدًا: النواةُ تُعرِّفُ نظيرَه فيغلبُه، فتصيرُ لافتةُ الهلعِ العربيّةُ في النواةِ هي التشخيصَ الوحيد. ولذلك يوكِّدُ test_bounds_diag_sovereign.py أنّ مسارَ فشلِ فحصِ الحدِّ لا يبثُّ printf إنجليزيًّا في الوضعِ الحرّ.
٤ · الهدفُ المُستنتَج: تشخيصٌ لا صمت
--حرّ بلا --هدف يقعُ على ثالوثِ المضيف. وذلك يُقالُ لا يُسكَتُ عنه:
$ sad-build hello.ص --حرّ --أظهر-llvm -o h.llwarning: الوضع الحرّ بلا «--هدف»: افتُرض ثالوث المضيف (بلا واجهة نداءات نظام
مبثوثة)، فلن تُبثّ جسور العتاد (ساعة CMOS، المنفذ التسلسليّ) ولا يصلح الناتج
للمعدن العاري ولا لربط «-nostdlib». مرّر «--هدف=i686-unknown-elf» أو نظيره
لبناء نواة: x86_64-pc-windows-msvcورتبةُ التشخيصِ مشروطةٌ بالمضيف عمدًا: على لينكس ملاحظةٌ لا تحذير — كي لا تنقلبَ خطأً تحتَ -Werror في بناءٍ حرٍّ صحيحٍ لنطاقِ المستخدم؛ وعلى مضيفٍ بلا نداءِ نظامٍ مبثوثٍ تحذيرٌ، لأنّ الناتجَ لا يصلحُ لشيء.
وثمّةَ تشخيصٌ ثالثٌ لمعدنٍ عارٍ بمعماريّةٍ لا جسرَ لها:
note: … جسرا الإخراج (المنفذ التسلسليّ) والوقت (الساعة) كعبان يعيدان لا شيء/
صفرًا. عرِّف «__sad_serial_putc» و«time» في حزمة دعم اللوحة (BSP) وإلّا فلا
مخرجات زمن التشغيل: aarch64-unknown-elfفالنواةُ الصامتةُ لها سببٌ مكتوبٌ، لا تُترَكُ للمطوّرِ يخمّنه.
٥ · الكومةُ: رقمٌ يُخفِقُ الإقلاعَ لا البناء
الكومةُ الحرّةُ مصفوفةٌ في .bss. والفرضيّةُ المكتوبةُ يومًا في الشفرة — «.bss صفريّةٌ لا تُكلِّف؛ التصفيحُ عند أوّلِ مسّ» — صحيحةٌ في نطاقِ المستخدمِ وباطلةٌ على المعدنِ العاري: لا مُصفِّحَ هناك، النواةُ هي المُصفِّح. فحينَ عُمِّمَ رقمُ ٥١٢م.ب على الأهدافِ كلِّها امتدَّ _kernel_end إلى ≈٥١٤م.ب وحجزَ مخصِّصُ الإطاراتِ ذلك المدى كلَّه، فماتت كلُّ خدمةٍ تحجزُ ذاكرةً على آلةٍ بـ-m 128M.
الدرسُ لا الرقم
الحجمُ الكبيرُ لا يُخفِقُ البناءَ ولا الربط — يُخفِقُ الإقلاعَ، على عتادٍ آخر، بعدَ الدمجِ بأيّام. ولذلك صارَ الافتراضيُّ مشروطًا بالهدفِ ومثبَّتًا بتوكيدٍ في test_heap_size.py، ويعلوه --حجم-الكومة صراحةً. والنافذةُ قد لا يحدُّها الرامُ بل خريطةُ المستهلك — ولا سبيلَ للمترجّمِ إلى معرفةِ ذلك.
٦ · حدودٌ تُقالُ ولا تُبتَلع
- معالجاتُ المقاطعاتِ على
x86_64وحدَها. مقيسٌ:i686وaarch64وriscv64تُرفَضُ كلُّها بـSEM046. انظر معالجاتِ المقاطعات. - مدمَجاتُ x86 مقصورةٌ على عائلتِها.
اكتب_منفذعلىaarch64تُرفَضُ بـSEM038تسمّيoutbوالعائلةَ المطلوبة — لا ثنائيٌّ لا يعمل. riscv64يُنزِّلُ سبعةً من ١٠٤ رموزِ عمليّةٍ مُنزَّلةٍ أصليًّا (arm64وx86_64١٠٤ لكلٍّ)، وقد أُعيدَ القياسُ منsir_opcodes.yaml؛ والحدُّ المُعلَنُ في حدودِ المسار يقولُ خمسةً من ١٠٣ — العددُ يتحرّكُ والحدُّ لا يتحرّك.- لا نواةَ ولا مُحمِّلَ إقلاعٍ في المستودع. فالتعليمُ يشحنُ نواتَه معه — انظر أوّلَ نواةٍ تُقلِعُ في QEMU.
أين تمضي من هنا
| تريد | اذهبْ إلى |
|---|---|
| نواةً تُترجَمُ وتُنتِجُ ELF | أوّلُ نواةٍ تُقلِعُ في QEMU |
| منافذَ وذاكرةً وتخزينًا ساكنًا | MMIO والمنافذُ والتخزينُ الساكن |
| جدولَ مقاطعاتٍ يعملُ | معالجاتُ المقاطعات |
| ربطًا مع C | التعاملُ مع C |
| تجميعًا مضمَّنًا بالعربيّة | لهجةُ التجميعِ العربيّة |