|
تدبير الذاكرة الافتراضية في الدرس
السابق
بدأنا
سلسلتنا عن
إرساء نظام
لتدبير
الذاكرة في
النواة
التدريبية.
ألقينا نظرة
عامة على
مفاهيم و
سياسات
تدبير الذاكرة
ثم قمنا بتنجيز
مدبر
للذاكرة
الحقيقية. في هذا
الدرس
سنواصل
مسيرتنا و
إضافة طبقة
جديدة فوق مدبر
الذاكرة
الحقيقية: أقصد
مدبر
الذاكرة
الافتراضية. كما هي
العادة
سنتحدث في
مرحلة أولى
عن مفهوم الذاكرة
الافتراضية
و كيفية
تنشيطها في
بيئة x86. ثم في
المرحلة
التطبيقية
سنقدم مثالا لتنجيزها
في نواتنا. 1-
مفهوم
الذاكرة
الافتراضية ما هي
الذاكرة
الافتراضية؟
في
الدرس
الثاني عن
الوضع
المحمي،
أدخلنا مفهوم
فضاء
العنونة،
وقلنا أنه
عبارة عن
مجال تشكله
جميع القيم
التي يمكن
للمعالج
إرسالها على
ناقلة
البيانات.
قلنا أيضا أن
عدد الخطوط
المرصودة
لهذه
الناقلة
يحدد بصفة
مباشرة هذا الفضاء.
في معالجات x86 عدد
الخطوط هو 32
(إذا
استثنينا
معالجات Pentium التي
تتوفر على 36 خط
لكن
استعمالها
يتطلب إعدادات
خاصة). هذا
يعني أن فضاء
عنونة هذه
المعالجات
يمتد من 0 إلى 232 =
4 جيغ (64 جيغ
في بنتيوم). من جهة
أخرى،
الذاكرة
الحقيقية في
الجهاز قد
تكون أقل
بكثير من هذا
الحجم،
فأنظمة التشغيل
الحالية
تدير برامج
متعددة في آن
واحد، و
بالتالي
فاحتياجاتها
من الذاكرة
يمكن أن تتجاوز
حجم الذاكرة
الحقيقية:
تخيل أن لديك
256ميغ من
الذاكرة
الحقيقية
مثبتة على
حاسوبك. و أن
نظام
التشغيل في
هذا الحاسوب
استنفذ كل
هذه الذاكرة
في وقت ما،
كيف سيقوم
بمواجهة طلبات
جديدة من
الذاكرة. يمكنه
طبعا أن يجمد
كل طلب جديد
أو أن يعلم
المستعمل
لتوقيف بعض
البرامج و
إخلائها من
الذاكرة. لكن
هناك حل أفضل
بكثير. الحل
الذي تم
اختياره
ينبع من
ملاحظة
مفادها أن
الحاسوب
يتوفر
بالإضافة
إلى الذاكرة
الحقيقية
على ذواكر
أخرى، أقصد
خصوصا القرص
الصلب، وهذه
الأخيرة لها
قدرة تفوق بكثير
قدرة
الذاكرة
الحقيقية. يمكن
إذن
الاستفادة
من هذه
الأخيرة
لتخزين
مساحات من
الذاكرة غير
مستعملة
حاليا من طرف
المستعمل
لتوفير
مساحة
إضافية في
الذاكرة
الحية. و عندما
يطلب برنامج
ما بيانات و
اكتشف نظام
التشغيل أن
هذه الأخيرة
قد تم "طردها"
إلى القرص
الصلب، فإنه
يقوم
بإرجاعها
إلى الذاكرة
الحية بعد
اختيار
بيانات أخرى
لطردها إلى
القرص الصلب.
تعرف هذه
التقنية
بالاستبدال Swap. طبعا
هناك مشكل
أساسي في هذه
العملية،
البرامج تلج
للذاكرة
مباشرة و لا
تقوم بإشعار
نظام التشغيل
قبلا لمعرفة
ما إذا كانت
البيانات
متوفرة في الذاكرة
أو مخزنة على
القرص. إذن لا
بد من وسيلة
ما، تنجز
بالضرورة
على مستوى الهاردوير،
لإعلام نظام
التشغيل في
هذه الحالة
حتى يقوم
باللازم. و
يجب أن تكون
هذه الوسيلة
شفافة للبرامج
بحيث يبدو
لهذه
الأخيرة أن
البيانات التي
طلبتها
لطالما
تواجدت في
الذاكرة. الحل
الذي يستعمل
في هذه
الحالة هو
بإضافة مكون
جديد إلى
المعالج يسمى
وحدة تدبير
الذاكرة و.ت.ذ.
Memory Management Unit MMU، تتدخل
هذه الوحدة
بين المعالج
و الذاكرة. عندما
يطلب
المعالج
الولوج إلى
موقع في
الذاكرة فإنه
يعطى
العنوان
لهذه الوحدة.
هذه الأخيرة
تقوم بالتأكد
من أن
العنوان
موجود فعلا
على الذاكرة
الحقيقية.
إذا كان
الأمر كذلك فإن
الولوج يتم
بطريق عادية. في الحالة
العكسية فإن
المعالج
يقوم بإشعار
نظام
التشغيل حتى
يقوم بتوفير
البيانات
غير المتوفرة
حاليا في
الذاكرة. بعد
تنفيذ
اللازم و
توفير
البيانات،
يقوم المعالج
بإعادة
تنفيذ
تعليمة
الولوج
الخاطئة فيما
قبل فيتواصل
سير التنفيذ
بطريقة
عادية كأن شيئا
لم يكن. هناك
أيضا وظيفة
أخرى لهذه
الوحدة و هي
تحويل العناوين
المستلمة من
المعالج و
ترجمتها إلى
قيم مخالفة
تماما. مثلا
يمكن لبرنامج
ما أن يطلب
الولوج إلى
العنوان 0x44120000، و
تقوم وحدة
تدبير
الذاكرة
بترجمتها مثلا
إلى العنوان 0x000053FF. بعبارة
أخرى فإن
الفضاء الذي
يشتغل فيه
البرنامج،
أو يتوهم أنه
يشتغل فيه،
مخالف تماما
للفضاء الذي
يتم
الاشتغال
فيه فعلا على
الذاكرة
الحقيقية.
هذا بالضبط
هو المقصود
بالذاكرة
الافتراضية.
مجموعة من
العناوين
التي يشتغل
عليها
البرنامج في
حين أن العمل
الفعلي يتم
على عناوين
مختلفة
تماما و ربما
لا وجود لها
في الذاكرة
الحقيقية بل
في مكان آخر
مثل القرص
الصلب. بطبيعة
الحال فإن
وحدة تدبير
الذاكرة لا
تترجم العناوين
الافتراضية
إلى عناوين
حقيقية
بطريقة عشوائية
و حسب
أهوائها. بل
إن نظام
التشغيل هو
الذي يحدد
بدقة كيف تتم
هذه الترجمة
و ذلك وفقا
للأهداف
التي
يتوخاها من
تدبير
الفضاء
الافتراضي و
كيفية
تخصيصه بين
مختلف
البرامج. طريقة
ترجمة
العناوين
الافتراضية
قد تختلف في
التفاصيل
بين مختلف
المعالجات
لكن القاعدة
(في الغالب)
واحدة. يتم
استعمال
جدول
للترجمة و
هذا الجدول
يحدد للمعالج
كيف يترجم كل
عنوان
افتراضي إلى
نظيره الحقيقي.
كل عنوان؟ كم
يلزمنا إذن
من مدخل في هذا
الجدول
لتخزين قيم
فضاء عنونة
من حجم 4جيغ؟
الجواب 4294967296
مدخل. هذا طبعا
غير ممكن
عمليا. في
الواقع يتم
تقسيم فضاء
العنونة
الافتراضي إلى
قطع متساوية
من حجم معين
تسمى صفحات.
في x86 تكون
هذه الصفحات
عادة بحجم 4كيلب
(قد تكون أيضا
بحجم 4ميغ أو
2ميغ لكن
دائما بإعدادات
خاصة). هذا إذن
يقلص عدد
مداخل
الجدول إلى
4كيلب/4جيغ = 1048576
مدخل. بفرض أن
طول كل مدخل
هو 32 بيت فإن الجدول
سيأخذ 4ميغ و
هذا مقبول
بفرض أننا
نتوفر على
4جيغ من
الذاكرة
الحقيقية.
لكن إن كنا
نتوفر فقط
على 16ميغ فما
العمل؟ الجواب:
نستعمل
مستويين في
التقسيم، أو
الفهرسة Paging كما
تسمى هذه
العملية، أي
سنستعمل
جدولين،
جدول أساسي و
جدول ثانوي.
في الجدول
الأساسي
نخزن فقط
عناوين
الجداول
الثانوية. بينما
نخزن في هذه
الأخيرة
عناوين
الصفحات.
ميزة
هذه الطريقة
تنبع من واقع
أنه في وقت
معين لا نحتاج
لترجمة جميع
الصفحات في
الفضاء
الافتراضي.
هذا يعني
أننا لسنا
مضطرين لصنع
و تخزين جميع
الجداول
الثانوية. بل
فقط الجدول
الرئيسي و
الجداول
التي
سنحتاجها
فعلا. يمكننا
أيضا أن نمضي
طبعا أبعد من
ذلك و نستعمل
ثلاث مستويات
للتبويب
(تستعمل في
بعض
المعالجات مثل
إنتل 64 بيت). لكن
ذلك لن يكون
مفيدا إلا
إذا كان حجم
الفضاء الافتراضي
كبيرا
ويتطلب ذلك. معالجات x86 تستعمل
مستويين
للتبويب.
الجدول
الأساسي يسمى
مجلد
الصفحات و
الجدول الثانوي
يسمى
جدول
الصفحات. فيما يلي سنتحدث عن
كيفية إعداد
الذاكرة
الافتراضية
و جداول الترجمة
في هذه
المعالجات. الذاكرة
الافتراضية
في x86 في
معالجات x86 نميز بين ثلاث
أنواع من
العناوين: 1- العنوان
المنطقي Logical Adress: و هي
النوع الذي
تستعمل من طرف
البرامج.
يكتب على شكل محددالقسم:البعد
داخل القسم.
في حال عدم
تحديد القسم
علنا، يتم
استعمال
المسجل cs
لعناوين
الشفرة و ds
لعناوين
البيانات. 2- العنوان
الخطي Linear Adress:
يستنتج بعد
حساب عنوان
بداية محدد
القسم و إضافة
البعد عليه.
مما يعطينا
عنوانا واحدا
من 32 بيت. 3- العنوان
الحقيقي Physical Adress:
يستنتج بعد
ترجمة
العنوان
الخطي إثر
مروره على
وحدة تدبير
الذاكرة. كما
رأينا في
الفقرة السابقة. في
حال ما كان
عنوان القسم
يبدأ في 0، كما
هو الأمر
بالنسبة
لنواتنا
التدريبية،
فإن العنوان
المنطقي
يكافئ العنوان
الخطي. فيما
تبقى من
الدرس، و
تماشيا مع
التسمية
الدارجة،
سنتحدث عن
العنوان
الافتراضي
بمعنى العنوان
الخطي (أو
المنطقي ما
داما
متساويان في
حالتنا). رأينا
في الفقرة
السابقة أن
العادة في
ترجمة العناوين
الافتراضية
تعتمد على: 1- تقسيم
الذاكرة إلى
مجموعة قطع
متساوية
تسمى صفحات. 2- استعمال
جدول أو أكثر
لترجمة
عناوين
الصفحات من
الفضاء
الافتراضي
إلى الفضاء
الحقيقي (أو
إعلام نظام
التشغيل في
حال ما كان
العنوان
الافتراضي
ليس له مكافئ
على الذاكرة
الحقيقية). معالجات
x86 لا
تشذ عن هذه
القاعدة. فهي
تقسم الفضاء
الافتراضي
إلى صفحات من
حجم 4كيلب أو
2ميغ أو 4ميغ -
تبعا
للبيانات
الموجودة في
جدول الترجمة
و بعض
المسجلات – في
درسنا
سنقتصر على
الحجم
الأكثر
شيوعا و هو
4كيلب. في
x86
نستعمل
الفهرسة (Paging أي
الترجمة عبر
الجداول
تماما كما
نفعل مع الكتب)
على مستويين: - في
المستوى
الأول نجد
مجلد
الصفحات Page Directory.
يتكون هذا
الجدول من 1024
مدخل كل واحد
بسعة 32 بيت. و كل
مدخل يستعمل
كمؤشر على
جدول ثانوي و
يتعلق الأمر
ب - جدول
الصفحات Page Table: أيضا
يتكون من 1024
مدخل كل واحد
بسعة 32 بيت. كل
مدخل يستعمل
كمؤشر على
صفحة حقيقية
في الذاكرة (=
صورة صفحة Page Frame). باستعمال
هذا التصميم
يمكن إذن
لمجلد الصفحات
أن يعنون على
الأكثر 1024 جدول
للصفحات. مع
كل جدول
يستطيع أن
يعنون على
الأكثر 1024 صفحة
(ما مجموعه
4ميغ). فالفضاء
الذي يمكن
تغطيته هو
إذن 1024x1024 x
4كيلب = 4جيغ بنية
المداخل في
مجلد
الصفحات لا
تختلف كثيرا
عن مثيلاتها
في جدول
الصفحات
وهذا
سيفيدنا كما
سنرى لاحقا
في المرحلة
التطبيقية. الشكل
التالي
يعطينا بنية
مدخل في مجلد
الصفحات مع
شرح
البيانات
التي تضمها:
و فيما
يلي بنية
مدخل في جدول
الصفحات
الآن
رأينا كيف
يتم تخزين
عناوين
الصفحات الحقيقية
في الجداول.
لكن السؤال
ما زال قائما:
كيف يتم
ترجمة العناوين
الافتراضية
إلى
العناوين
الحقيقية؟ في
بيئة x86 العناوين
تكون من حجم
32بيت.
لاستخلاص
العنوان المكافئ
في الذاكرة
الحقيقية
يتم اتباع
المراحل
التالية: 1- يتم
استخلاص
العشر البيتات
الأقوى من
العنوان
الافتراضي
(من 22 إلى 31). و اعتبار
العدد
الناتج
كمؤشر على
مجلد
الصفحات. مثلا
إذا كانت
قيمة البيتات
العشرة
تساوي 45 فإنها
تشير إلى
المدخل 45 في
مجلد الصفحات.
بعد
العثور على
المدخل في
مجلد
الصفحات
يستخلص العنوان
الحقيقي
لجدول
الصفحات
المؤشر عليه في
هذا المدخل و
يحتفظ به
للاستعمال
في المرحلة
الموالية. 2- يتم
استخلاص البيتات
العشرة
المجاورة (في
الوسط من 12 إلى
21). الآن نعتبرها
كمؤشر على
جدول
الصفحات
الذي عثر على
عنوانه في
المرحلة
الأولى. مثلا
إذا كانت
قيمة البيتات
العشرة
تساوي 32 فإننا
نستعمل
المدخل 32 في
جدول الصفحات
المستخلص من
المرحلة
الأولى. من
هذا المدخل تستخلص
عنوان صورة
صفحة في
الذاكرة
الحقيقية. 3- الآن
نستخلص ال
12 بيت الأضعف
من العنوان
(من 0 إلى 11) و
نستعمله كبعد
داخل صورة
الصفحة
المستخلصة
من المرحلة السابقة.
فنحصل في
النهاية على
العنوان
الحقيقي في
الذاكرة. للانطلاق
لا بد طبعا من
تخزين
العنوان
الحقيقي
لمجلد الصفحات
في مكان
معروف
للمعالج. في x86 يستعمل
المسجل CR3 لهذا
الغرض. الشكل
التالي يعطي
صورة أوضح عن
هذه العملية
عندما
نقوم
بمقابلة
صفحة في
الفضاء
الافتراضي
بصورة لها في
الفضاء الحقيقي
فإننا نسمي
هذه العملية ترسيم Mapping صورة
الصفحة
الحقيقية في
صفحة افتراضية. قد نقوم
أيضا بترسيم
عدة صفحات في
منطقة كاملة
من الفضاء
الافتراضي –
أي عدة صفحات
متجاورة – هذا
يعني فقط
مقابلة
الصفحات
المشكلة
للمنطقة
الافتراضية
مع صور
الصفحات
المذكورة.
لاحظ أنه عند ترسيم
صفحات
افتراضية
متجاورة
فهذا لا يعني
أن صور الصفحات
المكافئة
لها يجب أن
تكون
متجاورة في
الذاكرة
الحقيقية،
يمكننا أن
نستعمل صفحات
مبعثرة في
الذاكرة
الحقيقية
لتشكيل عدة
صفحات
افتراضية
متجاورة. عندما
يقلع الحاسب
و نمر إلى
الوضع
المحمي فإن
الفهرسة Paging تكون
معطلة مسبقا.
و قبل
تنشيطها يجب
علينا أولا
صنع جداول
الترجمة و ملئها
بما يناسبنا.
أي علينا صنع
الفضاء
الافتراضي الذي
سنشتغل عليه.
بعد ذلك نحمل المسجل CR3 بالعنوان
الحقيقي
للصفحة التي
تحتوي على مجلد
الصفحات. لهذا
الغرض نحن
مضطرون
لاستعمال
تعليمات الأسمبلي.
مثلا إذا كان
المتغير pgdir يؤشر
على العنوان
الحقيقي
لمجلد
الصفحات فإننا
نستعمل
التعليمات
الآتية mov eax, pgdir mov CR3, eax
آنذاك
فقط يمكننا
تنشيط
الفهرسة
بوضع العلم PG flag – البيت 31
في المسجل CR0 –
مما يعطي mov eax, 0x80000000 or CR0, eax
هناك
طبعا سؤال
يفرض نفسه في
هذه المرحلة.
نحن الآن
نشتغل في
الفضاء
الافتراضي
مما يعني أن
العناوين
الحقيقية لم
تعد صالحة –
ابتداء من
اللحظة التي
تم فيها
تنشيط
الفهرسة -. كيف
يمكن للنواة
أن تواصل
عملها من دون
خطأ بعد
تنشيط
الذاكرة
الافتراضية؟
هناك عدة
أجوبة
وبالتالي
عدة حلول.
أسهل هذه الحلول
هو استعمال الترسيم
المثلي Identity Mapping لكل
المجال الذي
يشغله
برنامج
النواة – شفرة و
بيانات – أي
ملئ جداول
الترجمة
بحيث أن كل ترجمة
للعناوين
الافتراضية
تعطي تماما
نفس القيمة
في الذاكرة
الحقيقية –
لكل الفضاء
الذي تشغله
النواة في
الذاكرة -.
بهذه
الطريقة لن
يتأثر سير
التنفيذ. هناك
أيضا الحل
المستعمل في لينوكس.
عندما تتم
ترجمة شفرة لينوكس
إلى ملف
تنفيذي. فإن
مخطوط محرر
الروابط
يخبر هذا
الأخير
بإنتاج العناوين
كما لو أن
النواة ستنفذ
في العنوان 0xC0010000. لكن
هذا لا يمنع
مشكل أن
تحميل
النواة يتم
في العنوان 0x10000 وأنه عند
التحميل
الفهرسة
معطلة. من أجل
ذلك فإن لينوكس
يقوم في البداية
بتنفيذ شفرة
مكتوبة بالأسمبلي.
هذه الأخيرة
تستعمل
العناوين
بحذر و تطرح
عند استعمال
أية بيانات
القيمة 0xC0000000. ما
يعطي القيمة
الصحيحة. هذا
فقط حالما
تقوم بإعداد
جداول
الفهرسة
بحيث أن كل
عنوان افتراضي
في المساحة
التي تشغلها
النواة
يكافئ العنوان
الحقيقي+0xC0000000 و
تنشيط هذه
الأخيرة.
مثلا عنوان
بداية النواة
الافتراضي 0xC0010000 يكافئه
العنوان
الحقيقي 0x10000. كيفما
كان الحل
الذي تم
اختياره، فإن
نتائجه ليست
هينة كما
يعتقد لأول
وهلة. لأن هذا
يحدد الفضاء
الذي ستشتغل
فيه كل
مكونات النواة
فيما بعد. كما
سنرى في
مراحل لاحقة –
بإذن الله –
فإنه يتم
تقسيم ال4جيغ
من الفضاء
الافتراضي
إلى قسمين:
قسم تشتغل
فيه النواة
بصفة دائمة
وقسم تتناوب
عليه برامج
المستعمل
تباعا فيما
بينها عندما
تكون في طور
التنفيذ. لاحقا
سنعرف كنه
هذه الأمور،
يكفي أن تعرف
الآن أن النواة
تحتاج لجزء
من الفضاء
الافتراضي
تقيم فيه
بصفة دائمة.
في حالة لينوكس،
فإن الاختيار
وقع على
الفضاء الذي
يبدأ من 0xC0000000 وحتى
نهاية
الذاكرة
الافتراضية.
أي ال1جيغ الأخيرة
من الفضاء
الافتراضي
كلية. لا داعي
لأؤكد أننا
نتحدث عن
الذاكرة
الافتراضية
بمعنى ليس
ضروريا أن
يكون لها
مقابل كاملة
في الذاكرة
الحقيقية. في
نواتنا
التدريبية،
و كما سنرى
بعد قليل،
اخترت الحل
الأول أي الترسيم
المثلي حتى لانضطر
للعب
بالعناوين
في أولى
مراحل
التنفيذ. كما
أن هذا سيتيح
لنا تنشيط
الذاكرة
الافتراضية
متى نشاء. سؤال آخر لا
يقل أهمية:
كيف نلج إلى
جداول
الترجمة في
الذاكرة
الافتراضية؟ رأينا
كيف يقوم
المعالج باستعمال
البيانات في
جداول
الترجمة
لاستخلاص العنوان
الحقيقي
انطلاقا من
عنوان
افتراضي. الآن
نعرف أنه
لبناء فضاء
افتراضي ما
علينا سوى تغيير
بيانات هذه
الجداول. في
أغلب
الأحيان ستكون
هناك دالة
تأخذ عنوانا
حقيقيا و
عنوانا افتراضيا
لإتمام الترسيم.
قد تختلف التنجيزات
لكن الطريقة
العامة تظل
دائما واحدة: 1- استخلاص
البيتات
العشرة
الأقوى من
العنوان
الافتراضي و استعمالها
كمدخل في
مجلد
الصفحات. 2- في
حال كان جدول
الصفحات موجودا
(البيت Present = 1) نمر إلى
المرحلة
التالية
مباشرة. في
الحالة العكسية،
علينا تخصيص
صورة صفحة
لجدول
الصفحات و
تغيير
المدخل
السابق في
مجلد
الصفحات
ليؤشر على
هذه الصورة. 3- الآن
نستعمل
العشر البيتات
الوسطى
كمدخل في
جدول
الصفحات. ثم
نكتب العنوان
الحقيقي
لصورة
الصفحة في
هذا المدخل. لاحظ
أننا نتبع
نفس طريقة
المعالج في
تتبع البيانات
مع فارق أننا
بدل أن نقرأ
البيانات
فإننا نقوم
بتغييرها،
الأمر تماما
كما لو أننا
نمهد الطريق لهذا
الأخير حتى
لا يتعثر في
تتبعه
للعناوين. لكن
هناك فرق
أساسي آخر
بيننا و بين
المعالج. هذا
الأخير
عندما يقوم
بالولوج إلى
مجلد أو جداول
الصفحات
فإنه يقوم
باستعمال
العناوين
الحقيقية:
العنوان
الحقيقي
لمجلد الصفحات
في المسجل CR3 و
العناوين
الحقيقية
لجداول
الصفحات في
مداخل مجلد
الصفحات. بالنسبة
لنا الأمر
مختلف، لقد
قمنا بتنشيط
الفهرسة و
أصبحنا
نشتغل على
العناوين
الافتراضية،
و هذا
القانون
يسري على
الجميع بما
فيهم دالتنا
التي تقوم بالترسيم،
بعبارة أخرى
فالعناوين
الحقيقية
ليس لها معنى
في الذاكرة
الافتراضية.
إذن لكي نلج
إلى مجلد
الصفحات و
جداول
الصفحات
عليهم أن
يتوفروا على
عناوين
افتراضية
صالحة بمعنى
أن يتم ترسيمهم
في مكان ما في
الفضاء
الافتراضي. لهذا قمت
بوضع خط تحت
بعض الجمل في
مراحل الترسيم
التي فوق. هذه
الأخيرة
توضح
العمليات
التي تلج إلى
جداول
الترجمة (مجلد
الصفحات+جداول
الصفحات). و
التي ينبغي
أن نوفر لها طرقا
ملائمة
للولوج
إليها في
الفضاء
الافتراضي. كما عي
العادة هناك
عدة حلول.
سأستعرض بعضا
منها هنا. الحل
الأول الذي
يتبادر إلى
الذهن، هو
وضع جداول
الترجمة في
فضاء النواة
الذي يستخدم الترسيم
المثلي – تذكر
في هذا
الفضاء تكون
العناوين الحقيقية
مساوية
للعناوين
الافتراضية –
هكذا يمكن أن
نستخدم
عناوين صور
الصفحات
التي يمدنا بها
المخصص
الحقيقي
مباشرة، حيث
أنها أيضا
عناوين
افتراضية.
هذا الحل
برغم سهولته
إلا أنه ليس
عمليا لأنه
سيستهلك
كمية كبيرة
من الذاكرة.
إذ علينا –
قبلا – تخصيص
المساحة
لمجلد
الصفحات و
جميع جداول
الصفحات،
بما أن كل
جدول صفحات
يأخذ 4كيلب من
الذاكرة فإن
المساحة
التي
سيأخذها هذا
الحل هي 4 ميغ
من الذاكرة
الحقيقية. تذكر
أن الترجمة
على مستويين –
باستخدام
جدول رئيسي و
جداول
ثانوية -
تهدف إلى
تلافي مثل
هذا الضياع
في الذاكرة
الحقيقية. حيث
أننا لا نخصص
كل جداول
الصفحات بل
فقط تلك التي
نستخدمها في
الوقت
الراهن
بينما نضع 0 في
البيت Present في
مداخل مجلد
الصفحات
التي لا توجد جداول
صفحات
مكافئة لها
في الذاكرة. حل آخر –
المستعمل في لينوكس –
يعتمد على
إمكانية صنع
صفحات بحجم 4 ميغ. إذا
رجعت إلى
الشكل الذي
يوضح بنية
مجلد الصفحات
ستلاحظ أن
البيت PS يمكننا من
صنع صفحات
تمتد على 4 ميغ
وأنه في هذه
الحالة
العنوان في
مجلد
الصفحات لا
يشير إلى
جدول
للصفحات بل
مباشرة إلى
صفحة في الذاكرة
من حجم 4ميغ.
باستعمال
هذه التقنية
يمكننا أن
نصنع ترسيما
مثليا لكل
الذاكرة
الحقيقية – في
حدود فضاء النواة
– بحيث يمكن
للنواة أن
تلج لكل موقع
في الذاكرة
الحقيقية
بما فيها
المواقع
التي تضم جداول
الترجمة.
مثلا إذا كنا
نتوفر على
ذاكرة
حقيقية بحجم
64ميغ. سنقوم بترسيم
مثلي لهذه
الذاكرة
كلية في فضاء
النواة الافتراضي.
ميزة هذه
الطريق أنها
تسهل كثيرا
برمجة باقي
مكونات
النواة حيث
تتيح
استخداما
مباشرا لصور
الصفحات
التي تؤخذ من
المخصص
الحقيقي. كما
أن استخدام
مداخل من حجم
4ميغ يجنبنا
تضييع
المساحات
التي تأخذها
جداول
الصفحات. بالمقابل،
فإن استعمال
هذه التقنية
ممكن فقط في
حدود فضاء
النواة. مثلا
إذا كانت
النواة
تستعمل فقط 1جيغ
من ال4جيغ
المشكلة
للفضاء
الافتراضي
فإن أقصى حجم
للذاكرة
يمكن للنواة ترسيمه
بهذه
الطريقة هو
1جيغ (إذا
أرادت
النواة أن
ترسم 4جيغ
كلها بهذه
الطريقة
عليها
استعمال كل
مداخل مجلد
الصفحات مما لا
يبقي شيئا
لبرامج
المستعمل). أي
أنه للولوج
إلى الذاكرة
العليا High Memory
(الذاكرة
التي لا
تستطيع
النواة
استعمالها مباشرة)
يجب استعمال
تقنيات أخرى
(لهذا تجد في نواة
لينوكس
دالة تدعى kmalloc تخصص
فقط في فضاء
النواة و
دالة أخرى vmalloc تتسطيع
أن تخصص في
الذاكرة
العليا). هناك
حل آخر شائع،
و هو الذي
سنستعيره في
درسنا، يعتمد
على وضع
جداول
الترجمة في
مكان مسبق في
الذاكرة الافتراضية.
نحن نعرف أن
جداول
الصفحات
تأخذ كلية 4 ميغ من الذاكرة.
سنخصص
"نافذة" من
حجم 4ميغ في
الفضاء الافتراضي
(و ليس
الحقيقي)
للاستعمال
خصيصا من
جداول
الصفحات.
انتبه لن
نقوم بتخصيص
أي صفحة
حقيقية
مسبقة بل فقط
نحدد منطقة
مسبقة من 4ميغ
في الذاكرة
الافتراضية
و ستحدد لكل
جدول صفحات
منطقة من
4كيلب في هذا
الفضاء. عندما
نريد
استعمال
جدول
للصفحات نحن
نعرف مسبقا بناء
على ترتيب
هذا الجدول
أين سنجده في
هذه "النافذة".
للتوضيح
أكثر سنلجأ
إلى بعض
الأمثلة. مثلا
نأخذ آخر 4ميغ
في الفضاء
الافتراضي
(أي ال4ميغ
العليا في
الذاكرة
الافتراضية)
لنسمي هذه
المنطقة
"منطقة الترسيم".
نحن نعرف أن
كل جدول
للصفحات
يستطيع
عنونة 4 ميغ
في الذاكرة
الافتراضية.
"سنحجز" إذن 4 كيلب
الأولى من
منطقة الترسيم
للجدول الذي
يعنون
الفضاء
الافتراضي
من0 الى
4ميغ، ثم نحجز
ال4كيلب
الثانية
للجدول الذي
يعنون من 4 إلى 8
و هكذا. ليس
ضروريا أن
نقوم بتخصيص
صور حقيقة لهذه
الجداول في
الحين، يكفي
أن نعرف أن
جداول الصفحات
ستحتل هذه
المواقع
المحجوزة
لها، وعندما
نريد أن نلج
لجدول غير
موجود في
الذاكرة
الحقيقية
سنرسمه في
هذا المكان
المحجوز له بالضبط. الآن،
القارئ
النبيه لا بد
سيلاحظ شيئا
فريدا هنا.
بما أن كل
جدول في
منطقة الترسيم
يعنون ال4ميغ
المكافئة
لترتيبه في
هذه المنطقة
فإن آخر جدول
في هذه
المنطقة
سيعنون طبعا
آخر 4ميغ في
الفضاء
الافتراضي.
لكن هذه
الأخيرة
ليست 4ميغ
عادية لأنها
تضم منطقة الترسيم.
أي أن الجدول
الأخير
سيعنون
منطقة الترسيم
نفسها، هذا
الجدول
سيعمل مثل
مرآة لأنه
بدل أن
يرسلنا إلى
منطقة أخرى
فإنه يعيدنا
إلى منطقة الترسيم،
سنسمي موقع
هذا الجدول بمنطقة
الانعكاس. الآن
لندفع
الأمور
أكثر، ماذا
يحدث لو
اخترنا، بدل
وضع جدول
صفحات في
منطقة
الانعكاس،
أن نضع مجلد
الصفحات
نفسه؟ لقد
رأينا أن
بنية مجلد
الصفحات
شبيهة ببنية جداول
الصفحات.
بعبارة أخرى
فإن مجلد
الصفحات، بالإضافة
إلى وظيفته
الأساسية و
هي عنونة
مواقع جداول
الصفحات،
"سيشتغل"
أيضا كجدول
صفحات عادي
لعنونة آخر
4ميغ في
الذاكرة أي
منطقة الترسيم. هناك
ميزة أساسية
لهذه
الطريقة،
عندما تقوم بتغيير
مدخل في مجلد
الصفحات
فإننا نضرب
عصفورين
بحجر واحد :
تذكر عندما
نريد ترسيم
صفحة عادية
فإننا نحتاج
لتغيير
مدخلين: مدخل في
مجلد
الصفحات و
آخر في جدول
الصفحات. لكن
بما أن مجلد
الصفحات
يشغل
الوظيفتين
معا بالنسبة
لجداول
الصفحات (1-
مجلد
الصفحات لكل
الذاكرة + 2-
جدول
الصفحات
لمنطقة الترسيم
التي تضم
جداول
الصفحات).
فإنه لترسيم
جدول
للصفحات في
منطقة الترسيم
يكفي تغيير
المدخل
المكافئ في
مجلد
الصفحات فنتمم
العمليتين
بضربة واحدة. يمكننا
تلخيص هذه
التقنية –
التي
سنسميها الترسيم
الذاتي –
فيما يلي: -
نضع
(بمعنى حجز
عناوين
افتراضية
وليس
بالضرورة
صور صفحات
حقيقية) مجلد
الصفحات و
جداول في
منطقة مسطرة
على 4ميغ – 0 أو
4ميغ او
8ميغ...- و من حجم
4ميغ. -
داخل هذه
المنطقة
سنرتب جداول
الصفحات
بحسب ال4ميغ
التي
تعنونها في
الذاكرة:
جدول الصفحات
0 يوضع في أول
صفحة في
منطقة الترسيم،
جدول
الصفحات رقم 1
يوضع في ثاني
صفحة وهكذا. -
سنعتبر
مجلد
الصفحات –
بالإضافة
لوظيفته الطبيعية
– كجدول صفحات
لعنونة
منطقة الترسيم
التي تضم
جداول
الصفحات. هذا
يعني أن
ترتيبه في
منطقة الترسيم
مكافئ
لعنوان
منطقة الترسيم
في الذاكرة:
إذا وضعنا
منطقة الترسيم
في أول 4ميغ في
الذاكرة
الافتراضية
فإن مجلد الصفحات
يجب أن يوضع
في أول صفحة
في منطقة الترسيم،
إذا وضعنا
منطقة الترسيم
في ثاني 4ميغ
نضع مجلد
الصفحات في
ثانية صفحة و
هكذا. سنسمي رقم هذا
الترتيب بالمدخل
الذاتي. مما
سبق، يمكننا
حل مشكلة
الولوج على
جداول الترجمة
في الذاكرة
الافتراضية
بطريقة سلسة: -
نريد أن
نلج إلى مجلد
الصفحات نستعمل
الصيغة
التالية عنوان
منطقة الترسيم
+ (المدخل
الذاتي * حجم
الصفحة) -
نريد أن
نلج إلى
جدول
الصفحات رقم
"س" نستعمل
الصيغة
التالية عنوان
منطقة الترسيم
+ (س * حجم
الصفحة) هذا يعني
أننا نحتاج
فقط لتذكر
قيمة المدخل
الذاتي لأن
عنوان منطقة الترسيم
يتحدد
تلقائيا
بهذه القيمة
(المدخل
الذاتي * 4ميغ). أعرف
أن استيعاب
هذه الطريقة
أمر صعب في
البداية (أذكر
أنها سببت لي
صداعا عندما
قرأتها أول
مرة). لكنها تتطلب فقط
وقتا للتآلف
معها. أما تنجيزها
فيظل أمرا
سهلا
بالمقارنة. مخزون
الترجمة Translatin lookaside
buffers TLB كما
رأينا فإن
مجرد الولوج
إلى موقع في
الذاكرة الافتراضية
أصبح يتطلب
عدة عمليات
ولوج وسيطة
لمجلد
الصفحات و
جداول
الصفحات.
عملية الولوج
إلى الذاكرة
تعتبر مكلفة
بالمقارنة
مع سرعة عمل
المعالج. من
أجل ذلك توجد
على الحاسب
ذاكرة صغيرة
تعمل مثل
مخزون cache لترجمة
الصفحات.
تحتوي هذه
الذاكرة على
العناوين
الحقيقية
للصفحات
التي تم
الولوج
إليها مؤخرا. هكذا بدل
الولوج إلى
جداول
الترجمة عند
كل ولوج إلى
الذاكرة فإن
المعالج
يقوم أولا
بالنظر ما
إذا كانت الترجمة
متوفرة في
مخزون
الترجمة TLB. باستعمال
هذه التقنية
فإن الأداء
قد يتحسن
بصورة ملحوظة
إذا كان
البرنامج
يستعمل
عناوين
متقاربة - بلغة
تقنية نقول
أن البرنامج
لديه محلية
جيدة Good locality -. ملحوظة:
لا يجب الخلط
بين مخزون
الترجمة TLB و مخزونات
المعالج
الأخرى L1 & L2 caches. برغم
أن مبدأ
المحلية
يسري على
جميع هذه المخزونات. ينبغي
جيدا إدراك
أن الأخذ
بعين
الاعتبار عملية
التخزين في TLB ليس ترفا أو
اختيارا
يمكن تجاهله.
تذكر جيدا
أنه إذا تم تغيير
الترجمة
لصفحة ما –
مثلا بفهرسة
صورة صفحة في
مدخل كان
يشير سابقا
إلى "غير
موجود"- و لم
يتم إخبار
المعالج بهذا
التغيير،
فإن المعالج
سيظل يستعمل
الترجمة
القديمة في
حال ما كانت
موجودة قبلا
في مخزون
الترجمة. لذلك
ينبغي
الانتباه
إلى تحديث
معلومات
المعالج عند
تغيير ترجمة
كل مدخل سواء
في مجلد
الصفحات أو
في جداول
الصفحات. لتحديث
مداخل الTLB يمكننا
استعمال
التعليمة invlpg <عنوان
الصفحة>
2- التنجيز في هذا
الدرس سننشئ
فئة جديدة Pager
مهمتها : 1- تنشيط
الذاكرة
الافتراضية 2- ترسيم
الصفحات أو
محوها من
الذاكرة
الافتراضية. فيما يلي
تعريف الفئة Pager. class Pager { … هذين المتغيرين يعرفان حدود المنطقة الممنوع الترسيم فيها: من بداية ذاكرة الفيديو إلى نهاية النواة
DWORD baseDeneid, topDeneid; … public: مصنع الفئة يأخذ مؤشرا على مخصص الذاكرة الحقيقية
Pager(PhysMManager *pmm);
تقوم بتثبيت جداول الترجمة و تنشيط الذاكرة الافتراضية
pde_t* initPaging(physAddr_t kbase,
physAddr_t ktop); تقوم بترسيم صورة صفحة paddr في الصفحة الافتراضية vaddr
int map(virtAddr_t vaddr,
physAddr_t paddr, DWORD attribs); تقوم بمحو صورة الصفحة في العنوان الافتراضي vaddr
int unmap(virtAddr_t vaddr); };
لتنجيز الفئة Pager نحتاج أولا لتعريف بعض القيم و المتغيرات مثل منطقة الترسيم رقم
المدخل
الذاتي =
ترتيب مجلد
الصفحات في
منطقة الترسيم #define SELF_MAP_INDEX 255 عنوان منطقة الترسيم : في آخر 4ميغ في فضاء النواة 4ميغ-1جيغ = المدخل الذاتي*4ميغ #define
ZONE_MAPPING
(SELF_MAP_INDEX<<22) حجم منطقة الترسيم 4ميغ #define ZONE_MAPPING_SIZE
(1UL<<22) عنوان مجلد الصفحات = عنوان منطقة الترسيم+(رقم المدخل الذاتي*4كيلب) #define
PAGE_DIR (ZONE_MAPPING+(SELF_MAP_INDEX<<12))
عنوان المدخل الذاتي في مجلد الصفحات=عنوان مجلد الصفحات+المدخل الذاتي*4 هذا
المدخل يجعل
مجلد
الصفحات
يؤشر على
نفسه كجدول
صفحات. #define SELF_PDE_MAP
(PAGE_DIR+(SELF_MAP_INDEX*4)) عنوان بداية فضاء تطبيقات المستعمل: بعد فضاء النواة مباشرة #define
USER_ZONE (ZONE_MAPPING+ ZONE_MAPPING_SIZE) حجم الصفحة الافتراضية #define VPAGE_SHIFT 12
#define
VPAGE_SIZE 4096 لاستخلاص البيتات ما بعد 12 بيت #define
VPAGE_MASK (VAGE_SIZE-1) #define PDE_FLAGS_MASk
(VPAGE_SIZE-1) #define PTE_FLAGS_MASk (VPAGE_SIZE-1)
#define PDE_SHIFT 22 #define PTE_SHIFT 12
تعريف البيتات المشكلة لمداخل جداول الترجمة #define
PRESENT 1 #define WRITE 2 #define USER 4 #define WRITE_THROUGH 8 #define CACHE_DISABLE 16 #define ACESSED 32 #define DIRTY 64 #define PT_ATTRIB 128 #define GLOBAL 256
سنستعمل unsigned int لتعريف مداخل مجلد و جداول الصفحات. typedef unsigned int pde_t; //مدخل في
مجلد
الصفحات typedef unsigned int pte_t;
//مدخل
في جدول
الصفحات #define NULL_PDE 0 #define NULL_PTE 0
ثم بعض
الدوال
المساعدة
للولوج عناوين
جداول
الترجمة const DWORD selfIndex
= 255; pte_t *pageTables = (pte_t *) ZONE_MAPPING; pde_t *pageDir = (pde_t *)PAGE_DIR; pde_t *selfPde = (pde_t *)SELF_PDE_MAP;
بعض الدوال لصنع مداخل جداول الترجمة inline void makePde(pde_t *pde, physAddr_t paddr, DWORD flags) { *pde = paddr | (flags & PDE_FLAGS_MASk); }
inline void makePte(pte_t *pte, physAddr_t paddr, DWORD flags) { *pte = paddr | (flags & PTE_FLAGS_MASk); }
دوال لاستخراج رقم المدخل في جداول الترجمة من العنوان الافتراضي inline DWORD pdeIndex(virtAddr_t vaddr) {
return (vaddr >>
PDE_SHIFT); }
inline DWORD pteIndex(virtAddr_t vaddr) { return ((vaddr >> PTE_SHIFT)& 0x3ff); } تستخرج البعد داخل الصفحة من العنوان الافتراضي inline DWORD pageOffset(virtAddr_t vaddr) {
return (vaddr & VPAGE_MASK); }
تعيد لنا عنوان المدخل في مجلد الصفحات مباشرة من العنوان الافتراضي inline pde_t* pdeAddr(virtAddr_t vaddr) { return
(pageDir+pdeIndex(vaddr)); }
تعيد لنا عنوان المدخل في جدول الصفحات مباشرة من العنوان الافتراضي inline pte_t* pteAddr(virtAddr_t vaddr) { return
pageTables+(vaddr>>VPAGE_SHIFT); }
للتيقن من أن العنوان لا ينتمي إلى المنطقة المحظورة inline bool isSystemAddr(virtAddr_t vaddr) {
return ((vaddr >= baseDeneid && vaddr < topDeneid) || !vaddr); };
للتيقن من أن العنوان لا ينتمي إلى منطقة الترسيم inline bool isMapAddr(virtAddr_t vaddr) {
return (vaddr >= ZONE_MAPPING && vaddr < USER_ZONE); };
تقوم
بتحديث مدخل
في TLB. ضرورية
بعد تغيير ترسيم
الصفحات void invlPage(virtAddr_t page) { asm volatile("invlpg %0" ::"m"(*(DWORD *)page));
}
لتحديث
جميع مداخل الTLB. يكفي
إعادة تحميل
المسجل cr3 void invlAllPages() { DWORD tmp; asm volatile( "mov %cr3, %0 \n\t\ mov %0, %cr3\n\t" :"=r"(tmp)::"memory"); }
لتخصيص صورة صفحة في الذاكرة الحقيقية. إذا استنفذت الذاكرة في هذه المرحلة لا نستطيع الاستمرار inline DWORD criticalAlloc(PhysMManager *pmm, DWORD flgs) {
DWORD frame = pmm->allocFrame(flgs); if(!frame) // MSG: no enough memory Kernel::FATAL_ERROR("لا توجد ذاكرة كافية\n"); return frame; }
الطريقة initPaging تقوم
بتثبيت
جداول
الترجمة ثم
تنشيط
الذاكرة
الافتراضية kbase:
بداية
المنطقة
المحظورة – ktop: نهاية
النواة
(العناوين
مسطرة على
4كيلب) pde_t* Pager::initPaging(physAddr_t kbase, physAddr_t ktop) { تخصيص صورة صفحة لمجلد الصفحات pde_t *pd = (pde_t*)criticalAlloc(pmem, 0); ثم
ملئ جميع
المداخل ب0 (= غير مموجودة Not present) for (int i = 0; i < 1024; ++i) {
pd[i] = 0; }
تثبيت الترسيم المثلي identity mapping للمنطقة المحظورة
pde_t *kpde; pte_t *kpte; for (physAddr_t paddr = kbase; paddr < ktop; paddr+=PAGE_SIZE) { استخراج المدخل في مجلد الصفحات لkbase. انتبه لا زلنا نشتغل في الذاكرة الحقيقية
kpde = pd + pdeIndex(paddr);
if(!(*kpde & PRESENT)) { جدول الصفحات غير موجود في هذا المدخل، نخصص صورة صفحة و نصنع جدول الصفحات
kpte = (pte_t*)criticalAlloc(pmem, 0);
for (int i = 0; i < 1024; ++i) { kpte[i] = 0; } makePde(kpde, (physAddr_t)kpte, PRESENT|WRITE); } makePte(kpte+pteIndex(paddr), paddr, PRESENT|WRITE); }
تثبيت الترسيم الذاتي self mapping. المدخل selfIndex يشير إلى مجلد الصفحات نفسه makePde(pd+selfIndex, (physAddr_t)pd, PRESENT|WRITE);
إعداد قيمة المسجل cr3 وتنشيط الفهرسة pbdr
= ((DWORD)pd);
asm volatile ( "movl %0, %%cr3 \n\t\ movl %%cr0, %%eax \n\t\ orl $0x80010000, %%eax \n\t\ movl %%eax, %%cr0 \n\t\ jmp 1f \n\t\ #prefetch queue لتنظيف 1: \n\t\" ::"r"(pbdr):"memory","eax"); baseDeneid = kbase; topDeneid = ktop; return pd;
}
الطريقة map تتكفل بترسيم صور الصفحات في الذاكرة الافتراضية vaddr
العنوان
الافتراضي
الذي سيتم الترسيم
فيه paddr
عنوان صورة
الصفحة المطلوب
ترسيمها user هل
الصفحة توجد
في فضاء
المستعمل (<> فضاء
النواة) int Pager::map(virtAddr_t vaddr, physAddr_t paddr, DWORD attribs)
{ أولا التأكد من تسطير العناوين على 4كيلب vaddr
= pageBase(vaddr);
paddr = pageBase(paddr);
لا يسمح بالترسيم في صورة النواة و منطقة الترسيم if(isSystemAddr(vaddr) || isMapAddr(vaddr))
return INVPARAM; استخلاص عنوان المدخل في مجلد الصفحات pde_t
*pde = pdeAddr(vaddr);
إذا كان المدخل فارغا. نصنع جدول صفحات جديد if(!(*pde & PRESENT)) { عنوان
جدول الصفحة
مكافئ
لترتيبه في
مجلد الصفحات
pte_t *pt = (pte_t*)(PAGES_TABLES+(pdeIndex(vaddr)*PAGE_SIZE));
physAddr_t ppt = pmem->allocFrame(0); if(!ppt) return NOMEM; DWORD flags = PRESENT | WRITE | ((vaddr < USER_ZONE)?0:USER);
لترسيم جدول صفحات يكفي تغيير قيمة المدخل في مجلد الصفحات
makePde(pde, (physAddr_t)ppt, flags); و لا
ننسى تحديث
بيانات الTLB
invlPage((DWORD)pt);
for (int i = 0; i < 1024; ++i) { pt[i] = 0; } }
الآن تأكدنا من وجود جدول الصفحات. نقوم بترسيم الصفحة في هذا الجدول pte_t *pte = pteAddr(vaddr); makePte(pte, (physAddr_t)paddr, attribs); invlPage(vaddr); return SUCCESS; }
عكسا، الطريقة unmap تقوم بمحو ترسيم صورة صفحة في الذاكرة vaddr
عنوان
الصفحة
(الافتراضي)
المطلوب
إزالتها int Pager::unmap(virtAddr_t vaddr) {
vaddr = pageBase(vaddr);
غير مسموح إزالة صفحات النواة و منطقة الترسيم if(isSystemAddr(vaddr) || isMapAddr(vaddr))
return INVPARAM;
استخراج المدخل في مجلد الصفحات pde_t
*pde = pdeAddr(vaddr);
إذا كان جدول الصفحات موجودا نزيل ترسيم الصفحة منه if((*pde & PRESENT)) {
*(pteAddr(vaddr)) = 0; } return SUCCESS; }
الآن و بعد أن أتممنا تنجيز الفئة Pager. علينا صنع عنصر من هذه الفئة و نداء الطريقة initPaging لتثبيت الذاكرة الافتراضية. سنقوم بذلك، على غرار ما فعلنا مع المخصص الحقيقي، في شفرة الفئة Kernel. لهذا الغرض أنشئنا الطريقة initPaging في الفئة Kernel void Kernel::initPaging() {
pager = new Pager(physMManager); ننادي الطريقة initPaging للعنصر pager. بداية الترسيم المثلي في START_VGA حيث تبدأ ذاكرة الفيجا، ونهايته مع نهاية النواة مسطرة على 4كيلب. pager->initPaging(START_VGA, pageTop(kernelEnd)); }
كل ما
علينا فعله
الآن هو نداء Kernel::initPaging في
الدالة Kmain. تجربة
الدرس تجربة
هذا الدرس
بسيطة جدا و غرضها
إبراز كيف
تتيح
الذاكرة
الافتراضية
اللعب بفضاء
عنونة
المعالج. في
البدء و قبل
تنشيط
الفهرسة
سنخصص صورة
صفحة حقيقية
و كتابة العدد
0x55AA في بداية
هذه الصفحة.
بعد تنشيط
الذاكرة
الافتراضية
سنقوم بترسيم
صورة الصفحة
نفسها في
عنوانين
مختلفين. إذا
نجح الترسيم
فإن قراءة
العنوانين
الجديدين
ستعيد لنا نفس
القيمة
المكتوبة في
العنوان
الحقيقي
القديم. extern "C" void kmain(DWORD magic, multiboot_info
*mbi) { ... Kernel::initPhysMManager();
قبل تنشيط الذاكرة الافتراضية نخصص صفحة و نكتب فيها العدد 0x55AA DWORD *paddr = (DWORD *)Kernel::physMManager->allocFrame(0);
*paddr = 0x55AA; video->printf("القيمة في العنوان %x = %x\n", (DWORD)paddr, *paddr); ... ننشط الذاكرة الافتراضية Kernel::initPaging(); video->printf("[نجح]\n");
video->printf("تجريب المفهرس الافتراضي\n");
سنرسم صورة الصفحة السابقة في عنوانين مختلفين ثم نتأكد من وجود القيمة 0x55AA فيهما عبر الدالة testVirtMem
DWORD *vad = (DWORD *) 0xA00000; DWORD *vad1 = (DWORD *) 0xB10000;
testVirtMem(paddr, vad); testVirtMem(paddr, vad1);
نمحي ترسيم الصفحة في 0xB10000 ثم نحاول الكتابة في الصفحة. بما أن الصفحة لم تعد موجودة فإن المعالج ينبغي أن يعلن استثناء Page Fault في هذا العنوان Kernel::pager->unmap((virtAddr_t)0xB10000);
*(vad1+1) = 0x51; ...
}
تقوم بترسيم صورة الصفحة السابقة و التأكد من وجود العدد 0x55AA void testVirtMem(DWORD *paddr, DWORD *vaddr) {
int err = Kernel::pager->map((virtAddr_t)vaddr,
(physAddr_t)paddr, false); if(err<0) error(video, err);
if(*vaddr != 0x55AA) video->printf("خطأ، القيمة في العنوان %x = %x غير مطابقة\n", (DWORD)vaddr, *vaddr ); else video->printf("نجح،القيمة في العنوان %x = %x مطابقة\n", (DWORD)vaddr, *vaddr); }
ملف الmakefile للترجمة (لا زلت متكاسلا في إدراج الترابطات dependencoes L). CPPFLAGS = -c -fno-builtin
-fno-rtti -fno-exceptions .PHONY: clean
OBJS = multiboot.o system.o Machine.o pic.o Kernel.o IntManager.o \ main.o FontMap.o video.o isrs.o exception.o timer.o PFrameStack.o \ physMManager.o errors.o Pager.o
all: kernel.elf
kernel.elf: $(OBJS) link.lds ld -T link.lds $(OBJS) -o kernel.tmp objcopy -O elf32-i386 kernel.tmp kernel.elf $(RM) kernel.tmp
clean: $(RM) $(OBJS)
%.o: %.cpp g++ $(CPPFLAGS) $< -o $@
%.o: %.s nasm -f elf -DLEADING_USCORE $< -o $@
-DLEADING_USCORE فقط إذا كان المترجم يضيف _ على أسماء الرموز (كما في ويندوز) لمحة من الشاشة
لا تنسوا
الدرس
القادم حيث
سننجز مخصص
الذاكرة
الافتراضية.
في انتظار ذلك
أرجو قراء موفقة
للجميع. ياسين
الوافي طنجة
– المغرب 17-03-2006 الساعة 17h35mn
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||