Professional Documents
Culture Documents
علوم الحاسوب من الألف إلى الياء أكاديمية حسوب v1.0 1
علوم الحاسوب من الألف إلى الياء أكاديمية حسوب v1.0 1
Translator: Ola Abbas - Zainab Al Zaeem المترجم :عال عباس -زينب الزعيم
Editor: Ayat Alyatakan - Jamil Bailony المحرر :آيات اليطقان -جميل بيلوني
https://academy.hsoub.com
academy@hsoub.com
Copyright Notice إشعار حقوق التأليف والنشر
The author publishes this work under ينشر المصِّنف هذا العمل وفقا لرخصة المشاع
Creative Commons Attribution- الترخيص- غير تجاري- اإلبداعي َنسب الُم صَّنف
NonCommercial-ShareAlike 4.0 .)CC BY-NC-SA 4.0( دولي4.0 بالمثل
International (CC BY-NC-SA 4.0).
This license is acceptable for Free Cultural .هذه الرخصة متوافقة مع أعمال الثقافة الحرة
Works. ال يمكن للمرِّخص إلغاء هذه الصالحيات طالما
The licensor cannot revoke these freedoms :اتبعت شروط الرخصة
as long as you follow the license terms:
• Attribution — You must give َنسب الُم صَّنف — يجب عليك َنسب •
appropriate credit, provide a link to وتوفير،العمل لصاحبه بطريقة مناسبة
the license, and indicate if changes وبيان إذا ما قد ُأجريت أي،رابط للترخيص
were made. You may do so in any يمكنك القيام بهذا.تعديالت عىل العمل
reasonable manner, but not in any ولكن عىل أال يتم ذلك،بأي طريقة مناسبة
way that suggests the licensor بطريقة توحي بأن المؤلف أو المرِّخص
endorses you or your use. .مؤيد لك أو لعملك
• NonCommercial — You may not use غير تجاري — ال يمكنك استخدام هذا •
the material for commercial .العمل ألغراض تجارية
purposes. ،الترخيص بالمثل — إذا قمت بأي تعديل •
• ShareAlike — If you remix, فيجب، أو إضافة عىل هذا العمل،تغيير
transform, or build upon the عليك توزيع العمل الناتج بنفس شروط
material, you must distribute your .ترخيص العمل األصلي
contributions under the same
license as the original.
No additional restrictions — You may not منع القيود اإلضافية — يجب عليك أال تطبق أي
apply legal terms or technological شروط قانونية أو تدابير تكنولوجية تقيد اآلخرين من
measures that legally restrict others from .ممارسة الصالحيات التي تسمح بها الرخصة
doing anything the license permits. :اقرأ النص الكامل للرخصة عبر الرابط التالي
Read the text of the full license on the
following link:
https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode
The illustrations used in this book is created الصور المستخدمة في هذا الكتاب من إعداد
by the author and all are licensed with a المؤلف وهي كلها مرخصة برخصة متوافقة مع
license compatible with the previously .الرخصة السابقة
stated license.
عن الناشـر
ُأنتج هذا الكتاب برعاية شركة حسوب وأكاديمية حسوب.
تهدف أكاديمية حسوب إىل تعليم البرمجة باللغة العربية وإثراء المحتوى البرمجي العttربي عttبر توفttير دورات
برمجة وكتب ودروس عالية الجودة من متخصصين في مجال البرمجة والمجاالت التقنية األخttرى ،باإلضttافة إىل
توفير قسم لألسئلة واألجوبة لإلجابة عىل أي سؤال يواجه المتعلم خالل رحلته التعليمية لتكون معه وتؤهلttه حttتى
حسوب شركة تقنية في مهمة لتطوير العالم العttربي .تبttني حسttوب منتجttات ترِّك ز عىل تحسttين مسttتقبل
العمل ،والتعليم ،والتواصل .تدير حسوب أكبر منصتي عمل حر في العttالم العttربي ،مسttتقل وخمسttات ويعمttل
جدول المحتويات
20 تمهيد
23 المساهمة
29 1.2.2المكتبات
41 .2التحويل
7
علوم الحاسوب من األلف إىل الياء جدول المحتويات
50 2.2.3األنواع
8
علوم الحاسوب من األلف إىل الياء جدول المحتويات
68 3.1.2الدورات
9
علوم الحاسوب من األلف إىل الياء جدول المحتويات
98 4.1.4األمن
99 4.1.5األداء
10
علوم الحاسوب من األلف إىل الياء جدول المحتويات
121 5.2.2الذاكرة
11
علوم الحاسوب من األلف إىل الياء جدول المحتويات
143 6.5.1الصفحة
145 6.6.2الحماية
12
علوم الحاسوب من األلف إىل الياء جدول المحتويات
157 ب .أداة إيتانيوم العتادية للمرور عىل جدول الصفحات Page-Table
164 7.3.1الصياغة
13
علوم الحاسوب من األلف إىل الياء جدول المحتويات
173 7.6تطبيق عملي لبناء برنامج تنفيذي من شيفرة مصدرية بلغة C
14
علوم الحاسوب من األلف إىل الياء جدول المحتويات
199 8.5المكتبات
15
علوم الحاسوب من األلف إىل الياء جدول المحتويات
16
علوم الحاسوب من األلف إىل الياء جدول المحتويات
فهرس األشكال
83 شكل :12نظرة عامة عىل متحكم ( UCHIمأخوذة من توثيق إنتل )Intel
17
علوم الحاسوب من األلف إىل الياء جدول المحتويات
18
علوم الحاسوب من األلف إىل الياء جدول المحتويات
فهرس الجداول
52 جدول :16أنواع وأحجام أنواع البيانات القياسية التي تتضمن قيمة مفردة
19
تمهيد
يوفر كتاب (علوم الحاسوب من األلف إىل الياء) معلومات شاملة حول علوم الحاسttوب ،ويشttرح المواضttيع
األساسية لفهم آليttة عمttل عتttاد الحاسttوب ونظttام تشttغيله بأسttلوب تصttاعدي يبttدأ من شttرح التفاصttيل ذات
المستوى المنخفض ،ثم ينتقل تدريجًي ا إىل مفاهيم أكثر تقدًم ا كي يساعدك عىل فهمها بسهولة أكبر.
حيث يبدأ الكتاب بشرح المفاهيم األساسية التي تبنى عليها أجهزة الحاسوب مثل طريقttة تمثيttل البيانttات
باستخدام النظام الثنائي والست عشري ويشرح أهم العمليات البوليانية التي تنفذ عليها ،ثم يتعمق في الفصول
الالجقة في موضوعات ومفاهيم متقدمة كشرح الذاكرة الوهمية virtual memoryوآلية عملهttا وطريقttة عمttل
كمttا يشttرح الكتttاب العديttد من المواضttيع الttتي تهم المttبرمجين ويوضttح طريقttة عمttل سلسttلة األدوات
Toolchainالتي تتعامل مع الtبرامج الحاسttوبية ،وأهم االختالفttات بين اللغttات الُم صَّtرفة compiledواللغttات
الُم فَّس رة interpretedإىل جtانب توضtيح مجموعtة واسtعة من المفtاهيم األخtرى المتعلقtة بعلtوم الحاسtوب
ال تحتاج إىل أن تكون مبرمًجا خبيًرا لفهم المواضيع الtواردة في هttذا الكتttاب ،لكنtك تحتttاج المتالك معرفtة
أساسية بأجهزة الحاسوب ومكوناته ومفهوم نظام التشغيل Operating Systemومعرفة بأساسيات البرمجة.
حول الكتاب
هttذا الكتttاب هttو ترجمttة لكتttاب Computer Science from the Bottom Upلكاتبttه إيttان وينانttد
Ian Wienandويوضح كافة المفاهيم التي يحتاج القtارئ لمعرفتهtا حtول عتtاد وبرمجيtات الحاسtوب ونظtام
يوفر الفصل األول عن نظرة متقدمة عىل نظام التشغيل يونكس ولغttة Cحيث ويوضttح مفهttوم "كttل شttيء
عبارة عن ملف" الذي يعني أن كل شيء في نظام يttونكس يعttد ملًف ا بمttا في ذلttك األجهttزة والttبرامج ودور هttذا
المفهوم في تسهيل إدارة النظام وتطوير البرامج ،ويشرح كذلك مفهوم التجريد abstractionفي لغة Cوكيفيttة
استخدام القوالب templatesإلنشاء تجريد عttام ألنtواع البيانttات أو الوظttائف وكتابttة كttود أكttثر مرونttة ،ويقttدم
لمفهوم المكتبات ومفهوم واصفات الملفات file descriptorsواستخدام واصف الملف لفتح ملف ما وقttراءة
البيانات منه ،وأخيًرا يشرح باألمثلة العملية طريقة استخدام صدفة يونكس.
ينتقل الفصل الثاني لشرح طريقة تمثيل البيانات في الحاسوب من خالل نظام العد الثنائي ويوضح مفاهيم
البتات والبايتات والتكافؤ وأنظمة البت المختلفttة والعمليttات البوليانيttة مثttل NOTو ANDو ORو .XORكمttا
يتناول النظام الست عشري ويشرح طريقة التحويل بين األنظمة العددية واسttتخدامها في الشttيفرات البرمجيttة،
كما يناقش طريقة تمثيل األعداد مثل األعداد العشرية والسالبة بهذه األنظمة.
أما الفصل الفصل الثالث فيتناول معمارية الحاسوب الداخليttة ،ويوضttح وظيفttة وحttدة المعالجttة المركزيttة
CPUوالعمليات األساسية التي تقوم بها وأنواع معماريات وحدة المعالجة المركزية مثل CISCو RISCو ،EPIC
ثم يشرح آلية عمل ذاكرة الحاسوب وتسلسل الذواكر الهرمي والذاكرة المخبئيttة وطريقttة عنونتهttا ،وأخttيًرا يشttرح
وينتقل الفصل الرابع لشرح آلية عمل نظام التشغيل ودوره في الحاسوب وتنظيمه الttذي يشttمل نttواة نظttام
التشغيل ومجاالت المستخدم والوحدات واالفتراضية ،كما يشرح استدعاءات النظام وهي واجهات يوفرهtا نظttام
التشغيل للبرامج لتفاعلها مع العتاد ،ويشرح طريقة إدارة الصالحيات في نظام التشغيل باستخدام األمثلttة حيث
يوضح عىل سبيل المثال كيف يستطيع نظام التشغيل منع برنامج ما من الوصول إىل بيانات برنامج آخر.
ويتوسع الفصل الخامس في شرح مفهوم العمليttات ودورهttا في تمكين نظttام التشttغيل من تشttغيل عttدة
برامج في نفس الوقت ويوضح عناصر العملية وتسلسtل العمليtات الهtرمي وكيفيtة ارتبtاط العمليtات ببعضtها
البعض ،ويناقش بعد ذلك استدعاءات النظام forkو execالمستخدمة إلنشاء عمليات جديدة وتنفيذ ملفttات
جديدة كما يوضح مفهوم الجدولة Schedulingالتي تمكن نظام التشغيل من تحديد ما هي العملية التي ستنفذ
ويتناول الفصل السادس طريقة عمل الذاكرة الوهمية التي هي تقنية تسمح للttبرامج بالوصttول إىل مسttاحة
من الذاكرة أوسع من المساحة المتاحة فعلًي ا عن طريق تقسيم الذاكرة الفعلية إىل صفحات بحجم ثttابت وتخttزين
هذه الصفحات في الذاكرة الفعلية أو عىل القرص الصلب ،وينttاقش بعض المفttاهيم األخttرى المتعلقttة بالttذاكرة
الوهمية مثل فضاءات العناوين والحماية والتبttديل ومشttاركة الttذاكرة والttذاكرة المخبيئttة للقttرص الصttلب ودعم
ويتطرق الفصل السابع لشرح مفهوم سلسلة األدوات ،Toolchainوهي مجموعة من الttبرامج الttتي تعمttل
مًع ا لتحويل شيفرة المصttدر إىل برنttامج قابttل للتنفيttذ ويعرفttك عىل نttوعين رئيسttيين من الttبرامج في سلسttلة
21
علوم الحاسوب من األلف إىل الياء تمهيد
األدوات همtا الtبرامج الُم َص َّرفة compiled programsوالtبرامج الُم َفَّس رة ،interpreted programsكمtا
ستتعرف عىل مفهوم اآلالت االفتراضية virtual machinesوهي نtوع من الtبرامج يمكنهtا تشtغيل تطبيقtات
مكتوبة بلغات مختلفة وتتعرف عىل الخطوات األساسية لبنttاء ملttف قابttل للتنفيttذ وهي التصttريف ،والتجميttع،
ويتوسع الفصل الثامن في شرح طريقة تمثيل الملفtات القابلtة للتنفيtذ والصtيغ المختلفtة لهtذه الملفtات
وأبرزها ملفات ELFويعرفك عىل مفهوم واجهات ABIوأنواعها ،كما يناقش مفهtوم المكتبtات وأنواعهtا ويوضttح
وأخيًرا يشرح الفصل التاسttع مفهttوم الربttط الttديناميكي وهttو تقنيttة تسttمح بتحميttل المكتبttات المشttتركة
ديناميكًي ا عند تشغيل البرامج ويبين فوائده ومميزاته كما يناقش أيًض ا بعض المفاهيم المتقدمة المتعلقة بالربttط
الديناميكي مثل االنتقtاالت وجtدول اإلزاحtة العtام وجtدول البحث عن اإلجtراءات ودورهtا في تسtهيل مشtاركة
عند انتهائك من فصول هذا الكتاب ستكون قادًرا عىل فهم كيفية عمttل الحاسttوب من المسttتوى المبتttدئ
إىل المستوى المتقدم وتفهم بتفصيل أكبر كيفيtة عمtل نظtام التشtغيل و إدارة الtذاكرة وطريقtة إنشtاء الtبرامج،
وكيفية بدء العمليات وستكون قادًرا عىل فهم معمارية الحاسوب والتعامل معه بكفاءة أكبر.
أضفنا المصطلحات األجنبية بجانب المصطلحات العربية لسببين ،أولهما التعرف عىل المصطلحات العربية
المقابلة للمصطلحات األجنبيttة األكttثر شttيوًع ا وعttدم الخلttط بين أي منهttا ،وثانًي ا تأهيلttك لالطالع عىل المراجttع
فتصبح محيًط ا بعد قراءة الكتاب بالمصطلحات األجنبية التي تخص أنظمة التشغيل ومعمارية الحاسوب وبttذلك
يمكنك قراءتها وفهمها وربطها بسهولة مع المصطلحات العربية المقابلة والبحث عنهtا والتوسtع فيهtا إن شtئت
وأيًض ا يسهل عليك قراءة الشيفرات وفهمها .عموًم ا ،نذكر المصطلح األجنبي بجttانب العttربي في أول ذكttر لttه ثم
نكمل بالمصطلح العربي ،فإذا انتقلت إىل قراءة فصول محددة من الكتاب دون تسلسttل ،فتttذكر إن مttررت عىل
أي مصطلح عربي أننا ذكرنا المصطلح األجنبي المقابل له في موضع سابق.
استخدام الشيفرة
شيفرة أمثلة هذا الكتاب متاحة في المسttتودع https://github.com/ianw/bottomupcsحيث أن Git
هو نظام تحكم باإلصدارات يسمح لك بتتبع الملفات التي يتكون منها المشروع ،وتسمى مجموعة الملفات الttتي
يتحكم بها Gitبالمستودع ،repositoryو GitHubهي خدمة استضافة تttوفر تخزيًن ا لمسttتودعات Gitوواجهttة
ويب مالئمة انظر فيديو "أساسيات "Gitوقسم Gitفي أكاديمية حسوب لمزيد من التفاصيل.
توّف ر صفحة GitHubالرئيسية لمستودع شيفرات الكتاب عدة طرق للعمل معها هي:
22
علوم الحاسوب من األلف إىل الياء تمهيد
يمكنttك إنشttاء نسttخة من المسttتودع عىل Forkبالضttغط عىل زر .Forkإذا لم يكن لttديك حسttاب •
،GitHubفستحتاج إىل إنشاء حساب ،ثم سيصبح لديك مستودعك الخاص عىل GitHubبعد ضغطك
عىل زر Forkوالذي يمكنك استخدامه لتتبttع التعليمttات البرمجيttة الttتي تكتبهttا أثنttاء العمttل عىل هttذا
الكتاب ،ثم يمكنك استنساخ cloneالمستودع repositoryأو اختصاًرا ،repoممttا يعttني أنttك تنسttخ
أو يمكنك استنساخ المستودع فال تحتاج إىل حساب GitHubللقيام بذلك ،لكنttك لن تتمكن من كتابttة •
إذا كنت ال تريد استخدام Gitعىل اإلطالق ،فيمكنك تنزيل الملفات في ملttف مضttغوط zipباسttتخدام •
المساهمة
يرجى إرسال بريد إلكtتروني إىل academy@hsoub.comإذا كtان لtديك اقtتراح أو تصtحيح عىل النسtخة
العربية من الكتاب أو أي مالحظة حول أي مصطلح من المصطلحات المسttتعملة .إذا ض َّtم نَت جttزًءا من الجملttة
التي يظهر الخطأ فيها عىل األقل ،فهذا يسِّهل علينا البحث ،وُتعد إضافة أرقام الصفحات واألقسام جيدة أيًض ا.
23
.1نظرة متقدمة عىل يونكس ولغة يس
التجريد abstractionهو المفهوم الرئيسي الذي تقوم عىل أساسه جميع أنظمة تشغيل الحواسيب الحديثة
و مفهوم الملف هو تجريد مناسب إما كحوض للبيانات أو مصدر لها ،وبالتالي هttو تجريttد ممتttاز لجميttع األجهttزة
التي قد يوصلها المرء بالحاسوب .هذا اإلدراك هو سر القوة العظيمة لنظام التشttغيل يttونكس Unixويتجىل في
مجَم ل تصميم كامل المنصة .وُيَع ّد توفير تجريد األجهزة هذا للمبرمج من األدوار الرئيسية لنظام التشغيل.
لنتخيل ملًف ا في إطار مألوف مثل معالج النصوص ،إذ تكون العمليتان األساسيتان اللتان نستطيع تنفيذهما
لنستعرض بعض الطرفيات الشائعة الموصولة بالحاسوب ،وما ارتباطها بالعمليات األساسية عىل الملفات:
.1الشاشة.
.2لوحة المفاتيح.
.3الطابعة.
تشبه كل من الشاشة والطابعة ملًف ا للكتابة فقط ،إذ ُتعَرض المعلومات بشكل نقاط عىل الشاشة أو خطttوط
عىل الصفحة بداًل من تخزينها عىل هيئة ِبّتات عىل القرص؛ أما لوحة المفاتيح ،فُتَع ّد مثل ملف للقttراءة فقttط ،إذ
ترد البيانات من ضغطات المستخِدم عىل المفاتيح ،وكttذلك األمtر بالنسtبة للقttرص المضttغوط CD-ROMمثاًل ،
لكن تخَّزن البيانات مباشرًة عىل القرص بداًل من أن يدخلها المستخِدم عشوائًي ا.
وبالتالي فإن مفهوم الملف هو تجريد abstractionمناسب إما لحوض البيانات أو مصدرها ،لذا فهو تجريد
ممتاز لجميع األجهزة التي قد يوصلها المرء بالحاسوب ،وُيَع ّد هذا اإلدراك هو سر القوة العظيمة لنظttام التشttغيل
يونكس ويتجىل في مجَم ل تصميم كامل المنصة ،كما ُيَع ّد توفير تجريد األجهزة هذا للمبرمج من األدوار الرئيسttية
لنظام التشغيل.
ربما ال نبالغ عندما نقول أّن التجريد هو المفهوم األساسي الذي يدعم جميع أشكال الحوسبة الحديثttة ،حيث
ال يمكن لشخص واحد فهم كل األمور من تصميم واجهة مستخِدم حديثًة إىل العمليات الداخلية لوحدة المعالجttة
المركزية CPUالحديثة ،ناهيك عن بنائها بكاملها بأنفسهم؛ أما بالنسبة للمبرمجين ،فالتجريد هو اللغttة المشttتركة
يمنحنا تعّلم التنقل بين التجريدات رؤيًة أعمق لطريقة استخدام التجريدات بأفضل األساليب وأكثرها ابتكاًرا،
وسندرس في هذا الكتاب التجريدات في الطبقات الدنيا وبين التطبيقات ونظام التشtغيل وبين نظttام التشtغيل
والعتاد الصلب ،كما توجد العديد من الطبقات األعىل منها وكل منها تستحق التفُّtرد بكتttاب خtاص بهtا ،ونأمtل
منك اكتساب بعض الرؤى عن التجريدات التي يقِّدمها نظام التشغيل الحديث مع دراسة كل فصttل من فصttول
هذا الكتاب.
1.2تطبيق التجريد
ُيطَّبق التجريد عموًم ا بما يسمى واجهة برمجة التطبيق ،APIوُيَع ّد APIمصطلًحا مبهًم ا نوًع ا ما ،إذ يشير إىل
أمttور مختلفttة حسttب سttياقات األعمttال البرمجيttة المتنوعttة ،يصttمم المttبرمج في األسttاس مجموعttة دوال
،functionsوُيوِّثق واجهتها ووظيفتها حسب مبدأ أن التنفيذ الفعلي الذي يزوده بواجهة APIيكون مخفًي ا.
25
علوم الحاسوب من األلف إىل الياء نظرة متقدمة عىل يونكس ولغة سي
تقِّدم العديد من تطبيقات الويب عىل سبيل المثال واجهttة APIيمكن الوصttول إليهttا عن طريttق بروتوكttول
،HTTPويطلق الوصول إىل البيانttات بهttذه الطريقttة عttدة سالسttل معقttدة من اسttتدعاءات اإلجttراءات البعيttدة
،data transfersوتكون جميعها غير مرئية بالنسبة للمستخِدم النهائي الذي يتلقى البيانات المقتضبة ببساطة.
سيألف الذين هم عىل دراية باللغات البرمجية كائنية التوجه object-orientedمثttل جافttا Javaأو بttايثون
Pythonأو C++مفهوم التجريد في األصttناف ،classesإذ تِّttزود التوابttع methodsالصttنف بالواجهttة لكنهttا
تجِّرد التنفيذ.
الشيفرات البرمجية األساسية المكتوبة بلغة Cوالتي ال يكون مفهوم كائنية التوجه مدمًجا فيها ،كما ُيَع ّد فهم هذا
المصطلح أمًرا رئيسًي ا لقراءة معظم الشيفرات البرمجية األساسية المكتوبة بلغة ،Cإذ يمّكنttك فهم طريقttة قttراءة
التجريدات الموجودة ضمن الشيفرة البرمجية من تكوين فكرة عن تصاميم واجهات APIالداخلية.
>#include <stdio.h
26
علوم الحاسوب من األلف إىل الياء نظرة متقدمة عىل يونكس ولغة سي
;return 0
}
;)exit(0
}
ُتَع ّد هذه الشيفرة البرمجية أبسtط نمtوذج عن البtنى الtتي يتكtرر اسttتخدامها في جميttع أجtزاء نtواة لينكس
والبرامج األخرى المبنية عىل اللغة ،Cولنلِق نظرًة عىل بعض العناصر المحددة.
نبدأ بالبنية التي تحِّدد الواجهة البرمجية ،struct greet_apiفالدوال التي أحيطت أسمائها بأقواس مع
محدد المؤشر pointer markerتصف مؤشر الدالة ،إذ يصف مؤشر الدالة النموذج األولي للدالة التي يجب أن
يشير إليها ،كما سيؤدي توجيهه إىل دالة دون إضافة النوع الُم عاد return typeالصحيح أو المعامالت الصحيحة
عىل األقل إىل توليد تحذير من المصِّرف ،وإذا تركته في الشيفرة البرمجية ،فيحتمttل أن يttؤدي إىل تنفيttذ عمليttة
خاطئة أو أعطال ،لذلك إذ ستجد غالًبا أّن أسماء المعاِم الت parametersقد ُحذفت ولم ُيحَّد د إال نوع المعاِم ل،
ويتيح هذا للمنفذ تحديد أسماء المعاِم الت لتجنب ورود تحذيرات من المصِّرف.
سنتناول اآلن تنفيذ الواجهة البرمجية ،إذ ستجد عادًة في الدوال األعقttد مصttطلًحا يttدل عىل أّن دوال تنفيttذ
الواجهة البرمجية هي عبارة عن غالف حول الدوال األخرى التي تكون عادًة مسبوقًة بشttرطة سttفلية أو اثنttتين ،إذ
ستستدعي الدالة () say_hello_fnدالًtة أخttرى () _say_hello_functionعىل سttبيل المثttال ،ولهttذه
عttدة اسttتخدامات ،إذ نسttتخدمها عموًم ا لنحظى بttأجزاء أبسttط وأصttغر من الواجهttة - APIفي تنظيم الوسttائط
27
علوم الحاسوب من األلف إىل الياء نظرة متقدمة عىل يونكس ولغة سي
قttار إىل تحقيtذا غالًب ا المسttّه ل هt ويس،دttذ األعقtt منفصلًة عن عملية التنفي- أو التحقق منها مثاًلarguments
ًدا والttيطة جttا بسtt إال أّن عملية التنفيذ هن،تغييرات ملموسة في العمليات الداخلية مع ضمان بقاء الواجهة ثابتة
دة _ أوttفلية واحttرطة سttون شtt كما يختلف مدلول بادئات الدالة التي تك،تحتاج حتى إىل دوال داعمة خاصة بها
ةtt لكنها عموًم ا ُتَع ّد تحذيًرا مرئًي ا بأنه ال ُيفتَرض استدعاء الدال،مزدوجة __ أو حتى ثالثية ___ باختالف المشاريع
وتكون فوdunder foo __ في المحادثات بالتابع السحريfoo قد ُيشار إىل دالة الشرطة السفلية المزدوجة
. مثل س أو ص في الجبرfoo
مtt إذ ُيَع ّد اس،struct greet_api greet_api يرة فيttنمأل مؤشرات الدالة في المرحلة ما قبل األخ
API ةttتدعاء دوال واجهttا اسttيًرا يمكننtt وأخ،&say_hello_fn لذا ال حاجة ألخذ عنوان الدالة مثل،الدالة مؤشًرا
كtح ذلt ويمكن أن نوض،source code دريةtستالحظ هذا المصطلح باستمرار عند تصفحك الشيفرة المص
واةttدرية لنttيفرة المصtt في الشinclude/linux/virtio.h فttاه من الملttذي اجتزأنttيط الttال البسttذا المثttفي ه
:نظام لينكس
/**
* virtio_driver - operations for a virtio I/O driver
* @driver: underlying device driver (populate name and owner).
* @id_table: the ids serviced by this driver.
* @feature_table: an array of feature numbers supported by this
driver.
* @feature_table_size: number of entries in the feature table array.
* @probe: the function to call when a device is found. Returns 0 or
-errno.
* @remove: the function to call when a device is removed.
* @config_changed: optional function to call when the device
configuration
* changes; may be called in interrupt context.
*/
struct virtio_driver {
struct device_driver driver;
const struct virtio_device_id *id_table;
const unsigned int *feature_table;
unsigned int feature_table_size;
int (*probe)(struct virtio_device *dev);
28
علوم الحاسوب من األلف إىل الياء نظرة متقدمة عىل يونكس ولغة سي
كل المطلوب هو أن نفهم فهًم ا سطحًي ا أّن هذه البنية هي وصف لجهtاز اإلدخtال واإلخtراج I/Oاالفتراضtي،
ونالحظ أن المتوَّق ع من مستخِدم واجهة APIهذه -أي كاتب تعريف الجهاز -device driverهو تقttديم عttدد من
الدوال التي سُتستدَع ى في شروط مختلفة أثناء تشغيل النظام ،أي عند تقّص ي عتttاد جديttد hardwareأو عنttد
إزالة عتاد ما … إلخ .عىل سبيل المثال ،كما يحتوي عىل مجموعة بيانات ،وهي الُبنى التي يجب تعبئتها بالبيانات
المرتبطة بها ،كما ُيَع ّد البدء بعناصر توصttيف مثttل هttذه أسttهل طريقttة لبttدء فهم الطبقttات المختلفttة لشttيفرة
النواة البرمجية.
1.2.2المكتبات
تؤدي المكتبات دوَر ين يوضحان التجريد ،هما:
تتيح للمبرمجين إعادة استخدام الشيفرة البرمجية المتاح الوصول إليها عموًم ا. •
تختص المكتبة التي تنفذ الوصول إىل البيانات غير المعاَلجة في الملفات عىل سبيل المثttال بالحقttة JPEG
بميزة تتيح للعديد من البرامج التي ترغب في الوصول إىل ملفات الصور استخدام المكتبة نفسها ،كما ال يضttطر
المبرمجون الذين يبرمجون هذه البرامج إىل االنشغال بالتفاصttيل الدقيقttة لصttيغة الملttف ،JPEGوإنمttا يركttزون
يشار إىل المكتبة القياسية في منصة يونكس باسم libcعموًم ا ،ومهمتها توفير الواجهة األساسية للنظttام،
واالسttتدعاءات األساسttية مثttل () readو () writeو () ،printfكمttا توَص ف واجهttة APIهttذه بمجملهttا
بتوصيف يسمى بوسيكس ،POSIXوهي متاحttة مجاًن ا عىل اإلنttترنت وتصttف العديttد من االسttتدعاءات الttتي
تتبع معظم منصات يونكس عموًم ا معايير بوسيكس ،مع وجود بعض الفروقات الطفيفة التي تكttون مهم ًtة
أحياًنا (وهذا ما يفسر تعقيد أنظمة بناء غنو Gnu autotoolsالمختلفة ،التي تحاول دوًم ا إخفاء هttذه الفروقttات
29
علوم الحاسوب من األلف إىل الياء نظرة متقدمة عىل يونكس ولغة سي
عنك) .يحتوي نظام لينوكس عىل العديد من الواجهات التي ال تتبع معttايير بوسttيكس ،لttذا فttإن بنttاء تطبيقttات
تستخدم هذه الواجهات دون غيرها لن يجعل تطبيقك محمواًل portableبما يكفي.
ُتّع ّد المكتبttات تجريًttدا أساسًttي ا يضttم الكثttير من التفاصttيل ،وسttنتناول في فصttول الحقttة آليttة عمttل
المكتبات بالتفصيل.
خرج رسائل الخطأ عىل الطرفية 2 stderr مجرى الخطأ القياسي
30
علوم الحاسوب من األلف إىل الياء نظرة متقدمة عىل يونكس ولغة سي
يستحضر هذا إىل أذهاننا السؤال عّم ا يمثله الملف المفتوح وكيفية فتحه ،إذ تسمى القيمة التي يعيدها استدعاء
openلفتح الملف اصطالًحا بواصttف الملttف ،file descriptorوهي أساًس ا فهttرس لمصttفوفة من الملفttات
ُتَع ّد واصفات الملفات فهرًس ا لجدول واصفات الملفttات تخزنttه النttواة ،بحيث تنشttئ النttواة واصttف ملttف
استجابًة الستدعاء openوتربطه ببعض التجريد لكائن يشبه الملtف سtواًء كtان جهtاًزا فعلًي ا أو نظtام ملفtات أو
شيء بعيد عن هذا كل البعد ،وبالتالي توجه النواة استدعاءات عمليات القراءة readوالكتابة writeالتي تشttير
إىل واصف الملف ذاك إىل الموضع الصحيح لتنفذ مهمة مفيدة في النهاية.
تعرض الصورة نظرًة عامًة عىل تجريد العتاد ،وباختصار ُيَع ّد واصف الملف البوابة إىل تجريدات النواة للعتttاد
واألجهزة األساسية.
لنبدأ من المستوى األدنى ،إذ يتطلب نظام التشغيل وجود مبرمج ينشttئ تعريًف ا للجهttاز device driverأو
برنامج تعريف حتى يتمكن من التواصل مع أحد أجهزة العتاد ،وُيكَتب تعريtف الجهtاز هttذا إىل واجهtة APIالtتي
توفرها النواة بالطريقة نفسها والتي وردت في المثال السابق ،إذ سيوفر تعريف الجهاز مجموعة دوال تسttتدعيها
النواة استجابًة للمتطلبات المختلفة ،ويمكننا في المثال المبَّس ط في الصورة السابقة رؤية أن تعريف الجهاز يوِّف ر
دالة القراءة readوالكتابtة writeاللtتين سُتسtتدعيان اسtتجابًة للعمليtات المماثلtة الtتي تنفtذ عىل واصtف
الملف ،كما يعلم تعريف الجهاز كيف يحِّو ل هذه الطلبات العامة إىل طلبات أو أوامر محددة لجهاز محدد.
31
علوم الحاسوب من األلف إىل الياء نظرة متقدمة عىل يونكس ولغة سي
تقدم النواة واجهة-ملف file-interfaceلتوفير التجريد لمساحة المسttتخِدم عttبر مttا يسttمى بطبقttة الجهttاز
device layerعموًم ا ،إذ تمَّث ل األجهزة المادية عىل المضيف بملف له نظttام ملفttات خttاص مثttل ،/devففي
أنظمة يونكس وما يشابهها تحتوي عقد الجهاز device-nodesعىل ما اصطلح تسميته بالعدد الرئيسttي major
numberوالعدد الثانوي ،minor numberمما يتيح للنtواة ربtط عقtد محtددة بمtا يقابلهtا ببرنtامج التعريtف
الموفر ،كما يمكنك االطالع عليها من خالل األمر lsكما هو موضح في المثال التالي:
ينقلنا هذا إىل واصف الملف ،وهو األداة التي تستخِدمها مساحة المستخِدم للتواصل مع الجهاز األساسttي،
وبصورة عامة ما يحدث عند فتح الملف هو أّن النواة تستخِدم معلومات المسار لربط mapواصف الملف بشيء
يوِّف ر واجهّتي APIقراءة وكتابة وغيرهما مناسبة ،فعندما تكون عملية فتح الملف openللجهاز مثل /dev/sr0
في مثالنا السابق ،فسيوفر العدد الرئيسي والثانوي لعقدة الجهاز المفتوح المعلومات التي تحتاجها النواة للعثttور
عىل تعريف الجهاز الصحيح وإتمام عملية الربط ،mappingكما ستعلم النواة بعد ذلك كيف توجه االسttتدعاءات
الالحقة مثل القراءة readإىل الدوال األساسية التي يوفرها تعريف الجهاز.
يعمل الملف غير المرتبط بجهاز non-device fileبآلية مشابهة ،عىل الtرغم من وجtود طبقtات أكtثر خالل
العملية ،فالتجريد هنا هو نقطة الوصttل أو الربttط ،mount pointوكمttا تملttك عمليttة توصttيل نظttام الملفttات
file system mountingغايًة مزدوجًة تتمثل في إعداد عملية الربط ،mappingبحيث يتعرف نظام الملفات
عىل الجهاز األساسي الذي يوِّف ر التخزين وتعلم النtواة أّن الملفttات المفتوحtة في نقطttة التوصttيل تلttك يجب أن
توَّجه إىل تعريف نظام الملفات ،كما ُتكَتب أنظمة الملفات عىل واجهة APIمحttددة لنظttام الملفttات العttام الttتي
بالطبع الصورة الكاملة معقدة أكثر في الواقع ،إذ تضم عدة طبقات أخرى ،فتبttذل النttواة عىل سttبيل المثttال
جهًدا كبيًرا لتخزين cacheأكبر قدر ممكن من البيانات الواردة من األقراص في الذاكرة الخالية ،ويقِّدم هذا العديد
من الميزات التي تحِّس ن السرعة ،كما تحاول النواة تنظيم الوصول إىل الجهاز بttأكثر طريقttة فعالttة وممكنttة مثttل
محاولة طلب الوصول إىل القرص للتأكد من أن البيانات المخَّزنة فيزيائًي ا بالقرب من بعضها ستستعاد مًع ا حttتى
لو لم ترد الطلبات بترتيب تسلسلي ،باإلضttافة إىل انتمttاء العديttد من األجهttزة إىل فئttة أعم مثttل أجهttزة USBأو
SCSIالتي توِّف ر طبقات التجريد الخاصة بها للكتابة عليهttا ،وبالتttالي سttتمر أنظمttة الملفttات في هttذه الطبقttات
المتعددة بداًل من الكتابة مباشرًة عىل األجهزة ،أي يكون فهم النواة هو فهم كيفية ترابttط واجهttات APIالمتعttددة
32
علوم الحاسوب من األلف إىل الياء نظرة متقدمة عىل يونكس ولغة سي
1.3.1الصدفة Shell
ُتَع ّد الصدفة بوابة التفاعل مع نظام التشttغيل سttواًء كttانت بttاش bashأو zshأو cshأو أّي نttوع من أنttواع
األصداف األخرى العديدة ،إذ تشترك جميعها أساًس ا في مهمة رئيسية واحttدة فقttط ،وهي أنهttا تttتيح لttك تنفيttذ
البرامج ،كما ستبدأ بفهم آلية تنفيذ الصدفة لهذه المهمة فعلًي ا عندما سنتحدث الحًق ا عن بعض العناصر الداخلية
لنظام التشغيل.
لكن األصداف قادرة عىل تنفيذ مهام أكبر بكثير من مجرد إتاحة تنفيذ برنامج ،إذ تتميز بقttدرات قويttة إلعttادة
توجيه الملفات ،وتتيح لك تنفيذ عدة برامج في الوقت نفسه وكتابة نصوص برمجية تبني بtرامج متكاملtة ،وهtذا
ال نريد في معظم األحيان أن تشير واصفات الملفات القياسية التي تحدثنا عنها في فقرة سابقة إىل مواضع
محددة افتراضًي ا ،فقد ترغب مثاًل في تسجيل كامل خttرج البرنttامج عىل ملttف تحttدده عىل القttرص أو في جعلttه
يتلقى أوامره من ملف أعددته مسبًق ا ،وقد ترغب في تمرير خرج برنامج ليكون دخل برنامج آخر ،إذ تيّس ر الصدفة
33
علوم الحاسوب من األلف إىل الياء نظرة متقدمة عىل يونكس ولغة سي
ُيَع ّد تنفيذ األمر ls | moreمثااًل آخر عىل قدرة التجريد ،فما يحدث هنا بصورة أساسية هtو أنtه بtداًل من
ربط واصف الملف لمجرى الخرج القياسي بإحدى األجهزة األساسية مثttل الطرفيttة لعttرض الخttرج عليهttا ،يوَّج ه
الواصف إىل مخزن مؤقت bufferفي الذاكرة توِّف ره النواة ويطلtق عليtه عtادًة األنبtوب ،pipeوالممtيز هنtا هtو
إمكانية عملية أخرى أن تربط دخلها القياسي standard inputبالجانب اآلخر من المخزن المؤقت ذاته buffer
وتستحوذ عىل خرج العملية األخرى بفعالية كما هو موَّض ح في الصورة التالية:
شكل :4األنبوب
األنبوب هو مخزن مؤقت في الذاكرة يربط عمليتين مًع ا ،وشtير واصtفات الملtف إىل كtائن األنبtوب الtذي
يخزن البيانات المرسلة إليه من خالل عملية الكتابة ليصِّرفها من خالل عملية القراءة.
تخِّزن النواة عمليات الكتابة في األنبوب حتى تصّرف عملية قراءة مقابلة من الجانب اآلخر للمخزن المؤقت،
وهذا مفهوم قوي جًدا وهو أحد األشكال األساسية للتواصل بين العمليات inter-process communication
-أو IPCاختصاًرا -في أنظمة يونكس وما يشابهها ،كما ال تقتصر عمليttة التمريttر عىل نقttل البيانttات ،إذ يمكن أن
تؤدي دور قناة إشارات ،signaling channelفإذا قttرأت إحttدى العمليttات أنبوًب ا فارًغ ا ،فسttتعطله أو تجمttده
blockافتراضًي ا أو تضعه في حالة سبات hibernationإىل حين توفر بعض البيانات ،وسنتعمق في هttذا أكttثر
وبالتالي قد تستخِدم عمليتان أنبوًبا لإلبالغ عن اتخاذ إجراء ما عن طريق كتابة بايت واحد من البيانات ،فبداًل
من أن تكttون البيانttات الفعليttة مهمًtة ،فttإن مجttرد وجttود أيttة بيانttات في األنبttوب يمكن أن تشttير إىل رسttالة،
فلنفترض مثاًل أّن إحدى العمليات تطلب طباعة عملية أخرى لملف وهو أمttر سيسttتغرق بعض الttوقت ،لttذا قttد
ُتِع ّد العمليتان أنبوًبا بينهما بحيث تقرأ العملية التي أرسلت الطلب األنبوب الفار غ،
34
علوم الحاسوب من األلف إىل الياء نظرة متقدمة عىل يونكس ولغة سي
وبما أنه فار غ ،فسيعّط ل هذا االستدعاء وتبِط ل العملية ،لكن بمجرد االنتهاء من الطباعttة ،سttتكتب العمليttة
األخرى رسالًة في األنبوب ويؤدي ذلك إىل إيقاظ العملية التي أرسلت الطلب بصورة فعالtة وإرسtال إشtارة تtدل
ينبثق عن السماح للعمليات بتمريttر البيانttات بين بعضttها بهttذه الطريقttة مصttطلح شttائع آخttر في يttونكس
لألدوات الصغيرة التي تنفذ أمًرا معيًنا ،ويضفي تسلسttل هttذه األدوات الصttغيرة مرونًtة ال تسttتطيع أداة موَّح دة
35
.2تمثيل األعداد والنظام الثنائي
ثالث لهما ،ويتكون العدد الثنائي من عناصر تسمى ِبتات bitsبحيث يمكن أن يكtون كtل بت بإحtدى الحtالتين
الثنائي من عناصر تسمى ِبّت ات ،bitsإذ يمكن أن يكون كل ِبّت بإحدى الحttالتين المحتَم لttتين واللttتين نمِّثلهمttا
عموًم ا بالرقمين 1و ،0ويمكننا القول أنهما تمِّثالن القيمَتين الصحيحة والخاطئة؛ أما من الناحية الكهربائية ،فقد
نبني األعداد الثنائية بالطريقة نفسها التي نبني بها األعداد في نظامنttا التقليttدي العشttري الttذي يكttون فيttه
األساس هو العدد ،10لكن بداًل من منزلة اآلحttاد ومنزلttة العشttرات ومنزلttة المئttات… ،إلخ .لttدينا منزلttة الواحttد
ومنزلة االثنين ومنزلة األربعة ومنزلة الثمانية… ،إلخ .أي كما هو موضح في الجدول التالي:
لنمِّثل العدد 203عىل سبيل المثال في األساس العشري ،إذ نعلم أننا نضع الرقم 3في منزلة اآلحاد ،والرقم
0في منزلttة العشttرات والttرقم 2في منزلttة المئttات ،ويمَّث ل هttذا من خالل اُألسttس exponentsكمttا في
الجدول التالي:
لنمِّث ل العدد نفسه بالنظام الثنائي ،إذ سيكون لدينا الجدول التالي:
ويكافئ هذا:
قد تتساءل كيف لعدد بسيط أن يكون األساس الذي بنَي ت عليه كل األمور المذهلة التي يستطيع الحاسوب
تنفيذها ،ورغم صعوبة تصديق ذلك إال أنهttا الحقيقttة ،إذ يحتttوي المعttالج الموجttود في حاسttوبك عىل مجموعttة
معقtدة -لكنهtا محtدودة في النهايtة -من التعليمtات instructionsالtتي يمكن تنفيtذها عىل قيم مثtل الجمtع
والضرب… ،إلخ .إذ ُيسَند عدد إىل كل تعليمtة من هtذه التعليمtات بصtورة أساسtية ،حtتى يمَّث ل برنtامج كامtل
بسلسلة من األعداد فقط ،أي أضف هذا إىل ذاك ،اضربه بذاك ،قّس م عليه ،وهكذا ،فttإذا كttان المعttالج مثاًل يعلم
أّن العملية 2هي الجمع ،فإّن العدد 252قد يعني "اجمع 5و 2وخِّزن الناتج في مكان مttا" ،وُتَع ّد العمليttات في
الواقع أعقد بكثير طبًع ا ،إذ سنتناولها في فصل معمارية الحاسوب الحًق ا ،لكن باختصار هذا هو الحاسوب.
كان بمقدور المرء في عهد البطاقات المثَّق بة punch-cardsأن يرى بعينه الواحدات واألصttفار الttتي تك ِّtو ن
مسار البرنامج من خالل النظر إىل الثقوب الموجودة عىل البطاقة ،طبًع ا تحّو ل ذلك اليوم إىل آلية التخزين السريع
والدقيق بواسطة قطبية الجزيئات الممغنطة الصغيرة مثل األشtرطة tapesأو األقtراص ،disksوالtذي أتtاح لنtا
37
علوم الحاسوب من األلف إىل الياء تمثيل األعداد والنظام الثنائي
إّن ترجمة هذه األعttداد إىل خtدمات تنفttع البشtرية هي مtا يجعttل الحاسttوب نافًع ا لهtذه الدرجtة ،وتتكّtو ن
الشاشات مثاًل من ماليين البكسالت pixelsالمنفصلة ،وكل منهttا صttغير لدرجttة ال تمّي زه عين اإلنسttان ،لكنهttا
تكِّو ن صورًة مكتملًة عندما تكون مجتمعًة ،إذ يحتوي كل بكسل عموًم ا عىل عناصtر محَّtد دة من األحمtر واألخضtر
واألزرق التي تكِّو ن اللون الذي يعرضه ،وبالتأكيttد يمكن تمثيttل هttذه القيم باألعttداد الttتي بttالطبع يمكن تمثيلهttا
بالنظام الثنائي ،وبالتالي يمكن تقسيم أّي صورة إىل ماليين النقاط الفردية ،وُتمَّثل كل نقطة بمجموعttة من ثالث
قيم تمِّث ل قيم األحمر واألخضر واألزرق للبكسل ،وبالتالي عندما يكون لدينا سلسلة طويلة من هذه األعداد وتكون
ُم صاغًة بصورة صحيحة ،فستتمكن أجهزة الفيديو في حاسttوبك من تحويttل هttذه األعttداد إىل إشttارات كهربائيttة
سنبني بيئة الحوسبة الحديثة بأكملها في الكتاب بدًءا من اللبنة األساسية هذه ،أي من القاعدة إىل القمttة أو
يمكننا بصورة أساسية تمثيل أّي شيء بعدد كمttا تحttدثنا في الفقttرات السttابقة ،ويمكن تحويلttه إىل النظttام
الثنائي وإجراء عمليات عليه بواسطة الحاسوب ،إذ سttنحتاج عىل األقttل لتمثيttل جميttع أحtرف األبجديtة مثاًل إىل
توليفات مختلفة وكافية لتمثيل جميع المحارف الصغيرة lower caseوالمحارف الكبيرة upper caseواألعداد
وعالمات الترقيم إىل جانب بعض األمور اإلضافية ،ويعني هذا أننا ربما سنحتاج إىل حوالي 80توليفة مختلفة.
إذا كان لدينا ِبّتان ،فيمكننا تمثيل 4توليفات فريدة محتملة وهي ،11 10 01 00أمttا إذا كttان لttدينا ثالث
ِبّت ات ،فيمكننtا تمثيtل 8توليفtات مختلفtة ،وبصtورة عامtة ،إذا كtان لtدينا عtدد nمن الِبّت ات يمكننtا تمثيtل
2nتوليفة فريدة.
تمنحنا ِ 8بّتات 256 = 28تمثياًل فريًدا ،وهذا عدد أكثر من كاف للتوليفات األبجدية التي نحتاجها ،كما أننttا
ندعو كل ِ 8بّتات ببايت ،كما أّن حجم المتغير من نوع charهو بايت واحد في لغة .C
أسكي ASCII
يستطيع أّي شخص اختالق رابط بين األحرف واألعداد عشوائًي ا بما أّن البttايت يمكنttه تمثيttل أّي قيمttة بين
0و ،255فقد تقِّرر الشركة المصنعة لبطاقات الفيديو مثاًل أّن رقم 1يمثل المحرف ' ،'Aلذا عندما ترَس ل القيمttة
1إىل بطاقة الفيديو ،ستعرض المحرف ' 'Aبحالتttه الكبttيرة عىل الشاشttة ،وقttد تقِّtرر الشttركة المصttنعة للطابعttة
لسبب ما أن الرقم 1يمثل ' 'zبالحالة الصغيرة ،وبالتالي سيتطلب عرض وطباعة الشيء نفسه تحويالت معقttدة،
ولتجنب حدوث ذلك ابُتِكرت الشيفرة المعيارية األميركية لتبادل المعلومات American Standard Code for
- Information Interchangeأو ASCIIاختصاًرا ،-وهذه الشيفرة مبنية عىل 7بتات ،bit code-7أي توجد 27
38
علوم الحاسوب من األلف إىل الياء تمثيل األعداد والنظام الثنائي
ينقسم مجال الشيفرات إىل جزأين رئيسيين هما الشيفرات الغير قابلة للطباعة والشيفرات القابلة للطباعttة،
إذ تكون المحارف القابلة للطباعttة مثttل األحttرف الكبttيرة والصttغيرة واألعttداد وعالمttات الttترقيم ،في حين تكttون
المحttارف الغttير قابلttة للطباعttة مخصصًttة للتحكم وتنفيttذ عمليttات مثttل محttارف الرجttوع إىل بدايttة السttطر
،carriage-returnأي العودة إىل بداية السطر الحالي دون النزول إىل السطر التالي ،أو رن جttرس الطرفيttة عنttد
ورود المحرف Bellأو شيفرة القيمة الفارغة NULLالخاصة التي ال تمثل شيًئا عىل اإلطالق.
تكفي المحارف 127الفريدة للغة اإلنجليزية األميركية ،لكنها تصبح محttدودًة جًtدا عنtدما يريtد المtرء تمثيttل
المحارف السائدة في اللغات األخtرى وخاصًtة اللغttات اآلسttيوية الtتي قttد تحتttوي عىل عttدة آالف من المحttارف
الفريدة ،وللحد من ذلك ،تنتقل األنظمة الحديثة من شيفرة أسكي إىل يونيكود Unicodeالتي تستخِدم ما يصل
التكافؤ Parity
يبقى بت واحد من البايت فائًض ا بما أّن شيفرة األسكي مبنية عىل 7بتات فقط ،ويمكن االستفادة منttه في
تحقيق التكافؤ ،parityإذ ُيَع ّد شكاًل بسيًط ا من أشكال التحقق من األخطاء ،فتخّي ل حاسttوًبا يسttتخِدم بطاقttات
مثقبة في عملية اإلدخال ،بحيث يمِّث ل وجttود الثقب الِبّت 1وغيابttه يمثttل الِبّت ،0وسttتؤدي أيttة تغطيttة غttير
مقصودة لثقب ما إىل قراءة قيمة غير صحيحة وستتسبب في سلوك غير معَّرف.
يتيح التكافؤ إجراء فحص بسيط للِبّتات المؤِّلفة للبايت للتأكد من أنها ُق ِرئت بصورة صحيحة ،ويمكننا تنفيttذ
التكافؤ الفردي أو الزوجي باستخدام الِبّت الفائض الذي َنعّده ِبّت التكافؤ ،فإذا كان عدد الواحدات في المعلومات
المخَّزنة عىل الِبّتات السبعة فردًيا ،فسيضبط بت التكtافؤ ويكtون حينهtا التكtافؤ فردًي ا ،Odd parityوإذا كtان
عددها زوجًي ا ،فال يضبط ِبّت التكافؤ؛ أما التكافؤ الزوجي ،Even parityفهو عكس ذلك ،فإذا كان عدد الواحدات
زوجي ،فسُيضبط ِبّت التكافؤ عىل الرقم ،1وبهذه الطريقة سينتج عن تغّي ر ِبّت واحد خطأ تكافؤ يمكن اكتشافه.
ال تتسع جميع األعداد في بايت أو مجموعة محددة من البايتات ،فبفرض أن كان رصttيدك المصttرفي كبttيًرا
مثاًل فهو يحتاج إىل مجال أوسع مما يمكن أن يتسع في بايت واحtد لتمثيلtه ،وتتtألف المعماريtات الحديثtة في
الحواسيب حالًي ا من أنظمة 32بت عىل األقل ،وهذا يعني أنها تعمل مع 4بايتات في وقت واحد عند المعالجttة
والقراءة أو الكتابة عىل الذاكرة ،ونشير آنذاك إىل كل 4بايتttات بالكلمTTة ،wordوهttذا مشttابه للغttة حيث تك ِّtو ن
األحرف -أو البتات -الكلمات في جملة ما ،والفارق في الحاسوب عن اللغة أنه تكون كل الكلمات بttالحجم نفسttه،
وهو حجم المتغير من نوع intفي اللغة Cالذي يساوي ِ 32بّت ،أما معماريات 64بت الحديثttة ،يضttاعف حجم
39
علوم الحاسوب من األلف إىل الياء تمثيل األعداد والنظام الثنائي
تتعامل الحواسيب مع عدد كبير من البايتات وهttذا مttا يجعلهttا شttديدة القttوة ،وبالتttالي نحتttاج إىل وسttيلة
للتحدث عن أعداد ضخمة من البايتات ،والوسيلة البديهية لttذلك هي اسttتخدام بادئttات نظttام الوحttدات الttدولي
- International System of Unitsأو SIاختصاًرا -كما هو مّتبع في معظم المجاالت العلمية األخرى ،إذ يشير
الكيلو مثاًل إىل 103أو 1000وحدة ،بحيث يكون الكيلوغرام الواحد هو 1000غرام.
ُيَع ّد 1000عدًدا تقريبًي ا roundجيًدا في األساس العشري ،لكنه يمَّثل في النظام الثنttائي بـ 1111101000
وهو ليس عدًدا تقريبًي ا ،لكن 1024أو 210هو عدد تقريtبي والtذي يمَّث ل في النظtام الثنtائي بـ ،10000000000
وهو قريب جًدا من الكيلو في النظام العشري ،أي العttدد 1000قttريب من العttدد ،1024وبالتttالي أصttبح 1024
أما الوحدة التالية في نظام الوحدات الدولي ،فهي ميغا megaالمقابلttة لقيمttة ،106كمttا تسttتمر البادئttات
باالزدياد بمقدار 103المقابلة للتجميع المعتاد المكون من ثالثة أرقام عند كتابة أعداد كبيرة ،كما يصttادف مجttدًدا
أن تكttون 220قريب ًtة من تحديttد نظttام الواحttدات الttدولي للميغttا في النظttام العشttري ،أي 1048576بttداًل من
،1000000فعند زيادة واحدات النظام الثنائي بالقوى من مضttاعفات 10تبقى قريبًtة وظيفًي ا من قيمtة النظttام
العشري في نظام الواحدات الدولي ،مع أنه يحيد قلياًل كل عامل متزايد عن داللة أساس نظام الواحدات الttدولي،
وبالتالي فإّن وحدات النظام العشري في نظام الواحدات الدولي قريبttة بمttا يكفي عىل قيم النظttام الثنttائي ،وقttد
معامل معامل
بايت في النظام العشري النظام العشري بايت النظام االسم
القريب الثنائي
1000 103 1024 1كيلوبايت 210
قد يفيدك ترسيخ معاِم الت النظام الثنائي في ذاكرتك كثيًرا في الربط السريع للعالقة بين عttدد الِبّت ات واألحجttام
التي يفهمها اإلنسان ،إذ يمكننا بسرعة مثاًل حسttاب إمكانيttة حاسttوب بنظttام ِ 32بّت أن يعttالج مttا يصttل إىل 4
غيغابايت من الذاكرة من خالل مالحظة إعادة التركيب ( ،230 + 22 )4وبالمثل يمكن أن تعاِلج قيمة ِ 64بّت مttا
40
علوم الحاسوب من األلف إىل الياء تمثيل األعداد والنظام الثنائي
يصل إىل 16إكسابايت ،أي ،24 + 260كما يمكنك حساب ضخامة هذا العدد ،ولتأخذ فكرًة عن مدى ضttخامته،
فيمكنك حساب المدة التي ستستغرقها في العد إىل 264إذا عددت رقًم ا واحًدا كل ثانية.
سيشار إىل السعات غالًبا بالِبّتات بدًال من البايتات إىل جانب االرتباك الttذي يحttدث نتيجttة العبء المفttرط
لتحويل واحدات نظام الواحدات الدولي SIبين النظاَم ين الثنائي والعشري ،ويحدث هذا عموًم ا عند التحttدث في
مجال الشبكات أو أجهزة التخزين ،فربما الحظت أّن اتصال ADSLلديك يشار إليه بقيمة مثل 1500كيلوِبت في
الثانية ،إن العملية الحسابية بسيطة ،إذ نضرب بالعدد 1000للكيلو ثم نقِّس م عىل 8لنحِّو له إىل بايت ثم نقِّس مه
عىل العدد 1024لنحوله إىل كيلوبايت ،وبالتالي تكون 1500كيلوِبت في ثانية = 183كيلوبايت في الثانية.
أقَّرت هيئة نظام الواحدات الدولي هذه االستخدامات المزدوجة وحددت بادئات فريدًة لالستخدام الثنائي ،إذ
تقابل 1024بايت بموجب المعيار كيبي بايت ،kibibyteوهو اختصار للكيلوبايت الثنttائي kilo binary byte
وُتختصر بـ KiB؛ أما البادئات األخرى ،فلها بادئة مماثلttة مثttل ميttبي بttايتس Mebibyteوتختصttر ،MiBويمنttع
العرف المَّت بع إىل حد كبير استخدام هذه المصطلحات ،لكنك قد تراها في بعض المؤَّلفات.
التحويل
ُيّع ّد استخدام الحاسوب الطريقة األسهل للتحويل بين األنظمة ،فبعد كل شيء هذا ما يبر ع فيه .ومttع ذلttك،
ُتَع ّد القسمة المتكررة الطريقة األسهل للتحويل بين األنظمة ،بحيث نقِّس م ناتج القسمة بصttورة متكttررة عىل
األساس إىل أن يصبح ناتج القسمة صفًرا مع تدوين الباقي في كل خطوة ،ثم ندِّو ن الباقي بالعكس ،أي نبttدأ من
األسفل ونلحق العدد بالجهة اليمين في كل مرة ،وسنذكر مثااًل للتوضيح ،كما سيكون األساس 2نظًرا ألننا نح ِّtو ل
41
علوم الحاسوب من األلف إىل الياء تمثيل األعداد والنظام الثنائي
ابدأ بقراءة الباقي من األسفل وأضف كل عدد منه إىل اليمين لتحصل عىل النتيجة ،11001011وقد وجttدنا
اكتشف جورج بول عالم الرياضيات مجااًل كاماًل في الرياضيات يسمى جبر ُبول ،Boolean Algebraوعىل
الرغم من أّن اكتشافاته كانت في منتصف القرن التاسع عشر ،إال أنها أصبحت الحًق ا أساسيات علوم الحاسttوب،
وُيَع ّد جبر بول هو موضوع واسع النطاق ،لذا سنتناول في هذا الكتاب بعض مبادئه األساسية فقط حتى تستطيع
تأخذ العمليات البوليانية ببساطة دخاًل معيًنا وتنتج خرًجا معيًنا حسب قاعدة معينة ،وأبسط عمليttة بوليانيttة
مثاًل هي ،notوهي تعكس قيمة معاِم ل operandالدخل؛ أمtا المعttاِم الت األخtرى ،فتأخtذ عttادًة دخلين وتنتج
خرًجا واحًدا.
يسهل تذكر العمليات البوليانية األساسية المستخَدمة في علوم الحاسوب وقttد أدرجناهttا في هttذا الفصttل،
ومثلناها بجداول الحقيقة truth tablesالtتي تtبِّين بمظهtر بسtيط جميttع المtدخالت والمخرجtات المحتَم لtة،
Not
تمَّثل عادًة بالرمز ! ،وهي تعكس قيمة الدخل فتحول 0إىل 1و 1إىل .0
الخرج الدخل
0 1
1 0
And
تذَّك ر العبارة التالية" :تكون النتيجة حقيقيًة إذا كان الدخل األول حقيقًي ا و الدخل الثاني حقيقًي ا" لكي يسttهل
42
علوم الحاسوب من األلف إىل الياء تمثيل األعداد والنظام الثنائي
Or
تذَّك ر العبارة التالية" :تكون النتيجة حقيقيًة إذا كان الدخل األول حقيقًي ا أو الدخل الثاني حقيقًي ا" لكي يسttهل
تختَص ر عبارة معامttل أو الحصttرية Exclusive Orبـ xorوهي حالttة خاصttة من معاِم ل ،orبحيث يكttون
الخرج حقيقًي ا عندما يكون أحد المدَخلين فقط حقيقًي ا ،وستدهشك الحيل المميزة الtتي يسtتطيع هtذا المعاِم ل
قد يصعب عليك تصديق أّن أساس كل ما ينِّف ذه حاسوبك هو تلك المعاِم الت التي تحttدثنا عنهttا ،فالجttامع
النصفي half adderمثاًل هو أحد أنواع الدارات التي تتكون من العمليات البوليانيtة الtتي تجمtع الِبّت ات ،وقtد
43
علوم الحاسوب من األلف إىل الياء تمثيل األعداد والنظام الثنائي
ُس ّم ي الجامع النصفي ألنه ال يعالج البتات الفائضة ،وستبدأ في بنttاء كيttان يجمttع أعttداد ثنائيttة طويلttة من خالل
وضع أكثر من جامع نصفي مًع ا ،ثم أضف إليه بعض الذواكر الخارجية وستكون قد بنيت حاسوًبا.
،transistorsلذا ال بد أنك سمعت عن عدد الترانزستورات transistor countsوقانون مور وغيرها ،وكلما زاد
عدد الترانزستورات زاد عدد البوابات وزاد عدد األشياء التي يمكنك جمعها ،كما ستحتاج لبناء الحاسوب الحttديث
إىل عدد هائل من البوابات وعدد هائttل من الترانزسttتورات ،إذ تحتttوي بعض معاِلجttات إيتttانيوم Itaniumعىل
توجد واجهة مباشرة لجميع المعاِم الت التي ذكرناها في اللغة ،Cويشرح الجدول التالي هذه المعاِم الت:
نطّبق هذه المعاِم الت عىل المتغيرات لتعديل الِبّتات ضمن المتغير ،ولكن يجب علينا أواًل أن نتنttاول شttرًحا
الحاسوب هو أنtه يسِّtهل عىل اإلنسtان التفكtير في األرقtام الثنائيttة ،إذ يسِّtهل عttدم تعامtل الحواسttيب إال مtع
لكن لماذا اختير األساس 16؟ إن الخيار الطبيعي هو األساس 10ألننا معتادون عىل التفكير في األساس 10
حسب نظامنا العددي اليومي ،لكن األساس 10ال يتوافق كثيًرا مع النظام الثنائي حيث أننا نحتاج إىل أربع بتات
لتمثيل 10عناصر مختلفة في النظام الثنائي ،لكن تلك األربع بتات توفر لنا ست عشرة توليفة محتَم لة ،لttذا نحن
أمام احتمالين؛ إما أن نختار الطريقة شديدة التعقيد المتمثلة في محاولة التحويttل بين النظttام العشttري والنظttام
الثنائي ،أو أن نختار الطريقة السهلة وننشئ نظاًم ا عددًيا أساسه العدد 16وهو النظام الست عشري.
يستخدم النظام الست عشري األعداد القياسية في النظام العشري مع إضافة األحرف A B C D E Fالتي
تشير إىل األعداد ،15 14 13 12 11 10مع االنتباه إىل بدء العّد من الصفر ،فمتى ما رأيت عدًدا مسبوًق ا بـ ،0x
44
علوم الحاسوب من األلف إىل الياء تمثيل األعداد والنظام الثنائي
فاعلم أنه يدل عىل عدد ست عشري ،وكما ذكرنا أنه سنحتاج إىل أربع بتات بالضبط لتمثيل 16نمط مختلف في
النظام الثنائي ،لذا يمِّثل كل عدد ست عشري أربع بتات بالضبط ،ويجب أن تعّttده تمريًن ا لتتعلم الجttدول التttالي
بالطبع ال يوجد سبب للتوقف عن متابعة النمط (مثل تحديttد Gللقيمttة ،)16ولكن القيم السttتة عشttرة هي
موازنة ممتازة بين تقلبات الذاكرة البشرية وعدد البتات التي يستخدمها الحاسوب ،كما سttتجد أيًض ا األسttاس 8
مستخَد ًم ا أحياًنا في سماحيات الملفات في أنظمة يونكس مثاًل ،ونمِّثل ببساطة أعttداًدا أكttبر من البتttات بأعttداد
أكثر ،إذ يمكن مثاًل تمثيل متغير يتألف من ستة عشر بت بالقيمة ،0xAB12وما عليك سوى تحويل كل رقم عىل
حدى وفًق ا للجدول السابق ثم جمع القيم مًع ا لتجد مقابلها في النظام الثنائي ،أي لتكون القيمة المقابلttة للقيمttة
0xAB12هي العدد الذي يتألف من 16بت في النظام الثنائي ،1010101100010010كمttا نسttتطيع التحويttل
من النظام الثنائي إىل النظام الست عشري بعكس تلك العملية ،كما نستطيع االستعانة بنهج القسttمة المتكttررة
ذاته لتغيير أساس أي عدد ،فإليجاد قيمة العدد 203بالنظام الست عشري مثاًل :
45
علوم الحاسوب من األلف إىل الياء تمثيل األعداد والنظام الثنائي
ُتَع ّد برمجة حاسوب بلغات عالية المستوى high levelدون معرفة أّي شيء عنه هttو أمttر عملي بحت عىل
الرغم من أّن النظام الثنائي هو اللغة األساسية لكل حاسوب ،وعىل أيtة حtال نهتم ببعض مبtادئ النظtام الثنtائي
األساسttية والمسttتخَدمة بصttورة متكttررة بالنسttبة شttيفرة البرمجيttة منخفضttة المسttتوى low level code
التي سنتناولها.
سنشرح مفهوم عمليتي التقنع والرايات وكيفية تطبيقهما عملًي ا عىل األنظمة العددية.
التقنع Masking
من المهم غالًبا جعل البtنى والمتغtيرات تحجtز مسtاحًة بtأكثر طريقtة فعالtة ممكنtة في الشtيفرة البرمجيtة
منخفضة المستوى ،وقد يتضمن هذا في بعض الحاالت تعبئة packingمتغيرين -يكونttان مttرتبطين ببعضttهما
تذَّك ر أّن كل ِبّت يمثل حالتين ،فإذا علمنا مثاًل أّن للمتغير 16حالة محتَم لة فقط ،فيمكن تمثيلttه بـ ِ 4بّت ات،
أي 16 = 24قيمًة فريدًة ،لكن أصغر نوع يمكننا التصريح عنه في اللغة Cهو 8بتات وهو نوع charأي محرف،
فإما نهدر أربع بتات ،أو نجد طريقًة نستخِد م فيها تلك البتات الفائضة ،ويمكننttا تحقيttق ذلttك بسttهولة من خالل
عملية التقُّنع التي تتبع قواعد العمليات المنطقية الستخراج القيم وهي موَّض حة في الصورة التالية.
نحتفظ بقيمَتين منفصلتين تتألفان من ِ 4بّتات داخل محرف واحد يتألف من ِ 8بّتات ،إذ ُنِع ّد الِبّتات األربعة
األوىل (الزرقاء) قيمًة واحدًة والِبَتات األربعة األخيرة (الحمراء) قيمًة أخرى ،وقد ضttبطنا القنttاع عىل تعttيين قيمttة
البتات األربعة األخيرة )0x0F( 1الستخراج الِبّتات األربعة السttفلية ،وبمttا أّن المعاِم ل andالمنطقي سيضttبط
البت إىل 1فقط إذا كtانت قيمtة كال البّتين ،1فسtتخفي الِبّت ات الtتي ضtبطنا قيمتهtا عىل 0في القنtاع وهي
46
علوم الحاسوب من األلف إىل الياء تمثيل األعداد والنظام الثنائي
شكل :5التقُّنع
نقلب القناع للحصول عىل الِبّتات األربعة األوىل (الزرقttاء) ،أي نضttبط الِبّت ات األربعttة األوىل عىل القيمttة 1
والبتات األربعة األخيرة عىل القيمة ،0وسوف تالحظ أّن نتيجة هttذا سttتكون 1010 0000أو 0xA0في النظttام
الست عشري ،عىل حين أننا نريد فعاًل أن نعتبر هذه القيمة الفريدة المؤلفttة من 4بتttات 1010أي ،0x0Aومن
أجل وضع هذه البتات في الموضع الصحيح نستخِدم المعاِم ل right shiftأربع مرات ،وهذا سوف يمنحنttا
>#include <stdio.h
يتطلب ضبط الِبّتات المعاِم ل orالمنطقي ،لكن سنستخدم األصفار 0بداًل من اسttتخدام الواحttدات 1عىل
أسttاس قنttاع ،كمttا ننصttحك برسttم مخطttط مشttابه للصttورة السttابقة والعمttل عىل ضttبط الِبّت ات بواسttطة
المعاِم ل orالمنطقي.
47
علوم الحاسوب من األلف إىل الياء تمثيل األعداد والنظام الثنائي
الرايات flags
يتضمن البرنامج غالًبا عدًدا كبيًرا من المتغيرات التي توجد فقط بصيغة رايات flagsفي شروط معينة ،فآلة
الحاالت state machineمثاًل هي خوارزمية تتنقل عبر عدد من الحاالت المختلفة ،لكنها ال تتواجد إال في حالttة
واحدة فقط في المرة الواحدة ،ولنقل أنه لديها 8حاالت مختلفة ،إذ نسttتطيع بسttهولة التصttريح عن 8متغttيرات
مختلفة ،بحيث يكون هناك متغير واحد لكل حالة ،لكن في كثير من الحtاالت يفَّض ل التصtريح عن متغtير واحtد
مؤلف من 8بتات وتعيين راية لكل ِبّت لإلشارة إىل حالة معينة.
ُتَع ّد الرايات حالًة خاصًة من التقُّنع ،لكن يمِّثل كل ِبّت حالًة بوليانيًة معينًة ،أي تشغيل أو إيقttاف ،كمttا يمكن
لمتغير مؤَّلف من عدد nمن البتات أن يحمل العtدد nمن الرايtات المختلفtة ،وُيَع ّد نمtوذج الشtيفرة البرمجيtة
التالي هو مثال نموذجي عىل استخدام الرايات ،وسttتلحظ اختالفttات في هttذه الشttيفرة البرمجيttة األساسttية في
معظم األحيان.
>#include <stdio.h
*/
* تعريف كافة الرايات الثمانية المحتَملة لمتغير بحجم 8بتات
* النظام الثنائي النظام الست عشري االسم
*/
#define FLAG1 0x01 /* 00000001 */
#define FLAG2 0x02 /* 00000010 */
#define FLAG3 0x04 /* 00000100 */
#define FLAG4 0x08 /* 00001000 */
*/وهكذا /* ...
#define FLAG8 0x80 /* 10000000 */
تحقق من الرايات بالمعامل andالمنطقي .إذا كانت الراية مضبوطة بالقيمة /* 1
سيرجع المعامل andقيمة * 1
مما سيحقق الشرط الوارد في * if */
)if (flags & FLAG1
48
علوم الحاسوب من األلف إىل الياء تمثيل األعداد والنظام الثنائي
;)"printf("FLAG1 set!\n
;return 0
}
ُيعِلم النوع المصِّرف مالذي يتوقع تخزينه في المتغير ،وبالتالي يستطيع المصّرف تخصيص مسttاحة كافيttة لهttذا
اللغات ،إذ ُتَع ّد Cبأنها اللغة السائدة في عالم برمجة األنظمة ،فكل نظام تشغيل ومكتباته المرتبطة به التي يشيع
استخدامها مكتوبة باللغة ،Cكما يوِّف ر كل نظام مصِّرًف ا compilerللغة ،Cوقد وِض ع معيار صارم لهذه اللغة للحد
من اختالفها بين هذه األنظمة والتي من المؤكtد أّن كtل منهtا سtيجري العديtد من التغيtيرات الtتي لن تتوافtق
مع بعضها.
ُيعَرف هذا المعيار رسمًي ا باسم ) ،ISO/IEC 9899:1999(Eلكن يشار إليه عttادًة باالختصttار ،C99إذ تشtرف
عليه منظمة المعايير الدولية ،ISOكما أتيح شراء المعيار كاماًل عىل اإلنttترنت ،ولم َتُع د اإلصttدارات القديمttة من
هذا المعيار مثل اإلصدار - C89الذي سبق C99وأصِدر في عام -1989و ANSI Cشائعة االستخدام ،وأصttبحت
جزًءا من أحدث معيار ،كما أّن توثيق المعيار تقني بحت ويذكر بالتفصيل تقريًبا جميع نواحي اللغة ،إذ يشرح مثاًل
بنيتهtttا بصtttيغة بtttاكوس نtttور Backus Naurوقيم #defineالمعياريtttة واآلليtttة الtttتي يجب أن تعمtttل
وفقها العمليات.
49
علوم الحاسوب من األلف إىل الياء تمثيل األعداد والنظام الثنائي
من الضروري أيًض ا مالحظة ما الذي ال تحدده معttايير اللغttة ،Cواألهم من ذلttك أنttه يجب أن يكttون المعيttار
مالئًم ا لكل معمارية حاسوبية حالية ومستقبلية ،وبالتالي يحttرص عىل عttدم تحديttد المجttاالت الttتي تعتمttد عىل
المعماريtttة ،كمtttا ُيَع ّد الرابtttط بين معيtttار اللغtttة Cوالمعماريtttة األساسtttية هtttو واجهtttة التطtttبيق الثنائيtttة
- Application Binary Interfaceأو ABIاختصاًرا -التي سنتحدث عنها الحًق ا ،كمtا سttيذكر المعيttار في عttدة
مواضع أن أية عملية أو بنية معّي نة سيكون لها نتيجة غير محددة أو نتيجة تعتمttد عىل التنفيttذ ،ومن البttديهي أن
المبرمج ال يمكنه االعتماد عىل هذه النتائج إذا كان يريد كتابة شيفرة برمجية محمولة .portable
مجموعة إضافات للمعيار سيستخدمها المبرمجون غالًبا للحصول عىل خصttائص وظيفيttة إضttافية عىل حسttاب
قابلية النقل إىل مصِّرف آخر ،إذ ترتبط هذه اإلضtافات عtادًة بالشtيفرة البرمجيtة ذات المسtتوى المنخفض low
level codeوهي أكثر شيوًع ا في مجال برمجة النظم؛ أما أكثر إضافة يشيع اسttتخدامها في هttذا المجttال ،فهي
شيفرة التجميع الُم ضّم ن ،inline assemblyكما يجب عىل المبرمجين قراءة توثيق مصِّرف GNU Cوفهم متى
يمكن توجيه مصِّرف GNU Cلاللتزام بدقة بالمعيار مثل راية -std = c99والتحذير أو توليttد خطttأ عنttد
تنفيذ أمور معينة ال تتوافق مع المعيار .وهذا طبًع ا يناسبك عندما تكون بحاجة إىل ضمان إمكانية نقttل شttيفرتك
2.2.3األنواع
نحن المtبرمجون معتtادون عىل اسtتخدام المتغtيرات لتمثيtل مسtاحة من الtذاكرة لتحمtل قيمًtة ،إذ يجب
التصريح عن نوع كل متغير في اللغة التي ُيحَّد د فيها نوع المتغtير typed languageمثtل اللغtة ،Cكمtا يخtبر
النوع المصِّرف مالذي يتوقع تخزينه في المتغير ،وبالتالي سيسttتطيع المص ِّtرف تخصttيص مسttاحة كافيttة لهttذا
االستخدام والتحقق من أّن المبرمج ال ينتهك قيود النوع المحَّد د ،وسنجد في الصورة التالية مثttااًل عىل المسttاحة
50
علوم الحاسوب من األلف إىل الياء تمثيل األعداد والنظام الثنائي
شكل :6األنواع
يذكر معيار C99أصغر حجم ممكن لكttل نttوع من أنttواع المتغttيرات المعَّرفttة في اللغttة Cفقttط ،وذلttك ألّن
الحجم األمثل لألنواع يختلف اختالًف ا كبtيًرا بين مختلtف معماريtات المعالجtات وأنظمtة التشtغيل ،ولكي تكtون
العملية صحيحًة تماًم ا يجب أال يفttترض المttبرمجون أبًttدا حجم أّي من متغttيراتهم ،لكن يحتttاج نظttام التشttغيل
الفعال بطبيعة الحال إىل اتفاقات حول األحجttام الtتي سttتحجزها أنtواع المتغttيرات في النظttام ،كمtا تتقيttد كttل
معمارية ونظام تشغيل بواجهة التطttبيق الثنائيttة - Application Binary Interfaceأو ABIاختصttاًرا ،-إذ تمأل
واجهة التطبيق الثنائية لنظام ما التفاصيل التي تربط بين معيttار اللغttة Cومتطلبttات العتttاد الصttلب األساسttي
ونظام التشغيل ،كما ُتكَتب واجهة التطبيق الثنائية لمجموعة محَّد دة من المعالج ونظام التشغيل.
51
علوم الحاسوب من األلف إىل الياء تمثيل األعداد والنظام الثنائي
نالحظ في مثالنا السابق أّن االختالف الوحيد عن المعيار C99هو أن حجم المتغير من نوع intهttو ِ 32بت
عادًة ،وهو ضعف الحد األدنى الصارم لحجم 16بت الذي يتطلبه المعيار ،C99كما أّن المؤشttرات Pointersهي
فعلًي ا عنوان فقط ،أي أّن قيمتها تكون عنواًنا وبالتالي "تشير" إىل موقع آخر في الذاكرة ،لذا يجب تخصيص حجم
ا 64 .بت
إحدى النواحي المرِبكة هي إدراج حوسبة 64بت ،إذ يعني هذا أّن المعالج يمكنه معالجة العناوين التي تخَّزن
عىل 64بت وتحديًدا تكون سعة السجالت ِ 64بّت ،وهو موضوع سنتناوله في فصل معمارية الحاسوب الحًق ا.
يعني هذا أواًل أّن جميع المؤشرات يجب أن تكون بحجم ِ 64بّت حتى تتمكن من تمثيttل أّي عنttوان محتمttل
في النظام ،لكن عندها يجب عىل منّف ِذي النظام system implementersتحديد حجم األنواع األخرى ،في حين
ينتشر استخدام نموذَجين شائَع ين عىل نطاق واسع كما هو موضح في الجدول التالي:
جدول :16أنواع وأحجام أنواع البيانات القياسية التي تتضمن قيمة مفردة
52
علوم الحاسوب من األلف إىل الياء تمثيل األعداد والنظام الثنائي
يمكنك مالحظة أنه في نموذج long pointer 64أي المؤشر الطويttل - 64أو LP64اختصttاًرا -يحَّtد د حجم
قيم المتغير من نوع Longبـ ِ 64بّت ،وهذا يختلف عن نموذج ِ 32بّت الذي عرضناه سtابًق ا ،إذ يسtتخَدم نمtوذج
LP64في أنظمة يونيكس UNIXعىل نطاق واسع؛ أما في النمttوذج اآلخttر ،فيبقى حجم المتغttير من نttوع long
بقيمة 32بت ،وهذا يحافظ عىل أقصى قدر ممكن من التوافق مع الشيفرة البرمجية بنظام ،32إذ ُيستخَدم هttذا
تكمن أسباب وجيهة خلف عدم زيادة حجم المتغير من نوع intإىل ِ 64بّت في أّي من النموذجين ،فإذا زاد
حجم هذا المتغير إىل ِ 64بّت ،فلن تttترك للمttبرمجين أّي طريقttة للحصttول عىل متغttير بحجم ِ 32بّت ،وسttتكون
الطريقة الوحيدة هي إعادة تعريف المتغيرات من نوع shortلتكون من نوع ِ 32بّت األكبر.
ُيَع ّد المتغير بحجم ِ 64بّت كبيًرا جًدا لدرجة أنه ليس مطلوًبا عموًم ا لتمثيل العديد من المتغيرات ،فنttادًرا مttا
تتكرر الحلقات loopsمثاًل عدد مرات أكبر من أن يتسع في متغير حجمه ِ 32بّت الttذي يتسttع لـ 4294967296
مرة ،وعادًة ما تمَّثل الصور بثمانية ِبّتات لكل من قيم األحمر واألخضر واألزرق وثمانيttة ِبّت ات إضttافية مخصصttة
للمعلومات اإلضافية (قناة ألفا) ما مجموعه ِ 32بّت ،وبالتالي سيؤدي استخدام متغير بحجم ِ 64بّت في كثير من
الحاالت إىل إهدار أول ِ 32بّت عىل األقل إذا لم ُيهَدر أكثر من ذلttك ،وليس هttذا فحسttب ،وإنمttا حجم مصttفوفة
يعني هذا أّن البرامج ستستهلك حجًم ا أكبر من ذاكرة النظام دون أّي تحسن يذكر في أدائttه ،وبالتttالي حجًم ا
أكبر من ذاكرة التخزين المؤقت cacheالتي سttنتحدث عنهttا بالتفصttيل في فصttل معماريttة الحاسttوب ،ولهttذا
السبب اختttار نظttام وينttدوز االحتفttاظ بتخttزين قيم المتغttيرات من نttوع longفي 32بت ،فبمttا أّن الكثttير من
واجهات APIعىل نظام ويندوز قد ُكتَبت في األصل الستخدام متغيرات من نوع longمخَّزنة عىل نظام ِ 32بّت ،
لذا ال تحتاج إىل ِبّتات إضافية ،مما سيوفر ذلك مساحًة مهttدورًة كبttيرًة في النظttام دون الحاجttة إىل إعttادة كتابttة
إذا جربنا البديل المقttتَر ح المتمثttل في إعttادة تعريtف المتغttير من نtوع shortليكtون متغttيًرا يخَّtزن عىل
ِ 32بت ،فسيستطيع المبرمجون الذين يعملون عىل نظام ِ 64بت تحديد هذا النوع للمتغيرات التي يعلمtون أنهtا
مرتبطة بقيم أصغر ،ولكن عند العودة إىل نظام ِ 32بّت ،فسيكون متغttير shortنفسttه الttذي حttدوده اآلن بحجم
ِ 16بّت فقط ،وهي قيمة تجاوزوها بمراحل كبيرة عملًي ا ،أي .210 = 65536
سيحقق جعل المبرمج يطلب متغيرات أكبر حجًم ا عندما يعلم أنه سيحتاج إليها توازًنا فيما يتعلttق بمخttاوف
يتحدث معيار اللغة Cأيًض ا عن بعض المttؤهالت qualifiersألنttواع المتغttيرات ،إذ يشtير المؤهttل const
مثاًل إىل أّن المتغير لن ُتعَّد ل قيمته األصلية أبًدا ،والمؤهل volatileيقترح عىل المصِّرف بttأّن قيمttة المتغttير
53
علوم الحاسوب من األلف إىل الياء تمثيل األعداد والنظام الثنائي
قد تتغير بعيًدا عن تدفق تنفيذ البرنامج ،لذا يجب أن يحرص المصِّرف عىل عدم إعادة ترتيب الوصttول إليttه بttأّي
شكل من األشكال ،كما ُيَع ّد كل من مؤهل المؤَّش ر signedومؤهtل غtير المؤَّش ر unsignedأنهمtا المtؤهَلين
األهم عىل األرجح ،فهمttا يحِّttددان فيمttا إذا كttان ُيسَtم ح للمتغttير بttأن يأخttذ قيمًtة سttالبًة أم ال ،وسttنتناول هttذا
الغرض من جميع المؤهالت هو تمرير معلومات إضافية للمصِّرف حول كيفيttة اسttتخداِم ه للمتغttير ،ويعttني
هذا أمَرين وهما أّن المصِّرف قادر عىل التحقق مما إذا انتهكت القواعد التي وضttعتها بنفسttك مثttل الكتابttة في
متغير قيمتttه ثابتttة ،constوقttادر عىل إجttراء تحسttينات بنttاًء عىل المعلومttات اإلضttافية ،وسttندرس هttذا في
فصول الحقة.
يدرك واضعو معيار C99أّن كل هذه القواعد واألحجام ومخاوف توفر قابليttة للنقttل قttد تصttبح مربكًtة جًttدا،
ولتسهيل األمر فقد قدموا في المعيار سلسلًة من األنواع الخاصttة الttتي تحِّttدد الخصttائص المضttبوطة للمتغttير،
وُتحَّtد د في الترويسtة < >stdint.hوصtيغتها ،qtypes_tإذ يرمtز المحtرف qإىل المؤهtل ويرمtز typeإىل
النوع األساسي ،في حين يرمز المحرف sإىل الحجم بواحدة الِبّت و _tهو امتداد يشير إىل أنك تستخدم األنواع
تشير الصيغة uint8_tمثاًل إىل عدد صحيح غير مؤَّش ر يخَّزن عىل ِ 8بّتات بالضبط ،وقد ُع ِّرَف ت العديttد من
األنواع األخرى ،إذ يمكنك االطالع عىل القائمة الكاملة المفَّص لة في مقطع المكتبة المعيارية 17.8لمعيار C99أو
في ملف الترويسة الموجود بصورة مشَّف رة ،كما إّن توفير هذه األنواع هي مهمة النظttام الttذي يطبttق معيttار C99
بأن يحِّدد لها األنواع ذات الحجم المالئم عىل النظام المسtتهَدف ،فمثاًل تtوِّف ر مكتبtات النظttام هttذه الترويسtات
الحظ أّن معيار C99فيه عوامل مساعدة لتحقيق قابلية النقل لـ ،printfإذ يمكن استخدام وحدات ماكرو
PRI macrosفي < >inttypes.hعىل أساس عوامل محددة لألنواع التي ُحِّددت أحجامها ،وكما ذكرنا يمكنك
نرى في النموذج التالي الذي يمِّثل التحذيرات التي تttرد عنttدما ال تتطttابق األنttواع مثttااًل عىل فttرض األنttواع
قيوًدا تحِّدد أّي العمليات متاح تنفيذها عىل المتغير وكيف يستعين المصِّرف بهذه المعلومات لعرض تحذير عند
استخدام المتغيرات بطريقة تخالف تلك القيود ،إذ تبدأ الشيفرة البرمجية بإسttناد قيمttة عttدد صttحيح integer
للمتغير ،charوبما أّن حجم المتغير charأصغر ،فسنفقد القيمة الصحيحة للعدد الصحيح .integer
نحttاول بعttدها تعttيين مؤشttر pointerللمتغttير charيشttير إىل الttذاكرة الttتي حttددنا بأنهttا عttدد صttحيح
،integerويمكن تنفيذ هذه العمليttة ،لكنهtا ليسtت آمنًtة ،لtذا ُنِّف ذ المثttال األول عىل جهtاز معالجttه بينtتيوم
54
علوم الحاسوب من األلف إىل الياء تمثيل األعداد والنظام الثنائي
Pentiumذو ِ 32بّت ،وأعيَدت القيمة الصحيحة ،لكن يبلغ حجم المؤشر 64بّت -أي 8بايت -في نظttام معالجttه
إيتانيوم Itaniumذو ِ 64بّت كمtا هttو موضttح في المثttال الثttاني ،ولكن حجم العttدد الصttحيح integerيبلtغ
يمكننا محاولة خداع المصرف بتحويل القيمة قبtل إسtنادها ،والحtظ أننtا في هtذه الحالtة فاقمنtا المشtكلة
عندما نَّف ذنا هذا التحويل وتجاهلنا تحذير المصرف ،ألّن المتغttير األصttغر ال يمكنtه االحتفttاظ بجميttع المعلومtات
*/
* types.c
*/
>#include <stdio.h
>#include <stdint.h
)int main(void
{
;char a
;"char *p = "hello
;int i
;return 0
}
$ uname -m
i686
55
علوم الحاسوب من األلف إىل الياء تمثيل األعداد والنظام الثنائي
2.2.4تمثيل األعداد
سنشرح كيفية تمثيل األعداد بمختلف مجاالتها مثل األعداد السالبة واألعداد العشرية وغيرهما.
نمِّيز العدد السالب في نظامنا العشري الحtديث بوضtع عالمtة الطtرح -قبلtه؛ أمtا عنtدما نسtتخِدم النظtام
الثنائي ،فعلينا اتباع أسلوب مختلف عند اإلشارة إىل األرقام السالبة ،إذ يوجttد نظttام وحيttد شttائع اسttتخدامه في
العتاد الصلب الحديث ،لكن معيار C99يحِّدد ثالثة أساليب مقبولة لتمثيل القيمة السلبية.
أبسط طريقة هي تخصيص بت واحد من العدد يشير إىل قيمة سالبة أو موجبة حسب هل هttو مح َّtد د أم ال،
وهذا مشابه للنهج الرياضي الذي يبين قيمة العدد بإشارتي +و ،-إذ ُيَع ّد هذا منطقًي ا نوًع ا ما ،وقttد مّثلت بعض
أجهزة الحاسوب األولية أعداًدا سالبًة بهذه الطريقة ،لكن يتيح استخدام األعداد الثنائية بعض االحتمttاالت األخttرى
56
علوم الحاسوب من األلف إىل الياء تمثيل األعداد والنظام الثنائي
الحظ أّن القيمة 0قد أصبح لها اآلن قيمتان مكافئتان ،واحدة ُحِّدد فيها ِبّت إشارة وواحدة دون تحديده ،وقد
يطِّبق نهج المتِّم م األحادي العملية notعىل العدد الموجب من أجل تمثيل العدد السالب ،لذا تمَّثل القيمة
الحظ أن العاِم ل ~ هو عاِم ل في اللغة Cالذي يطبق عاِم ل NOTعىل القيمة ،كما يدعى أحياًنا بعاِم ل المتمم
الميزة األكبر في هذا النظام هي أنه ال يشترط تطبيق منطق خاص عند إضافة عدد سالب إىل عttدد مttوجب،
باستثناء أنه يجب إضافة أّي حمل carryإضافي متبقي إىل القيمة النهائية ،لذا تأمل الجدول التالي:
01100100 100
-------- ---
1
9 00001001 10
10 00001010
إذا أضفت البتات الواحد تلو اآلخر ،فستجد أنه سينتج لديك في النهايtة ِبّت حمtل carry bitالموَّض ح في
الجدول ،وستنتج لدينا القيمة الصحيحة 10بإضافته مجدًدا إىل العدد األصلي.
مجدًدا ال تزال لدينا مشكلة تمثيل الِص فرين ،وال يوجد حاسوب حديث يسttتخِدم المتمم األحttادي ،والسttبب
يتشابه المتمم الثنائي تماًم ا مع المتمم األحادي ،باستثناء أّن التمثيل السالب يضاف إليه واحد ونتجاهل أّي
ِبّتات حمل متبقية ،فإذا طبقناه عىل المثال السابق ،فسنمِّثل العدد -90وفق ما يلي:
~01011010+1=10100101+1 = 10100110
يعني هذا أّن هناك تماثاًل غريًبا بعض الشيء في األعداد التي يمكن تمثيلها؛ ففي العtدد الصtحيح integer
مثاًل الذي يخَّزن عىل ِ 8بت لدينا 256 = 82قيمة ممكنة ،كما يمكننا تمثيل -127في نهج تمثيttل ِبت اإلشttارة
بواسطة ،127لكن يمكننا تمثيل -127في نظام المتمم الثنائي بواسطة 128ألننا أزلنا مشttكلة وجttود صttفرين،
57
علوم الحاسوب من األلف إىل الياء تمثيل األعداد والنظام الثنائي
01100100 100
-------- ---
00001010 10
ال بّد أنك الحظت أّن تطبيق المتمم الثنائي لن ُيحيج مصممي العتاد الصلب إال إىل توفير عمليات منطقيttة
لدارات اإلضافة ،إذ يمكن إجراء عملية الطرح عن طريق متمم ثنائي ينفي القيمة المراد طرحها ثم يضيف القيمttة
الجديدة ،وبالمثل يمكنك تنفيذ عملية الضرب بالجمع المتكرر وعملية القسمة بالطرح المتكttرر .وبالتttالي يخttتزل
المتمم الثنائي جميtع العمليtات الحسtابية البسtيطة بعمليtة الجمtع ،ومن الجtدير بالtذكر أن جميtع الحواسtيب
بناًء عىل صيغة المتمم الثنائي ،عند زيادة حجم القيمة المؤَّش رة ،signed valueمن المهم أن تم َّtد د إشttارة
sign-extendedالبتات اإلضافية ،أي المنسوخة من الِبّت األولي للقيمة الحالية ،إذ تمَّثل قيمة العدد الصttحيح
-10من نttوع intالمخَّttزن عىل 32بت في المتمم الثنttائي في النظttام الثنttائي عttبى سttبيل المثttال بالعttدد
،111111111111111111111111110110فإذا أردنا تحويله إىل عttدد صttحيح من نttوع long long int
مخَّttزن عىل ِ 64بّت ،فعلينttا أن نحttرص عىل تعttيين الttرقم 1للـ ِ 32بّت اإلضttافية لالحتفttاظ باإلشttارة نفسttها
للعدد األصلي.
بفضل المتمم الثنائي ،يكفي أخذ الِبّت األولي من قيمttة الخttرج exiting valueواسttتبدال جميttع البتttات
المضافة بهذه القيمة ،ويشار إىل هذه العمليات باسم امتداد اإلشارة ،وعادًة يتعامل معها المصِّtرف في الحttاالت
المحَّد دة في معيار اللغة ،مع توفير المعالج عموًم ا تعليمات خاصة ألخذ قيمة وتمديد إشارتها إىل قيمة أكبر.
تحدثنا حتى اآلن عن األعداد الصحيحة integerأو األعداد الكاملة فقط ،وتسمى فئة األعttداد الttتي يمكن أن
58
علوم الحاسوب من األلف إىل الياء تمثيل األعداد والنظام الثنائي
نحتاج إلنشاء عدد عشري إىل طريقة لتمثيل مفهوم الجزء العشري في النظام الثنائي ،وُيعرف النظام األشttهر
الذي يحقق ذلك بمعيار األعداد العشرية IEEE-754ألن من نشره كان معهد مهندسي الكهربtاء واإللكtترون ،كمtا
ُيَع ّد النظام بسيط للغاية من ناحية المفهوم ،وهو مشابه إىل حد ما للصيغة العلمية .scientific notation
قد تمَّثل القيمة 123.45عموًم ا في الصttيغة العلميttة بالصttيغة ،1.2345*102إذ نسttمي 1.2345الجttزء
المعنttوي ( significantأو الجttزء األهم األساسttي الttذي لttه أهميttة)؛ أمttا 10فهttو األسttاس radixو 2هttو
اُألس .exponent
نفكك البتات المتاحة في نموذج العدد العشري IEEEلنمِّثل اإلشارة والجزء العشري وأس العدد العشري ،إذ
يمَّث ل العدد العشري بالصيغة" :اإلشارة × الجزء المعنttوي × األس ،"2ويعttادل ِبّت اإلشttارة إمttا 1أو ،-1وبمttا أننttا
نعمل في النظام الثنائي ،فسيكون لدينا دائًم ا األسttاس الضttمني ،2كمttا تتنttوع أحجttام قيمttة العttدد العشttري،
وسندرس في الفقرة التالية القيمة التي تخَّزن عىل 32بت فقط ،وكلما زاد عدد البتات حظينا بدقة أكبر.
العامل المهم اآلخر هو انحياز biasاألس ،إذ يجب أن يمِّثل األس القيم الموجبttة والسttالبة ،وبالتttالي ُتطَtر ح
القيمة الضمنية للعدد 127من األس ،إذ يحتوي األس 0مثاًل عىل حقل أس يسttاوي ،127في حين يمِّث ل 128
يضيف كل ِبّت من الجزء المعنوي مزيًدا من الدقة إىل القيم التي يمكننا تمثيلها ،وَض ع في الحسبان تمثيttل
الصيغة العلمية للقيمة ،198765إذ يمكننا كتابة هذا بالصيغة ،1.98765x106الذي يقابل التمثيل التالي:
يتيح كل رقم إضافي مجااًل أكبر من القيم العشرية التي يمكننا تمثيلها ،إذ يزيد كل رقم بعد الفاصلة العشرية
من دقة العدد بمقدار 10مرات في النظام العشري ،فيمكننا مثاًل تمثيل 0.0بـ - 0.9أي 10قيم -برقم واحد بعد
الفاصلة عشرية ،و 0.00بـ - 0.99أي 100قيمة -برقمين ،وهكذا؛ أما في النظام الثنائي ،فبداًل من أن يمنحنا كل
رقم إضافي دقة أكبر بعشر أضعاف ،ال نحظى إال بضعَف ي الدقة كما هو موَّض ح في الجدول التالي ،ويعني هttذا أّن
التمثيل الثنائي الخاص ال يوّجهنا دائًم ا بطريقة مباشرة إىل التمثيل العشري.
59
علوم الحاسوب من األلف إىل الياء تمثيل األعداد والنظام الثنائي
ال تكون دقة كسورنا كبtيرًة جًtدا باسttتخدام ِبّت واحtد فقttط للدقttة ،فال يسtعنا إال أن نقttول أّن الكسtر إمtا 0
أو ،0.5فإذا أضفنا ِبًّتا آخًرا للدقة ،فيمكننا اآلن القول أن القيمttة العشttرية هي إمttا 0أو 0.25أو 0.5أو .0.75
ومع إضافة ِبّت آخر للدقة يمكننا اآلن تمثيل القيم .0.875 ،0.75 ،0.625 ،0.5 ،0.375 ،0.25 ،0.125 ،0
وبالتالي فكلما زدنا عدد الِبّتات حظينا بدقة أكبر ،لكن بما أّن مجال األعداد المحتملة غير محttدود ،فلن تكفي
الِبَتات أبًدا لتمثيل أية قيمة محتَم لة ،فإذا كان لدينا ِبّتين فقط للدقttة عىل سttبيل المثttال ،وأردنtا تمثيttل القيمtة
،0.3فال يمكننا القول إال أنها أقرب إىل ،0.25وطبًع ا هtذا غtير كtاف في معظم التطبيقtات ،لكن عنtدما يكtون
لدينا ِ 22بّت للجزء المعنوي ،فسنحظى بدقة أفضل بكثير ،لكن ال يزال ذلك غير كاف في معظم التطبيقات.
تزيد قيمة متغير من نوع doubleعدد بتات الجttزء المعنttوي إىل 52بت ،كمttا أنهttا تزيttد مجttال قيم األس
أيًض ا ،كما تخصص بعض األجهزة ِ 84بّت للعدد العشttري ،وِ 64بّت للجttزء المعنttوي ،إذ تttتيح ِ 64بّت تلttك دقًtة
هائلًة ال بّد أن تكون مناسبًة لجميع التطبيقات باستثناء التطبيقات شديدة التعقيد والتي تحتاج حجًم ا أكبر ( هttل
)int main(void
{
;float a = 0.45
;float b = 8.0
;double ad = 0.45
;double bd = 8.0
;return 0
}
$ gcc -o float float.c
60
علوم الحاسوب من األلف إىل الياء تمثيل األعداد والنظام الثنائي
$ ./float
float+float, 6dp : 8.450000
double+double, 6dp : 8.450000
float+float, 20dp : 8.44999998807907104492
dobule+double, 20dp : 8.44999999999999928946
$ python
)Python 2.4.4 (#2, Oct 20 2006, 00:23:25
[GCC 4.1.2 20061015 (prerelease) (Debian 4.1.1-16.1)] on linux2
Type "help", "copyright", "credits" or "license" for more
information.
>>> 8.0 + 0.45
8.4499999999999993
ُيَع ّد النموذج السابق مثااًل عملًي ا لما تحدثنا عنه ،والحظ تطttابق اإلجttابتين بالنسttبة لألجttزاء العشttرية السttتة
االفتراضية لتحقيق الدقة التي حددناها في ،printfوذلttك ألن عمليttة تقريبهمttا نِّف ذت تنفي ًtذ ا صttحيًحا ،لكن
عندما ُيطلب منك إعطاء نتائج بدقة أكبر ولتكن في هذه الحالة 20منزلة عشرية ،فسنجد أنها تبدأ في االختالف.
منحتنا الشيفرة البرمجية التي تستخِدم النوع doubleنتيجًة أدق ،لكنها ال تزال غttير صttحيحة كلًي ا ،كمttا أّن
المبرمجين الذين ال يتعاملون بوضوح مع القيم من نوع floatال يزالون يواجهون مشاكل في دقة المتغيرات.
يمكننا تمثيttل قيمtة بعttدة أسttاليب مختلفttة في الصttيغة العلميttة مثttل = 10023x100 = 1002.3x101
،100.23x102وبالتالي نعّرف صيغة التوحيد بأنه الصيغة التي يكون فيها < 1/radix <= significand
،1إذ تعني radixاألساس وتعني significandالجزء المعنttوي ،والعttدد الموَّح د normalized numberهttو
العدد المكتوب بالصيغة العلمية scientific notationمع رقم عشري واحد غير صفري عىل األقل بعد الفاصلة.
يضمن هذا في النظام الثنائي أن يكون الِبّت الذي يقع أقصى اليسار leftmost bitمن الجزء المعنوي دائًم ا
،1فعند معرفتنا لذلك ،يمكننا الحصول عىل ِبّت إضافي للدقة حسب ما ورد في المعيار أنttه عنttدما يكttون البت
20 . 2-1 2-2 2-3 2-4 2-5 األس العملية الحسابية
0 . 0 1 1 0 0 0
)2 0.375 = 1* (0.25+0.125
61
علوم الحاسوب من األلف إىل الياء تمثيل األعداد والنظام الثنائي
كما ترى في المثال السابق ،يمكننا جعل القيمة قيمttة موَّح دة من خالل تحريttك الِبّت ات لألمttام طالمttا أننttا
من المشttكالت الشttائعة الttتي يواجههttا المttبرمجون هي العثttور عىل أول ِبّت ضttبط في مجموعttة الِبّت ات
،bitfieldولنأخذ مجموعttة البتttات 0100مثttااًل ،فعنtد البtدء من اليمين ،يكtون ِبّت الضttبط األول هttو الِبّت ،2
الطريقة المعيارية للعثور عىل هذه القيمة هي اإلزاحة إىل اليمين والتحقق مما إذا كttان الِبّت األول هttو 1أي
بت الضبط ،ثم إنهاء العملية أو تكرارها ،وُتَع ّد هذه عمليًة بطيئًة ،فإذا كان طول مجموعة الِبّتات ِ 64بّت وكان ِبّت
الضبط هو األخير فقط ،فيجب أن تمر بجميع البتات الثالثة والستين التي تسبقها.
لكن إذا كانت قيمة مجموعة البتات هذه هي الجزء المعنوي لعدد عشري وكان علينا توحيدها ،فسنعرف من
قيمة األس عدد مرات إزاحتها ،كما تضَّم ن عملية التوحيد عموًم ا في وحدة عتاد العدد العشري عىل المعالج ،لttذا
تؤَّد ى بسرعة كبيرة ،وعادًة أسر ع بكثير من عمليات اإلزاحة واالختبار المتكررة.
يوِّض ح البرنامج التالي طريقتين للعثttور عىل أول ِبّت ضttبط مَّتبعَتين عىل معttالج إتttانيوم .إذ يttدعم المعttالج
إتانيوم -مثل حال معظم معالجات الخوادم -نوع العدد العشري الموَّس ع الذي يخَّزن عىل ِ 80بّت ،والجزء المعنوي
الذي يخزن عىل ِ 64بّت ،ويعني هذا أّن نوع unsigned longيتوافttق بدقttة في الجttزء المعنtوي لنtوع long
،doubleفعندما تحَّم ل القيمة توَّحد ،وبالتالي من خالل قراءة قيمة األس مطروًح ا منهttا انحيttاز ِ 16بّت يمكننttا
>#include <stdio.h
)int main(void
{
في التمثيل الثنائي = // 1000 0000 0000 0000
// 5432 1098 7654 3210 عدد البتات
;int i = 0x8000
;int count = 0
{ ) )while ( !(i & 0x1
;count ++
;i = i >> 1
}
;)printf("First non-zero (slow) is %d\n", count
62
علوم الحاسوب من األلف إىل الياء تمثيل األعداد والنظام الثنائي
}
نستخرج مكونات العدد العشري ونطبع القيمة التي يمثلها في نموذج الشيفرة البرمجيttة التاليttة ،إذ سttنحرز
نتيج ًtة فقttط عنttدما تكttون القيمttة عttدًدا عشttرًيا بحجم ِ 32بّت بصttيغة المعيttار ،IEEEوهttذا شttائع في معظم
>#include <stdio.h
>#include <string.h
>#include <stdlib.h
63
علوم الحاسوب من األلف إىل الياء تمثيل األعداد والنظام الثنائي
double two_to(int n)
{
if (n >= 0)
return two_to_pos(n);
if (n < 0)
return two_to_neg(n);
return 0;
}
if (argc != 2)
{
printf("usage: float 123.456\n");
exit(1);
}
64
علوم الحاسوب من األلف إىل الياء تمثيل األعداد والنظام الثنائي
الجزء المعنوي يمأل المنازل العشرية ،ويكون أول بت ضمنًيا /* 1
. */بت OR 24وبالتالي هو قيمة المعامل
;significand = (m & 0x7FFFFF) | 0x800000
65
علوم الحاسوب من األلف إىل الياء تمثيل األعداد والنظام الثنائي
{
)if ((significand >> i) & 1
printf("%s1/%d", (i == 23) ? "" : " + ",
;))(int)two_to(23-i
}
;)printf(") * 2^%d\n", exponent
;return 0
}
نستخلص من هذا المثال فكرًة عن تسلل عدم الدقة إىل أعدادنا العشرية.
66
.3معمارية الحاسوب
اآلتي أواًل ضبط R1عىل القيمة 100وتحميل القيمة من موقع الذاكرة 0x100إىل R2وجمع القيمتين ،ثم وضع
يتكون الحاسوب من وحدة معالجة مركزية - Central Processing Unitأو CPUاختصاًرا -متصلة بالذاكرة،
إذ توّض ح الصورة السابقة المبدأ العام لجميع عمليات الحاسوب ،كما تنّف ذ وحtدة المعالجttة المركزيtة التعليمtات
التعليمات التي تحّم ل القيم من الذاكرة إىل المسجالت وتخّزن القيم من المسجالت إىل الذاكرة. .1
التعليمات التي ُتشَّغ ل عىل القيم المخَّزنة في المسّجالت مثل جمع أو طرح أو ضرب أو قسمة قيمttتين .2
موجودتين في مسجلين ،أو إجراء العمليات الثنائية andو orو xorوغيرها ،أو إجراء عمليات حسttابية
لذا نجمع في مثالنا ببساطة العدد 100مع قيمة ُم خَّزنة في الذاكرة ونخّزن النتيجة الجديدة في الذاكرة.
3.1.1التفريع Branching
ُيَع ّد التفريع عمليًة مهمًة لوحدة المعالجة المركزية ،وذلك بغض النظttر عن عمليttتي التحميttل أو التخttزين ،إذ
تحتفttظ وحttدة المعالجttة المركزيttة داخلًي ا بسttجل للتعليمttة التاليttة الttتي سttتنَّف ذ في مؤشttر التعليمttات
،Instruction Pointerبحيث ُيزاد هذا المؤشtر ليؤّش ر إىل التعليمtة التاليtة تسلسtلًي ا ،إذ سtتتحقق التعليمtة
الفرعية مما إذا كان لمسجل معّي ن القيمة صفر ،أو تتحقttق من وجttود من ضttبط رايttة flagمttا .فttإذا كttان األمttر
كذلك ،فسُيعَّد ل المؤشر ليؤّش ر إىل عنوان مختلف ،وبالتالي ستكون التعليمة التالية للتنفيذ من جزء مختلف من
يمكن مثاًل تنفيذ التعليمة ) if (x==0من خالل إيجاد ناتج تطبيق عملية orعىل اثttنين من المسttجالت،
أحدهما يحمل القيمة xواآلخر يحمل القيمة صفر ،فإذا كانت النتيجة صفًرا ،فسttتكون المقارنttة صttحيحة ،أي أّن
جميع بتات xأصفار ويجب تنفيذ جسم التعليمة ،وإاّل فستتجاوز التعليمة الفرعية هذه الشيفرة.
3.1.2الدورات
جميعنا عىل دراية بسرعة الحاسttوب المعطttاة بالميجttاهرتز Megahertzأو الجيجttاهرتز Gigahertzالtتي
تقابل ماليين أو آالف الماليين من الدورات في الثانية ،ويسمى ذلك بسرعة الساعة Clock Speedألنها السرعة
التي تنبض بها ساعة الحاسوب الداخلية ،إذ ُتستخَدم النبضttات ضttمن المعttالج إلبقائttه متزامًن ا داخلًي ا ،ويمكن
إذ يجب عىل وحدة المعالجة المركزية تطبيق الخطوات التالية لتنفيذ تعليمة addالسابقة مثاًل :
.2فك التشفير :Decodeفك تشفير ما يجب أن تفعله داخلًي ا ،أي الجمع في هذه الحالة.
.4التخزين :Storeتخزين النتيجة في مسجل آخر ،كما يمكن رؤية مصطلح انتهاء Retiringالتعليمة.
68
علوم الحاسوب من األلف إىل الياء معمارية الحاسوب
تحتوي وحدة المعالجttة المركزيttة داخلًي ا عىل العديttد من المكونttات الفرعيttة المختلفttة الttتي تطّب ق كاًل من
الخطوات المذكورة سابًق ا ،كما يمكن أن تحدث جميعها بصورة مستقلة عن بعضها البعض ،وهي مشttابهة لخttط
اإلنتاج في المصانع ،حيث توجد العديد من المحطات ولكل خطوة َم همة معينة ألدائها ،ثم يمكنttه تمريttر النتttائج
تتكون وحدة المعالجة المركزية من العديد من المكونات الفرعية المختلفة ،وتطّبق كل منها مهمًة ُم خَّص صًة ،
وتوِّض ح الصورة السابقة مخطًط ا بسيًط ا لبعض األجزاء الرئيسية لوحدة المعالجة المركزية الحديثttة ،حيث يمكنttك
رؤية التعليمات تأتي ثم يفك المعالج تشفيرها؛ كما تحتوي وحttدة المعالجttة المركزيttة عىل نttوعين رئيسttيين من
المسّجالت ،هما مسttجالت العمليttات الحسttابية الخاصttة باألعttداد الصttحيحة ومسttجالت العمليttات الحسttابية
ُتَع ّد األعداد العشرية Floating Pointطريقًة لتمثيل األعداد ذات المنزلة العشtرية بصttيغة ثنائيttة ،ويجttري
التعامل معها بطريقة مختلفtة ضtمن وحtدة المعالجtة المركزيtة ،كمtا ُتَع ّد المسtجالت ( MMXتوسtع الوسtائط
المتعttددة )Multimedia Extensionو ( SSEمجttرى بيانttات متعttددة لتعليمttة مفttردة Streaming Single
69
علوم الحاسوب من األلف إىل الياء معمارية الحاسوب
ُيَع ّد ملtف المسtجالت Register Fileاسًtم ا يجمtع جميtع المسtجالت الموجtودة ضtمن وحtدة المعالجtة
المركزية ،وتوجد ضمنه أجزاء وحدة المعالجة المركزية التي تنّف ذ كل العمل ،إذ تحّم ل المعالجttات أو تخّttزن قيمًtة
في مسttجل أو من مسttجل إىل الttذاكرة ،أو تنّف ذ بعض العمليttات عىل القيم الموجttودة في المسttجالت كمttا
ُتَع ّد وحدة الحساب والمنطق - Arithmetic Logic Unitأو ALUاختصاًرا -قلب عمليtات وحtدة المعالجtة
المركزية ،إذ تأخذ القيم من المسجالت وتنّف ذ أًيا من العمليات المتعددة التي تستطيع وحttدة المعالجttة المركزيttة
تنفيذها ،كما تحتوي جميع المعالجات الحديثة عىل عدد من وحدات ،ALUبحيث يمكن لكل منها العمل بصttورة
مستقلة ،وتحتوي المعالجات مثل المعالج بنتيوم Pentiumعىل وحدات ALUسريعة ووحدات ALUبطيئttة ،إذ
تكون الوحدات السريعة أصغر حجًم ا ،لttذا يمكنttك اسttتخدام المزيttد منهttا عىل وحttدة المعالجttة المركزيttة ،ولكن
يمكنك تنفيذ العمليات األكثر شيوًع ا فقط؛ أما وحدات ALUالبطيئttة ،فيمكنهttا تنفيttذ جميttع العمليttات ولكنهttا
تعاِلج وحدة إنشاء العناوين - Address Generation Unitأو AGUاختصاًرا -التواصل مع الذاكرة المخبئية
Cache Memoryوالذاكرة الرئيسية لجلب القيم إىل المسجالت لكي تعمل وحttدة ،ALUثم اسttتعادة القيم من
المسجالت إىل الذاكرة الرئيسية ،كما تحتوي مسجالت األعداد العشرية عىل المفttاهيم نفسttها ،ولكنهttا تسttتخِدم
ُتَع ّد عملية وحدة ALUالتي تجمع قيم المسجالت منفصلًة تماًم ا عن عمليtة وحtدة AGUالtتي تكتب القيم
في الذاكرة ،إذ ال يوجد سبب يمنع وحدة المعالجة المركزية من تطبيق هاتين العمليتين مًع ا في وقت واحد ،كمttا
توجد عدة وحدات ALUفي النظام والتي يمكن أن تعمل كل منها عىل تعليمات منفصلة.
يمكن لوحدة المعالجة المركزية تنفيذ بعض عمليات األعداد العشرية باستخدام منطق األعداد العشرية أثنttاء
تشغيل تعليمات األعداد الصحيحة أيًض ا ،إذ تسمى هذه العملية باستخدام خط األنابيب ،Pipeliningويشار إىل
المعالج الذي يمكنه تطبيق هذه العملية بtأن لtه معماريtة عدديtة فائقtة ،Superscalar Architectureإذ ُتَع ّد
جميع المعالجات الحديثة معالجات عدديًة فائقًة ،ويحتttوي أّي معttالج حttديث عىل أكttثر من أربttع مراحttل يمكنttه
اسttتخدامها ضttمن خttط أنttابيب ،وكلمttا زاد عttدد المراحttل الttتي يمكن تنفيttذها في الttوقت نفسttه ،زاد عمttق
خط األنابيب.
يمكن تشبيه خط األنابيب بtأنبوب مملtوء بكtرات زجاجيtة ،باسtتثناء أن هtذه الكtرات هي تعليمtات وحtدة
المعالجة المركزية ،إذ ستضع الكرات الزجاجية في نهاية واحدة ،بحيث تضttعها واحttدًة تلttو األخttرى -أي كttرة لكttل
نبضة ساعة -حتى تمأل األنبوب ،وستنتقل كل كرة زجاجية -أو تعليمة -تدفعها للداخل إىل الموضع التttالي بمجttرد
70
علوم الحاسوب من األلف إىل الياء معمارية الحاسوب
تؤدي التعليمات الفرعية إىل إحداث فوضtى في هtذا النمtوذج ،إذ يمكن أن تتسtبب أو ال تتسtبب في بtدء
التنفيذ من مكان مختلف ،فإذا أردت استخدام خط األنابيب ،فسيتعين عليك تخمين االتجاه الttذي سttتتجه فيttه
التعليمة الفرعية حتى تعtرف التعليمtات الtتي يجب إحضtارها إىل خtط األنtابيب ،فtإذا خّم نت وحtدة المعالجtة
المركزية ذلك بصورة صحيحة ،فسيسير كل شيء عىل ما يرام ،إذ تستخِدم المعالجات مثل معttالج بنttتيوم ذاكttرة
تخزين مttؤقت Trace Cacheلتعقب مسttار التعليمttات الفرعيttة ،حيث يمكن في كثttير من األحيttان أن تخّم ن
الطريق الذي ستذهب إليه التعليمة الفرعية من خالل تذكر نتائجها السابقة ،فإذا تّذكرَت نتيجة التعليمttة الفرعيttة
األخيرة في حلقة تتكرر 100مرة مثاًل ،فستكون عىل صواب 99مرة ،ألن المرة األخيرة فقط ستستمر في البرنامج
فعلًي ا؛ بينما إذا جرى تخمين المعالج بطريقة غير صحيحة ،فهذا يعني أّن المعالج قد أهدر كثيًرا من الوقت ويجب
يشار إىل هذه العملية عادًة باسم تفريغ خttط األنttابيب Pipeline Flushوهي مماثلttة للحاجttة إىل التوقttف
وإفراغ كل الكرات من األنبوب ،كما تتكّو ن عملية تخمين التعليمة الفرعية Branch Predictionمن تفريغ خttط
األنابيب وأخذ التخمين أو عدم األخذ به وفتحات تأخير التعليمة الفرعية .Branch delay slots
إذا كانت وحدة المعالجة المركزية هي األنبوب ،فسنكون لك الحرية في إعادة تttرتيب الكttرات ضttمنه طالمttا
أنها تخرج من نهايته بالترتيب نفسه الذي وضعَتها فيه ،إذ نسمي ذلك بترتيب البرنامج Program Orderألنttه
ترتيب التعليمات الُم عَط ى في البرنامج الحاسttوبي ،كمttا يمكنttك االطالع عىل المثttال التttالي الttذي يمثttل إعttادة
r3 = r1 * r2
r4 = r2 + r3
r7 = r5 * r6
r8 = r1 + r7
افترض مجرى التعليمات الموضح سابًق ا ،إذ يجب عىل التعليمة 2انتظار اكتمال التعليمttة 1قبttل أن تبttدأ،
وهذا يعني أّن خط األنابيب يجب عليه التوقف أثناء انتظار القيمة المراد حسابها ،كمttا تعتمttد التعليمتttان 3و 4
عىل قيمttة ،r7ولكن التعليمتttان 2و 3ال تعتمttدان عىل بعضttهما البعض أبًttدا ،وهttذا يعttني أنهمttا يعمالن في
مسجالت منفصلة تماًم ا ،فإذا بّدلنا بين التعليمتين 2و ،3فسنحصل عىل ترتيب أفضل لخط األنابيب ،إذ يمكن
أن ينّف ذ المعالج عماًل مفيًدا بداًل من انتظار اكتمال خط األنابيب للحصول عىل نتيجة التعليمة السابقة.
يمكن أن تتطلب التعليمttات بعض األمttان حttول كيفيttة تttرتيب العمليttات عنttد كتابttة شttيفرة منخفضttة
المستوى ،إذ نطلق عىل هttذا المتطلب دالالت الttذاكرة ،Memory Semanticsفttإذا أردت اكتسttاب الttدالالت
،Acquire Semanticsفهذا يعني أنه يجب عليك التأكد من إكمال نتائج جميttع التعليمttات السttابقة للتعليمttة
71
علوم الحاسوب من األلف إىل الياء معمارية الحاسوب
الحالية ،وإذا أردت تحرير الدالالت ،Release Semanticsفهذا يعني أّن جميtع التعليمtات بعtد هtذه التعليمtة
توجد دالالت أخرى أكثر صرامة وهي حاجز الttذاكرة Memory Barrierأو سttور الttذاكرة Memory Fence
الذي يتطلب أن تكون العمليات مرتبطًة بالذاكرة قبttل المتابعttة ،كمttا يضttمن المعttالج هttذه الttدالالت في بعض
المعماريات ،بينما يجب أن تحددها بصtورة صtريحة في المعماريtات األخtرى ،وال يحتtاج معظم المtبرمجين إىل
- Instruction Set Computerأو CISCاختصاًرا -ومعمارية حاسوب مجموعة التعليمات الُم خَّف ضة Reduced
الحظ أننا في المثال األول من مقالنا حّم لنttا القيم صttراحًة في المسttجالت وأجرينttا عمليttة الجمttع ،ثم خّزنttا
القيمة الناتجة المحفوظة في مسجل آخر في الذاكرة ،إذ ُيَع ّد ذلttك مثttااًل عن نهج RISCللحوسttبة الttذي يشttمل
تنفيذ العمليات عىل القيم الموجودة في المسجالت وتحميل القيم وتخزينها بصttورة صttريحة من الttذاكرة وإليهttا،
كما يمكن أن يكون نهج CISCمجرد تعليمات مفردة تأخذ قيًم ا من الذاكرة وتنفذ عمليttة الجمttع داخلًي ا ثم تكتب
النتيجttة ،وهttذا يعttني أّن التعليمttات يمكن أن تسttتغرق عttدة دورات ،ولكن كال النهجين يحققttان في النهايttة
الهدف نفسه.
ُتَع ّد جميع المعماريات الحديثة معماريات RISCحتى معمارية إنتل بنttتيوم Intel Pentiumاألكttثر شttيوًع ا
والتي تهدم التعليمات داخلًي ا إىل تعليمات فرعية بأسلوب RISCداخل الشريحة قبل التنفيttذ ،بttالرغم من وجttود
مجموعة تعليمات مصَّنفة عىل أنها ،CISCوهناك عدة أسباب لذلك وهي:
تجعل معمارية RISCالبرمجة بلغة التجميع Assemblyأكثر تعقيًدا ،نظًرا ألن جميttع المttبرمجين تقريًب ا •
يستخِد مون لغات عالية المستوى ويتركون العمل الشاق إلنتاج شtيفرة التجميtع للمصّtرف ،Compiler
بما أّن التعليمات الموجودة في معالج RISCأبسط ،فهناك مساحة أكtبر ضtمن شtريحة المسtجالت ،إذ •
ُتَع ّد المسجالت أسر ع أنواع الذواكر كما نعلم من تسلسل الذواكر الهرمي ،ويجب في النهاية تنفيذ جميع
التعليمات عىل القيم المحفوظة في المسجالت ،لذا ستؤدي زيttادة عttدد المسttجالت إىل أداء أعىل عنttد
بما أّن جميع التعليمات ُتنَّف ذ في الوقت نفسه ،فسيكون استخدام خطوط األنابيب ممكًنا ،وكما نعلم أّن •
استخدام خط األنابيب يتطلب تدفقات من التعليمات باستمرار إىل المعالج ،لذلك إذا اسttتغرقت بعض
72
علوم الحاسوب من األلف إىل الياء معمارية الحاسوب
التعليمات وقًتا طوياًل جًدا دون أن تتطلب التعليمات األخttرى ذلttك ،فسيصttبح خttط األنttابيب معقًttدا |
ُيَع ّد معtالج إيتtانيوم Itaniumمثtااًل عىل معماريtة معَّد لtة تسtمى الحوسtبة الصtريحة للتعليمtات الفرعيtة
ناقشنا سابًق ا كيف أّن المعالجات الفائقة لها خطوط أنttابيب بهttا العديttد من التعليمttات في الttوقت نفسttه
ضمن أجزاء مختلفة من المعالج ،إذ يمكن تحقيق ذلك من خالل إعطاء التعليمات للمعالج بالترتيب الttذي يمكن
أن يحقق أفضل استفادة من العناصر المتاحة في وحدة المعالجة المركزيttة ،وقttد كttان تنظيم مجttرى التعليمttات
الواردة تقليدًيا مهمة العتاد ،إذ يصدر البرنامج التعليمات بطريقة تسلسttلية ،ويجب أن ينظttر المعttالج إىل األمttام
الفكرة وراء معمارية EPICهي أّن هناك مزيد من المعلومات المتاحة عىل مسttتويات أعىل والttتي يمكن أن
تجعل هذه القرارات أفضل مما يفعله المعttالج ،ويttؤدي تحليttل مجًtرى من تعليمttات لغttة التجميttع -كمttا تفعttل
المعالجات الحالية -إىل فقدان الكثير من المعلومات التي قّدمها المبرمج في الشيفرة البرمجية األصلية.
فكر في األمر عىل أنه الفرق بين دراسة مسرحية لشكسبير وقراءة نسخة مالحظttات الجttرف Cliff's Notes
من المسttرحية نفسttها ،فكالهمttا يمنحttك النتيجttة نفسttها ،ولكن النسttخة األصttلية تحتttوي عىل جميttع أنttواع
المعلومات اإلضافية التي تحدد المشهد وتعطيك فهًم ا جيًدا للشخصttيات ،وبالتttالي يمكن نقttل منطttق تttرتيب
التعليمات من المعالج إىل المصّرف ،وهذا يعني أّن مطِّو ري المصّرفات يجب أن يكونوا أذكى في محاولة العثttور
عىل أفضttل تttرتيب للشttيفرة البرمجيttة للمعttالج ،كمttا يجب تبسttيط المعttالج كثttيًرا ،إذ ُنِق ل الكثttير من عملttه
إىل المصِّرف.
يوجد مصطلح آخر غالًبا ما ُيسttتخَدم مttع معماريttة EPICوهttو عttالم التعليمttات الطويلttة جًttدا Very Long
- Instruction Worldأو VLIWاختصاًرا ،-إذ ُتوَّس ع كل تعليمة للمعالج إلخباره بالمكان الذي يجب أن ينّف ذ فيttه
التعليمة في وحداته الداخليttة ،وتكمن مشttكلة هttذا األسttلوب في أّن الشttيفرة البرمجيttة تعتمttد كلًي ا عىل طttراز
المعالج الذي ُص ِّرفت الشيفرة البرمجية من أجله ،كما ُتجري الشركات دائًم ا مراجعات عىل العتاد ،وتجعل العمالء
يعيدون تصريف تطبيقاتهم في كل مرة ،مما جعل صيانة مجموعة من الشيفرات البرمجية الثنائية المختلفة أمًرا
غير عملي.
تحل معمارية EPICهذه المشكلة بطريقة علوم الحاسوب المعتادة من خالل إضافة طبقة من التجريttد ،كمttا
تنشئ معمارية EPICعرًض ا مبسًط ا مع بعض الوحttدات األساسttية مثttل الttذاكرة ومسّtجالت األعttداد الصttحيحة
والعشرية بداًل من التحديد الصريح للجزء الدقيق من المعالج الذي يجب أن تنّف ذ التعليمات عليه.
73
علوم الحاسوب من األلف إىل الياء معمارية الحاسوب
الموجودة عىل شريحة المعالج فقط ،لttذا يجب تحميttل الttذاكرة المخبئيttة من ذاكttرة النظttام الرئيسttية ،أي ذاكttرة
الوصول العشوائي - Random Access Memoryأو RAMاختصاًرا ،-ولكن تحتفظ الttذاكرة RAMبمحتوياتهttا
فقط عند الوصل بمصدر طاقة ،لذلك يجب تخزينها عىل مساحة تخزين دائمة وغير متطايرة.
تجدر اإلشارة ألننا نستخدم مصطلح القرص الصلب HDDفي سياق هذا الكتاب مع العلم بأن قرص الحالة
النقطة المهمة التي يجب معرفتها حول تسلسل الذواكر الهرمي هي المقايضttات بين السttرعة والحجم عىل
حساب بعضهما البعض ،فكلما كانت الذاكرة أسر ع ،كان حجمها أصغر.
74
علوم الحاسوب من األلف إىل الياء معمارية الحاسوب
سtبب فعاليtة الtذواكر المخبئيtة هtو أّن شtيفرة الحاسtوب البرمجيtة تعtرض شtكَلين من أشtكال المحليtة
Localityهما:
.1تشير المحلية المكانية Spatial Localityإىل احتمالية الوصول إىل البيانات الموجودة ضمن الكتل مع
.2تشير المحلية الزمانية Temporal Localityإىل أن البيانات المستخَدمة مؤخًرا ُيحتَم ل أن ُتستخَدم مرة
أخرى قريًبا.
يعني ذلك أنه يمكن االسttتفادة من تنفيttذ أكttبر قttدر ممكن من عمليttات الوصttول السttريعة إىل الttذاكرة أي
المحلية الزمانية وتخزين كتل صغيرة من المعلومات ذات الصلة أي المحلية المكانية.
عمل الذاكرة المخبئية في أنظمتهم لكتابة شيفرة برمجية فعالtة ،كمtا ُتَع ّد نسtخًة سtريعًة جًtدا من ذاكtرة النظtام
الرئيسية األبطأ ،وهي أصغر بكثير من الذواكر الرئيسية ألنها مضttمنة داخttل شttريحة المعttالج جنًب ا إىل جنب مttع
المسجالت ومنطق المعالج ،وهناك حدود اقتصادية ومادية ألقصى حجم لها.
تجد الشركات المصنعة مزيًدا من الطرق لحشر مزيد من الترانزستورات عىل الشريحة ،مما يttؤدي إىل زيttادة
أحجام الذواكر المخبئية بصورة كبيرة ،ولكن ُيقَّد ر حجم حتى أكبر الذواكر المخبئيttة بعشttرات الميجابايتttات بعكس
حجم الذاكرة الرئيسية المقَّد ر بالجيجابايتات أو حجم القرص الصلب المقَّد ر بالتيرابايتات.
تتكون الذاكرة المخبئية من قطع صغيرة تعكس محتوى أجزاء من الذاكرة الرئيسية ،إذ ُيطَل ق عىل حجم هttذه
القطع بحجم الخط ،Line Sizeويسtاوي تقريًب ا 32أو 64بtايت ،ومن الشtائع التحtدث عن حجم الخtط أو خtط
الذاكرة المخبئية عند الحديث عن الttذاكرة المخبئيttة ،والttذي يشttير إىل قطعttة واحttدة تعكس محتttوى قطعttة من
الذاكرة الرئيسية ،كما يمكن للtذاكرة المخبئيtة فقtط تحميtل وتخtزين الtذاكرة بأحجtام مضtاعفة من خtط الtذاكرة
المخبئية.
تحتوي الذواكر المخبئية عىل تسلسلها الهttرمي الخttاص ،ويطلttق عليttه عttادًة L1و L2و ،L3إذ ُتَع ّد الttذاكرة
المخبئية L1هي األسر ع واألصغر و L2أكبر وأبطأ منها و L3هي األكبر واألبطأ ،كمttا ُتقَس م الttذاكرة المخبئيttة L1
إىل ذواكر مخبئية خاصة بالتعليمات وأخرى بالبيانات ،وُتعرف باسم معمارية هارفttارد Harvard Architecture
تساعد الذواكر المخبئية المقسtمة عىل تقليttل االختناقttات في خطttوط األنtابيب ،حيث تشtير مراحtل خtط
األنابيب السابقة إىل تعليمات الذاكرة المخبئية وتشير المراحل الالحقة إىل بيانات الذاكرة المخبئيttة ،كمttا يسttمح
توفير ذاكرة مخبئية منفصلة للتعليمات بإجراء تطبيقات بديلة تستفيد من طبيعة مجرى التعليمات بغض النظttر
75
علوم الحاسوب من األلف إىل الياء معمارية الحاسوب
عن فائttدة تقليttل التنttاز ع عىل مttورد مشttترك ،إذ تكttون الttذاكرة المخبئيttة الخاصttة بالتعليمttات للقttراءة فقttط،
أي ال تحتاج إىل ميزات باهظة الثمن عىل الشريحة مثل تعدد المنافذ ،وال تحتاج إىل التعامل مع عمليttات قttراءة
الكتل الفرعية ألن مجرى التعليمات يستخِدم عموًم ا عمليات وصول ذات أحجام أكثر انتظاًم ا.
يوضح الشكل السابق ترابط الذاكرة المخبيئة حيث يمكن أن يجد خط ذاكرة مخبئية معّين مكاًن ا صttالًحا في
يطلب المعالج باستمرار من الذاكرة المخبئية أثنttاء التشttغيل العttادي التحقَtق من تخttزين عنttوان معّين في
الذاكرة المخبئية ،لذلك تحتاج الذاكرة المخبئية لطريقة ما لمعرفة مttا إذا كttان لttديها خttط صttالح أم ال ،فttإذا أمكن
تخزين عنوان معّين في أّي مكان ضمن الذاكرة المخبئية ،فيجب البحث في كل خط من الذاكرة المخبئية في كل
مرة ُينَش أ فيها مرجع لتحديد وصول صحيح أو خاطئ ،كمttا يمكن االسttتمرار في البحث السttريع من خالل إجرائttه
عىل التوازي في عتاد الذاكرة المخبئية ،ولكن يكون البحث في كل إدخال مكلًف ا للغايtة بحيث يتعttذر تطبيقttه في
ذاكرة مخبئية ذات حجم معقول ،لذا يمكن جعل الذاكرة المخبئية أبسط من خالل فttرض قيttود عىل مكttان وجttود
عنوان معّين.
ُيَع ّد ذلك مقايضًة ،فالذاكرة المخبئية أصغر بكثير من ذاكرة النظام ،لذا يجب أن تحمل بعض العناوين أسماء
بديلة Aliasللعنtاوين األخtرى ،فtإذا جtرى تحtديث عنtواَنين يحمالن أسtماء بديلًtة لبعضtهما البعض باسtتمرار،
فسيقال أنهما يتنازعان عىل خط الذاكرة المخبئية ،كما يمكننا تصنيف الذواكر المخبئية إىل ثالثة أنواع عامttة كمttا
الذواكر المخبئية المربوطة مباشرًة Direct mapped Cachesالتي تسمح لخط الذاكرة المخبئية •
بالتواجد فقط في إدخال واحد في الذاكرة المخبئية ،وُيَع ّد ذلك أبسط تطبيق في العتttاد ،ولكن -كمttا هttو
76
علوم الحاسوب من األلف إىل الياء معمارية الحاسوب
موضح في الشكل السابق -ال توجد إمكانيttة لتجنب اسttتخدام األسttماء البديلttة ألن العنttواَنين المظَّللين
الذواكر المخبئية الترابطية بالكامل Fully Associative Cachesالتي تسمح بوجود خttط الttذاكرة •
المخبئية في أّي إدخال منها ،مما يؤدي إىل تجّنب مشكلة األسماء البديلtة ،ألن أّي إدخtال يكtون متاًح ا
لالستخدام ،لكن ُيَع ّد تطبيق ذلك في العتاد مكلًف ا للغايtة ألنtه يجب البحث عن كttل موقttع محتَم ل في
الذواكر المخبئية التجميعيTTة Set Associative Cachesالttتي ُتَع ّد عبttارًة عن مttزيج من الttذواكر •
المخبئية المربوطة مباشرًة والذواكر المخبئيtة الترابطيtة بالكامtل ،وتسtمح بوجtود قيمtة معينtة للtذاكرة
المخبئية في بعض المجموعات الفرعية من الخطوط الموجودة ضمن هذه الذاكرة المخبئية ،كمttا ُتقَس م
الذاكرة المخبئية إىل منtاطق تسَّtم ى طرًق ا ،Waysويمكن وجtود عنtوان معّين في أّي طريtق ،وبالتtالي
ستسمح الذاكرة المخبئية التجميعية المؤلفة من مجموعة من الطttرق عttددها nلخttط الttذاكرة المخبئيttة
بالتواجد ضمن مجموعة اإلدخاالت الttتي عttددها يسttاوي بttاقي قسttمة مجموعttة الكتttل اإلجماليttة ذات
الحجم المحدد عىل ،nويظِه ر الشكل السابق عينًة من ذاكرة تجميعية مؤلفة من 8عناصttر و 4طttرق ،إذ
يكون للعنواَنين أربعة مواقع محتملة ،مما يعني أنه يجب البحث عن نصف الttذاكرة المخبئيttة فقttط في
كل عملية بحث ،وكلما زاد عدد الطرق ،زادت المواقع الممكنة ونقصت األسماء البديلة ،ممttا يttؤدي إىل
أداء أفضل.
يجب أن يتخلص المعاِلج من الخط بمجرد امتالء الذاكرة المخبئية إلفساح المجال لخط جديد ،وهناك العديد
من الخوارزميات التي يمكن للمعالج من خاللها اختيار الخط الذي سيتخلص منه مثل خوارزمية األقل اسttتخداًم ا
مؤخًرا - Least Recently Usedأو LRUاختصاًرا -والتي ُتَع ّد خوارزميًة يجري فيها التخلص من أقttدم خttط غttير
ليس هناك داع لضمان التوافق مع الذاكرة الرئيسية عندما تكون البيانات للقراءة فقط من الذاكرة المخبئية،
لكن يحتاج المعالج التخاذ بعض القرارات حول كيفية تحديث الذاكرة الرئيسية األساسية عنttدما يبttدأ في الكتابttة
في خطوط الذاكرة المخبئية ،إذ ستكتب طريقة التخزين الخاصة بالذاكرة المخبئية التي ُتس َّtم ى Write-through
Cacheالتغييرات مباشرًة في ذاكرة النظام الرئيسية عندما يحّدث المعالج الذاكرة المخبئية ،وُيَع ّد ذلttك أبطttأ ألن
عملية الكتابة في الذاكرة الرئيسية أبطأ ،في حين تؤخر طريقtة التخtزين الخاصtة بالtذاكرة المخبئيtة الtتي ُتسَّtم ى
Write-back Cacheكتابَة التغييرات عىل الذاكرة RAMحتى الضرورة القصوى ،والميزة الواضحة لttذلك هي أّن
الوصول إىل الذاكرة الرئيسية مطلوب عند كتابة إدخاالت الذاكرة المخبئية.
ُيشار إىل خطوط الذاكرة المخبئية المكتوبة دون وضعها في الذاكرة عىل أنها متسخة ،Dirtyفعيبهttا هttو أنttه
يمكن أن يتطلب األمر وصوَلين إىل الذاكرة أحttدهما لكتابttة بيانttات الttذاكرة الرئيسttية المتسttخة واآلخttر لتحميttل
77
علوم الحاسوب من األلف إىل الياء معمارية الحاسوب
إذا كان اإلدخال موجوًدا في كل من الttذاكرة المخبئيttة ذات المسttتوى األعىل والمسttتوى األدنى في الttوقت
نفسه ،فإننا نسّم ي الذاكرة المخبئية ذات المستوى األعىل بالشاملة .Inclusiveبينما إذا أزالت الذاكرة المخبئيttة
ذات المستوى األعىل التي تحتوي عىل خط معّين إمكانيَة احتواء ذاكرة مخبئية ذات مستوى أقل عىل هذا الخط،
لم نناقش حتى اآلن كيف تقرر الذاكرة المخبئية ما إذا كان عنوان معّين موجوًدا في الذاكرة المخبئية أم ال ،إذ
يجب أن تحتفظ الذواكر المخبئية بمجلد للبيانات الموجودة حالًي ا في خطوط الذاكرة المخبئية ،ويمكن وضع مجلد
وبيانات الذاكرة المخبئية عىل المعالج مًع ا ،ولكن يمكن أن يكونا منفصَلين أيًض ا كما في حالة المعالج POWER5
الذي يحتوي عىل مجلد ذاكرة L3عىل المعالج ،ولكن يتطلب الوصول إىل البيانات اجتيttاز ناقttل L3للوصttول إىل
ذاكرة خارجية ليست عىل المعالج ،ويمكن أن يسّه ل هذا الترتيب معالجة عمليات الوصول الصحيحة أو الخاطئttة
بصورة أسر ع دون التكاليف األخرى لالحتفاظ بالذاكرة المخبئية بالكامل عىل المعالج.
وسوم الذاكرة المخبئية :Cache Tagsيجب التحقق من الوسوم عىل التوازي للحفاظ عىل وقت االسttتجابة
منخفًض ا ،إذ يتطلب المزيُد من بتات الوسوم (أي ارتباطات مجموعات أقttل) عتttاًدا أكttثر تعقيًttدا لتحقيttق ذلttك.
بينما تعني ارتباطاُت المجموعات األكثر وسوًم ا أقل ،ولكن يحتاج المعالج اآلن إىل عتttاد لمضttاعفة خttرج العديttد
يمكن تحديد ما إذا كان العنوان موجttوًدا في الttذاكرة المخبئيttة بسttرعة من خالل فصttله إىل ثالثttة أجttزاء هي
تعتمد بتات اإلزاحة عىل حجم خط الذاكرة المخبئية ،إذ يمكن استخدام خط بحجم 32بايت مثاًل آخر 5بتttات
أي 25من العنوان بوصفه إزاحًة في الخط ،وُيَع ّد الفهرس خط ذاكرة مخبئية معّي ن يمكن أن يتواجد فيtه اإلدخtال،
78
علوم الحاسوب من األلف إىل الياء معمارية الحاسوب
فلنفترض أنه لدينا ذاكرة مخبئية تحتوي عىل 256إدخااًل مثاًل ،فإذا كانت هذه الذاكرة هي ذاكtرة مخبئيtة مربوطtة
مباشرًة ،فيمكن أن تكون البيانات موجودة في خط واحد محتَم ل فقط ،لذا تصف 8بتات التالية ( )28بعد اإلزاحة
لنفترض اآلن أّن الttذاكرة المخبئيttة المكونttة من 256عنصًtرا مقسttمة إىل طttريقين ،وهttذا يعttني أّن هنttاك
مجموعتين مؤلفتين من 128خط ،ويمكن أن يقع العنوان المحدد في أّي من هاتين المجموعتين ،وبالتالي فttإن
المطلوب هو 7بتات فقط عىل أسttاس فهtرس لإلزاحtة في الطttرق المؤلفttة من 128إدخtااًل ،كمtا نخّف ض عttدد
البتات المطلوبة عىل أساس فهرس ألن كل طريق يصبح أصغر عندما نزيد عttدد الطttرق بالنسttبة إىل حجم ذاكttرة
مخبئية معّين.
ال يزال مجلد الذاكرة المخبئية بحاجttة إىل التحقttق ممttا إذا كttان العنttوان المخttزن في الttذاكرة المخبئيttة هttو
العنوان الذي يريده ،وبالتالي فإن البتttات المتبقيttة من العنttوان هي بتttات الوسttوم الttتي يتحقttق مجلttد الttذاكرة
المخبئية منها مقابل بتات وسم العنوان الواردة لتحديttد مttا إذا كttان هنttاك عمليttة وصttول صttحيحة أم ال ،وهttذه
إذا كان هناك طرق متعددة ،فيجب إجراء هذا التحقق عىل التوازي في كل طريق ،ثم ُتمَرر النتيجة بعد ذلttك
إىل معttدد إرسttال Multiplexorينتج عنttه نتيجttة وصttول صttحيحة hitأو خاطئttة ،missوكلمttا كttانت الttذاكرة
المخبئية أكثر ارتباًط ا ،قل عدد البتات المطلوبة للفهرس وزاد عtدد البتtات المطلوبtة للوسtم ،حtتى الوصtول إىل
أقصى حد للذاكرة المخبئية الترابطية بالكامل حيث ال ُتستخَدم بتttات كبتttات للفهtرس ،كمtا ُتَع ّد المطابقttة عىل
التوازي لبتات الوسوم مكوًنا باهًظ ا لتصميم الttذاكرة المخبئيttة وهي عموًم ا العامttل المحّttدد لعttدد الخطttوط -أي
للمعالج طريقة ما للتواصل مع هذه األجهزة الطرفية لجعلها مفيدة ،وتسمى قناة االتصttال بين المعttالج واألجهttزة
79
علوم الحاسوب من األلف إىل الياء معمارية الحاسوب
تسمح المقاطعة للجهاز بمقاطعة المعالج حرفًي ا بما تعنيه الكلمة لإلشارة إىل بعض المعلومttات ،فمثاًل ُتنَش أ
مقاطعة لتسليم حدث الضttغط عىل مفتttاح إىل نظttام التشttغيل عنttد الضttغط عليttه ،إذ تسِttند تركيبttة من نظttام
ترتبط األجهزة عموًم ا بمتحكم المقاطعة القابل للبرمجة Programmable Interrupt Controllerأو PIC
اختصاًرا ،وهو شريحة منفصلة ُتَع ّد جزًءا من اللوحة األم التي تخّزن معلومات المقاطعة مؤقًتا وتنقلها إىل المعالج
الرئيسي ،كما يحتوي كل جهاز عىل خط مقاطعة فيزيائي بينه وبين أحد خطوط PICالتي يوفرها النظام ،فttإذا أراد
هناك وصف واسع جًدا لttدور متحكم PICوهttو أنttه يتلقى هttذه المقاطعttة ويحولهttا إىل رسttالة ليسttتخدمها
المعالج الرئيسي ،كما يختلف هذا اإلجراء حسب المعمارية ،ولكن المبدأ العام هو أن يضبط نظام التشغيل جدول
واصف المقاطعات Interrupt Descriptor Tableالذي تربط فيه كل مقاطعة محتَم لة بعنوان شيفرة برمجية
كتابة معالج المقاطعة Interrupt Handlerهttو عمttل مطttور برنttامج تشttغيل الجهttاز بttالتزامن مttع نظttام
التشغيل.
توضح الصورة السابقة نظرة عامة عىل معالجة المقاطعttة إذ يرفttع الجهttاز المقاطعttة إىل متحكم المقاطعttة،
وتمِّرر هذه المقاطعة المعلومttات إىل المعttالج .ينظttر المعttالج إىل جttدول واصttف مقاطعاتttه الttذي يملttؤه نظttام
تقّس م معظم المشّغ الت معالجة المقاطعات إىل نصفين سttفلي وعلttوي ،إذ يتعttرف النصttف السttفلي عىل
المقاطعة ويضع اإلجراءات في رتل للمعالجة ويعيد المعالج إىل ما كان يفعله سttابًق ا بسttرعة ،في حين سُيش َّtغ ل
النصف العلوي الحًق ا عندما تكون وحدة المعالجة المركزية متاحًة ،وسينّف ذ المعالجة اإلضttافية ،كمttا يttؤدي ذلttك
80
علوم الحاسوب من األلف إىل الياء معمارية الحاسوب
حفظ الحالة
بما أّن المقاطعة يمكن أن تحدث في أّي وقت ،فيجب أن تتمكن من العودة إىل العملية الجارية عند االنتهاء
من معالجة المقاطعة ،كما أّن مهمة نظام التشغيل هي التأكttد من أنttه يحفttظ أّي حالttة Stateعنttد الttدخول إىل
معالج المقاطعة ،أي يسجلها ويستعيدها عند العودة من معالج المقاطعة ،وتكون بذلك المقاطعة واضttحًة تماًم ا
ترتبط المقاطعة عموًم ا بحدث خارجي من جهاز فيزيائي ،ولكن ُتَع ّد اآللية نفسها مفيدًة للتعامل مع عمليات
النظام الداخلية ،فإذا اكتشف المعالج مثاًل حاالت مثل الوصttول إىل ذاكttرة غttير صttالحة أو محاولttة القسttمة عىل
صفر أو تعليمات غير صالحة ،فيمكنه داخلًي ا رفع استثناء ليعالجه نظام التشغيل ،كما ُتستخَدم هذه اآللية ليلتقط
نظام التشغيل استدعاءات النظام ولتطبيق الذاكرة الوهمية ،virtual memoryفي حين تبقى مبttادئ مقاطعttة
الشيفرة البرمجية الُم شَّغ لة بطريقة غير متزامنة كما هي بالرغم من إنشائها داخلًي ا وليس من مصدر خارجي.
أنواع المقاطعات
هناك طريقتان رئيسيتان إلصدار إشارات إىل المقاطعات عىل الخttط همttا المسttتوى levelوالحافttة edge
الُم نَّبهة ،إذ تحّدد المقاطعات ذات المستوى الُم نَّبه جهد خط المقاطعة الذي ُيحتَف ظ به مرتفًع ا لإلشارة إىل وجttود
مقاطعة معَّلقة ،في يحن تكتشف المقاطعات ذات الحافة الُم نَّبهة االنتقاالت في الناقل عندما ينتقل جهد الخط
من منخفض إىل مرتفع ،ويكتشف متحكم المقاطعة PICنبضة الموجة المربعة باستخدام المقاطعة ذات الحافttة
يظهر الفرق عندما تشترك األجهزة في خط مقاطعة ،إذ سيكون خط المقاطعttة مرتفًع ا في نظttام المقاطعttة
ذي المستوى المنَّبه حتى معالجة جميع األجهزة التي رفعت المقاطعة وإلغاء تأكيد مقاطعتها ،كما تشttير النبضttة
الموجودة عىل الخط إىل متحكم المقاطعة PICالذي تنشئه المقاطعttة في نظttام المقاطعttة ذي الحافttة المنَّبهttة،
وستصدر هذه النبضة إشارًة إىل نظام التشttغيل لمعالجttة المقاطعttة في حالttة ظهttور نبضttات أخttرى عىل الخttط
تكمن مشكلة المقاطعات ذات المستوى المنَّبه في أنهttا يمكن أن تتطلب قttدًرا كبttيًرا من الttوقت لمعالجttة
مقاطعة أحد األجهزة ،إذ يظل خط المقاطعة مرتفًع ا أثناء هذا الوقت وال يمكن تحديد ما إذا تسّtبب أّي جهttاز آخttر
في حttدوث مقاطعttة عىل الخttط ،وهttذا يعttني أنttه يمكن أن يكttون هنttاك زمن تttأخير كبttير وغttير متوقttع في
خدمة المقاطعات.
يمكن مالحظة المقاطعة طويلة األمد ووضعها في رتل انتظار في المقاطعات ذات الحافttة الُم نَّبهttة ،ولكن ال
يزال بإمكان األجهزة األخرى التي تشترك في الخط االنتقال -وبالتالي رفع المقاطعات -أثناء حدوث ذلك ،ويttؤدي
81
علوم الحاسوب من األلف إىل الياء معمارية الحاسوب
ذلك إىل حدوث مشاكل جديدة ،إذ يمكن تفويت أحد المقاطعات في حالة مقاطعة جهازين في الtوقت نفسtه أو
يمكن أن يؤدي التشويش البيئي أو غيره إىل حدوث مقاطعة زائفة يجب تجاهلها.
يجب أن يكون النظام قادًرا عىل إخفاء المقاطعtات أو منعهtا في أوقtات معينtة ،ويمكن وضtع المقاطعtات
لتكون قيد االنتظار ،لكن هناك صنف معّين من المقاطعات يسttمى المقاطعttات غttير القابلttة للتقّن ع أو اإلخفttاء
Non-maskable Interruptsأو NMIاختصاًرا ،إذ ُتَع ّد هذه المقاطعات استثناًء من هذه القاعدة مثل مقاطعة
يمكن أن تكون مقاطعات NMIمفيدًة لتطبيق أشياء مثل مراقبة النظttام ،حيث ُترَف ع مقاطعttة NMIدورًي ا
وتضِب ط بعض الرايات التي يجب أن يقّر بها نظام التشغيل ،فإذا لم يظهر هذا اإلقرار قبل مقاطعttة NMIالدوريttة
التالية ،فيمكن َع ّد النظام أنه ال يحرز أّي تقدم ،كما يمكن استخدام مقاطعات NMIلتشخيص Profilingالنظام،
إذ يمكن رفع مقاطعات NMIالدورية واستخدامها لتقييم الشيفرة البرمجية التي يعمل بهttا المعttالج حالًي ا ،ممttا
يؤدي بمرور الوقت إىل إنشاء ملف تعريف للشيفرة البرمجية التي تعمل والحصول عىل رؤية مفيدة للغايttة حttول
أداء النظام.
يجب أن يتصل المعالج بالجهاز الطرفي عبر عمليات اإلدخال واإلخراج ،IOوُيطَلق عىل الشكل األكثر شttيوًع ا
من عمليات IOعمليات اإلدخال واإلخراج المرتبطة بالذاكرة ،Memory Mapped IOإذ ترتبط مسجالت الجهاز
مع الذاكرة ،وما عليك سوى القراءة أو الكتابة في عنوان محدد من الذاكرة للتواصل مع الجهاز.
ُيَع ّد الوصول المباشر للذاكرة - Direct Memory Accessأو DMAاختصاًرا -طريقًة لنقل البيانات مباشttرًة
بين الجهاز الطرفي وذاكرة RAMالخاصة بالنظام ،ويمكن لمشّغ ل الجهttاز إعttداده إلجttراء نقttل باسttتخدام طريقttة
الوصول DMAمن خالل إعطائه منطقًة من ذاكرة RAMلوضع بياناته فيها ،ثم يمكنttه بttدء نقttل DMAوالسttماح
سيرفع الجهاز المقاطعة بعد االنتهttاء ويرسttل لمشّtغ ل الجهttاز إشttارًة باكتمttال النقttل ،ثم سttتكون البيانttات
القادمttة من الجهttاز مثttل ملttف من قttرص صttلب أو إطttارات من بطاقttة التقttاط الفيttديو موجttودًة في الttذاكرة
وجاهزًة لالستخدام.
82
علوم الحاسوب من األلف إىل الياء معمارية الحاسوب
3.3.3نواقل أخرى
تصل نواقل أخرى بين ناقل PCIواألجهزة الخارجية مثل ناقل USBالذي سنتعرف عليه فيما يلي.
اUSB .
ُيَع ّد جهاز USBمن وجهة نظر نظام التشغيل أنه مجموعة من نقاط النهاية المجَّم عttة مًع ا في واجهttة مttا ،إذ
يمكن أن تكون نقطة النهاية إما نقطة إدخال أو إخراج ،بحيث تنقل نقطة النهاية البيانات باتجاه واحد فقttط ،كمttا
نقاط نهاية خاصة بعمليات التحكم :Control End-pointsمخصصة إلعداد الجهاز وغير ذلك. •
نقاط نهاية خاصة بالمقاطعات ُ :Interrupt End-pointsتستخَدم لنقل كميات صttغيرة من البيانttات، •
نقاط النهاية المجَّم عة :Bulk End-pointsتنقل كميات كبيرة من البيانات ولكنها ال تحصtل عىل قيtود •
زمنية مضمونة.
عمليات النقل المتزامنة :Isochronous Transfersهي عمليات نقtل ذات أولويtة عاليtة في الtوقت •
الحقيقي ،ولكن إذا جرى تفويتها ،فلن يعاد تجربتها ،وُتسttتخَدم لبيانttات البث مثttل الفيttديو أو الصttوت
يمكن أن يكtون هنtاك العديtد من الواجهtات المكونtة من نقtاط نهايtة متعtددة ،وُتجَّم ع الواجهtات ضtمن
83
علوم الحاسوب من األلف إىل الياء معمارية الحاسوب
يوضح الشtكل السtابق نظttرة عامtة عىل واجهtة متحكم المضttيف العامtة Universal Host Controller
Interfaceوالبرمجيات ،كما تضبط البرمجيات قالب بيانات بتنسيق محدد لمتحكم المضيف لقراءته وإرسttاله
عبر ناقل .USBأو UHCIاختصاًرا ،كما يوفر نظرًة عامًة حول الكيفية التي تنقل بها بيانات USBخارج النظام عن
يحتوي المتحكم بدًءا من أعىل يسار الشكل السابق عىل مسجل إطارات مع عّttداد ُي زاد دورًي ا في كttل ميلي
ثانية ،إذ ُتستخَدم هذه القيمة للفهرسة ضttمن قائمttة إطttارات تنشttئها البرمجيttات ،ويؤّش ر كttل إدخttال في هttذا
الجدول إىل رتل واصفات النقttل ،Transfer Descriptorsكمttا تضttبط البرمجيttات هttذه البيانttات في الttذاكرة
ويقرؤها المتحكم المضيف الذي ُيَع ّد شريحًة منفصttلًة تشّtغ ل ناقttل ،USBويجب أن تجttدول البرمجيttات أرتttال
العمل بحيث ُيمَنح %90من وقت اإلطار للبيانات المتزامنة وُيمَنح %10المتبقيttة لبيانttات المقاطعttة والتحكم
تعني الطريقة التي ُترَبط بها البيانات أّن واصفات النقل للبيانات المتزامنة ترتبtط بمؤشtر إطtار معّين واحtد
فقط -أي فترة زمنية معينة واحttدة فقttط -ثم سُttتهَم ل ،لكن ُتوَض ع جميttع بيانttات المقاطعttة والتحكم والبيانttات
الُم جَّم عة ضمن رتل انتظار بعد البيانات المتزامنة ،وبالتالي إذا لم ُترَس ل في إطttار واحttد -أو فttترة زمنيttة واحttدة-
تتواصل طبقات USBعبر كتل طلبات USBأو URBاختصttاًرا ،إذ تحتttوي كتttل URBعىل معلومttات حttول
نقطة النهاية التي يرتبط بها هذا الطلب والبيانات وأي معلومات أو سمات ذات صttلة ودالttة رد نttداء call-back
ُ functionتستدَع ى عند اكتمال كتلة ،URBكما ترِس ل مشّغ الت USBكتل URBبتنسيق ثttابت إىل مركttز USB
الذي يديرها بالتنسيق مع متحكم مضيف USBعىل النحو الوارد أعاله ،وُترَس ل بياناتك إىل جهاز USBعttبر مركttز
مألوًف ا أن تحتوي أّي خوادم عالية الجودة عىل وحدة معالجة مركزية واحدة فقط مع إمكانية تحقيق ذلك باستخدام
حالًي ا لتضمين وحدات المعالجة المركزية CPUالمتعددة في نظام واحد ،ويشير المصطلح متماثttل Symmetric
إىل حقيقة أّن جميع وحدات المعالجة المركزية في النظام هي نفسها من حيث المعماريttة وسttرعة السttاعة مثاًل ،
كما توجد في نظام SMPمعالجات متعددة تشترك في جميع موارد النظام األخرى مثل الذاكرة والقرص الصttلب
وغير ذلك.
84
علوم الحاسوب من األلف إىل الياء معمارية الحاسوب
تعمل وحدات المعالجة المركزية في النظام بصورة مستقلة عن بعضها بعًض ا ،فلكل منها مجموعته الخاصttة
من المسجالت وعّداد البرنامج وغttير ذلttك ،ولكن يوجttد مكِّtو ن واحttد يتطلب تزامًن ا صttارًم ا بttالرغم من تشttغيل
وحدات المعالجة المركزية بصورة منفصلة عن بعضها بعًض ا ،وهذا المكِّو ن هو الttذاكرة المخبئيttة Cacheالخاصttة
تذّك ر أّن الذاكرة المخبئية هي مساحة صغيرة من الذاكرة يمكن الوصول إليهttا بسttرعة وتعكس القيم المخّزنttة
في ذاكرة النظام الرئيسية ،فإذا عّدلت إحدى وحدات المعالجة المركزية البيانات في الذاكرة الرئيسttية وكttان لttدى
وحدة معالجة مركزية أخttرى نسttخة قديمttة من تلttك الttذاكرة في ذاكرتهttا المخبئيttة ،فلن يكttون النظttام في حالttة
متناسقة ،والحظ أّن هذه المشكلة تحدث عندما تكتب المعالجات في الذاكرة فقط ،إذ ستكون البيانات متناسقًة
يستخِدم نظام SMPعملية التنصت Snoopingلتنسيق الحفtاظ عىل ترابtط الtذواكر المخبئيtة عىل جميtع
المعالجات ،إذ ُيَع ّد التنصت العمليَة التي يستمع فيها المعttالج إىل ناقttل تتصttل بttه جميttع المعالجttات لمعرفttة
يمكن تحقيttق ذلttك باسttتخدام بروتوكttول واحttد هttو بروتوكttول MOESIالttذي يرمttز إىل الكلمttات ُم ع َّtد ل
يمكن أن يكون فيها خtط الtذاكرة المخبئيtة عىل معtالج في النظtام ،كمtا توجtد بروتوكtوالت أخtرى لtذلك ،ولكن
إذا طلب المعttالج قttراءة خttط ذاكttرة مخبئيttة من الttذاكرة الرئيسttية ،فيجب عليttه أواًل التنصttت عىل جميttع
المعالجات األخرى في النظام لمعرفة ما إذا كانت تعرف حالًي ا أّي شيء عن تلك المنطقة من الذاكرة مثل تخزينها
في الttذاكرة المخبئيttة ،فttإذا لم تكن موجttودًة في أّي عمليttة أخttرى ،فيمكن للمعttالج تحميttل الttذاكرة في الttذاكرة
المخبئيttة وتمييزهttا عىل أنهttا حصttرية ،Exclusiveويغttير الحالttة إىل معَّد لttة Modifiedعنttد الكتابttة في
الذاكرة المخبئية.
تلعب هنا تفاصيل الذاكرة المخبئية دوًرا أساسًي ا ،إذ سttتعيد بعض الttذواكر المخبئيttة مباشttرًة كتابttة الttذاكرة
المخبئية المعَّد لة إىل ذاكرة النظام المعروفttة باسttم الttذاكرة المخبئيttة من النttوع ،Write-throughألن عمليttات
الكتابة تنتقل إىل الذاكرة الرئيسية ،في حين لن تفعل ذلك الذواكر المخبئية األخرى ،بل سttتترك القيمtة الُم عَّد لtة
في الذاكرة المخبئية فقط حتى التخلص منها عندما تمتلئ الذاكرة المخبئية مثاًل .
الحالة األخرى هي المكان الذي يطّبق فيه المعالج عملية التنصت ويكتشttف أن القيمttة موجttودة في ذاكttرة
مخبئية خاصة بمعالجات أخرى ،فإذا كانت هذه القيمttة ممَّي زًة بوصttفها ُم عَّد ل ًtة ،Modifiedفسينسttخ المعttالج
البيانات في ذاكرته المخبئية ويمّي زها عىل أنها مشتركة ،Sharedكمttا سيرسttل رسttالًة إىل المعttالج اآلخttر الttذي
85
علوم الحاسوب من األلف إىل الياء معمارية الحاسوب
حصلنا عىل البيانات منه لتمييز خط ذاكرته المخبئية بوصفه المالك ،Ownerلنفtترض اآلن أن معالًج ا ثالًث ا في
النظام يريد استخدام تلك الذاكرة أيًض ا ،فسيتنصت ويبحث عن نسخة مشتركة ونسخة مالكttة ،وبالتttالي سttيأخذ
تقرأ جميع المعالجات األخرى القيمة فقط ،ولكن يبقى خط الذاكرة المخبئية مشترًك ا في النظام ،فttإذا احتttاج
معالج ما تحديث القيمة ،فإنه يرسل رسالة إلغاء صالحية Invalidateعبر النظttام ،كمttا يجب عىل أّي معttالج لttه
هذا الخط الخاص بالذاكرة المخبئية تمييزه بوصفه غير صالح Invalidألنه لم َيُع د يعكس القيمة الحقيقية ،ويميز
المعالج خط الذاكرة المخبئية بوصفه معَّداًل في ذاكرته المخبئيtة عنtدما يرسtل رسtالة إلغtاء الصtالحية وسtتمّي زه
الحظ أنه إذا كان خط الذاكرة المخبئية حصرًيا ،فسيعلم المعالج أنه ال يوجد معالج آخر يعتمد عليه ،لذا يمكنه
تجّنب إرسال رسالة إلغاء صالحية ،وتبدأ بعدها العملية من جديد ،وبالتالي يتحمل أُّي معالج لttه القيمttة المعّدلttة
مسؤوليَة كتابة القيمة الحقيقية مرًة أخرى إىل الذاكرة RAMعند التخلص منهtا من الtذاكرة المخبئيtة ،والحtظ أّن
هناك العديد من المشاكل في هذا النظام عند زيادة عدد المعالجات ،إذ يمكن التحكم في تكلفة التحقق من
وجود معالج آخر يحتوي عىل خط ذاكرة مخبئية (التنصت عىل عملية القراءة) أو إلغttاء صttالحية البيانttات في كttل
معالج آخر (إلغاء عملية التنصت) عند استخدام عدد قليل من المعالجات ،ولكن تttزداد حركttة النواقttل مttع زيttادة
عدد المعالجات وهذا هو السبب في أن أنظمة SMPتصل إىل حوالي 8معالجات فقط.
يعطي وجttود جميttع المعالجttات في الناقttل نفسttه مشttاكل فيزيائيttة أيًض ا ،إذ تسttمح خصttائص األسttالك
الفيزيائية بوضعها عىل مسافات معينة من بعضها بعًض ا وتسمح بأن يكون لها أطوال معينة فقttط ،وتبttدأ سttرعة
الضوء في أن تصبح أحد الجوانب التي يجب مراعاتها في المدة التي تستغرقها الرسtائل للتنقtل في النظtام مtع
الحظ أّن برمجيات النظام ليس لها أّي جزء من هذه العملية ،بالرغم من أّن المttبرمجين يجب أن يكونttوا عىل
دراية بما يطّبقه العتاد استجابًة للبرمجيات التي يصّم مونها لزيادة األداء إىل الحد األقصى.
شttرحنا في فقttرة سttابقة الttذواكر المخبئيttة الشttاملة Inclusiveوالحصttرية ،Exclusiveإذ تكttون الttذواكر
المخبئية L1شاملًة ،أي أن جميع البيانات الموجودة في الذاكرة المخبئيtة L1موجtودة في الtذاكرة المخبئيtة ،L2
وتعني الذاكرة المخبئية L1الشاملة أّن الذاكرة المخبئيttة L2يجب أن تتنصttت حركttة مttرور الttذاكرة للحفttاظ عىل
ترابطها في نظام متعدد المعالجات ،إذ ستضttمن L1عكس أّي تغيttيرات في الttذاكرة ،L2ممttا يقلttل من تعقيttد
ذاكرة L1ويفصله عن عملية التنصت ،وبالتالي سيسمح لها بأن تكون أسر ع.
86
علوم الحاسوب من األلف إىل الياء معمارية الحاسوب
تحتوي معظم المعالجات الحديثة المتطورة مثل المعالجات التي ليست مدَم جة عىل سياسtة كتابtة الtذاكرة
المخبئيttة L1من النttوع Write-throughوسياسttة الكتابttة من النttوع Write-backفي الttذواكر المخبئيttة ذات
المستوى األدنى ،وهناك عدة أسباب لذلك ،فبما أّن ذواكر L2المخبئية في هttذا الصttنف من المعالجttات تكttون
حصريًة تقريًبا عىل الشريحة وسريعًة جًدا عموًم ا ،فليست العقوبات المفروضة عىل كتابة الذاكرة المخبئية L1من
النوع Write-throughاألمر الرئيسي ،كما يمكن أن تتسّبب مجّم عات البيانات المكتوبة التي ال ُيحتَم ل قراءتهttا
ليس هناك داع للقلق بشttأن الكتابttة في L1من النttوع Write-throughإذا احتttوت عىل بيانttات متسttخة
معَّلقttة ،وبالتttالي يمكن أن تمّtرر منطttق الترابtط اإلضttافي إىل ذاكttرة L2الtتي لtديها دور أكttبر تلعبtه في ترابtط
الذاكرة المخبئية.
يمكن أن يقضي المعالج الحديث كثيًرا من وقته في انتظار أجهزة أبطttأ بكثttير في تسلسttل الttذواكر الهttرمي
لتقديم البيانات للمعالجة ،وبالتالي فإن إستراتيجيات الحفاظ عىل خط أنابيب المعالج ممتلًئا لها أهمية قصttوى،
وتتمثل إحدى اإلستراتيجيات في تضمين عدد كاف من المسجالت ومنطق الحالة بحيث يمكن معالجة مجريين
من التعليمات في الوقت نفسه ،مما يجعttل وحttدة معالجttة مركزيttة واحttدة تبحث عن جميttع النوايttا واألهttداف
تحتوي كل وحدة معالجة مركزية عىل مسجالتها الخاصة ،ولكن يجب عليها مشاركة منطق المعالج األساسي
والذاكرة المخبئية وحيز النطاق التراسلي لإلدخال واإلخراج من وحدة المعالجة المركزيtة إىل الtذاكرة ،لtذا يمكن أن
يحافظ مجريان من التعليمات عىل المنطق األساسي للمعttالج أكttثر انشttغااًل ،ولكن لن تكttون زيttادة األداء كبttيرًة
بسبب وجود وحدَتي CPUمنفصلتين فيزيائًي ا ،ويكون تحسين األداء أقل من ،%20ولكن يمكن أن يكون أفضل
أصبح وضع معاِلَجين أو أكtثر في الحزمtة الفيزيائيtة نفسtها ممكًن ا مtع زيtادة القtدرة عىل احتtواء مزيtد من
الترانزستورات عىل شريحة واحدة ،ولكن المعالجات األكثر شيوًع ا هي المعالجttات ثنائيttة النttواة ،إذ توجttد نواتttان
للمعالج عىل الشريحة نفسها ،وُتَع ّد هذه األنوية -عىل عكس تقنية خيttوط المعالجttة الفائقttة -Hyperthreading
معالجات كاملًة ،وبالتالي تبدو عىل أنها معالجات منفصلة فيزيائًي ا مثل نظام .SMP
تحتوي المعالجات عىل ذاكرة L1المخبئية الخاصة بها ،ولكن يجب عليها مشttاركة الناقttل المتصttل بالttذاكرة
الرئيسية وأجهزة أخرى ،وبالتالي لن يكون األداء جيًدا مثل نظام SMPالكامل ،ولكنه أفضل بكثير من نظام خيttوط
المعالجة الفائقة ،ويمكن لكل نواة تطبيق تقنية خيوط المعالجة الفائقة لتحسين إضافي.
87
علوم الحاسوب من األلف إىل الياء معمارية الحاسوب
تتمتع المعالجات متعددة األنوية ببعض المزايا التي ال تتعلق باألداء ،كمttا أّن للنttاقالت الفيزيائيttة الخارجيttة
بين المعالجات حدود فيزيائية ،ولكن يمكن حttل بعض هttذه المشttاكل من خالل احتttواء المعالجttات عىل قطعttة
ُتَع ّد متطلبات الطاقة للمعالجات متعددة األنوية أقل بكثير من المعالجات المنفصلة عن بعضها بعًض ا ،وهذا
يعني أن هناك حاجة أقل لتبريد الحرارة والتي يمكن أن تكون ميزًة كبيرًة في تطبيقات مراكز البيانات حيث ُتجَّم ع
الحواسيب مع وجود حاجة كبيرة للتبريد ،كما يجعل وجود األنوية في الحزمة الفيزيائية نفسها المعالجَة المتعttددة
عمليًة في التطبيقات التي لن تكون فيها كذلك مثل الحواسيب المحمولة ،كما ُيَع ّد إنتاج شريحة واحدة بttداًل من
3.4.2العناقيد Clusters
تتطلب العديد من التطبيقات أنظمًة أكبر بكثير من عدد المعالجات التي يمكن لنظttام SMPالتوسttع إليهttا،
وُتَع ّد العناقيد Clustersإحدى الطرق لتوسيع النظام أكثر ،وهي عدد من الحواسيب التي لديها بعض القدرة عىل
التواصل مع بعضها بعًض ا ،كما ال تعرف األنظمة بعضttها بعًض ا عىل مسttتوى العتttاد ،إذ ُت تَرك مهمttة ربttط هttذه
الحواسيب للبرمجيات.
تسمح البرمجيات مثل MPIللمبرمجين بكتابة برامجهم ثم وضع أجزاء منها عىل حواسيب أخرى في النظttام
مثل تمثيل حلقة ُتنَّف ذ عدة آالف من المرات وتطّبق إجراًء مستقاًل ،أي ال يوجttد تكttرار للحلقttة يttؤثر عىل أّي تكttرار
آخر ،ويمكن للبرمجيات جعل كل حاسوب يشّغ ل 250حلقة لكل منها مع وجود أربعة حواسيب في العنقود.
يختلف الترابط بين الحواسيب ،إذ يمكن أن يكون بطيًئا مثل روابط شبكة اإلنترنت أو سريًع ا مثttل النttاقالت
المخَّص صة والخاصة مثل روابtط إنفيttني بانtد ،Infinibandومهمtا كttان هttذا الترابtط ،فسtيبقى في المسtتوى
األخفض من تسلسل الذواكر الهرمي وسيكون أبطأ بكثير من الذاكرة ،RAMوبالتالي لن يقّدم العنقttود أداًء جيًttدا
في الموقف الذي تتطلب فيه كل وحدة معالجة مركزية الوصول إىل البيانات الُم خَّزنة في الttذاكرة RAMالخاصttة
بحاسوب آخر ،إذ ستحتاج البرمجيات في كل مرة أن تطلب نسخًة من البيانttات من الحاسttوب اآلخttر ،وتنسttخها
عبر الرابط البطيء إىل الذاكرة RAMالمحلية قبل أن يتمكن المعالج من إنجاز أّي عمل.
ال تتطلب العديد من التطبيقات هذا النسخ المستمر بين الحواسيب ،وأحttد األمثلttة الشttائعة عن ذلttك هttو
،SETI@Homeإذ ُتحَّلل البيانات التي جرى جمعها من هوائي راديو بحًث ا عن عالمات عىل وجttود كttائن فضttائي،
ويمكن توزيع كل حاسوب لبضع دقائق للحصول عىل البيانات لتحليلها ويعطي تقريًرا ملخًص ا لمtا وجtده ،إذ ُيَع ّد
يوجد تطبيق آخر هو تطبيق تصيير الصور Rendering of Imagesالذي ُيستخَدم خاصًة للتأثيرات الخاصة
في األفالم ،إذ ُيسَّلم كل حاسوب إطاًرا واحًدا من الفيلم يحتوي عىل نمttاذج إطttارات شttبكية وخامttات Textures
ومصادر إضاءة يجب دمجها أو تصييرها في التأثيرات الخاصة المذهلة الtتي نحصttل عليهtا ،كمtا ُيَع ّد كttل إطttار
88
علوم الحاسوب من األلف إىل الياء معمارية الحاسوب
ساكًنا ،لذلك ال يحتاج الحاسوب بمجرد حصوله عىل الدخل األولي لمزيد من االتصال حتى يصبح اإلطttار النهttائي
جاهًزا إلرساله ودمجه في الحركة ،فقد كان لفيلم سيد الخواتم مثاًل تأثيرات خاصة مصَّيرة عىل عنقود ضخم يعمل
بنظام لينكس.
العناقيد السابق تقريًبا ،ولكنه -كما هو الحال في نظام العنقود -يتكون من عقد فردية مرتبطة ببعضttها بعًض ا ،إال
أّن االرتباط بين العقد شديد التخصص ومكلف ،وال يمتلك العتاد أّي معرفة بالربط بين العقد في نظttام العنقttود،
في حين ال تمتلك البرمجيات في نظام NUMAمعرفًة جيدًة أو تمتلك معرقًة أقل حول تخطيط النظام ،إذ يطّب ق
يأتي مصطلح الوصول غير الموّحد إىل الذاكرة من حقيقttة أن الttذاكرة RAMليسttت محليttة بالنسttبة لوحttدة
المعالجة المركزية ،وبالتالي يمكن أن هناك حاجة ألن تصل عقدة عىل بعد مسtافة مtا إىل البيانtات ،إذ يسtتغرق
ذلك وقًتا أطttول عىل النقيض من معtالج واحtد أو نظtام SMPحيث يمكن الوصtول إىل الtذاكرة RAMمباشtرًة ،
ُيَع ّد تقليل المسافة بين العقد أمًرا بالغ األهمية مع وجtود العديtد من العقtد الtتي تتواصtل مtع بعضtها في
النظام ،إذ ُيفَّض ل أن يكون لكل عقدة رابط مباشر بكل عقدة أخرى ألنtه يقّل ل المسtافة الtتي تحتاجهtا أّي ة عقtدة
للعثور عىل البيانات ،لكن ال ُيَع ّد ذلك موقًف ا عملًي ا عندما ينمو عدد العقد إىل المئات واآلالف كما هttو الحttال مttع
الحواسيب العمالقة الكبيرة ،فاألساس في هذا النمو هtو مجموعtة مؤلفtة من عقtدتين تتواصtالن مtع بعضtهما
!n
. بعًض ا ثم ستنمو إىل
!)2×(n−2
ُتستخَدم التخطيطات البديلة لمقايضة المسافة بين العقد مع الوصtالت المطلوبtة بهtدف التقليtل من هtذا
النمو األسي ،فأحد هذه التخطيطات الشائعة في معماريات NUMAالحديثة هttو المكعب الفttائق Hypercube
الذي يحتوي عىل تعريف رياضي صارم ،ويكون المكعب الفائق هو نظير ربtاعي األبعtاد للمكعب الtذي هtو نظtير
89
علوم الحاسوب من األلف إىل الياء معمارية الحاسوب
يوضح الشكل السابق مثااًل عن المكعب الفائق Hypercubeالذي يttوفر مقايض ًtة جيttدًة بين المسttافة بين
العقد وعدد الوصالت المطلوب .يمكننا أن نرى في هذا الشكل أن المكعب الخارجي يحتوي عىل 8عقttد ،والحttد
األقصى لعدد المسارات المطلوبة ألي عقدة للتواصل مع عقدة أخرى هو ،3فtإذا وضtعنا مكعًب ا آخtر داخtل هtذا
المكعب ،فسيكون لدينا ضعف عدد المعالجات ولكن زادت التكلفة القصوى للمسار إىل ،4مما يعني نمو تكلفة
المسار القصوى خطًي ا فقط عند نمو عدد المعالجات بمقدار .2n
ال يزال الحفاظ عىل ترابط الذاكرة المخبئية في نظام NUMAممكًنا ،إذ يشttار إىل ذلttك باسttم نظttام NUMA
مع ترابط الذاكرة المخبئية Cache Coherent NUMA Systemأو ccNUMAاختصاًرا ،وال يتوّس ع المخطttط
القائم عىل البث اإلذاعي الُم ستخَدم للحفاظ عىل ترابط ذاكرة المعالج المخبئية في نظام SMPإىل مئات أو حttتى
يشار إىل أحد المخططات الشائعة لترابط الtذاكرة المخبئيtة في نظtام NUMAباسtم النمtوذج المسtتند إىل
الtدليل Directory Based Modelالtذي تتصtل فيtه المعالجtات الموجtودة في النظtام بعتtاد دليtل الtذاكرة
المخبئية ،إذ يحافظ عتاد الدليل عىل صورة متناسقة لكtل معtالج ،كمtا يخفي هtذا التجريtد عمtل نظtام NUMA
عن المعالج.
يحتفظ المخطط المستند إىل الدليل لصtاحبيه Censierو Feautrierبtدليل مركtزي ،إذ تحتtوي كtل كتلtة
ذاكرة عىل ِبت راية ُيعَرف بالِبت الصالح Valid Bitلكل معttالج وِبت واحttد ُيسَّtم ى بttالِبت المتسttخ ،Dirty Bit
ويضبط الدليل الِب ت الصالح للمعالج الذي يقرأ الذاكرة إىل ذاكرته المخبئية.
90
علوم الحاسوب من األلف إىل الياء معمارية الحاسوب
إذا أراد المعالج الكتابة إىل خط الذاكرة المخبئية ،فيجب أن يضِبط الttدليل الِبَّت المتسttخ لكتلttة الttذاكرة من
خالل إرسال رسالة إلغاء صالحية إىل تلك المعالجات التي تستخِدم خط الذاكرة المخبئية والمعالجات الttتي جttرى
ضبط رايتها فقط بهدف تجنب حركة مرور البث .broadcast traffic
يجب بعد ذلك أن يحاول أّي معالج آخر قراءة كتلة الذاكرة ،وسيجد الدليل ضبط الِب ت المتسخ ،كما يجب أن
يحصل الدليل عىل خط الذاكرة المخبئية الُم حَّد ث من المعالج مttع الِبت الصttالح المضttبوط حالًي ا ،ويعيttد كتابttة
البيانات المتسخة إىل الذاكرة الرئيسية ثم إعادة هذه البيانات إىل المعالج المطلوب ،ممtا يtؤدي إىل ضttبط الِبت
الصالح للمعالج الطالب في هذه العملية ،والحظ أّن هذا األمر واضح للمعttالج الطttالب ويمكن أن يحتttاج الttدليل
الحصول عىل تلك البيانات من مكان قريب جًدا أو من مكان بعيد جًدا.
ال يمكن أن يتوسع المخطط المؤَّلف من آالف المعالجات التي تتصل بدليل واحد بصورة جيttدة ،إذ تتضttمن
توّس عات المخطط وجود تسلسل هرمي من الدالئل التي تتواصل فيما بينها باستخدام بروتوكttول منفصttل ،كمttا
يمكن أن تستخِد م الدالئل شبكة اتصاالت ذات أغراض أعم للتواصل فيمtا بينهtا بtداًل من ناقttل وحtدة المعالجttة
ُتَع ّد أنظمة NUMAاألنسب ألنواع المشاكل التي تتطلب قدًرا كبيًرا من التفاعل بين المعالج والttذاكرة ،فمن
المصطلحات الشائعة في محاكاة الطقس مثاًل هو تقسيم البيئة إىل صناديق صغيرة تسttتجيب بطttرق مختلفttة،
بحيث تعكس المحيطات واألرض أو تخزن كميات مختلفة من الحرارة مثاًل ،ويجب تغذيttة االختالفttات الصttغيرة
يؤثر كل صندوق عىل الصناديق المحيطة ،إذ يعني وجود الشttمس أكttثر قلياًل مثاًل أّن صttندوًق ا معيًن ا ينشttر
مزيًدا من الحرارة مما يؤثر عىل الصtناديق المجtاورة لtه ،ولكن سtيكون هنtاك الكثtير من االتصtاالت عىل عكس
إطارات الصور الفردية في عملية التصيير Renderingالttتي ال تttؤثر عىل بعضttها ،كمttا يمكن أن تحttدث عملي ًtة
مماثلًة إذا أردت تصميم نموذج لحادث سttيارة ،حيث سُtيطوى كttل صttندوق صttغير من السttيارة الttتي تحاكيهttا
ليس للبرمجيات معرفة مباشرة بأن النظام األساسي هttو نظttام ،NUMAولكن يجب أن يتttوّخى المttبرمجون
الحذر عند البرمجة لهذا النظام للحصول عىل أفضل أداء ،وسيؤدي االحتفاظ بالttذاكرة بttالقرب من المعttالج الttذي
سيستخِد مها إىل أفضل أداء ،ولكن يجب أن يستخِدم المtبرمجون تقنيtات مثtل التشtخيص Profilingلتحليtل
مسارات الشيفرة البرمجية المّتَبعة والعواقب التي تسببها الشيفرة البرمجية للنظام الستخراج أفضل أداء.
بكيفية رؤية المبرمج لشيفرة المعالج البرمجية التي تكون قيد التشغيل.
91
علوم الحاسوب من األلف إىل الياء معمارية الحاسوب
لنفترض أّن شيفرة البرنامج البرمجية تعمل عىل معttالَجين في الttوقت نفسttه ،وأّن كال المعttالجين يشttتركان
بفعالية في منطقة واحدة كبيرة من الذاكرة ،فإذا أصدر أحد المعالَجين تعليمات تخزين لوضttع قيمttة مس ّtجل في
الذاكرة ،فال بد أنك تتساءل عن الوقت الذي يمكن فيه التأكد من أن المعالج اآلخر يحّم ل تلك الذاكرة التي سttيرى
قيمتها الصحيحة.
يمكن للنظام في أبسط الحاالت أن يضمن أنtه في حالtة تنفيtذ أحtد الtبرامج لتعليمtات التخtزين ،وبالتtالي
سترى أّي تعليمات تحميل الحقة هذه القيمة ،إذ ُيشار إىل ذلك باسم تttرتيب الttذاكرة الصttارم Strict Memory
،Orderingألن القواعد ال تسمح بأّي مجال للحركة ،كمttا يجب أن تttدرك أّن هttذا النttوع من األشttياء ُيَع ّد عائًق ا
ال ُيطَلب من ترتيب الذاكرة أن يكون صارًم ا جًدا في كثير من األحيان ،إذ يمكن للمبرمج تحديد النقttاط الttتي
يحتاجها للتأكد من رؤية جميع العمليات الُم عَّلقة بطريقة عامة ،ولكن يمكن أن يكون هناك العديد من التعليمات
من بين هذه النقاط حيث ال تكtون الtدالالت Semanticsمهمtة ،ولنفtترض الموقtف التtالي مثاًل الtذي يمثtل
ترتيب الذاكرة:
{ typedef struct
;int a
;int b
;} a_struct
*/
مّرر مؤشًرا لتخصيصه بوصفه بنيًة جديدًة *
*/
)void get_struct(a_struct *new_struct
{
;))void *p = malloc(sizeof(a_struct
92
علوم الحاسوب من األلف إىل الياء معمارية الحاسوب
;new_struct = p
}
لدينا في هذا المثال عمليَتي تخزين يمكن تطبيقهما بأّي ترتيب معّين بما يناسب المعttالج ،ولكن يجب في
الحالة األخيرة تحديث المؤشر فقط بمجرد التأكد من اكتمttال عمليَتي التخttزين السttابقتين ،وإاّل فيمكن أن ينظttر
معالج آخر إىل قيمة pويتبع المؤشر إىل الذاكرة ويحّم لها ويحصttل عىل قيمttة غttير صttحيحة تماًم ا ،لttذا يجب أن
توَص ف دالالت الttذاكرة من حيث األسttوار Fencesالttتي تحّttدد كيفيttة إعttادة تttرتيب عمليttات التحميttل
والتخزين ،كما يمكن افتراضًي ا إعادة طلب عملية التحميل أو التخttزين في أّي مكttان ،ويشttبه اكتسttاب الttدالالت
Acquire Semanticsالسوَر الذي يسمح فقط لعمليات التحميل والتخزين بالتحرك لألسفل عttبره ،أي يمكنttك
ضمان أّن أّي عملية تحميل أو تخزين الحقة سترى القيمة -ألنه ال يمكن نقلها فوقها -عنttد اكتمttال هttذا التحميttل
أو التخزين.
ُيَع ّد تحرير الدالالت Release Semanticsعكس ذلك ،أي يسمح السور بttأي عمليttة تحميttل أو تخttزين أن
تكتمل قبله -أي التحرك لألعىل ،-ولكن ال يوجد شيء قبلها للتحرك لألسtفل .وبالتtالي يمكنtك تخtزين أّي عمليtة
تحميل أو تخزين سابقة مكتملة عند معالجة التحميل أو التخزين باستخدام تحرير الدالالت.
93
علوم الحاسوب من األلف إىل الياء معمارية الحاسوب
يمّث ل الرسtttم التوضtttيحي عمليtttات إعtttادة الtttترتيب الصtttالحة للعمليtttات باسtttتخدام اكتسtttاب
الدالالت وتحريرها.
سور الذاكرة الكامل full memory fenceهو مزيج من اكتساب الدالالت وتحريرهtا ،حيث ال يمكن إعttادة
ترتيب عمليات التحميل أو التخزين في أّي اتجاه حول عملية التحميل أو التخزين الحاليttة ،كمttا يسtتخِدم نمttوذج
الذاكرة األكثر صرامة سور ذاكرة كامل لكل عملية ،في حين سيترك النموذج األضعف كل عملية تحميttل وتخttزين
تطّبق المعالجات المختلفة نماذج ذاكرة مختلفة ،إذ يحتوي معالج x86ومعtالج AMD64عىل نمtوذج ذاكtرة
صارم تماًم ا ،حيث تحتوي جميtع عمليtات التخtزين عىل تحريtر دالالت ،أي يجب أن تtرى أّي ة عمليtة تحميtل أو
تخزين الحقة نتيجَة عملية التخزين ،ولكن جميع عمليات التحميل لها دالالت عادية ،كما تعطي بادئة القفل سوًرا
للذاكرة ،في حين يسمح المعالج إيتانيوم Itaniumلجميع عمليtات التحميtل والتخtزين بtأن تكtون عاديًtة مtا لم
ب .القفل
ليست معرفة متطلبات ترتيب الذاكرة لكل معمارية عمليًة ومناسبًة لجميع المبرمجين وسيجعل ذلttك نقttل
البرامج وتنقيحها عبر أنواع المعالجات المختلفة أمًرا صttعًبا ،إذ يسttتخِدم المttبرمجون مسttتًو ى أعىل من التجريttد
يسمى القفل Lockingللسماح بالتشغيل المتزامن للبرامج عندما يكون هناك وحدات معالجة مركزيttة متعttددة،
كما ال يمكن ألّي معالج آخر الحصول عىل القفل حتى ُيحَّرر عندما يحصل برنامج ما عليه لجزء من شيفرة برمجية،
كما يجب أن يحاول المعالج أخذ القفل قبل أّي أجزاء مهمة من الشيفرة البرمجية ،فإذا لم يستطع الحصول عليه،
يمكنك رؤية كيف أّن ذلك مقَّيد بتسمية دالالت ترتيب الذاكرة الموَّض حة سابًق ا ،كما نريttد التأكttد من أنttه لن
ُيعاد طلب أّي عمليات يجب أن يحميها القفل قبل الحصول عليttه ،وهttذه هي الطريقttة الttتي تعمttل بهttا عمليttة
اكتساب الدالالت ،في حين يجب التأكد من أّن كل عملية طّبقناها أثناء احتفاظنا بالقفttل مكتملttة عنttدما نحttرره
مثل مثال تحديث المؤشر الموَّض ح سابًق ا ،وهذا ما يسمى بتحرير الدالالت.
هناك العديد من المكتبات البرمجية المتاحة التي تسttمح للمttبرمجين بعttدم القلttق بشttأن تفاصttيل دالالت
الذاكرة واستخدام المستوى األعىل من تجريد القفل )( lockوإلغاء القفل )(.unlock
صعوبات األقفال
تجعل أنظمة القفل البرمجة أكثر تعقيًدا ،إذ يمكنها أن تtؤدي إىل تعطيtل الtبرامج ،ولنفtترض أّن معالًج ا مtا
يحتفظ بقفل عىل بعض البيانات ،وينتظر قفاًل عىل بيانات أخttرى حالًي ا ،فttإذا انتظttر معttالج آخttر البيانttات الttتي
94
علوم الحاسوب من األلف إىل الياء معمارية الحاسوب
يحتفظ بها المعالج األول وكان قبل ذلك وقبل دخوله في حالة قفل يحتفttظ ببيانttات يريttدها المعttالج األول ذاك
لفك قفله ،فسنواجه حالة تعطل تام ،بحيث ينتظر كل معالج المعالج اآلخر وال يمكن ألّي منهما االسttتمرار بttدون
ينشأ هذا الموقف بسبب حالttة التسttابق Race Conditionفي أغلب األحيttان الttتي ُتَع ّد إحttدى أصttعب
األخطاء التي يمكن تعّق بها ،فإذا كان هناك معاِلجان يعتمttدان عىل عمليttات تحttدث بttترتيب معّين في الttوقت،
فهناك دائًم ا احتمال حدوث حالة تسابق ،كما يمكن أن تصطدم أشعة جامttا المنبعثttة من نجم متفجttر في مجttرة
أخرى بأحد المعالجات ،مما يؤدي إىل الخروج عن ترتيب العمليات ،ثم ستحدث حالة تعطل تام كما رأينttا سttابًق ا،
لذا يجب ضمان ترتيب البرامج باستخدام الدالالت وليس عبر االعتماد عىل سلوكيات محددة لمرة واحدة.
يوجttد وضttع مماثttل يسttمى المنttع Livelockوهttو عكس التعطttل ،Deadlockإذ يمكن أن تكttون إحttدى
االستراتيجيات لتجنب التعطل أن يكون لديك قفل مttؤدب Politeيttرفض إعطttاء القفttل لكttل َم ن يطلبttه ،وقttد
يتسبب هذا القفل المؤّدب في جعل خيطين Threadsيمنحان بعضهما القفل باسttتمرار دون الحاجttة إىل أخttذ
القفل لفترة كافية إلنجاز العمttل المهم واالنتهttاء من القفttل ،إذ يمكن أن يكttون هنttاك وضttع مشttابه في الحيttاة
الواقعية لشخصين يلتقيان عند الباب في الوقت نفسه ،ويقول كالهمttا" :ال ،أنت أواًل ،أنttا أصttر عىل ذلttك" دون
اسرتاتيجيات القفل
هناك العديد من االستراتيجيات المختلفة لتطبيق سلوك األقفال ،إذ ُيشار إىل القفل البسيط الttذي يحتttوي
ببساطة عىل حالتين -مقفل Lockedأو غttير مقفttل -Unlockedعىل أنttه كttائن مزامنttة ،Mutexوهttو اختصttار
لالستبعاد المتبادل Mutual Exclusionالذي يعني أنه إذا كان لttدى شttخص مttا قفاًل ،فال يمكن لشttخص آخttر
الحصول عليه ،وهناك عدد من الطرق لتطبيق قفل كائن المزامنة ،إذ لدينا في أبسط الحاالت ما يسttمى بالقفttل
الدوار Spinlockإذ يبقى المعالج ضمن حلقة في انتظار أخذ القفttل مثttل طفttل صttغير يطلب من والديttه شttيًئا
تكمن مشكلة هذه االستراتيجية في أنها تضيع الوقت ،إذ ال ينّف ذ المعالج أّي عمٍل مفيد بينما يكttون متوقًف ا
ويطلب القفل باستمرار ،وقد يكون ذلك مناسًبا لألقفال الttتي ُيحتَم ل أن ُتقَف ل لفttترة قصttيرة جًttدا من الttوقت
فقط ،ولكن يمكن أن يكون مقدار الوقت الذي يستغرقه القفل أطول بكثير في كثير من الحاالت.
االستراتيجية األخرى هي السttكون ،Sleepحيث إذا لم يتمكن المعttالج من الحصttول عىل القفttل ،فسttينّف ذ
بعض األعمال األخرى في انتظار إشعار بأن القفل متاح لالستخدام ،وسttنرى في المقttاالت القادمttة كيttف يمكن
ُيَع ّد كtائن المزامنtة حالًtة خاصًtة من متغtير تقييtد الوصtول Semaphoreالtذي اخترعtه عtالم الحاسtوب
الهولندي ديكسترا ،Dijkstraإذ يمكن ضبط متغير تقييد الوصول Semaphoreلحسttاب عttدد مttرات الوصttول
95
علوم الحاسوب من األلف إىل الياء معمارية الحاسوب
إىل الموارد في حالة توفر العديد منها ،في حين يكون لديك كائن المزامنttة Mutexفي الحالttة الttتي يكttون فيهttا
لكن ال تزال أنظمة القفل هذه تواجه بعض المشtاكل ،إذ يtرغب معظم األشtخاص في قtراءة البيانtات الtتي
ُتحَّد ث في حاالت نادرة فقط .يمكن أن يؤدي وجود جميع المعالجات التي ترغب في قtراءة البيانtات فقtط الtتي
تتطلب قفاًل إىل تنttاز ع القفttل حيث ُينَج ز القليttل من العمttل ألن الجميttع ينتظttر الحصttول عىل القفttل نفسttه
لبعض البيانات.
96
.4نظام التشغيل
4.1.1تجريد العتاد
تتمثل العملية األساسية لنظام التشغيل - Operating Systemأو OSاختصاًرا -في تجريttد Abstraction
العتاد للمبرمج والمستخِدم ،إذ يوّف ر نظام التشغيل واجهات عامة للخدمات التي يقttدمها العتttاد األساسttي ،كمttا
يجب عىل المبرمجين معرفة تفاصيل العتاد األساسttي األكttثر خصوصttيًة لتشttغيل أّي شttيء في عttالم خttال من
أنظمة التشغيل ،إذ لن تعمل برامجهم عىل عتاد آخر حتى عند وجود اختالفات طفيفة في هذا العتاد.
جميع البرامج المختلفة التي تعمل عىل النظام ،وُيَع ًد ذلك وظيفttة أنظمttة التشttغيل الttتي تسttمح بحttدوث ذلttك
بسالسة ،فنظام التشغيل مسؤول عن إدارة الموارد داخل النظام ،إذ تتنافس المهttام المتعttددة عىل مttوارده أثنttاء
تشغيله بما في ذلك وقت المعالج والذاكرة والقرص الصلب ودخل المستخِدم ،كما تتمثttل وظيفتttه في التحكيم
ال بد أنك مررت بفشل حاسttوبك وتعطلttه مثttل ظهttور شاشttة المttوت الزرقttاء Blue Screen of Death
دعم نظام التشغيل للواجهات الموَّحدة المعياريttة ،فttإذا كttانت دالttة فتح ملttف مثاًل عىل أحttد األنظمttة )(open
وكانت )( open_fileو )( openfعىل نظام آخر ،فسيواجه المبرمجون مشكلًة مزدوجًة تتمثttل في االضttطرار
إىل تذّك ر ما يفعله كل نظام مع عدم عمل البرامج عىل أنظمة متعددة.
ُتَع ّد واجهة نظام التشttغيل المتنقلttة - Portable Operating System Interfaceأو بوسttيكس POSIX
اختصاًرا -معياًرا مهًم ا للغاية تطّبقه أنظمة تشغيل من نوع يونيكس ،UNIXكما يملك نظام مايكروسوفت ويندوز
معايير مشابهة ،ويأتي حرف Xفي POSIXمن نظام يونيكس Unixالذي نشأ منtه المعيtار ،وهtو اليtوم اإلصtدار
رقم 3من مواصفات يونيكس الواحدة Single UNIX Specification Version 3أو ISO/IEC 9945:2002
كttانت مواصttفات يttونيكس الواحttدة ومعttايير POSIXكيانttات منفصttلًة سttابًق ا ،وقttد أصttدر اتحttاد يسّtم ى
المجموعة المفتوحة Open Groupمواصفات يونيكس الواحدة ،وكان متاًحا مجاًنا وفًق ا لمتطلبات هذا االتحاد،
وأحدث إصدار هو اإلصدار الثالث من مواصفات يونيكس الواحدة ،كما ُأصِttدرت معttايير IEEE POSIXبوصttفها
معttايير بالشttكل [رقم المراجعttة ،رقم اإلصttدار] ،IEEE Std 1003ولم تكن متاحًttة مجاًن ا ،ومن إصttداراتها
ُدِم ج هذان المعياران المنفصالن فيما ُيعرف باسم اإلصدار الثالث من مواصفات يونيكس الواحدة ،ووّحدتttه
منظمة ISOباالسم ISO/IEC 9945:2002في بداية عام ،2002لذا عندما يتحدث الناس عن معيttار POSIXأو
4.1.4األمن
ُيَع ّد األمن مهًم ا جًttدا في األنظمttة متعttددة المسttتخِدمين ،إذ يكttون نظttام التشttغيل -بصttفته المتحّكم في
الوصول إىل النظام -مسؤواًل عن ضمان أّن األشخاص الذين لديهم األذونات الصحيحة فقط يمكنهم الوصول إىل
الموارد ،فإذا امتلك مستخِدم أحد الملفات مثاًل ،فال ينبغي السماح لمسtتخِدم آخtر بفتحtه وقراءتtه ،ولكن هنtاك
حاجة لوجود آليات لمشاركة هذا الملف بأمان بين المستخِدمين إذا أرادوا ذلك.
أنظمttة التشttغيل هي بttرامج كبttيرة ومعقttدة تحتttوي عىل مشttكالت أمنيttة في أغلب األحيttان ،إذ يسttتفيد
الفيروس أو الدودة الفيروسية من هذه األخطاء غالًبا للوصttول إىل المtوارد الtتي ال ينبغي السtماح لهtا بالوصttول
إليها مثل الملفات أو اتصال الشبكة ،لذا يجب عليك تثبيت حtزم التصttحيح أو التحttديثات الtتي يوفرهtا مصّttنع
98
علوم الحاسوب من األلف إىل الياء نظام التشغيل
4.1.5األداء
يوِّف ر نظام التشغيل العديد من الخدمات للحاسوب ،لذلك ُيَع ّد أداؤه أمًرا بالغ األهمية ،إذ تعمل أجttزاء كثttيرة
من نظام التشغيل بصtورة متكtررة ،كمtا يمكن أن تtؤدي زيtادة عtدد دورات المعtالج إىل انخفtاض كبtير في أداء
النظام ،ويحتاج نظام التشغيل السttتغالل مttيزات العتttاد األساسttي للتأكttد من الحصttول عىل أفضttل أداء ممكن
إلجراء العمليtات ،وبالتtالي يجب عىل مtبرمجي األنظمtة فهم التفاصtيل الدقيقtة للمعماريtة الtتي يبنtون نظtام
التشغيل من أجلها.
تكون مهمة مبرمجي األنظمة في كثير من الحttاالت هي تحديttد سياسttات النظttام ،إذ تttؤدي اآلثttار الجانبيttة
لجعل جزء من نظام التشغيل يعمل بصورة أسر ع إىل جعل جزء آخر يعمل بصtورة أبطtأ أو أقtل كفtاءة ،لtذا يجب
عىل مبرمجي األنظمة فهم كل هذه المقايضات عند بناء نظام التشغيل.
تنظيم النواة ُ :Kernelتشَّغ ل عمليات النtواة مباشtرًة في مجtال المسtتخِدم ،Userspaceوتتواصtل النtواة
99
علوم الحاسوب من األلف إىل الياء نظام التشغيل
4.2.1النواة Kernel
ُتَع ّد النواة نظام تشغيل ،وتجّرد المشّغ الت Driversالعتاد للنواة كما تجّرد النواة العتttاد لttبرامج المسttتخِدم،
حيث يوجد العديد من أنواع بطاقات الرسوم المختلفة عىل سبيل المثال ،ولكttل منهttا مttيزات مختلفttة قلياًل عن
بعضها البعض ،ولكن طالما أن النواة تصّدر واجهة برمجttة تطبيقttات ،APIفيمكن لألشttخاص الttذين لttديهم إذن
الوصول إىل مواصفات العتاد كتابُة برامج للمشّغ الت لتطبيق هذه الواجهة ،ويمكن للنواة باستخدام هذه الطريقtة
ُتوَص ف النواة بأن لها صالحيات ،Privilegedفللعتاد أدوار مهمة يؤديهttا لتشttغيل مهttام متعttددة والحفttاظ
عىل أمان النظام ،ولكن ال ُتطَّبق هذه القواعد عىل النواة ،كما يجب أن تتعامل النttواة مttع الttبرامج الttتي تتعطttل،
فوظيفة أنظمة التشغيل هي فقط تنظيم العمل والتحكيم بين العديد من البرامج التي تعمل عىل النظttام نفسttه،
وليس هناك ما يضمن أنها ستتصرف لحل المشاكل ،ولكن سيصبح النظام بأكمله عديم الفائدة في حالttة تعطttل
أي جزء داخلي من نظام التشغيل ،كما يمكن أن تستغل عمليات المستخِدم مشاكل األمttان لttترفع مسttتواها إىل
مستوى صالحيات النواة ،وبالتالي يمكنها الوصول إىل أّي جزء من النظام.
دقيقة .Microkernel
ُتَع ّد النواة األحادية األكثر شيوًع ا كما هو الحttال في معظم أنظمttة يttونيكس الشttائعة مثttل لينكس ،إذ تكttون
النواة في هذا النمttوذج ذات صttالحيات كبttيرة ،وتحتttوي عىل مشّtغ الت العتttاد ومتحكمttات الوصttول إىل نظttام
الملفات وفحص األذونات والخدمات مثل نظام ملفات الشبكة - Network File Systemأو NFSاختصاًرا.
تتمتع النواة دائًم ا بصالحيات ،لذلك إذا تعطل أّي جزء منها ،فُيحتَم ل أن يتوقف النظام بأكمله ،وإذا كان لدى
مشّغ ل خطأ برمجي ما ،bugفيمكنه الكتابة في أّي ذاكرة في النظام دون أّي مشاكل ،مما يttؤدي في النهايttة إىل
تعطل النظام.
تحاول معمارية النواة الدقيقة تقليل هذا االحتمال من خالل جعل الجزء الذي يمتلك الصttالحيات من النttواة
صغيًرا قدر اإلمكان .هذا يعني أن معظم النظام يعمل كبرامج دون صالحيات ،مما يحد من الضرر الttذي يمكن أن
يسّببه أّي مكوٍن معَّط ل ،فمثاًل يمكن تشغيل مشّغ الت العتاد في عمليات منفصلة ،وبالتالي إذا تعّط ل أحد هttذه
المشّغ الت ،فلن يتمّكن من الكتابة في أّي ذاكرة غير تلك المخصصة له.
تبدو معمارية النواة الدقيقة جيدًة ،ولكنها ستؤدي إىل المشكلتين التاليتين:
.1انخفاض األداء ،إذ يمكن أن يؤدي التواصل بين العديد من المكونات المختلفة إىل تقليل األداء.
100
علوم الحاسوب من األلف إىل الياء نظام التشغيل
تأتي هذه المشاكل بسبب تطبيق معظم األنوية الدقيقة باستخدام نظام قائم عىل تمرير الرسائل Message
Passingبهدف الحفاظ عىل الفصل بين المكونات ،ويشار إىل هذا النظام عttادًة باسttم التواصttل بين العمليttات
يحدث التواصل بين المكونات باستخدام رسائل منفصلة يجب تجميعها ضttمن حttزم وإرسttالها إىل المك ِّtو ن
اآلخر وتفكيكها وتشغيلها وإعادة تجميعها وإعادة إرسttالها ثم تفكيكهttا مttرًة أخttرى للحصttول عىل النتيجttة ،وهttذه
خطوات كثيرة لطلٍب بسيط إىل حد ما من مكون خارجي ،ويمكن أن تجعttل أحttد الطلبttات المكttون اآلخttر ُيجttري
كانت تطبيقات تمرير الرسtائل البطيئtة مسtؤولة إىل حtد كبtير عن األداء الضtعيف ألنظمtة النtواة الدقيقtة
القديمة ،وكانت مفاهيم تمرير الرسtائل أصtعب قلياًل عىل المtبرمجين ،ولم تكن الحمايtة الُم حَّس نة من تشtغيل
المكونات بصورة منفصلة كافيًة للتغلب عىل هذه العقبات في أنظمة النواة الدقيقة القديمة ،لذا أصبحت قديمة
الطراز ،في حين تكون االستدعاءات بين المكونات استدعاءات وظيفيًة بسيطًة في النواة األحادية كما هو معتttاد
ال توجد إجابة محددة حول أفضل تنظيم ،وقد بtدأت العديtد من المناقشtات في األوسtاط األكاديميtة وغtير
األكاديمية حول ذلك ،لذا نأمل أن تكون قادًرا عىل اتخاذ قرار بنفسك عندما تتعلم المزيد عن أنظمة التشغيل.
تطّبق نواة لينكس نظttام الوحttدات ،حيث يمكن تحميttل المشّtغ الت في النttواة المشَّtغ لة مباشttرًة كمttا هttو
مطلوب ،وهذا أمر جيد ألّن المشّغ الت التي تشّكل جزًءا كبيًرا من شttيفرة نظttام التشtغيل ال ُتحَّم ل لألجهtزة غttير
الموجودة في النظام ،كما يمكن ألّي شخص يريtد أن يصttنع أكttثر نtواة عامtة ممكنtة -أي تعمtل عىل العديtد من
األجهزة المختلفة مثل RedHatأو -Debianتضميَن معظم المشّغ الت بوصفها وحدات ُتحَّم ل فقttط إذا احتttوى
النظام الذي يعمل عليه عىل العتاد المتاح ،لكن ُتحَّم ل الوحدات مباشرًة في النواة ذات الصttالحيات وتعمttل عىل
مستوى الصالحيات نفسه لبقية أجزاء النواة ،لذلك ال يزال ُيَع ّد النظام نواًة أحاديَة .
4.2.3االفرتاضية Virtualisation
يرتبط مفهوم العتاد الوهمي أو االفتراضي ارتباًط ا وثيًق ا بttالنواة ،إذ ُتَع ّد الحواسttيب الحديثttة قويttة جًttدا ،وال
ُيفَّض ل استخدامها عىل أساس نظام واحد كامttل ،وإنمttا تقسttيم الحاسttوب الحقيقي الواحttد إىل آالت افتراضttية
منفصلة ،virtual machinesإذ تبحث كٌّل من هذه اآلالت االفتراضية عن جميع األهداف واألغراض بوصفها آلة
101
علوم الحاسوب من األلف إىل الياء نظام التشغيل
يمكن تنظيم االفتراضية بعدة طttرق مختلفttة ،إذ يمكن تشttغيل مttراقب آلttة افتراضttية Virtual Machine
Monitorصغير مباشرًة عىل العتاد وتوفير واجهة ألنظمة تشغيل المضيف التي تعمttل في األعىل ،وُيطَل ق عىل
يشترك المشرف في كثير األمور مع النواة الدقيقة ،كما يسعيان ليكّو نا طبقات صغيرًة لتقديم العتاد بطريقة
آمنة عن الطبقات التي تعلوها ،ويمكن أاّل يكون لدى نظام التشغيل الموجود في الطبقة العليا أّي فكرة عن وجtود
المشرف Hypervisorعىل اإلطالق ،إذ يقدم هذا المشرف ما يبدو أنه نظام كامل ،ويعترض العمليات بين نظttام
التشغيل المضيف والعتاد ويقّدم مجموعًة فرعيًة من موارد النظام لكل منها.
ُيستخَدم المشرف غالًبا عىل األجهزة الكبيرة التي تحتوي عىل العديد من وحدات المعالجttة المركزيttة والكثttير
من ذواكر RAMلتطبيق عملية التجزيء ،Partitioningوهذا يعني أنه يمكن تقسيم الجهاز إىل أجهزة افتراضttية
أصغر ،كما يمكنك تخصيص المزيد من الموارد لتشغيل األنظمة حسب المتطلبات ،وُيَع ّد المشرفون الموجودون
عىل العديد من أجهزة IBMالكبيرة معقدًة للغاية مع ماليين األسطر من الشيفرة البرمجية مttع توفttير العديttد من
الخيار اآلخر هو جعل نظام التشغيل عىل دراية بالمشرف األساسي وطلب مttوارد النظttام عttبره ،إذ يشttار إىل
ذلك في بعض األحيان باسttم شttبه الوهميttة Paravirtualisationنظًtرا لطبيعتttه غttير المكتملttة ،وهttو مشttابه
102
علوم الحاسوب من األلف إىل الياء نظام التشغيل
للطريقة التي تعمل بها اإلصدارات األوىل من نظام Xenالذي ُيَع ّد حاًل وسًط ا ،إذ تttوّف ر هttذه الطريقttة أداًء أفضttل
ألن نظام التشغيل يطلب صراحًة موارد النظام من المشرف عند الحاجة بداًل من أن يطّبق المشرف األمور آلًي ا.
أخيًرا ،يمكن أن تصادف موقًف ا حيث يقّدم التطبيق الذي يعمttل عىل نظttام التشttغيل الحttالي نظاًم ا وهمًي ا
يتضمن وحدة معالجة مركزية وذاكرًة ونظام BIOSوقرص صلب وغير ذلك ،ويمكن تشغيل نظttام تشttغيل عttادي
عليه ،إذ يحّو ل التطبيق الطلبات إىل العتاد ثم إىل العتttاد األساسttي عttبر نظttام التشttغيل الحttالي ،وهttذا مشttابه
تتطلب هذه الطريقة تكلفًة أكبر ،إذ يتعين عىل عملية التطبيق محاكاة نظttام بأكملttه وتحويttل كttل شttيء إىل
طلبات من نظام التشغيل األساسي ،ولكنها تتيح محاكاًة معماريًة مختلفًة تماًم ا ،إذ يمكنك ترجمة التعليمات آلًي ا
من نوع معالج إىل آخر كما يفعل نظام روزيتا Rosettaمع برمجيات Appleالتي انتقلت من معttالج PowerPC
ُيَع ّد األداء مصدر قلق كبير عند استخدام أّي من تقنيات الوهمية ،إذ يجب أن تمر العمليات -التي كانت ُتَع ّد
ناقشت شركة إنتل Intelدعم العتاد للوهمية لتكون موجودًة في أحدث معالجاتها ،إذ تعمل هذه التوسعات
من خالل رفع استثناء خtاص للعمليtات الtتي يمكن أن تتطلب تtدّخل مtراقب اآللtة االفتراضtية ،وبالتtالي فtإن
المعالج يشبه المعالج غير االفتراضي الخtاص بtالتطبيق الtذي يعمtل عليtه ،ولكن يمكن اسtتدعاء مtراقب اآللtة
االفتراضية عندما يقّدم هذا التطبيق طلبات للحصول عىل موارد يمكن مشاركتها بين أنظمttة تشttغيل المضttيف
األخرى.
يوّف ر ذلك أداًء فائًق ا ألّن مراقب اآللة االفتراضية ال يحتاج إىل مراقبة كل عمليttة لمعرفttة مttا إذا كttانت آمن ًtة ،
ولكن يمكنه االنتظار حتى ُيعِلم المعالج بحدوث شيء غير آمن.
إذا لم يكن تقسيم النظام ساكًنا وإنما آلًي ا ،فهناك مشكلة أمنية محتملة متضمنة في النظام وُيَع ّد هttذا عيًب ا
أمنًي ا يتعلق باآلالت االفتراضيةُ .تخَّص ص الموارد ألنظمة التشغيل التي تعمل في الطبقttة العليttا حسttب الحاجttة
في النظام اآللي ،وبالتالي إذا كان أحد هذه األنظمة ينّف ذ عمليات مكثفًة لوحttدة المعالجttة المركزيttة بينمttا ينتظttر
النظام اآلخر وصttول البيانtات من األقttراص الصttلبة ،فسُtتمَنح المهمtة األوىل مزيًtدا من طاقttة وحtدة المعالجttة
المركزية ،في حين سيحصل كل منهما عىل %50من طاقة وحدة المعالجة المركزية في النظام الساكن ،وسُيهَدر
يفتح التخصيص اآللي قناة اتصال بين نظاَم ي التشغيل التي تكون كافيًة للتواصttل في نظttام ثنttائي في أّي
مكttان يمكن اإلشttارة فيttه إىل تلttك الحttالتين ،لكن تخيttل أّن كال النظttامين آمنttان جًttدا ،وال ينبغي أن تكttون أّي
103
علوم الحاسوب من األلف إىل الياء نظام التشغيل
معلومttات قttادرًة عىل المttرور بينهمttا عىل اإلطالق ،كمttا يمكن أن يتttآمر شخصttان لttديها إذن وصttول لتمريttر
المعلومات فيما بينهما من خالل كتابة برنامجين يحاوالن أخذ كميات كبيرة من الموارد في الوقت نفسه.
إذا أخذ أحدهما مساحًة كبيرًة من الذاكرة ،فسيكون هناك قدر أقل من المساحة المتاحة لآلخر؛ أمttا إذا تعّق بttا
الحد األقصى من التخصيصات ،فيمكن نقل القليل من المعلومات فقط ،ولنفترض أنهما اتفقا عىل التحقق في
كل ثانية مما إذا كان بإمكانهما تخصيص هذا القدر الكبير من الذاكرة ،فإذا كان الطttرف الهttدف قttادًرا عىل ذلttك،
فسُتَع ّد هذه الحالة 0ثنائًي ا ،وإذا لم يستطع ذلك -أّي أن الجهاز اآلخر يحتوي عىل كل الذاكرة ،-فسُتَع ّد هذه الحالttة
1ثنائًي ا ،كمttا أنttه ليس معttدل البيانttات الُم قَّttد ر ببت واحttد في الثانيttة مttذهاًل ،ولكن هttذا يttدل عىل وجttود
تدفق للمعلومات.
يسمى ذلك بالقناة السرية ،Covert Channelوهttذا يظِه ر أّن األمttور ليسttت بهttذا البسttاطة عىل مttبرمج
4.2.4مجال المستخدم
نسمي المكان الذي يشِّغ ل فيه المستخِد م البرامج باسم مجال المستخِدم ،Userspaceإذ يعمل كل برنامج
في مجال مستخِد م ،ويتواصل مع النواة عبر استدعاءات النظام التي سنوضّحها في المقال القادم ،كمtا ال يتمتtع
مجال المستخِدم بصالحيات ،Unprivilegedإذ يمكن لبرامج المسttتخِدم تطttبيق مجموعttة محttدودة فقttط من
األشياء ،ويجب أاّل تكون قادرًة عىل تعطيل البرامج األخرى حتى إذا تعطلت هي نفسها.
،Kernelإذ سنشرح فيما يلي المبدأ العام لكيفية عمل هذه االستدعاءات ،وسنتعّرف عىل الصالحيات في نظttام
االستدعاء )( openورقم استدعاء النظام 11هو االستدعاء )( readعىل سبيل المثال.
ُتَع ّد واجهة التطبيق الثنائية - Application Binary Interfaceأو ABIاختصاًرا -مشابهًة جًدا لواجهة برمجة
التطبيقttات ،APIولكنهttا ُم خَّص صttة للعتttاد بttداًل من أن تكttون خاص ًtة بالبرمجيttات ،إذ سttتحّدد واجهttة برمجttة
التطبيقات APIالمسّجل Registerالذي يجب إدخال رقم استدعاء النظام فيttه لتتمّكن النttواة من العثttور عليttه
104
علوم الحاسوب من األلف إىل الياء نظام التشغيل
4.3.2الوسائط Arguments
ال تكون استدعاءات النظام جيدًة بدون الوسائط ،فاالستدعاء )( openمثاًل يحتttاج إىل إعالم النttواة بالضttبط
بالملف الذي يجب فتحه ،وستحّدد واجهة ABIأًيا من وسائط المسّجالت التي يجب وضعها الستدعاء النظام.
4.3.3المصيدة Trap
يجب أن تكون هنtاك طريقtة مtا لالتصtال بtالنواة الtتي نريtد إجtراء اسtتدعاء نظtام إليهtا ،إذ تعِّtرف جميtع
المعماريات الحاسوبية تعليمًة تسمى عادًة breakأو شيًئا آخر مشابه يشير إىل العتاد الذي نريد إجttراء اسttتدعاء
النظام إليه ،وستخبر هذه التعليمة العتاد بتعديل مؤشر التعليمة ليؤّش ر إىل معttالج اسttتدعاءات النظttام الخttاص
بالنواة ،إذ يخبر نظام التشغيل العتاد بمكان وجود معالج استدعاء النظام عندما يضبط نفسه ،لtذلك يفقttد مجttال
المستخِد م السيطرة عىل البرنامج وتمريره إىل النواة بمجرد أن يستدعي التعليمة .break
ُيَع ّد ما تبقى من هtذه العمليtة بسtيًط ا إىل حtد مtا ،إذ تبحث النtواة في المسtجل الُم عَّtرف مسtبًق ا عن رقم
استدعاء النظام وتبحث عنه في جدول لمعرفة الدالة التي يجب أن تستدعيها ،وُتسtتدَع ى هttذه الدالtة وتنّف ذ مtا
يجب تنفيذه ،ثم تضع القيمة الُم عادة في مسجل آخر تعّرفه الواجهة ABIبوصفه مسّجل إعادة .Return
تتمثل الخطوة األخيرة في أن تنّف ذ النواة تعليمات قفز إىل برنامج مجال المستخِدم لتتمّكن من المتابعة من
حيث توقفت ،ويحصل برنامج مجال المستخِدم عىل البيانات التي يحتاجها من مسِّجل اإلعادة ثم يكمttل عملttه،
كما يمكن أن تصبح تفاصيل هذه العملية خطيرة للغاية ،إاّل أّن هذا كله يتعلق باستدعاء النظام.
4.3.4مكتبة libc
يمكنك تنفيذ كل ما سبق يدوًيا لكل استدعاء نظام ،لكن تنّف ذ مكتبات النظام معظم العمل نيابًة عنك عttادًة ،
والمكتبة القياسية التي تتعامل مع استدعاءات النظام عىل أنظمة يونيكس هي مكتبة .libc
لتوضيح كيفية عمل استدعاءات النظام ،وسنوضح كيفية عمل أبسط استدعاء نظام )( getpidالذي ال يأخذ أّي
وسيط ويعيد معّرف البرنامج أو العملية التي تكون قيد التشغيل حالًي ا.
>#include <stdio.h
105
علوم الحاسوب من األلف إىل الياء نظام التشغيل
)void function(void
{
;int pid
;)pid = __syscall(__NR_getpid
}
نبدأ بكتابة برنامج صغير بلغة Cلتوضيح آلية عمل استدعاءات النظام ،وأول شيء يجب مالحظته هttو وجttود
الوسيط syscallالذي توّف ره مكتبات النظام إلجراء استدعاءات النظام مباشttرًة ،إذ يttوِّف ر هttذا الوسttيط طريق ًtة
سهلًة للمبرمجين إلجراء استدعاءات النظام مباشرًة دون الحاجة إىل معرفة إجراءات لغة التجميع الدقيقttة إلجttراء
نسtتخِدم الدالtة )( getpidألّن اسtتخدام اسtم دالtة رمtزي في شtيفرتك البرمجيtة أوضtح وتعمtل الدالtة
)( getpidبطرق مختلفة جًدا عىل أنظمة مختلفة ،إذ يمكن تخزين االستدعاء )( getpidفي الذاكرة المخبئيttة
في نظام لينكس مثاًل ،لذا إذا جرى تشغيله مرتين ،فلن تتحمل مكتبة النظام عقوبة االضطرار إىل إجttراء اسttتدعاء
ُتعَّرف أرقام استدعاءات النظام في الملف asm/unistd.hمن مصدر النواة في نظام لينكس ،وبمttا أّن هttذا
الملف موجود في المجلد الفرعي ،asmفسيختلف ذلك لكل معمارية يعمل عليها نظام لينكس ،كما ُتعَط ى أرقام
استدعاءات النظام اسًم ا #defineيتكون من _ ،__NRوبالتttالي يمكنttك رؤيttة أّن شttيفرتك البرمجيttة سttتجري
سنلقي نظرًة عىل كيفيttة تطttبيق العديttد من المعماريttات لهttذه الشttيفرة البرمجيttة وسttنّط لع عىل الشttيفرة
البرمجية الحقيقية التي يمكن أن تكون خطيرًة ولكن يجب االلتزام بها ،فهttذه هي بالضttبط الطريقttة الttتي يعمttل
بها نظامك.
ُيَع ّد نظام PowerPCمعماريَة RISCشائعة في حواسيب Appleالقديمة ،وهو جوهر أجهttزة أحttدث إصttدار
يتِلف استدعاُء النظام المسّج الِت نفسها الستدعاء الدالة في نظام /* ،powerpc
باستثناء المسّج ل LRالذي يحتاجه التسلسل "* "sc; bnslr
والمسّج ل CRحيث ُيتَلف المسجل CR0.SOفقط الذي يشير إلى *
106
علوم الحاسوب من األلف إىل الياء نظام التشغيل
107
علوم الحاسوب من األلف إىل الياء نظام التشغيل
__sc_loadargs_0(name);
__sc_3 = (unsigned long) (arg1)
#define __sc_loadargs_2(name, arg1, arg2)
__sc_loadargs_1(name, arg1);
__sc_4 = (unsigned long) (arg2)
#define __sc_loadargs_3(name, arg1, arg2, arg3)
__sc_loadargs_2(name, arg1, arg2);
__sc_5 = (unsigned long) (arg3)
#define __sc_loadargs_4(name, arg1, arg2, arg3, arg4)
__sc_loadargs_3(name, arg1, arg2, arg3);
__sc_6 = (unsigned long) (arg4)
#define __sc_loadargs_5(name, arg1, arg2, arg3, arg4, arg5)
__sc_loadargs_4(name, arg1, arg2, arg3, arg4);
__sc_7 = (unsigned long) (arg5)
#define _syscall0(type,name)
type name(void)
{
__syscall_nr(0, type, name);
}
#define _syscall1(type,name,type1,arg1)
type name(type1 arg1)
{
__syscall_nr(1, type, name, arg1);
}
#define _syscall2(type,name,type1,arg1,type2,arg2)
type name(type1 arg1, type2 arg2)
108
علوم الحاسوب من األلف إىل الياء نظام التشغيل
{
;)__syscall_nr(2, type, name, arg1, arg2
}
)#define _syscall3(type,name,type1,arg1,type2,arg2,type3,arg3
)type name(type1 arg1, type2 arg2, type3 arg3
{
;)__syscall_nr(3, type, name, arg1, arg2, arg3
}
#define
)_syscall4(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4
)type name(type1 arg1, type2 arg2, type3 arg3, type4 arg4
{
;)__syscall_nr(4, type, name, arg1, arg2, arg3, arg4
}
#define
_syscall5(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4,type5,
)arg5
)type name(type1 arg1, type2 arg2, type3 arg3, type4 arg4, type5 arg5
{
;)__syscall_nr(5, type, name, arg1, arg2, arg3, arg4, arg5
}
يوِّض ح جttزء الشttيفرة البرمجيttة السttابق من ملttف ترويسttة النttواة asm/unistd.hكيttف يمكننttا تطttبيق
استدعاءات النظام عىل نظام ،PowerPCويمكن أن يبدو األمر معقًدا للغاية ،ولكن لنشرحه خطوًة خطوة.
انتقل أواًل إىل نهاية المثال إىل تعريف وحدات الماكرو ،_syscallNإذ يمكنك رؤيttة أّن هنttاك العديttد من
وحدات الماكرو ويأخذ كل منها وسيًط ا آخر تدريجًي ا ،وسنرّك ز عىل أبسط إصدار وهو _syscall0للبدء بttه والttذي
ال يتطلب سوى وسيَط ين هما نوع القيمة الُم عادة الستدعاء النظام مثل intأو charواسم استدعاء النظttام ،إذ
سنبدأ اآلن بتفكيك الماكرو __syscall_nrالذي ال يختلف عما كان عليه سابًق ا ،إذ سنأخذ عttدد الوسttائط
عىل أنه المعاِم ل األول ثم النttوع واالسttم والوسttائط الفعليttة ،فttالخطوة األوىل هي التصttريح عن بعض األسttماء
للمسّttجالت ،إذ يشttير االسttم __sc_0إىل المسّttجل r0أي المسّttجل ،0ويسttتخِدم المصِّttرف Compiler
109
علوم الحاسوب من األلف إىل الياء نظام التشغيل
المسجالت بالطريقة التي يريدها ،لهttذا السttبب يجب أن نعطيttه قيttوًدا حttتى ال يقّtرر اسttتخدام المسّtجل الttذي
نستدعي بعد ذلك sc_loadargsمع المعاِم ل ##الذي ُيَع ّد أمر لصق ُيسttتبَدل بttالمتغير ،nrوسنوّس عه
رقم استدعاء النظام ،والحظ معاِم ل اللصق مرًة أخرى مع البادئة _ __NRواسم المتغttير الttذي يشttير إىل مس ّtجل
معّي ن ،لذا ُتستخَدم هذه الشيفرة البرمجية السابقة ذات المظهر الصعب لوضع رقم استدعاء النظام في المسّجل
،0كما يمكنك باتباع الشيفرة البرمجية السابقة رؤية أن وحدات الماكرو األخرى ستضع وسttائط اسttتدعاء النظttام
في المسّttجل r3عtttبر المسّttجل ،r7ويمكنttك فقtttط الحصtttول عىل 5وسtttائط عىل أسtttاس حttد أقصtttى
الستدعاء النظام.
سنعالج اآلن القسم __ ،__asmإذ لدينا هنا ما يسّم ى بالتجميع الُم ضَّم ن Inline Assemblyألنهttا شttيفرة
تجميع مختلطة مع الشيفرة المصدرية ،وهذه الصيغة معقtدة بعض الشtيء ،لtذلك سنشtير إىل األجtزاء المهمtة
منها فقط ،كما عليك تجاهل البت __ __volatileحالًي ا والذي يخttبر المصّtرف أّن هttذه الشttيفرة البرمجيttة ال
يمكن التنبؤ بها ،لذا ال تحtاول التعامtل معهtا بtذكاء ،كمtا أّن كtل األشtياء الموجtودة بعtد النقطtتين هي طريقtة
للتواصل مع المصّرف حول ما يفعله التجميع المضَّtم ن لمسtجالت وحtدة المعالجttة المركزيtة ،ويجب أن يعِtرف
المصّرف ذلك حتى ال يحاول استخدام أّي من هذه المسجالت بطرق يمكنها التسبب في حدوث عطل.
لكن الجزء المهم هو وجود تعليمَتي التجميع في الوسيط األول ،إذ ينِّف ذ االستدعاء scكل العمل ،وهttذا كttل
ما عليك تطبيقه إلجراء استدعاء نظام ،وبالتttالي يحttدث مttا يلي عنttد إجttراء اسttتدعاء النظttام ،إذ يعttرف المعttاِلج
الُم قاَط ع أنه يجب عليه نقل التحكم إىل جttزء معّين من إعttداد الشttيفرة البرمجيttة في وقت بttدء تشttغيل النظttام
لمعالجة المقاطعات ،كما توجد هناك العديد من المقاطعttات ،وُتَع ّد اسttتدعاءات النظttام إحttداها ،وتبحث هttذه
الشيفرة البرمجية بعد ذلك في المسجل 0للعثور عىل رقم اسttتدعاء النظttام ،ثم تبحث في جttدول إليجttاد الدالttة
الصحيحة لالنتقال إليها لمعالجة استدعاء النظام ،وتتلقى هذه الدالة وسائطها من المسجل 3إىل المسجل .7
يعود التحكم إىل التعليمة التالية بعttد scوهي في هttذه الحالttة تعليمttات سttور الttذاكرة Memory Fence
بمجرد تشغيل معالج استدعاء النظام واكتماله ،كما تتأكد تعليمات سور الذاكرة من أن كل شttيء ملttتزم بالttذاكرة،
حيث تضمن هذه التعليمة أّن كل ما نعتقد أنه مكتوب في الذاكرة قد حدث فعلًي ا دون المttرور عttبر خttط أنttابيب
انتهينا تقريًب ا ،ولكن الشيء الوحيد المتبقي هو إعادة القيمة من استدعاء النظام ،إذ نرى ضبط القيمة __sc
_retمن المسttجل r3وضttبط القيمttة __sc_errمن المسttجل ،r0فالقيمttة األوىل هي القيمttة الُم عttادة،
واألخرى هي قيمة الخطأ ،إذ يمكن أن تفشل استدعاءات النظام مثل أّي دالة أخttرى ،لكن تكمن المشttكلة في أّن
استدعاء النظام يمكن أن يعيد أّي قيمة ممكنة ،إذ ال يمكننا أن نقول أن القيمة السttالبة تشttير إىل الفشttل ،ألنهttا
110
علوم الحاسوب من األلف إىل الياء نظام التشغيل
يمكن أن تكون مقبولًة لبعض استدعاءات النظام ،لذا تضمن دالة استدعاء النظttام أّن نتيجتهttا في المسttجل r3
يجب التحقق من رمز الخطأ للتأكد من ضبط الِبّت العلوي الذي من شأنه اإلشارة إىل عدد سالب ،فtإذا كtان
األمtر كtذلك ،فسنضtبط قيمtة المتغtير errnoالعtام عىل هtذه القيمtة وهي المتغtير القياسtي للحصtول عىل
معلومات الخطأ عند فشل االستدعاء ،كما سنضبط القيمة الُم عادة عىل ،-1وسtنعيد النتيجtة مباشtرًة في حالtة
تلّق ي نتيجة صالحة ،لذا يجب عىل دالة االسtتدعاء التحقtق من أّن القيمtة المعtادة ليسtت ،-1فtإذا كtان األمtر
كذلك ،فيمكنها التحقق من المتغير errnoللعثور عىل سبب فشل االستدعاء ،وهذا هttو اسttتدعاء نظttام كامttل
توجد أرقام األخطاء المرئية للمستخِدم ضمن المجال من -1إلى /* 124-
* راجع ><asm-i386/errno.h
*/
111
علوم الحاسوب من األلف إىل الياء نظام التشغيل
#define _syscall1(type,name,type1,arg1)
type name(type1 arg1)
{
long __res;
__asm__ volatile ("int $0x80"
: "=a" (__res)
: "0" (__NR_##name),"b" ((long)(arg1)));
__syscall_return(type,__res);
}
#define _syscall2(type,name,type1,arg1,type2,arg2)
type name(type1 arg1,type2 arg2)
{
long __res;
__asm__ volatile ("int $0x80"
: "=a" (__res)
: "0" (__NR_##name),"b" ((long)(arg1)),"c" ((long)(arg2)));
__syscall_return(type,__res);
}
#define _syscall3(type,name,type1,arg1,type2,arg2,type3,arg3)
type name(type1 arg1,type2 arg2,type3 arg3)
{
long __res;
__asm__ volatile ("int $0x80"
: "=a" (__res)
: "0" (__NR_##name),"b" ((long)(arg1)),"c" ((long)(arg2)),
"d" ((long)(arg3)));
__syscall_return(type,__res);
}
#define
_syscall4(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4)
type name (type1 arg1, type2 arg2, type3 arg3, type4 arg4)
{
long __res;
112
علوم الحاسوب من األلف إىل الياء نظام التشغيل
#define
_syscall5(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4,
type5,arg5)
type name (type1 arg1,type2 arg2,type3 arg3,type4 arg4,type5 arg5)
{
long __res;
__asm__ volatile ("int $0x80"
: "=a" (__res)
: "0" (__NR_##name),"b" ((long)(arg1)),"c" ((long)(arg2)),
"d" ((long)(arg3)),"S" ((long)(arg4)),"D" ((long)(arg5)));
__syscall_return(type,__res);
}
#define
_syscall6(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4,
type5,arg5,type6,arg6)
type name (type1 arg1,type2 arg2,type3 arg3,type4 arg4,type5
arg5,type6 arg6)
{
long __res;
__asm__ volatile ("push %%ebp ; movl %%eax,%%ebp ; movl %1,%%eax ; int
$0x80 ; pop %%ebp"
: "=a" (__res)
: "i" (__NR_##name),"b" ((long)(arg1)),"c" ((long)(arg2)),
"d" ((long)(arg3)),"S" ((long)(arg4)),"D" ((long)(arg5)),
"0" ((long)(arg6)));
__syscall_return(type,__res);
}
وعtt عىل أنه معاِلج من النx86 إذ ُيصَّنف، التي تحّدثنا عنها سابًق اPowerPC كثيًرا عنx86 تختلف معمارية
113
علوم الحاسوب من األلف إىل الياء نظام التشغيل
اّط لع عىل أبسط ماكرو _syscall0الذي يستدعي تعليمtة من النtوع intوالقيمtة ،0x80إذ تعمtل هtذه
التعليمة عىل جعل وحدة المعالجة المركزية ترفع المقاطعة 0x80التي ستنتقل إىل الشيفرة البرمجية التي تعttالج
لنفحص اآلن كيفية تمرير الوسائط باستخدام وحدات الماكرو األطول ،والحظ كيtف أّن نظtام PowerPCقtد
طّبق تتال وانسياب cascadeمن وحدات الماكرو من خالل إضافة وسيط واحد في كttل مttرة ،كمttا يحتttوي هttذا
التطبيق عىل شيفرة برمجية منسوخة ولكن اتباعه أسهل قلياًل .
تسttتند أسttماء المسّttجالت في معماريttة x86إىل األحttرف عوًض ا عن أسttماء المسّttجالت الرقميttة في
،PowerPCإذ يمكننا رؤية من الماكرو عديم الوسائط أّن المسّجل ُ Aيحَّم ل فقط ،وبالتtالي يمكننtا القtول أّن رقم
استدعاء النظام متوَّق ع وجوده في المسّجل ،EAXكمttا يمكنttك رؤيttة أسttماء المسttجالت المختصttرة في وسttائط
الحظ الماكرو __syscall6الذي يأخذ 6وسائط ،إذ تعمل التعليمتان pushو popمع المكدس في ،x86
بحيث تدفع إحداهما قيمًة إىل أعىل المكدس في الذاكرة وتسحب األخرى القيمة من المكدس في الttذاكرة ،كمttا
يجب تخزين قيمة المسجل ebpفي الذاكرة ووضع الوسيط في التعليمة movوإجراء استدعاء للنظttام ،ثم إعttادة
القيمة األصلية إىل المسtجل ebpفي حالtة وجtود سtتة مسtجالت ،كمtا يمكنtك هنtا رؤيtة عيtوب عtدم وجtود
مسجالت كافية ،إذ ُيَع ّد التخزين في الذاكرة باهظ الثمن ،لذا كلما تمكنت من تجّنبها ،كان ذلك أفضل.
الحظ عدم وجود تعليمات سور الذاكرة التي رأيناها سابًق ا مع PowerPCألّن معمارية x86تضمن أن يكttون
تأثير جميع التعليمات مرئًي ا عند اكتمالها ،مما يسّه ل البرمجة عىل المصّرف والمبرمج ولكنه يقلل من المرونة.
يوجد أيًض ا اختالف في القيمة الُم عttادة ،فقttد كttان لttدينا مسّtجَلين مttع قيم ُم عttادة من النttواة في معماريttة
،PowerPCإحداهما هي القيمة واألخرى هي رمز الخطtأ ،في حين لtدينا قيمtة ُم عtادة واحtدة في معماريtة x86
ُتمَّرر إىل الماكرو __syscall_returnالذي يغّي ر نوع القيمة الُم عادة إىل النttوع unsigned longويوازنهttا
مع مجال من القيم السالبة تعتمد عىل المعمارية والنواة ،حيث تمّث ل هذه القيم رموز الخطأ.
الحظ أّن قيمة رمز الخطأ errnoموجبة ،مما يؤدي إىل إلغاء النتيجة السالبة من النttواة ،لكن يعttني هttذا أّن
استدعاءات النظام ال يمكنها إعادة قيم سالبة صttغيرة ،إذ ال يمكن تمييزهttا عن رمttوز الخطttأ ،كمttا تضttيف بعض
استدعاءات النظttام الttتي لttديها هttذا المتطلب مثttل االسttتدعاء )( getpriorityإزاحًtة إىل القيمttة الُم عttادة
إلجبارها بأن تكون دائًم ا موجبة ،فاألمر متروك لمجال المستخِدم إلدراك ذلك وطرح هذه القيمة الثابتة للحصttول
114
علوم الحاسوب من األلف إىل الياء نظام التشغيل
أّي تطبيق آخر يعمل في النظام ،وهذا يعني أن التطبيقات يجب أاّل تكون قادرًة عىل الكتابة في ذاكttرة أو ملفttات
التطبيقات األخرى ،ويجب أن تصل فقط إىل الموارد وفق سياسة النظام.
لكن يكون ألحد التطبيقات عند تشغليها استخدام حصري للمعالج ،إذ سنرى كيف يعمل ذلك عندما نتع ّtرف
عىل العمليات في المقال التالي ،ويمكن التأكد من وصول التطttبيق إىل الttذاكرة الttتي يمتلكهttا فقttط باسttتخدام
نظام الذاكرة الوهمية ،Virtual Memoryإذ ُيَع ّد العتاد مسؤواًل عن تطبيق هذه القواعد.
ُتَع ّد واجهة استدعاء النظام بوابة التطبيق للوصول إىل موارد النظام ،إذ يمكن للنواة فرض قواعttد حttول نttوع
الوصول الذي يمكن توفيره من خالل إجبttار التطttبيق عىل طلب المttوارد من خالل اسttتخدام اسttتدعاء نظttام إىل
النواة ،فإذا أجرى أحد التطبيقات استدعاء النظام )( openلفتح ملف عىل القرص الصلب مثاًل ،فسttيتحقق من
4.4.1مستويات الصالحيات
ُتَع ّد حماية العتاد مجموعًة من الحلقات متحدة المركز حول مجموعة أساسية من العمليات.
توجد التعليمات ذات الحماية األكبر في الحلقة الداخلية ،وهي التعليمttات الttتي يجب السttماح للنttواة فقttط
باستدعائها مثل التعليمة HLTالُم ستخَدمة إليقاف المعالج ،إذ يجب أاّل ُيسَم ح بأن يشّغ لها تطبيق مستخِدم ،ألن
ذلك سيوقف الحاسوب بأكمله عن العمtل ،لكن يجب أن تكtون النtواة قtادرًة عىل اسtتدعاء هtذه التعليمtة عنtد
إيقاف تشغيل الحاسوب بطريقة نظاميttة ،إذ يرفtع العتttاد اسttتثناًء عنtدما يسtتدعي تطttبيق مtا هttذه التعليمtة،
115
علوم الحاسوب من األلف إىل الياء نظام التشغيل
ويتضّم ن هذا االستثناء القفز إىل معالج محدد في نظام التشغيل مشابه لمعالج استدعاء النظام ،كمtا ُيحتَم ل أن
ينهي نظام التشغيل بعد ذلك البرنامج ويعطي المستخِد م بعض األخطاء حول كيفية تعطل التطبيق.
يمكن لكل حلقة داخلية الوصول إىل أّي تعليمات تحميها حلقة خارجية ،ولكن ال يمكنها الوصول إىل تعليمttة
تحميها حلقة داخلية ،كما ال تحتtوي جميtع المعماريtات عىل مسtتويات متعtددة من الحلقtات كمtا في الشtكل
السابق ،ولكن سيوفر معظمها عىل األقل مستوى النواة Kernelومستوى المستخِدم .User
يحتوي نموذج الحماية 386عىل أربع حلقttات بttالرغم من أن معظم أنظمttة التشttغيل مثttل لينكس ووينttدوز
تستخِدم حلقَتين فقط للحفاظ عىل التوافق مع المعماريات األخرى الttتي تسttمح اآلن بttأكبر عttدد من مسttتويات
الحماية المنفصلة ،كمtا يحتفtظ النمtوذج 386بالصtالحيات من خالل أن يكtون لكtل جtزء من شtيفرة التطtبيق
البرمجية الُم شَّغ لة في النظام واصف صغير يسمى واصف الشيفرة البرمجيttة Code Descriptorالttذي يِص ف
مستوى صالحياتها.
تقفز شيفرة التطبيق عند تشغليها سريًع ا إىل الشيفرة البرمجية الموجودة خارج المنطقة التي يِص فها واصُف
شيفرة التطبيق مع التحقق من مستوى صالحيات الهدف ،فإذا كانت الصtالحيات أعىل من صtالحيات الشtيفرة
الُم شَّغ لة حالًي ا ،فلن يسمح العتاد بهذه القفزة وسيتعطل التطبيق.
يمكن أن ترفttع التطبيقttات مسttتوى صttالحياتها فقttط من خالل اسttتدعاءات محttددة تسttمح بttذلك مثttل
التعليمات الخاصة بتنفيذ استدعاء النظام ،إذ يشار إليها عادًة باسم بوابة االسttتدعاءات Call Gateألنهttا تعمttل
مثل بوابة حقيقية تسمح بمدخل صغير عبر جدار غير قابل لالختراق.
رأينا كيف يوِقف العتاد التطبيق الttذي يكttون قيttد التشttغيل ويسّtلم التحكم إىل النttواة عنttد اسttتدعاء هttذه
التعليمات ،ويجب أن تعمل النواة بوصفها حارًس ا للبوابة للتأكttد من عttدم دخttول أّي شttيء غttير مرغttوب بttه من
البوابة ،إذ يجب التحقق من وسائط استدعاء النظttام بعنايttة للتأكttد من أنttه لن ينخttدع بفعttل شttيء ال ينبغي أن
تعمل النواة في الحلقة الداخلية ،لذا فهي تمتلك األذونات الالزمة إلجراء أّي عملية تريدها ،ثم ستعيد التحكم
في النهاية إىل التطبيق الذي سيعمل مرًة أخرى بمستوى صالحيات أقل.
تتمثل إحدى مشاكل المصائد كما هو موضح سابًق ا في أنها باهظة الثمن بالنسبة للمعالج لتطبيقها ،فهنttاك
الكثير من الحاالت التي يجب حفظها قبل تبديل السياق ،وقد أدركت المعالجات الحديثttة هttذا الِح مttل وتسttعى
جاهدة لتقليله.
116
علوم الحاسوب من األلف إىل الياء نظام التشغيل
يتطلب فهم آلية بوابة االستدعاءات الموضحة سابًق ا التدقيق في مخطttط التقطيttع المبتكttر والمعقttد الttذي
يستخدمه المعالج ،وقد كان السبب األصلي لتطبيق التقطيع هو القدرة عىل استخدام أكثر من ِ 16بًتا متوفًرا في
يوضttح الشttكل السttابق تقطيttع العنونttة Segmentation Addressingفي معماريttة x86حيث يttؤدي
التقطيع إىل توسيع مساحة عناوين المعttالج من خالل تقسttيمه إىل أجttزاء .يحتفttظ المعttالج بمسttجالت مقttاطع
خاصة ،ويمكن تحديد العناوين من خالل مسّجل المقطع واإلزاحةُ .تضاف قيمة مسجل المقطع إىل جttزء اإلزاحttة
بقي مخطط التقطيع كما هو ولكن بتنسيق مختلttف عنttدما انتقلت معماريttة x86إىل مس ّtجالت بحجم 32
ِبًتا ،إذ ُيسَم ح للمقاطع بأن تكون بأّي حجم بداًل من اسttتخدام أحجttام ثابتttة ،ويجب أن يتعّق ب المعttالج كttل هttذه
تكون واصفات المقاطع المتاحة للجميع محفوظًة في جدول الواصفات العام Global Descriptor Table
أو GDTاختصاًرا ،كما تحتوي كل عملية عىل عدد من المسجالت التي توّش ر إىل مدخالت في جدول ،GDTوهذه
المدخالت هي المقاطع التي يمكن للعملية الوصول إليها ،كما توجد جداول واصفات محليttة ،وتتفاعttل جميعهttا
117
علوم الحاسوب من األلف إىل الياء نظام التشغيل
الحظ كيف يمر االستدعاء البعيد عبر بوابtة االسttتدعاءات الtتي توّجهtه إىل مقطttع من الشtيفرة يعمtل عىل
مستوى الحلقttة األدنى .الطريقttة الوحيttدة لتعttديل محّttدد مقطttع الشttيفرة -المسttتخَدم ضttمنًي ا لجميttع عنttاوين
الشيفرة -هي استخدام آلية االستدعاء ،حيث تضمن آلية بوابة االسtتدعاءات اختيtار واصtف مقطtع جديtد ،ممtا
يؤدي إىل تغيير مستويات الحماية التي يجب عليك االنتقال إليها عبر نقطة دخول معروفة.
يضبط نظام التشغيل مسجالت المقطع بوصفها جزًءا من حالttة العمليttة ،لttذا يعِtرف عتttاد المعttاِلج مقttاطع
الذاكرة التي يمكن للعملية الُم شَّغ لة الوصول إليها ،كما يمكنه فtرض الحمايtة لضtمان عtدم وصtول العمليtة ألّي
شttيء ال ُيفttتَرض أن تصttل إليttه ،فttإذا خttرجت العمليttة خttارج حttدودها المفروضttة ،فسttتتلّق ى خطttأ تقطيttع
إذا احتاج تشغيل الشيفرة البرمجية إىل إجttراء اسttتدعاءات إىل شttيفرة موجttودة في مقطttع آخttر ،فسttتطّبق
معمارية x86ذلttك كمttا في الحلقttات ،Ringsإذ تكttون الحلقttة 0هي الحلقttة ذات اإلذن األعىل والحلقttة 3هي
األدنى ،ويمكن للحلقات الداخلية الوصول إىل الحلقات الخارجية ولكن ليس العكس.
118
علوم الحاسوب من األلف إىل الياء نظام التشغيل
إذا أرادت شيفرة الحلقة 3القفز إىل شيفرة الحلقة ،0فستعّدل محّدد مقطع الشيفرة الخاص بها ليؤّش ر إىل
مقطع مختلف ،لذلك يجب أن تستخِد م تعليمة استدعاءات بعيدة خاصة بحيث يتأكد العتاد من مرورها عبر بوابة
االستدعاءات ،وال توجد طريقة أخرى للعملية الُم شَّغ لة الختيار واصف مقطع شيفرة جديد ،وسttيبدأ المعttالج بعttد
ذلك في تنفيذ الشيفرة البرمجية عند اإلزاحة المعروفة في مقطع الحلقة ،0وهذا هو سبب الحفاظ عىل السttالمة
مثل عدم قراءة الشيفرة البرمجية العشوائية والضارة وتنفيذها ،كما سيبحث المهttاجمون دائًم ا عن طttرق لجعttل
يسمح ذلك بتسلسل هرمي كامل للمقاطع واألذونات ،والحظ أّن استدعاء المقطع العرضي يشttبه اسttتدعاء
النظام ،فإذا سبق لttك أن شttاهدت لغttة تجميttع لينكس ،x86فالطريقttة القياسttية إلجttراء اسttتدعاء النظttام هي
باستخدام int 0x80التي ترفع المقاطعة ،0x80إذ توِقف المقاطعttة المعttالج وتنتقttل إىل بوابttة المقاطعttات
التي تعمل بعد ذلك بطريقة بوابة االسtتدعاءات نفسtها بحيث تغّي ر مسtتوى الصtالحيات وتعيtدك إىل منطقtة
مشكلة هذا المخطط أنه بطيء ،إذ يتطلب األمر الكثير من الجهttد لتطttبيق كttل هttذا الفحص ،ويجب حفttظ
العديد من المسجالت للوصول إىل الشيفرة الجديدة ،كما يجب استعادة كل شيء مرًة أخرى في طريق العودة.
ال ُيستخَدم نظام الحلقات ذو المستويات األربعtة في تقطيtع نظtام x86الحtديث بفضtل الtذاكرة الوهميtة،
والشيء الوحيد الذي يحدث فعلًي ا مع تبديل التقطيع هو استدعاءات النظام التي تتحّو ل من الوضع - 3أي مجttال
المستخِدم -إىل الوضع 0وتقفز إىل شيفرة معالج استدعاء النظام في النواة.
يوّف ر المعاِلج تعليمات استدعاء نظام فائقة السرعة تسمى ( sysenterو sysexitللعودة) ،إذ تسّر ع هذه
التعليمات العملية برمتها عبر االستدعاء int 0x80من خالل إزالة الطبيعة العامة لالستدعاء البعيد ،أي إمكانية
االنتقال إىل أّي مقطع في أّي مستوى حلقة ،وتقييد االسttتدعاء لالنتقttال فقttط إىل شttيفرة الحلقttة 0في مقطttع
بما أننا استبدلنا هtذه الطبيعtة العامtة بtالكثير من المعلومtات المعروفtة مسtبًق ا ،فيمكن تسtريع العمليtة،
وبالتالي سنحصل عىل استدعاء النظام السريع الذي ذكرناه سttابًق ا ،والشttيء اآلخttر الttذي يجب مالحظتttه هttو أّن
الحالة ال ُتحَف ظ عندما ينتقل التحكم إىل النttواة ،إذ يجب أن تكttون النttواة حريصًtة عىل عttدم تttدمير الحالttة ،ولكن
يعني هذا أنها حرة في حفظ الحالة الصغيرة كما هو مطلوب لتنفيذ المهمة ،لذلك يمكن أن تكون أكثر فاعليًttة ،إذ
تتعلق هذه الفكرة بمعمارية ،RISCوتوّض ح كيفية تالشي الخط بين معالجات RISCو .CISC
هناك طرق أخرى للتواصل مع النواة مثل ioctlوأنظمة الملفات مثل procو sysfsو debugfsوغير ذلك.
119
.5العمليات في نظام تشغيل الحاسوب
5.1ما هي العملية؟
جميعنا عىل دراية بنظام التشغيل الحديث الذي يدير عدة مهام في وقت واحtد أو مtا يسtمى بتعtدد المهtام
،Multitaskingحيث ُتَع ّد العملية حزمًة من العناصر التي تحتفظ بها النواة لتعّق ب جميع المهام قيد التشغيل.
5.2عنارص العملية
5.2.1معرف العملية
يضبط نظام التشغيل معّرف العملية - Process IDأو PIDاختصاًرا -ويكون فريًدا لكل عملية ُم شَّغ لة.
5.2.2الذاكرة
سنتعلم كيف تحصل عملية ما عىل ذاكرتها الحًق ا ،وُتَع ّد الttذاكرة أحttد األجttزاء األساسttية لكيفيttة عمttل نظttام
التشغيل ،ولكن سنكتفي حالًي ا بمعرفة أّن كل عملية لها قسمها الخاص من الذاكرة.
ُتخَّزن شيفرة البرنامج في هذه الذاكرة مع المتغيرات وأّي عمليات تخزين أخttرى مخَّص صttة ،ويمكن مشttاركة
أجزاء من الذاكرة بين العمليات ،إذ تسَّم ى بالttذاكرة المشttتركة ،Shared Memoryكمttا يمكن أن تراهttا باالسttم
- System Five Shared Memoryأو SysV SHMاختصاًرا -بعد التطبيق األصلي في نظام تشغيل أقدم.
مفهوم مهم آخر يمكن أن تستخِد مه العملية هو مفهوم ربط ملف موجود في القرص الصttلب مtع الtذاكرة أو
ما ُيسمى ،mmapingإذ يبدو الملف كما لو كان أّي نوع آخر من الذاكرة RAMبداًل من االضطرار إىل فتح الملف
واستخدام أوامر مثل )( readو )( ،writeكمttا تمتلttك منttاطق mmapedأذونttات يجب تعّق بهttا مثttل القttراءة
والكتابة والتنفيذ ،فمهّم ة نظام التشغيل هي الحفاظ عىل األمن واالستقرار ،لttذلك يجب التحقttق ممttا إذا كttانت
يمكن تقسيم العمليttة بصttورة أكttبر إىل قسtمين همtا الشtيفرة Codeوالبيانtات ،Dataإذ يجب االحتفttاظ
بشيفرة البرنامج وبياناته بصورة منفصلة ألنها تتطلب أذونات مختلفة من نظام التشttغيل ،ويسّttه ل هttذا الفصttل
بينهما مشاركة الشيفرة كما سنرى الحًق ا ،كما يجب أن يعطي نظtام التشtغيل إذًن ا لشtيفرة البرنtامج للتمّكن من
قراءتها وتنفيذها دون الكتابة فيها ،في حين تتطلب البيانات (المتغيرات) أذونات القttراءة والكتابttة وال ينبغي أن
تكون قابلًة للتنفيذ ،ولكن ال تدعم جميع المعماريات ذلك ،مما أدى إىل مجموعة واسعة من مشttاكل األمttان في
العديد منها.
ُيَع ّد المكدس جزًءا مهًم ا آخttر من العمليttة وهttو منطقttة من الttذاكرة وجttزء من قسttم البيانttات في العمليttة،
ويشارك في تنفيذ أّي برنامج ،كما ُيَع ّد بنية بيانات عامtة تعمtل مثtل مجموعtة األطبtاق ،إذ يمكنtك دفtع push
عنصttر أو وضttع طبttق أعىل كومttة من األطبttاق بحيث يصttبح العنصttر العلttوي ،أو يمكنttك سttحب popعنصttر
المكدسات أساسية الستدعاءات الدوال ،إذ تحصttل عىل إطttار مكttدس stack frameجديttد في كttل مttرة
ُتستدَع ى فيها الدالة ،وهي منطقة من الذاكرة تحتوي عىل األقل عىل العنوان الذي يجب الرجوع إليه عند االنتهttاء
121
علوم الحاسوب من األلف إىل الياء العمليات في نظام تشغيل الحاسوب
تنمو المكدسات عادًة إىل األسفل ،إذ يبدأ المكtدس عنtد عنtوان مرتفtع في الtذاكرة وينخفض تtدريجًي ا ،في
حين تحتوي بعض المعماريات مثل PA-RISCمن HPعىل مكدسات تنمو لألعىل ،وتوجد في بعض المعماريات
األخرى مثل IA64مناطق تخزين أخرى (مخزن داعم للمسّجل) تنمو من األسفل باتجاه المكدس.
شكل :21المكدس
أواًل ،لكل دالة نسختها الخاصة من وسائط الدخل ،إذ ُيخَّص ص إطار مكدس جديد لكل دالة مع وسttائطها في
منطقة جديدة من الذاكرة ،وبالتالي ال يمكن أن ترى الدوال األخرى المتغير الُم عَّtرف في دالtة مtا ،في حين ُتخَّtزن
المتغيرات العامة التي يمكن أن تراها أّي دالة في منطقة منفصلة من ذاكرة البيانات ،مما يسّttه ل االسttتدعاءات
العودية ،Recursive Callsوهذا يعني أّن الدالة حرة في استدعاء نفسها مرًة أخرى ،إذ سُينَش أ إطار مكدس جديد
ثانًي ا ،يحتوي كل إطار عىل عنوان للعودة إليه ،وتسمح لغة Cفقط بإعادة قيمة واحدة من الدالtة ،لtذلك ُتعtاد
ثالًث ا ،يحتوي كttل إطttار عىل مرجttع لإلطttار الttذي يسttبقه ،لttذا يمكن لمنّق ح األخطttاء العttودة للخلttف متتّبًع ا
المؤشرات ليصل إىل أعىل المكدس ،ويمكن أن ينتج متعّق ب مكدسات Stack Traceالttذي ُيظِه ر لttك جميttع
الدوال التي جرى استدعاؤها حتى الوصول إىل هذه الدالة ،وُيَع ّد ذلك مفيًttدا لتنقيح األخطttاء ،كمttا يمكنttك رؤيttة
كيف تتناسب الطريقة التي تعمل بها الدوال مع طبيعttة المكttدس ،إذ يمكن ألّي دالttة اسttتدعاُء أّي دالttة أخttرى،
وبالتالي تصبح الدالة األعىل بحيث ُتوَض ع في أعىل المكدس ،وستعيد هذه الدالة في النهايttة النتيجttة إىل الدالttة
122
علوم الحاسوب من األلف إىل الياء العمليات في نظام تشغيل الحاسوب
رابًع ا ،تجعل المكدسات استدعاء الدوال أبطأ ،ألنه يجب نقل القيم خttارج المسttجالت إىل الttذاكرة ،في حين
تسمح بعض المعماريtات الحاسttوبية بتمريtر الوسttائط في المسtجالت مباشttرًة ،ولكن يجب تtدوير المسtجالت
خامًس ا ،ال بد أنك سمعت بمصطلح طفحان المكدس Stack Overflowالذي ُيَع ّد طريقًtة شtائعًة الخtتراق
النظام من خالل تمرير قيم وهمية ،فإذا كنت مبرمًجا تقبل باإلدخال العشوائي في متغير المكttدس مثttل القttراءة
من لوحة المفاتيح أو عبر الشبكة ،فيجب عليك تحديد حجم هذه البيانات صراحًة ،إذ يؤدي السماح بأّي كمية من
البيانات دون تحديد إىل الكتابة فوق الذاكرة ،مما يؤدي إىل حttدوث عطttل ،ولكن أدرك بعض األشttخاص أنهم إذا
كتبوا في ذاكرة كافية فقط لوضع قيمة محددة في جزء العنوان الُم عاد من إطttار المكttدس ،فيمكنهم إعادتهttا في
البيانات التي أرسلوها للتو عند اكتمال الدالة بداًل من اإلعادة إىل المكان الصحيح الtذي اسtتدعاها ،فtإذا احتtوت
هذه البيانات عىل شيفرة ثنائية قابلة للتنفيذ وتخترق النظام مثل تشغيل طرفية للمستخِدم مع صالحيات الجذر،
فهذا يعني تعّرض حاسوبك لالختراق .يحدث ذلك بسبب نمو المكدس لألسttفل ،ولكن ُتق َtرأ البيانttات لألعىل أي
هناك عدة طرق للتغلب عىل هذه المشكلة ،إذ يجب عليك التأكد -بصفتك مبرمًجا -من أنttك تتحقttق دائًم ا
من كمية البيانات التي تتلقاها في متغير ،ويمكن أن يساعد نظام التشغيل في تجّنب ذلك نيابًة عن المبرمج من
خالل التأكد من تمييز المكدس عىل أنه غير قابل للتنفيذ ،وبالتالي لن يشّغ ل المعالج أّي شيفرة برمجية ،حttتى إذا
حttاول مسttتخِدم سttيئ تمريttر شttيفرة برمجيttة إىل برنامجttك ،وتttدعم المعماريttات وأنظمttة التشttغيل الحديثttة
هذه الوظيفة.
سادًس ا ،يدير المصّرف Compilerالمكدسات ،فهو المسؤول عن إنشاء شttيفرة البرنttامج ،ويبttدو المكttدس
بالنسبة لنظام التشغيل مثل أّي منطقة أخرى من الذاكرة الخاصة بالعملية.
يعِّرف العتاد المسِّجل بوصttفه مؤشttر المكttدس Stack Pointerبهttدف تعّق ب نمttو المكttدس الحttالي ،إذ
يستخِدم المصّرف -أو المبرمج عند الكتابة باستخدام لغة التجميع -هذا المسِّجل لتعّق ب الجزء العلوي الحttالي من
123
علوم الحاسوب من األلف إىل الياء العمليات في نظام تشغيل الحاسوب
عرضنا في المثال السابق دالًة بسيطًة تخّص ص ثالثة متغيرات عىل المكدس ،إذ توّض ح شيفرة فك التجميttع
أواًل ،نخّص ص مساحًة عىل المكدس لمتغيراتنا المحلية ،كما تنمو المكدسات لألسفل ،لذلك يجب أن نطttرح
ُتَع ّد القيمة 16قيمًة كبيرًة بما يكفي لالحتفاظ بمتغيراتنا المحليttة ،ولكن يمكن أاّل تكttون بttالحجم المطلttوب
للحفاظ عىل محاذاة المكدس في الذاكرة ضمن الحدود الttتي يتطلبهttا المص ّtرف ،إذ نحتttاج مثاًل 12بttايت فقttط
وليس 16مttع 3قيم من النttوع intالمكَّtو ن من 4بايتttات ،وننقttل بعttد ذلttك القيم إىل ذاكttرة المكttدس الttتي
أخيًرا،يتوجب علينا أن نسحب القيم من المكدس قبل العttودة إىل الدالttة األصttلية من خالل تحريttك مؤشttر
الحظ أننا استخدمنا رايًة خاصًة في مصّرف ،gccوهذه الراية هي -fomit-frame-pointerالتي تحِّدد أنه
ال ينبغي استخدام مسجل إضافي لالحتفاظ بمؤشر إىل بداية إطار المكدس ،إذ يساعد وجود هذا المؤشر منّق حات
األخطاء لالنتقال لألعىل عبر إطارات المكدس ،ولكنه يجعل المسِّجل متاًحا بصورة أقل للتطبيقات األخرى.
124
علوم الحاسوب من األلف إىل الياء العمليات في نظام تشغيل الحاسوب
الكومة Heapهي مساحة من الذاكرة تديرها العملية لتخصيص الذاكرة السttريع ،وُتسttتخَدم مttع المتغttيرات
التي ال تكون متطلبات ذاكرتها معروفًة في وقت التصريف ،وُيعَرف الجزء السفلي من الكومtة باالسttم brkوهttو
اسttتدعاء النظttام الttذي يعّttدل الكومttة ،كمttا يمكن للعمليttة أن تطلب من النttواة تخصttيص مزيttد من الttذاكرة
يدير استدعاء مكتبة mallocالكومَة ،وهذا يجعttل إدارة الكومttة أمًtرا سttهاًل للمttبرمج من خالل السttماح لttه
بتخصيص وتحرير كومة ذاكرة باستخدام االستدعاء ،freeكما يمكن أن يستخِدم االستدعاء mallocأنظمًة مثل
مخّص ص األصدقاء Buddy Allocatorإلدارة كومة ذاكرة المستخِدم ،ويمكن أن يكون االستدعاء mallocذكًي ا
فيما يتعلق بعملية التخصيص ويمكنه استخدام عمليات ربttط مجهولttة Anonymous mmapsلttذاكرة عمليttة
إضافية ،وهو المكان الذي يربط منطقة من ذاكرة RAMالخاصة بالنظttام مباشttرًة بtداًل من ربtط ملttف مtع ذاكttرة
العملية ،إذ يمكن أن يكون ذلك أكثر كفاءًة ،كما أنه ليس مألوًف ا أن يكون ألّي برنامج حديث سبب الستدعاء brk
تمتلك العملية مناطق أصغر من الذاكرة المخَّص صة لها ويكون لكل منها غرض محدد.
ذكرنا في الشكل السابق كيفية وضع العملية في الذاكرة باستخدام النttواة ،إذ تحتفttظ النttواة لنفسttها ببعض
الذاكرة في الجزء العلوي من العملية ،ويمكن مشاركة هذه الذاكرة فعلًي ا بين جميtع العمليtات باسtتخدام الtذاكرة
الوهمية ،كما يوجد أسفل ذلك مساحة للملفات والمكتبات المربوطة ،mmapedثم يوجد المكدس وتحته الكومة،
125
علوم الحاسوب من األلف إىل الياء العمليات في نظام تشغيل الحاسوب
في حين توجد في الجزء السttفلي صttورة البرنttامج كمttا جttرى تحميلهttا من الملttف القابttل للتنفيttذ عىل القttرص
الصلب ،وسنلقي نظرًة عىل عملية تحميل هذه البيانات في مقاالت الحقة.
لهذه الملفات دائًم ا رقم واصف الملف نفسه ( 0و 1و 2عىل التوالي) ،وبالتالي تحتفظ النواة بواصفات الملفات
تمتلك واصفات الملفات أذونات أيًض ا ،إذ يمكن أن تتمّكن من القراءة من ملف ولكن ال يمكنك الكتابة فيttه
مثاًل ،إذ يحتفظ نظام التشغيل عند فتح الملtف بسtجل أذونtات العمليtات لهtذا الملtف في واصtف الملtف وال
5.2.4المسجالت Registers
يطّبق المعالج عمليات بسيطة عىل القيم الموجtودة في المسِّtجالت ،وُتقَtرأ هtذه القيم وُتكَتب في الtذاكرة،
فلكل عملية منطقة مخَّص صة لها في الذاكرة التي تتعّق بها النواة ،لذا يجب تعّق ب المسجالت ،فإذا حttان الttوقت
لتتخىّل العملية الُم شَّغ لة عن المعالج لتشغيل عمليttة أخttرى ،فيجب حفttظ حالتهttا الحاليttة ،كمttا يجب أن نكttون
قادِرين عىل استعادة هذه الحالة عند منح العملية مزيًدا من الوقت للتشغيل عىل وحttدة المعالجttة المركزيttة ،لttذا
يجب عىل نظام التشغيل تخزين نسخة من مسِّجالت وحدة المعالجة المركزية في الذاكرة.
ينسخ نظام التشغيل قيم المسّجل مرًة أخرى من الذاكرة إىل مسجالت وحدة المعالجة المركزية عنttدما يحين
وقت تشغيل العملية مرًة أخرى وستعود العملية للعمل من حيث توقفت.
5.2.5حالة النواة
يجب أن تتعّق ب النواة عدًدا من العناصر لكل عملية والتي سنوّض حها فيما يلي.
يجب عىل نظام التشغيل تعّق ب حالة العملية ،فإذا كانت العملية قيد التشغيل حالًي ا ،فيجب أن تكون بحالة
تشغيل ،Runningلكن إذا طلبت العملية قراءة ملف من القرص الصلب ،فسنعَلم من تسلسل الذواكر الهttرمي
أّن ذلك يمكن أن يستغرق وقًتا طوياًل ،إذ يجب عىل العملية التخلي عن تنفيذها الحالي للسماح بتشttغيل عمليttة
أخرى ،ولكن ال يجب أن تسمح النواة بتشغيل العملية مرًة أخرى حتى تصبح البيانات من القرص الصttلب متاحًtة
في الذاكرة ،وبالتالي يمكن تحديد العملية عىل أنها في حالttة انتظttار القttرص الصttلب Disk Waitحttتى تصttبح
126
علوم الحاسوب من األلف إىل الياء العمليات في نظام تشغيل الحاسوب
ج .اإلحصائيات
يمكن للنواة االحتفاظ بإحصائيات حول سلوك كل عملية والتي يمكن أن تسttاعدها في اتخttاذ قttرارات حttول
كيفية تصرف العملية مثل معرفة ما إذا كانت العملية تقرأ من القttرص الصttلب في أغلب األحيttان أم أنهttا تنّف ذ
مباشرًة ُتدَع ى بالعملية األولية - initاختصاًرا للكلمة -Initialالتي ال ُتَع ّد عمليًة خاصًة باستثناء أًن معِّرف العمليttة
ُتَع ّد جميع العمليات األخرى أبناًء Childrenلهذه العملية األولية ،فللعمليttات شttجرة عائلttة مثttل أّي شttجرة
أخرى ،إذ يكون لكل عملية أًب ا Parentويمكن أن يكttون لهttا العديttد من األشttقاء Siblingsالttتي ُتَع ّد عمليttات
ُيستخَدم المصطلح "توّلد "Spawnعند الحديث عن العمليات اآلباء التي تنشئ العمليات األبناء مثل القول
بأن "عملية وّلدت ابًنا" ،كما يمكن أن تنشئ العمليات األبناء مزيًدا من األبناء وهكذا ،وإليك مثال عن تنفيذ األمtر
init-+-apmd
|-atd
|-cron
...
|-dhclient
]|-firefox-bin-+-firefox-bin---2*[firefox-bin
| ]|-java_vm---java_vm---13*[java_vm
| `-swf_play
يمكن إنشاء عمليات جديدة باستخدام واجهتين متعلقتين ببعضهما هما forkو .exec
127
علوم الحاسوب من األلف إىل الياء العمليات في نظام تشغيل الحاسوب
5.4.1استدعاءات Fork
إذا وصلَت إىل مفترق طرق ،فسيكون لديك خياران لتختار من بينهما وسttيؤثر هttذا القttرار عىل مسttتقبلك،
كما تصل البرامج الحاسوبية إىل مفترق طرق عنttدما تضttغط عىل اسttتدعاء النظttام )( ،forkإذ سُينشttئ نظttام
التشغيل عمليًة جديدًة مماثلًة للعملية األب ،إذ سُتنَس خ جميع الحtاالت الtتي تحّtدثنا عنهtا سtابًق ا بمtا في ذلtك
الملفات المفتوحة وحالة المسّجل وجميع عمليات تخصيص الذاكرة التي تتضمن شيفرة البرنامج.
ُتَع ّد القيمة الُم عادة من استدعاء النظام الطريقَة الوحيدة التي يمكن للعملية من خاللها تحديttد مttا إذا كttانت
العملية موجودًة مسبًق ا أم عملية جديدة ،إذ سttتكون القيمttة الُم عttادة إىل العمليttة األب هي معّtرف عمليttة االبن
Process IDأو PIDاختصاًرا ،في حين سيحصل االبن عىل القيمة الُم عادة ،0وعندها نقول أن العملية متفرعttة
5.4.2استدعاءات Exec
يوّف ر التفريع Forkingطريقًة للعملية الحالية بأن تبدأ عملية جديدة ،فإذا لم تكن العمليttة الجديttدة جttزًءا من
برنامج العملية األب كما هو الحال في الصَدفة ،Shellإذ يجب أن يشِّغ ل المستخِدم أمًرا في عملية جديttدة ليس
لها عالقة بالصَtدفة ،فيجب تشtغيل اسtتدعاء النظtام execالtذي سtيبّدل بمحتويtات العمليtة الُم شَّtغ لة حالًي ا
العملية التي تتبعها الصَدفة عند إطالق برنامج جديد هي forkأواًل ،مما يؤدي إىل إنشاء عمليttة جديttدة ،ثم
تنفيذ االستدعاء ،execأي تحميل البرنامج الثنائي الذي ُيفتَرض تشغيله في الذاكرة وتنفيذه.
ا .النسخ
ُينَّف ذ االسttتدعاء forkباسttتخدام اسttتدعاء النظttام cloneفي النttواة ،إذ تttوّف ر واجهttات cloneبفعاليttة
مستًو ى من التجريد لكيفية إنشاء نttواة لينكس للعمليttات ،كمttا يttتيح االسttتدعاء cloneتحديttد أجttزاء العمليttة
الجديدة المنسوخة في العملية الجديدة واألجزاء المشttتركة بين العمليttتين صttراحًة ،وقttد يبttدو هttذا غريًب ا بعض
الشيء في البداية ،لكنه يسمح لنا بسهولة بتطبيق الخيوط Threadsباستخدام واجهة واحدة بسيطة جًدا.
الخيوط Threads
ينسخ االستدعاء forkجميع السمات التي ذكرناها سابًق ا .تخّي ل نسخ كل شيء للعمليttة الجديttدة باسttتثناء
الذاكرة ،فهذا يعني اشتراك األب واالبن في الذاكرة نفسها التي تتضمن شيفرة البرنامج والبيانات.
128
علوم الحاسوب من األلف إىل الياء العمليات في نظام تشغيل الحاسوب
ُيسَّم ى االبن الهجين السابق بالخيط ،كما تحتوي الخيtوط عىل عtدد من المزايtا بالموازنtة مtع المكtان الtذي
ال يمكن للعمليات المنفصلة أن ترى ذاكرة بعضها بعًض ا ،وإنما يمكنها التواصttل مttع بعضttها بعًض ا عttبر .1
استدعاءات النظام األخرى فقط ،لكن مع ذلك تشترك الخيوط في الذاكرة نفسها ،لذا سيكون لديك ميزة
العمليات المتعددة مع االضطرار إىل استخدام استدعاءات النظام للتواصل فيمttا بينهttا ،وتكمن مشttكلة
ذلك في إمكانية تداخل الخيوط بسهولة مع بعضها بعًض ا ،إذ يمكن أن يزيد أحد الخيوط متغيًرا ،في حين
يمكن أن ينقصه خيط آخر بدون إعالم الخيط األول ،وتسمى هذه األنواع من المشاكل بمشttاكل الttتزامن
وهي كثيرة ومتنوعة ،ولكن يمكن حل هذه المشكلة باستخدام مكتبtات مجtال المسtتخِدم الtتي تسtاعد
المبرمجين عىل العمل مع الخيوط بصورة صحيحة ،وتسمى أكثر الخيوط شttيوًع ا بخيttوط POSIXأو كمttا
ُيَع ّد التبديل بين العمليات مكلًف ا جًدا ،ومن أكثر األمttور تكلفًtة هttو تعّق ب الttذاكرة الttتي تسttتخِدمها كttل .2
عملية ،ويمكن تجّنب ذلك من خالل مشاركة الذاكرة التي تزيد األداء بصورة ملحوظة.
هناك العديد من الطرق المختلفة لتطبيق الخيوط ،إذ يمكن أن يطِّبق مجال المستخِدم الخيوط ضمن عملية
دون أن تكون لدى النواة أّي فكرة عن ذلك ،إذ تظهر جميع الخيوط للنواة كأنها تعمل في عملية واحدة ،وُيَع ّد ذلttك
دون المستوى األمثل ألّن النواة تحجب معلومات عّم ا يجري تشغيله في النظام؛ أما مهمة النواة فهي التأكttد من
129
علوم الحاسوب من األلف إىل الياء العمليات في نظام تشغيل الحاسوب
استخدام موارد النظام بأفضل طريقة ممكنة ،وإذا كان ما تعتقtده النtواة هtو وجtود عمليtة واحtدة تشّtغ ل خيtوط
الطريقة األخرى هي أن تكون للنواة معرفة كاملttة بالخيttط ،إذ يمكن إنشttاء ذلttك في نظttام لينكس من خالل
جعل جميع العمليات قادرة عىل مشاركة الموارد عttبر اسttتدعاء النظttام ،cloneوال يtزال كttل خيttط يحتtوي عىل
موارد مرتبطة بالنواة ،لذلك يمكن أن تأخذها النواة في حساباتها عند إجراء عمليات تخصيص الموارد.
تحتوي أنظمة التشغيل األخرى عىل طريقة هجينة من الطريقتين السابقتين ،إذ يمكن تحديد بعض الخيttوط
للتشغيل في مجال المستخِدم فقtط -أي مخفّي ة عن النtواة -ويمكن أن يكtون البعض اآلخtر من الخيtوط عمليًtة
خفيفة الوزن ،وُيَع ّد ذلك مؤشًرا مشابًه ا للنواة عىل أن العمليات هي جزء من مجموعة خيوط.
ُيَع ّد نسخ ذاكرة عملية كاملة إىل عملية أخرى عند استخدام االستدعاء forkعمليًة مكلفًة كمtا ذكرنttا سttابًق ا،
ويمكن تحسين ذلك باستخدام ما ُيسَّم ى بالنسخ عند الكتابة ،Copy On Writeإذ يمكن مشاركة الذاكرة فعلًي ا
بداًل من نسخها بين العمليتين عند استدعاء ،forkفإذا كانت العمليات سttتقرأ الttذاكرة فقttط ،فلن يكttون نسttخ
البيانات ضرورًيا ،لكن يجب أن تكون النسخة خاصًة وغير مشتركة عندما تكتب عملية ما في ذاكرتها.
يعمل النسخ عند الكتابة -كما يوحي اسمه -عىل تحسين ذلك من خالل النسخ من الذاكرة فقط عندما ُتكَتب
النسخة فيها ،وللنسخ عند الكتابة فائدة كبيرة لالستدعاء execالذي سttيكتب البرنttامج الجديttد في الttذاكرة ،لttذا
سيضّي ع نسخ الذاكرة الكثير من الوقت ،وبالتالي ستوّف ر عملية النسخ عند الكتابة علينا النسخ فعلًي ا.
بدء التشغيل وتفّر ع وتنّف ذ هذه العملية بعد ذلك سtكربتات بtدء تشtغيل األنظمtة الtتي تفّtر ع وتنّف ذ مزيًtدا من
البرامج ،وينتهي بها األمر في النهاية إىل تفريع عملية تسجيل الدخول.
الوظيفة األخرى للعملية initهي الحصاد ،Reapingإذ سترغب العمليttة األب في التحقttق من الشttيفرة
الُم عادة للتأكttد من إنهttاء العمليttة االبن بصttورة صttحيحة أم ال عنttدما تسttتدعي العمليttة اسttتدعاء اإلنهttاء exit
باستخدام الشيفرة الُم عادة ،لكن ُتَع ّد شيفرة اإلنهاء جttزًءا من العمليttة الttتي اسttتدعت ،exitلttذا ُيقttال أّن هttذه
العملية ميتة Deadمثل أنها ال تعمل ،ولكنها ال تtزال بحاجtة إىل البقttاء حtتى جمtع الشtيفرة الُم عttادة ،وتسtمى
تبقى العملية في حالة زومtبي حtتى تجمtع العمليtة األب الشtيفرة الُم عtادة من االسtتدعاء ،waitولكن إذا
انتهت العملية األب قبل جمع الشيفرة الُم عادة ،فستبقى عملية الزومبي موجودًة وتنتظر بال هدف إلعطاء حالتهttا
130
علوم الحاسوب من األلف إىل الياء العمليات في نظام تشغيل الحاسوب
لعمليٍة ما ،ثم سُتنَس ب العملية االبن التي تكون في حالة زومبي إىل العملية األولية الttتي تمتلttك معالًج ا خاًص ا
يحصد القيمة الُم عادة ،وستكون العملية حرًة في النهاية ويمكن إزالة واصفها من جدول عمليات النواة.
)int main(void
{
;pid_t pid
;))(printf("parent : %d\n", getpid
;)(pid = fork
أنشأنا في المثال السابق عملية زومبي ،إذ ستكون العملية األب في حالة سكون Sleepإىل األبttد ،في حين
ستنتهي العملية االبن بعد بضع ثوان ،ويمكنك رؤية نتائج تشغيل البرنامج بعد الشيفرة البرمجية.
131
علوم الحاسوب من األلف إىل الياء العمليات في نظام تشغيل الحاسوب
تكون العملية األب ( )16168في الحالة Sلإلشارة إىل أنها في حالة سكون وتكون العملية االبن في الحالة Z
لإلشارة إىل أنها في حالة زومبي ،في حين يخبرنtا خtرج األمtر psأن العمليtة أصtبحت زومtبي أو defunctفي
وصف العملية.
األقواس المربعة حول الحرف " "zفي الكلمة " "zombieهي خدعة صغيرة لتزيل عمليات األمر grepنفسها من
خرج األمر ،psإذ يفّس ر األمر grepكل شيء بين األقواس المربعة عىل أنه صنف محرفي ،Character Class
أي يبحث عن تطابق واحد فقط بين المحارف الموجودة بين القوسين والنص ،ولكن بما أن اسم العملية سيكون
5.5الجدولة Scheduling
يحتوي النظام الُم شَّغ ل عىل مئات أو حتى ُألوف العمليttات ،وُيطَل ق عىل جttزء النttواة Kernelالttذي يتعّق ب
جميع هذه العمليات اسم المجدِو ل Schedulerألنه يجدول أّي عملية يجب تشغيلها الحًق ا.
ُتَع ّد خوارزميات الجدولة كثيرًة ومتنوعًة ،إذ يكون لمعظم المستخِدمين أهttداف مختلفttة تتعلttق بمtا يريtدون
تنفيذه من حواسيبهم ،وهذا يؤّثر عىل قرارات الجدولة ،فأنت تريد مثاًل التأكد من منح التطبيقttات الرسttومية في
حاسوبك المكتبي متسًع ا من الوقت للتشغيل حتى إذا استغرقت عمليات النظام وقًتا أطول قلياًل ،مما سttيؤدي
إىل زيادة االستجابة التي يشعر بها المستخِدم ،وبالتالي سttيكون ألفعttالهم اسttتجابات فوريttة ،في حين يمكن أن
ترغب في إعطاء األولوية لتطبيق خادم الويب إذا عملَت عىل خادم.
ينشئ الناس دائًم ا خوارزميات جديدًة ،كما يمكنtك إنشtاء خوارزمياتtك الخاصttة بسtهولة إىل حtد مtا ،ولكن
الجدولة التعاونية :Co-operative Schedulingهي المكان الذي تتخىل فيه العمليttة الُم ش َّtغ لة حالًي ا .1
طواعيًة عن التنفيذ للسماح بتشغيل عملية أخرى ،والعيب في هذه االستراتيجية هو أّن العملية يمكنهttا
اتخاذ قرار بعدم التخلي عن التنفيذ بسttبب خطttأ تسَّttبب في شttكل من أشttكال الحلقttة الالنهائيttة مثاًل ،
الجدولة االستباقية :Pre-emptive Schedulingهي المكان الذي ُتقاَط ع فيه العملية إليقافها للسماح .2
بتشغيل عملية أخرى ،إذ تحصل كل عملية عىل شttريحة زمنيttة Time-sliceلتعمttل فيهttا ،كمttا س ُtيعاد
ضبط عّداد الوقت عند كل عملية تبديل سياق Context Switchingوسُتشَّغ ل العملية ثم ُتقاَط ع عنttد
انتهاء الشريحة الزمنية ،فتبديل السياق Context Switchingهو العملية التي تطّبقها النttواة للتبttديل
من عملية إىل أخرى ،في حين يتعامل العتttاد مttع المقاطعttة عىل أنهttا مسttتقلة عن العمليttة الُم شَّtغ لة،
132
علوم الحاسوب من األلف إىل الياء العمليات في نظام تشغيل الحاسوب
وبالتالي سيعود التحكم إىل نظام التشغيل عند حدوث المقاطعة ،كما يمكن أن يق ِّtرر المجttدِو ل العمليttة
التالية التي سُتشَّغ ل ،وهذا هو نوع الجدولة الذي تستخدمه جميع أنظمة التشغيل الحديثة.
تحصل عىل شريحة زمنية أخرى لتعمل ،ولنفترض أنه لttديك نظاًم ا يشّtغ ل جهttاز القلب والرئttتين ،إذ ال تريttد أن
تقّدم أنظمة الوقت الفعلي الصارمة Hard Realtimeضمانات حول جدولة القttرارات مثttل الحttد األقصttى
لمقدار الوقت الذي سُتقاَط ع فيه العملية قبل تشغيلها مرًة أخرى ،إذ ُتستخَدم غالًبا في التطبيقات الحرجttة مثttل
التطبيقات الطبية والعسكرية وتطبيقات الطائرات ،في حين ال تكون الضttمانات في أنظمttة الttوقت الفعلي غttير
يمكن استخدام نظام لينكس عىل أساس نظام وقت فعلي غير صارم ،إذ ُيستخَدم في األنظمة الttتي تتعامttل
مع الصوت والفيديو ،وإذا أردَت تسجيل بث صوتي ،فال بد أنك ال تريد مقاطعتك لفترات طويلة من الوقت ألنك
هذه القيمة ويمكن أن يعطي األولوية لتلك العمليات التي تتمتع بأعىل قيمة لطيفة.
5.5.4مجدول لينكس
خضع مجدول لينكس وال يزال يخضع للعديد من التغييرات ،إذ يحttاول المطttورون الجttدد تحسttين سttلوكه،
وُيعَرف المجدول الحالي باسم المجدول ) O(1الذي يشير إىل الخاصية التي تمثل أّن المجttدول سttيختار العمليttة
التالية لتشغيلها في فترة زمنية ثابتة بغض النظر عن عدد العمليات التي يجب عليه االختيار من بينها.
ُتَع ّد صيغة Big-Oطريقًة لوصف الوقت الذي تستغرقه الخوارزمية للتشغيل بالنظر إىل الدخل المتزايد ،فإذا
استغرقت الخوارزمية ضعف الوقت للتشغيل مع ضعف الدخل ،فهذا يttؤدي إىل التزايttد خطًي ا ،وإذا اسttتغرقت
خوارزمية أخرى أربعة أضعاف الوقت للتشغيل مع ضعف الدخل ،فهtذا يtؤدي إىل تزايtد أسttي؛ أمtا إذا اسttتغرق
األمر الوقت نفسه مهما كان مقدار الدخل ،فسُتشَّغ ل الخوارزمية في وقت ثابت ،ويمكنك رؤية أنttه كلمttا كttانت
استخدمت مجدوالت لينكس السttابقة مفهttوم الجttودة Goodnessلتحديttد العمليttة التاليttة لتشttغيلها ،إذ
ُيحتَف ظ بجميع المهام الُم حتَم لة في رتل تشغيل ،Run Queueوهو قائمة مترابطttة من العمليttات الttتي تع ِtرف
النواة أنها في حالة قابلية للتشغيل ،أي ال تنتظر نشاًط ا من القرص الصلب أو ليست في حالة سكون.
133
علوم الحاسوب من األلف إىل الياء العمليات في نظام تشغيل الحاسوب
تبرز مشكلة أنه يجب حساب مدى جودة كل عملية قابلة للتشغيل بحيث تفttوز العمليttة الtتي تتمتtع بtأعىل
جودة لتكون العملية التالية التي يجب تشغيلها ،إذ سيستغرق األمر وقًتا أطول بكثttير لمزيttد من المهttام لتحديttد
يستخِدم المجدول ) O(1بنية رتل التشغيل الموضح في الشكل السابق ،كما يحتوي رتل التشغيل عىل عttدد
من الحزم Bucketsمرتبًة حسب األولوية وخارطة نقطية Bitmapتشttير إىل الحttزم الttتي تحتttوي عىل عمليttات
متاحة ،إذ ُيَع ّد البحث عن العملية التالية لتشغيلها بمثابtة قttراءة الخارطttة النقطيttة للعثttور عىل حزمttة العمليttات
يحتفظ المجدول ببنيَتين هما مصفوفة العمليات النشطة Activeالتي يمكن تشغيلها ومصفوفة العمليات
منتهية الصالحية Expiredالتي استخدمت شريحتها الزمنية بالكامل ،كما يمكن تبديل هاتين البtنيَتين ببسtاطة
من خالل تعديل المؤشرات عندما يكون لجميع العمليات بعض الوقت من وحدة المعالجة المركزية.
لكن الجزء المهم هو كيفية تحديد المكان الذي يجب أن تذهب إليه العملية في رتل التشغيل ،فمن األشttياء
التي يجب أخذها في الحسبان هو المستوى اللطيttف ،Nice Levelوتقttارب المعttالج Processor Affinityأو
الحفاظ عىل العمليات مرتبطة بالمعالج الذي ُتشَّغ ل عليه ألن نقل العمليttة إىل وحtدة معالجttة مركزيtة أخtرى في
نظام SMPيمكن أن يكون عمليًة مكلفًة ،باإلضافة إىل دعم أفضل لتحديد البرامج التفاعلية مثل تطبيقات واجهة
المستخدم الرسومية التي يمكن أن تقضي الكثير من الوقت في حالة سكون في انتظار الttدخل من المسttتخِدم،
5.6الصدفة Shell
ُتَع ّد الصَدفة في نظام يttونيكس الواجهttة المعياريttة لمعالجttة العمليttات عىل نظامttك ،ولكن تحتttوي أنظمttة
لينكس الحديثة عىل واجهة مستخِدم رسومية وتوّف ر صدفًة عبر تطبيق طرفية Terminalأو ما شttابه ذلttك ،كمttا
تتمثل مهمة الصَدفة األساسية في مساعدة المسttتخِدم عىل التعامttل مttع بttدء العمليttات الُم شَّtغ لة في النظttام
134
علوم الحاسوب من األلف إىل الياء العمليات في نظام تشغيل الحاسوب
إذا كتبَت أمًرا في موّجه أوامر الصدفة ،فسيؤدي ذلك إىل تطبيق االستدعاء forkعىل نسخة منه وتطttبيق
االستدعاء execعىل األمر الذي حددته ،ثم تنتِظ ر الصَدفة بعد ذلك افتراضًي ا حtتى ينتهي تشtغيل هttذه العمليttة
قبل العودة إىل موّجه األوامر لبدء العملية بأكملها مرًة أخرى.
كما تسمح لك الصَدفة بتشغيل وظيفttة مttا في الخلفيttة Backgroundمن خالل وضttع & بعttد اسttم األمttر
لإلشارة إىل وجوب تفر ع الصَدفة وتنفيذ األمر دون االنتظار حتى يكتمل األمر قبل أن ُتظِه ر لك موّجه األوامر مttرًة
أخرى ،في حين تعمل العملية الجديدة في الخلفية مع جهوزية الصَدفة في انتظار بدء عملية جديدة إذا رغبت في
ذلك ،لكن يمكنك إخبار الصَدفة بتنفيذ عملية ما في األمام ،Foregroundممtا يعtني أننtا نريtد بالفعtل انتظtار
انتهاء العملية.
5.7اإلشارات Signals
تتطلب العمليات الُم شَّغ لة في النظام طريقًة إلخبارنا باألحداث التي تؤثر عليها ،إذ توجد بنية تحتية في نظام
يونيكس بين النواة Kernelوالعمليات تسّم ى اإلشارات Signalsالتي تسttمح للعمليttة بتلقي إشttعار باألحttداث
تستدعي النواة معاِلًجا Handlerيجب أن تسّجله العملية مع النواة للتعامل مع اإلشارة الُم رَس لة إىل عمليttة
ما ،والمعالج هو دالة مصّم مة في الشيفرة البرمجية التي ُك ِتبت لمعالجة المقاطعة ،كما ُترَس ل اإلشttارة في أغلب
األحيان من النواة نفسها ،ولكن يمكن أن ترِس ل إحدى العمليات إشارًة إىل عملية أخرى ،وهذا يمِّث ل أحtد أشtكال
ُيستدَع ى معالج اإلشارة بصورة غير متزامنة ،إذ ُيقاَط ع البرنttامج المشَّtغ ل حالًي ا عّم ا يفعلttه لمعالجttة حttدث
اإلشارة ،كما ُتَع ّد المقاطعة أحد أنواع اإلشارات التي ُتحَّد د في ترويسttات النظttام باالسttم ،SIGINTإذ ُتس َّtلم إىل
تستخِدم العملية استدعاء نظام readلقراءة الدخل من لوحة المفاتيح ،إذ ستراقب النواة مجرى الدخل بحًث ا
عن محارف خاصة ،لكن ستنتقل إىل وضع معالجة اإلشارة في حالة ظهور االختصttار ،ctrl-cوسttتبحث النttواة
لمعرفة ما إذا سّجلت العملية معالًجا لهذه المقاطعة ،فإذا كان األمر كذلك ،فسُيمَّرر التنفيذ إىل تلك الدالttة الttتي
ستعالج المقاطعة ،وإذا لم تسّجل العملية معالًج ا لهttذه اإلشttارة ،فسttتتخذ النttواة بعض اإلجttراءات االفتراضttية،
يمكن أن تختttار العمليttة تجاهttل بعض اإلشttارات ولكن ال تسttمح بتجاهttل اإلشttارات األخttرى ،فاإلشttارة
SIGKILLمثاًل هي اإلشارة المرَس لة عنttدما يجب إنهttاء العمليttة ،حيث سttترى النttواة أن العمليttة أرسttلت هttذه
اإلشارة وتنهي تشغيل العملية دون طرح أّي أسئلة ،كما ال يمكن للعملية الطلب من النواة تجاهttل هttذه اإلشttارة،
إذ تكون النواة حريصًة للغاية بشأن العملية المسموح لها بإرسttال هttذه اإلشttارة إىل عمليttة أخttرى ،فال يجttوز لttك
إرسالها إال إىل العمليات التي تمتلكها إال إذا كنت المستخِدم الجذر.
135
علوم الحاسوب من األلف إىل الياء العمليات في نظام تشغيل الحاسوب
ال بد أنك رأيت األمر kill -9الذي يأتي من تطبيق اإلشارة ،SIGKILLإذ ُتعَّرف اإلشارة SIGKILLعىل
أنها ،0x9لذا ستتوقف العملية المحددة مباشرًة عند تحديدها عىل أساس وسttيط لبرنttامج ،killونظ ًtرا ألنttه ال
يمكن للعملية اختيار تجاهل هذه اإلشارة أو معالجتهttا ،فسُtينَظ ر إىل هttذه اإلشttارة عىل أنهttا المالذ األخttير ،إذ لن
ُيفَّض ل إرسال اإلشارة - SIGTERMلإلنهاء -Terminateإىل العملية أواًل ،فإذا تعطلت أو لم تنتهي ،فيمكنttك
اللجوء إىل اإلشارة ،SIGKILLكما تثّبت معظم البرامج معالًجا لإلشارة ،SIGHUPأي تعليق Hangupالطرفيات
وأجهزة الموِد م التسلسلية ،إذ سيعيد هذا المعالج تحميل البرنامج اللتقاط التغييرات في ملف اإلعداد أو ما شابه
ذلك.
إذا سبق لك وبرمجَت عىل نظام يونيكس ،فستكون عىل دراية بأخطاء التقطيع segmentation faults
عندما تحاول القراءة أو الكتابة في ذاكرة غير مخَّص صة لك ،فttإذا الحظت النttواة أنttك تحttاول الوصttول إىل ذاكttرة
ليست مخَّص صة لك ،فسترسل لك إشارة خطttأ تقطيttع ،segmentation fault signalولن تمتلttك العمليttة
معالًجا مثَّبًتا لهذه اإلشارة ،وبالتالي فإّن اإلجراء االفتراضي هttو إنهttاء البرنttامج وتعطيttل برنامجttك ،كمttا يمكن أن
لكن يمكنك التساؤل عّم ا يحدث بعد تلقي اإلشارة ،إذ سُيعاد التحكم إىل العملية الttتي تسttتأنف عملهttا من
حيث توقفت بمجرد انتهاء معالج اإلشارة من عمله ،ويقّدم البرنامج البسيط التالي تشغيل بعض اإلشارات:
)int main(void
{
;)signal(SIGINT, sigint_handler
;))(printf("pid is %d\n", getpid
)while (1
;)sleep(1
}
136
علوم الحاسوب من األلف إىل الياء العمليات في نظام تشغيل الحاسوب
$
يعّرف البرنامج البسيط السابق معالًجا لإلشارة SIGINTالتي ُترَس ل عندما يضغط المستخِدم عىل االختصttار
،ctrl-cإذ ُتعَّرف جميع إشارات النظام في مكتبة signal.hبما في ذلttك الدالttة signalالttتي تسttمح لنttا
يبقى البرنامج ضمن حلقة ال تفعل شtيًئا حtتى يتوقtف ،وحtاول الضtغط عىل االختصtار ctrl-cعنtد بtدء
البرنامج إلنهائه ،إذ ُيستدَع ى المعالج ونحصل عىل الخرج المتوَّق ع بtداًل من اتخtاذ اإلجtراء االفتراضtي ،ثم نضtغط
بعد ذلك عىل االختصار ctrl-zالذي يرسل اإلشارة SIGSTOPالتي تضع العملية افتراضًي ا في وضع السttكون،
أي أنها لم ُتوَض ع في رتل تشغيل المجدول وبالتالي ُتَع ّد خاملًة في النظام.
نستخدم برنامج killإلرسال اإلشارة نفسها من نافذة طرفية أخرى ،إذ يمكن تطبيق ذلك فعلًي ا باسttتخدام
استدعاء النظام killالذي يأخذ إشارة ومعّرف PIDإلرسالها ،وُيَع ّد اسم هttذه الدالttة خاطًئ ا بعض الشttيء ،إذ ال
تقتل جميُع اإلشارات العمليَة فعلًي ا ،ولكن ُتسttتخَدم الدالttة signalلتسttجيل المعttالج ،Handlerكمttا توَض ع
اإلشttارة في رتttل خttاص بهttذه العمليttة عنttد توقفهttا ،وبالتttالي تأخttذ النttواة مالحظًttة باإلشttارة وتسّttلمها في
الوقت المناسب.
ننّبه العملية بعد ذلك باستخدام األمر fgالذي يرسل اإلشارة SIGCONTإىل العملية ،مما يؤدي إىل تنشttيط
العملية افتراضًي ا ،كما تدرك النواة وضع العملية في رتل التشغيل وتمنحها وقًتا من وحدة المعالجة المركزية مttرًة
أخرى ،إذ نرى في هذه المرحلة تسليم اإلشارة الموجودة في رتل التشغيل.
نحاول أخيًرا الضغط عىل االختصار \ ctrl-الذي يرسل اإلشtارة - SIGQUITأي إلغtاء -إىل العمليtة ،ويtأتي
خرج اإللغاء Quitمن استخدام مزيد من اإلشارات بالرغم من إلغاء العملية ،وإذا كان لدى األب عمليttة ابن ميتttة
137
علوم الحاسوب من األلف إىل الياء العمليات في نظام تشغيل الحاسوب
أو منتهيttة ،فسيحصttل عىل اإلشttارة ،SIGCHLDإذ ُتَع ّد الصَttدفُة أنهttا العمليttة األب في هttذه الحالttة ،أي أنهttا
تذّك ر أّن العملية الزومبي Zombieالتي يجب حصادها باستخدام االسttتدعاء waitللحصttول عىل الشttيفرة
الُم عادة من العملية االبن ،ولكن هناك شيء آخر يمنحه االبن لألب وهttو رقم اإلشttارة الttتي أّدت إىل مttوت االبن،
وهكذا تعرف الصَدفة أّن العملية االبن قد مttاتت أو انتهت بسttبب اإلشttارة SIGABRTوتطبttع معلومttات أخttرى
للمستخِدم عىل أساس خدمة إعالمية ،إذ تحُدث العملية نفسها لطباعttة خطttأ التقطيttع Segmentation Fault
يمكنك رؤية استخدام حوالي خمس إشttارات مختلفttة للتواصttل بين العمليttات والنttواة والحفttاظ عىل سttير
األمور حتى في برنامج بسيط ،وهناك العديد من اإلشارات األخtرى ،لكننtا اسtتخدمنا في هtذا المثtال اإلشtارات
األكttثر شttيوًع ا ،إذ تحتttوي معظمهttا عىل دوال نظttام تعّرفهttا النttواة ،ولكن هنttاك بعض اإلشttارات المحجttوزة
138
.6الذاكرة الوهمية Virtual Memory
يمكن القول بأن الذاكرة الوهمية Virtual Memoryهي طريقة لتوسttيع الttذاكرة RAMمن خالل اسttتخدام
القرص الصلب بوصفه ذاكرة نظام إضافية ولكنهtا أبطtأ ،أي ينتقtل النظtام إىل القtرص الصtلب الtذي ُيسtتخَدم
ُيشار إىل الذاكرة الوهمية عادًة في أنظمة التشغيل الحديثة باسم ذاكttرة سttواب ،Swap Spaceألن األجttزاء
غير الُم ستخَدمة من الذاكرة ُتبَع د إىل القرص الصلب لتحريtر الtذاكرة الرئيسtية ،إذ ال يمكن تنفيtذ الtبرامج إال من
الذاكرة الرئيسيةُ .تَع د القدرة عىل إبعاد الذاكرة إىل القرص الصلب أمًرا مهًم ا ،ولكنهttا ليسttت الغttرض األساسttي
للذاكرة الوهمية ،بل لها تأثيٌر آخر مفيد للغاية سنراه الحًق ا.
الخاص بالمعالج إىل مجال العناوين الُم حتَم لة التي يمكن استخدامها عنttد التحميttل والتخttزين في الttذاكرةُ .يَع د
فضاء العناوين محدوًدا بعرض المسّجالت ،Registersألننا نحتاج لتحميل عنtواٍن إطالَق تعليمtة تحميtل load
مع العنوان الذي سُيحَّم ل منttه العنttوان الُم خَّtزن في المسّtجل ،إذ يمكن مثاًل أن تحتttوي المسttجالت الttتي يبلttغ
عرضها 32بت عىل عناوين في مجال المسجل من 0x00000000إىل .0xFFFFFFFيساوي 232ما مقttداره 4
جيجابايت ،لذلك يمكن للمعالج ذي 32بت تحميُل أو تخزين ما يصل إىل 4جيجابايتات من الذاكرة.
علوم الحاسوب من األلف إىل الياء الذاكرة الوهمية Virtual Memory
64بت ،حيث يكون فضاء العناوين المتاح لهذه المعالجttات كبttيًرا .تحتttوي المعالجttات ذات 64بت عىل بعض
المقايضات مقابل استخدام معالجات ذات عرض بتات أصغر ،حيث يتطلب كل برنامٍج ُم صَّرٍف Compiledفي
وضع 64بت مؤشرات حجمها 8بايتات ،والتي يمكن أن تزيttد من حجم الشttيفرة البرمجيttة والبيانttات ،وبالتttالي
تؤثر عىل أداء كل من الذاكرة المخبئية الخاصة بالتعليمة والبيانات ،ولكن تميttل معالجttات 64بت إىل الحصttول
عىل عدد أكبر من المسجالت ،مما يعني تقليل الحاجtة إىل حفttظ المتغttيرات المؤقتttة في الtذاكرة عنtدما يكtون
تحتوي معالجات 64بت عىل مسجالت بعرض 64بت ،ولكن ال تطّبق األنظمة جميع هذه 64بت للعنونttة،
إذ ال ُيَع د تحميل loadأو تخزين storeكل 16إكسابايت من الttذاكرة الحقيقيttة أمًtرا ممكًن ا .لttذا تحّttدد معظم
المعماريات منطقًة غير قابلة لالستخدام Unimplementedمن فضاء العناوين التي َيُع ّدها المعالج غير صttالحة
لالستخدام .تعِّرف ك ٌّtل من المعمttاريتين x86-64وإيتttانيوم Itaniumالبت الصttالح األكttثر أهميttة في العنttوان،
ويجب بعد ذلك تمديد إشارته إلنشاء عنوان صttالح ،والنتيجttة هي تقسttيم إجمttالي فضttاء العنttاوين بفعاليttة إىل
جزأين هما :جزء علوي وجزء سفلي مع وجود عناوين غير صالحة بينهما ،وهttذا موضttح في الشttكل التttالي ،حيث
ُتسَّم ى العناوين الصالحة عناوين معيارية ،Canonical Addressesبينما ُتسَّم ى العناوين غير الصالحة عنttاوين
140
علوم الحاسوب من األلف إىل الياء الذاكرة الوهمية Virtual Memory
يمكن العثور عىل قيمة البت األكثر أهمية للمعالج من خالل االستعالم عن المعالج نفسه باسttتخدام تعليمttة
الحصول عىل المعلومات .ستكون قيمة البت األكثر أهمية 48بالرغم من أن القيمة الدقيقة تعتمttد عىل التقttديم
،Implementationمما يؤدي إىل توفير 248 = 256تيرابايت TiBمن فضاء العناوين القابلة لالستخدام.
ُيَع د تقليل فضاء العناوين الُم حتَم ل أنه يمكن تحقيق توفيٍر كبير مع جميع أجزاء منطttق العنونttة في المعttالج
والمكونات ذات الصلة ،ألنها تعلم أنها لن تحتاج للتعامل مع عناوين 64بت كاملة .يحّدد التقttديُم البتttات العليttا
عىل أنه يجب تمديد إشارتها ،مما يؤدي إىل منع أنظمة التشغيل القابلة للنقل التي تستخدم هذه البتات لتخttزين
أو تحديد المعلومات اإلضافية وضمان التوافق عند الرغبة في تقديم مزيٍد من فضاء العناوين مستقباًل .
العناوين والذاكرة الحقيقية المتوفرة في النظام ،أي إذا استخدم برنامٌج ما عنواًن ا ،فلن يشttير العنttوان إىل البتttات
الموجودة في الموقع الفعلي الحقيقي في الذاكرة ،لtذا نقtول أن جميtع العنtاوين الtتي يسtتخدمها البرنtامج هي
عناوين وهمية .يتعّق ب نظام التشغيل العناوين الوهمية وكيفية تخصيصها للعنtاوين الحقيقيtة ،فtإذا طّب ق أحtد
البرامج عملية تحميل أو تخزين من عنواٍن ما ،فسيعمل المعالج ونظام التشغيل مع بعضهما البعض لتحويل هذا
6.2الصفحات Pages
ُيقَس م إجمالي فضاء العناوين إىل صفحات .Pagesيمكن أن تكون الصفحات بأحجام مختلفttة ،حيث يمكن
أن يبلغ حجمها حوالي 4كيلوبايت ،KiBولكنها ليست قاعدة صارمة ويمكن أن تكون أكttبر بكثttير ولكنهttا ليسttت
أصغر من ذلكُ .تَع د الصفحة أصغر وحدة ذاكرة يمكن لنظام التشغيل والعتاد التعامل معها.
تحتوي كل صفحة عىل عدد من السمات التي يضtبطها نظtام التشtغيل ،وتشtمل أذونtات القtراءة والكتابtة
والتنفيذ للصفحة الحالية ،حيث يمكن لنظام التشغيل مثاًل تمييز صفحات الشيفرة البرمجية لعمليٍة ما باستخدام
راية قابلة للتنفيذ ويمكن للمعالج اختيار عدم تنفيذ أّي شيفرة برمجية من الصفحات بدون ضبط هذه البتات.
141
علوم الحاسوب من األلف إىل الياء الذاكرة الوهمية Virtual Memory
يمكن أن يفكر المبرمجون في هذه المرحلة في أنه يمكنهم بسهولة تخصيص كميات صغيرة من الttذاكرة -أي
أصغر بكثير من 4كيلوبايتات -باستخدام االستدعاء mallocأو اسttتدعاءات مماثلttة .تttدعم عمليttات تخصttيص
حجم الصفحة كومَة Heapالذاكرة ،حيث يقسمها تقديم االستدعاء mallocويديرها بطريقة فّع الة.
إطttارات ،Framesحيث ُيَع د اإلطttار االسttم التقليttدي لقطعttة كبttيرة من الttذاكرة الحقيقيttة لهttا حجم صttفحة
النظام نفسها.
يحتفظ نظام التشغيل بجدول اإلطارات Frame-tableالذي ُيَع د قائمًة بجميع الصفحات الُم حتَم لttة للttذاكرة
الحقيقية ويحدد ما إذا كانت حرًة أو متاحة للتخصيص أم ال .إذا ُخِّص صت الذاكرة لعمليٍttة مttا ،فسُttتمَّيز عىل أنهttا
ُم ستخَدمة في جدول اإلطارات ،وبذلك يتعّق ب نظام التشtغيل جميtع عمليtات تخصtيص الtذاكرة .يعtرف نظtام
التشغيل الذاكرة المتوفرة من خالل تمرير المعلومات الخاصة بمكان وجود الذاكرة ومقدارها وسttماتها وغttير ذلttك
6.4جداول الصفحات
تتمثttل مهمttة نظttام التشttغيل في تعّق ب نقttاط الصttفحة الوهميttة المقابلttة لإلطttار الحقيقي ،حيث يجttري
االحتفاظ بهذه المعلومات في جدول صفحات .يمكن أن يكون جدول الصفحات في أبسط أشكاله جدواًل يحتttوي
كل صف فيه عىل اإلطار المرتبط به ،وهذا ما يسمى بجدول الصفحات الخطي .Linear Page-table
إن استخدمَت هذا النظام البسيط مع فضاء عناوين بحجم 32بت وصttفحات بحجم 4كيلوبttايت ،فسttيكون
هناك 1048576صفحة يمكن تعّق بها في جدول الصفحات (أي ،)232 ÷ 4096وبالتالي سيكون طول الجدول
1048576مدخلًة لضمان أنه يمكننا دائًم ا ربttط صttفحة وهميttة مttع صttفحة حقيقيttة .يمكن أن تحتttوي جttداول
الصفحات عىل العديد من البنى المختلفة ويمكن تحسينها بدرجة كبيرة ،إذ يمكن أن تستغرق عمليttة البحث عن
صفحة في جدول الصفحات وقًتا طوياًل .سنتطرق إىل جدول الصفحات بمزيد من التفصيل الحًق ا.
يخضع جدول صفحات العملية لتحكم نظام التشغيل الحصري ،فإذا طلبت إحدى العمليات ذاكttرًة ،فسttيجد
نظام التشغيل صttفحة خاليttة من الttذاكرة الحقيقيttة ويسّtجل ترجمttة الصttفحة الوهميttة إىل الصttفحة الحقيقيttة
Virtual-to-physicalفي جدول صفحات العمليات .بينما إن تخلت العملية عن الذاكرة ،فسُيزال سجل ترجمttة
الصفحة الوهمية إىل الصفحة الحقيقية ويصبح اإلطار األساسي حًرا لتخصيصه لعملية أخرى.
142
علوم الحاسوب من األلف إىل الياء الذاكرة الوهمية Virtual Memory
يعرف أن األمر متروك لنظام التشغيل والعتاد ،بحيث يتعاونttان للربttط مttع العنttوان الحقيقي الصttحيح وبالتttالي
توفير الوصول إىل البيانات التي يريدها .لذا نطلttق عىل العنtوان الtذي يسtتخدمه البرنttامج للوصttول إىل الtذاكرة
عنواًنا وهمًي ا Virtual Addressالذي يتكون من جزأين هما :الصفحة Pageواإلزاحة Offsetفي هذه الصفحة.
6.5.1الصفحة
ُيقَس م فضاء العنtاوين الُم حتَم ل إىل صttفحات ذات حجم ثtابت ،حيث يتواجtد كttل عنtوان ضttمن صttفحة،
ويعمل مكون الصفحة الخاص بالعنوان الوهمي بوصفه فهرًس ا إىل جدول الصفحاتُ .تَع د الصttفحة أصttغر وحttدة
لتخصيص الذاكرة في النظام ،لذلك هناك مقايضة بين جعل الصفحات صغيرة جًدا مع وجود عدد كبير جًدا منهttا
ليديرها نظام التشغيل وبين جعل الصفحات أكبر مع وجود احتمال في هدر الذاكرة.
6.5.2اإلزاحة Offset
ُتسَّم ى البتات األخيرة من العنttوان الttوهمي باإلزاحttة Offsetالttتي تعّب ر عن الفttرق في الموقttع بين عنttوان
البايت الذي تريده وبداية الصفحة ،ويجب وجود بتات كافيttة في اإلزاحttة لتتمكن من الوصttول إىل أّي بttايت في
الصفحة ،وتحتاج صفحة بحجم 4كيلوبايتات إىل 12بت لإلزاحة حيث أن .4K = 4 * 1024 = 4096 = 212
تذكر أن أقل قدر من الذاكرة يتعامل معه نظttام التشtغيل أو العتttاد يسtاوي صttفحة ،لtذا يوجtد كttل بت من
4096بايت ضمن صفحة واحدة ويجري التعامل معها عىل أنها كتلة واحدة.
الحقيقية المربوطة مع الصفحة الوهمية .سنتعامل فقط مع رقم الصttفحة عنttد ترجمttة عنttوان وهمي إىل عنttوان
حقيقي ،حيث نأخذ رقم الصفحة من العنوان الُم عَط ى ونبحث عنه في جدول الصttفحات للعثttور عىل مؤشttر إىل
عنوان حقيقي مع إضافة اإلزاحة من العنوان الوهمي إليه ،مما يؤدي إىل إعطاء الموقع الفعلي في نظام الذاكرة.
تخضttع جttداول الصttفحات لسttيطرة نظttام التشttغيل ،فttإن لم يكن العنttوان الttوهمي موجttوًدا في جttدول
الصفحات ،فسيعرف نظام التشغيل أن العملية تحاول الوصول إىل الذاكرة التي ليست مخَّص صًة لها ولن ُيسَم ح
لها بالوصول.
143
علوم الحاسوب من األلف إىل الياء الذاكرة الوهمية Virtual Memory
يوّض ح المثال السابق جدول صفحات خطي بسيط ،حيث سيتطلب فضاء العناوين ذو 32بت جدواًل مؤلًف ا
من 1048576مدخلًة عند استخدام صفحات بحجم 4كيلوبايتات ،وبالتالي ستكون الخطوة األوىل لربttط العنttوان
0x80001234هي إزالة بتttات اإلزاحttة .نعلم في هttذه الحالttة أن لttدينا 12بت ( )212 = 4096من اإلزاحttة مttع
صttفحات بحجم 4كيلوبايتttات .لttذا سttنزيح 12بت من العنttوان الttوهمي إزاحًttة يمttنى ،وبالتttالي يبقى لttدينا
،0x80001وستكون القيمة العشرية الموجودة في السطر رقم 524289من جدول الصفحات الخطي هي اإلطار
يمكن أن تttرى مشttكلة في جttدول الصttفحات الخطي ،حيث يجب حسttاب كttل صttفحة سttواء كttانت قيttد
االستخدام أم ال ،وبالتالي ال ُيَع د جدول الصفحات الخطي الحقيقي عملًي ا تماًم ا مttع فضttاء عنttاوين 64بت .ضttع
في حساباتك فضاء عناوين 64بت المقَّس م إىل صفحات مؤلفة من 64كيلوبايت (كبيرة جًدا) ،حيث ينشئ هttذا
الفضاء 264/216=252صفحة إلدارتهttا .لنفttترض أن كttل صttفحة تتطلب مؤش ًtرا بحجم 8بايتttات لموقttع حقيقي،
فسيتطلب ذلك 252*23=255أو 512جيجابايت GiBمن الذاكرة المتجاورة لجدول الصفحات فقط.
144
علوم الحاسوب من األلف إىل الياء الذاكرة الوهمية Virtual Memory
إعطاء كل عملية جدول صفحات خاص بها ،إذ يمكن أن تستخدم عمليتان العنوان نفسttه ،حيث سttتربط جttداوُل
الصفحات المختلفة العمليَة مع إطار مختلف من الذاكرة الحقيقية ،إذ توّف ر أنظمة التشغيل الحديثttة لكttل عمليٍttة
تصبح الذاكرة الحقيقية مجزأة Fragmentedبمرور الوقت ،مما يعني أن هناك ثقوب في الفضاء الحttر من
الذاكرة الحقيقية .سيكون االضطرار إىل حل مشكلة هذه الثقوب أمًرا مزعًجا في أحسttن األحttوال ولكنttه سيصttبح
أمًرا خطيًرا للمبرمجين ،فإذا نّفذَت االسttتدعاء mallocلتخصttيص 8كيلوبايتttات من الttذاكرة مثاًل ،فسttيتطلب
ذلك دعم إطارين بحجم 4كيلوبايتات ،وبالتالي لن تكون هذه اإلطارات متجاورة ،أي بجوار بعضttها البعض فعلًي ا.
ال ُيَع د استخدام العناوين الوهمية أمًرا مهًم ا بقدر مtا يتعلtق األمtر بtاحتواء العمليtة عىل 8كيلوبtايت من الtذاكرة
المتجاورة ،حتى لو كانت هذه الصفحات مدعومة بإطارات متباعدة جًدا .يمكن للمبرمج ترك مهمttة حttل مشttكلة
التجزئة لنظام التشغيل من خالل إسناد فضاء عناوين وهمية لكل عملية.
6.6.2الحماية
ُيدَع ى الوضع الوهمي للمعالج 386بالوضع المحمي ،Protected Modeوينشأ هذا االسم من الحماية التي
يمكن أن توفرها الذاكرة الوهمية للعمليات التي تعمtل عليهtا .تتمتttع كttل عمليttة في نظttام بtدون ذاكttرة وهميttة
بوصوٍل كامل إىل ذاكرة النظام بأكملها ،وهذا يعttني أنttه ال يوجttد شttيء يمنttع عمليًtة مttا من الكتابttة فttوق ذاكttرة
عمليات أخرى ،مما يؤدي إىل تعّط لها أو إعادة قيم غير صحيحة في أسوأ األحوال خاصة إذا كان هذا البرنامج يttدير
حسابك المصرفي مثاًل .لذا يجب توفير هttذا المسttتوى من الحمايttة ألن نظttام التشttغيل ُيَع د طبقttة تجريttد بين
العملية والوصول إىل الذاكرة ،فإذا أعطت العملية عنواًنا وهمًي ا ال يغطيه جدول الصفحات الخاص بهttا ،فسttيعلم
نظام التشغيل أن هذه العملية تطّبق شيًئا خاطًئا ويمكنه إبالغ العملية أنها تعّدت حدودها.
تمتلك كل صفحة سtمات إضtافية ،لtذا يمكن ضtبط الصtفحة للقtراءة فقtط أو للكتابtة فقtط أو غيرهtا من
الخاصيات األخرى .إذا حاولت العملية الوصول إىل الصفحة ،فيمكن لنظام التشغيل التحقق ممttا إذا كttان لttديها
أذونات كافية وإيقافها إن لم تكن كذلك مثل محاولة الكتابة في صفحة للقراءة فقط.
ُتَع د األنظمة التي تستخدم الذاكرة الوهميttة أكttثر اسttتقراًرا ألنttه يمكن للعمليttة في نظttام تشttغيل مثttالي أن
تعّط ل نفسها فقط دون تعطيل النظام بأكمله ،حيث ُتبرَم ج أنظمttة تشttغيل مttع تجاهttل األخطttاء الttتي يمكن أن
145
علوم الحاسوب من األلف إىل الياء الذاكرة الوهمية Virtual Memory
6.6.3التبديل Swap
يمكننا اآلن أن نرى كيفية تقديم تبديل ذاكرة ،حيث يمكن تغيير مؤشر الصفحة ليؤّش ر إىل موقع عىل القرص
الصلب بداًل من التأشير إىل منطقة من ذاكرة النظام .يحتاج نظttام التشttغيل عنttد الرجttوع إىل هttذه الصttفحة إىل
نقلها من القرص الصلب إىل ذاكرة النظام ،إذ ال يمكن تنفيذ شيفرة البرنامج إال من ذاكرة النظام .إذا كttانت ذاكttرة
النظام ممتلئة ،فيجب إخttراج صttفحة أخttرى من ذاكttرة النظttام وتبttديلها بttالقرص الصttلب قبttل وضttع الصttفحة
المطلوبة في الذاكرة .إذا كانت هنtاك عمليtة أخtرى تريtد الصtفحة الtتي ُأخِtرجت للتtو ،فسtتتكرر هtذه العمليtة
مرة أخرى.
يمكن أن يؤدي ذلك إىل مشكلٍة كبيرة في تبديل الذاكرة ،حيث ُيَع د التحميل من القرص الصلب بطيًئ ا جًttدا
بالموازنة مع العمليات التي ُتنَجز في الذاكرة ،وسيكون معظم الناس متآلفين مع فكرة الجلttوس أمttام الحاسttوب
أثناء توقف القرص الصلب مراًرا وتكراًرا مع بقاء النظام غير مستجيب.
اmmap .
ُتَع د عملية ربط الذاكرة Memory Mapأو ( mmapمن اسم اسttتدعاء النظttام) عمليًtة مختلفttة ولكنهttا ذات
صلة ،حيث إن لم يؤّش ر جدول الصفحات إىل الذاكرة الحقيقية أو لم يؤّش ر تبديل جدول الصفحات إىل ملف عىل
تحتاج عادًة إىل فتح openملف عىل القرص الصلب للحصول عىل واصف الملف ثم قراءته readوكتابتttه
writeفي صttيغة تسلسttلية .إذا كttان الملttف مربوًط ا بالttذاكرة ،فيمكن الوصttول إليttه مثttل الttذاكرة RAM
الخاصة بالنظام.
6.6.4مشاركة الذاكرة
تحصل كل عملية عىل جدول صفحات خاص بها ،لذلك ُيرَبط أّي عنوان تستخدمه مع إطار فريttد في الttذاكرة
الحقيقية ،ولكن إن أّش ر نظام التشغيل إىل مدخلَتين من جدول الصفحات إىل اإلطار نفسه ،فهذا يعني التشttارك
في هذا اإلطار ،وستكون أّي تغييرات تجريها إحدى العمليتين مرئية للعملية األخرى.
يمكنك أن ترى اآلن كيفية تقديم الخيوط .Threadsيمكن للدالة )( cloneالخاصة بنظام لينكس مشttاركة
قدر كبير أو صغير من العملية الجديدة مtع العمليtة القديمtة وفtق مtا هtو مطلtوب .إن اسtتدعت عمليٌtة الدالtة
)( cloneإلنشاء عملية جديدة ،ولكنها تطلب أن تشترك العمليتان في جدول الصفحات نفسه ،فسيكون لديك
كما يمكنك معرفة كيفية إجراء النسخ عند الكتابة ،حيث إذا ضبطَت أذونات إحدى الصفحات لتكون للقttراءة
فقط ،فسيجري إعالم نظام التشغيل عندما تحاول إحدى العملياُت الكتابَة في الصفحة .إذا َع ِلم نظام التشغيل أن
هذه الصفحة هي صفحة نسخ عند الكتابة ،فيجب إنشاء نسخة جديدة من الصttفحة في ذاكttرة النظttام ويجب أن
146
علوم الحاسوب من األلف إىل الياء الذاكرة الوهمية Virtual Memory
توّش ر الصفحة في جدول الصفحات إىل هذه الصفحة الجديدة .يمكن بعد ذلك تحديث سمات الصفحة للحصول
واالضطرار إىل تبديل الذاكرة .يخبرنا تسلسttل الttذواكر الهttرمي أن الوصttول إىل القttرص الصttلب أبطttأ بكثttير من
الوصول إىل الذاكرة ،لذلك ُيفَّض ل نقtل أكtبر قtدر ممكن من البيانtات من القtرص الصtلب إىل ذاكtرة النظtام إن
أمكن ذلك.
ينسخ نظام لينكس والعديد من األنظمة األخرى البيانtات من الملفtات الموجtودة عىل القtرص الصtلب إىل
الذاكرة عند استخدامهاُ .يحتَم ل أن يرغب البرنامج في الوصول إىل بقية الملف مع اسttتمراره في المعالجttة حttتى
إن طلب في البداية جزًءا صغيًرا فقط من الملف ،ويتحقق نظام التشغيل عند قراءة ملف أو الكتابة فيه أواًل ممttا
إذا كان الملف موجوًدا في الذاكرة المخبئttة .Cacheيجب أن تكttون هttذه الصttفحات هي أوىل الصttفحات الttتي
المصطلح الذي يمكن أن تسمعه عند مناقشة النواة Kernelهو ذاكرة الصفحة المخبئية Page Cacheالتي
تشير إىل قائمة الصفحات التي تحتفظ بها النواة والتي تشير إىل الملفات الموجودة عىل القرص الصttلب ،حيث
تندرج صفحة التبديل والصفحات المربوطة بالذاكرة وصفحات ذاكرة القرص الصلب المخبئية ضمن هttذه الفئttة.
تحتفttظ النttواة بهttذه القائمttة ألنهttا تحتttاج إىل أن تكttون قttادرة عىل البحث عنهttا بسttرعة اسttتجابًة لطلبttات
القراءة والكتابة.
التشغيل والعتاد.
وهذا يعني أن العناوين الموجودة في منفذ النواة لفضاء العناوين ترتبط مع الذاكرة الحقيقية نفسها لكttل عمليttة،
بينما يكون فضاء عناوين المستخدم خاًص ا بالعملية ،ويوجttد في نظttام لينكس فضttاء النttواة المشttترك في أعىل
فضاء العناوين المتاح .يحدث هttذا االنقسttام عىل المعttالج x86األكttثر شttيوًع ا المكttون من 32بت عنttد حجم 3
جيجابايتات ،وبما أن 32بت يمكنها ربط 4جيجابايتات كحد أقصى ،مما يؤدي إىل ترك المنطقtة العليtا بمقtدار
147
علوم الحاسوب من األلف إىل الياء الذاكرة الوهمية Virtual Memory
1جيجابايت لتكون منطقة النواة المشتركة .لكن تريد العديد من األجهزة دعم أكثر من 4جيجابايتات لكل عملية،
حيث يسمح دعم الذاكرة العالي للمعالجات بالوصول إىل 4جيجابايتات كاملة باستخدام توّس عات خاصة.
استخدام النظام الهرمي .تستخدم جداول الصفحات تسلساًل هرمًي ا بعمق ثالثة مستويات ،لذلك ُيشttار إىل نظttام
لينكس باسم جدول الصفحات المكَّو ن من ثالثة مستويات .أثبت جدول الصفحات المكون من ثالثtة مسtتويات
أنttه اختيttار قttوي بttالرغم من أنttه ال يخلttو من بعض المسttاوئ .تختلttف تفاصttيل تقttديم الttذاكرة الوهميttة بين
المعالجttات ،ممttا يعttني أن جttدول الصttفحات العttام الttذي يختttاره نظttام لينكس يجب أن يكttون قttاباًل للنقttل
ال ُيَع د مفهوم مستويات جدول الصفحات الثالثة أمًرا صعًبا ،ألننttا نعلم أن العنttوان الttوهمي يتكttون من رقم
صفحة وإزاحة في صفحة الذاكرة الحقيقية ،إذ ُيقَس م العنوان الوهمي إىل مستويات ُم رَّق مة في جدول الصttفحات
المكون من ثالثة مستوياتُ .يَع د كل مستًو ى جدوَل صفحات بحد ذاته ،أي أنه يرتبط مع رقم الصtفحة الحقيقيtة.
ترتبط مدخلة المستوى 1مباشرًة مع اإلطار الحقيقي في جدول صفحات مؤلٍف من مستًو ى واحد ،بينمttا يعطي
كل مستًو ى من المستويات العليا عنوان إطار الذاكرة الحقيقية الذي يحتفظ بجدول صفحات المسttتويات الttدنيا
148
علوم الحاسوب من األلف إىل الياء الذاكرة الوهمية Virtual Memory
يتضمن المثال السابق االنتقال إىل جttدول الصttفحات ذي المسttتوى األعىل ،والعثttور عىل اإلطttار الحقيقي
الذي يحتوي عىل عنوان المستوى التالي ،وقراءة مستويات ذلك الجدول وإيجاد اإلطار الحقيقي الذي يوجttد فيttه
يبدو أن هذا النموذج معقًدا في البداية ،ولكن السبب الرئيسي لتنفيttذ هttذا النمttوذج هttو متطلبttات الحجم.
تخيل مثاًل عملية ما لها صفحة واحدة مرتبطة بالقرب من نهاية فضttاء العنttاوين الوهميttة ،حيث قلنttا سttابًق ا أنttه
يمكن العثور عىل مدخلة جدول الصفحات بوصفها إزاحًة من مسجل جدول الصفحات األساسي ،لttذلك يجب أن
يكون جدول الصفحات مصفوفًة متجاورًة في الذاكرة ،وبالتالي تتطلب الصفحة القريبة من نهاية فضاء العنttاوين
المصفوفَة بأكملها والتي يمكن أن تشَغ ل مساحًة كبيرة ،أي العديد والعديد من صفحات الذاكرة الحقيقية.
يكون المستوى األول في نظام مؤلٍف من ثالثة مستويات هو إطttار ذاكttرة حقيقي واحttد فقttط ،ويرتبttط مttع
المستوى الثاني الذي هو إطttار ذاكttرة واحttد ،والttذي بttدوره يرتبttط مttع المسttتوى الثttالث ،وبالتttالي يقّل ل نظttام
المسttتويات الثالثttة من عttدد الصttفحات المطلوبttة إىل جttزء صttغير فقttط من الصttفحات المطلوبttة لنظttام
المستوى الواحد.
هناك عيوب واضحة في هذا النظام ،إذ يتطلب البحث عن عنوان واحد مزيًدا من المراجع ،ويمكن أن يكttون
ذلك مكلًف ا .يتفهم لينكس أن هذا النظام يمكن أاّل يكون مناسًtبا للعديtد من أنtواع المعالجtات المختلفtة ،لtذلك
يمكن أن تقلل بعض المعماريات من مستويات جدول الصفحات بسهولة مثل المعمارية x86األكثر شيوًع ا التي
149
علوم الحاسوب من األلف إىل الياء الذاكرة الوهمية Virtual Memory
يعمل مع نظام التشغيل لتقديم Implementationالذاكرة الوهمية ،وألقينا نظttرة عىل تفاصttيل كيفيttة حttدوث
ذلك ،حيث تعتمد الذاكرة الوهمية بصورة كبيرة عىل معمارية العتاد ،حيث يكون لكل معمارية خواصها الدقيقttة،
ولكن هناك عدد من العناصر الخاصة بالذاكرة الوهمية في العتاد التي سنستعرضها في الفقرات التالية.
،Virtual Modeإذ يتوقع العتاد في الوضع الحقيقي أن يشttير أّي عنttوان إىل عنttواٍن موجttود في ذاكttرة النظttام
الفعلية ،بينما يعرف العتاد في الوضع الوهمي أنه يجب ترجمة العناوين للعثور عىل العنوان الحقيقي.
ُيشار إىل هذين الوضعين في العديد من المعالجttات مثttل المعttالج إيتttانيوم Itaniumببسttاطة عىل أنهمttا
الوضع الحقيقي والوضع الوهمي .المعالج x86األكثر شيوًع ا لديه الكثير من الخاصيات منذ األيام الماضttية الttتي
تسبق الذاكرة الوهمية ،حيث ُيشار إىل هذين الوضعين عىل أنهما الوضع الفعلي Real Modeوالوضع المحمي
.Protected Modeكان أول معالج يطبق الوضع المحمي هttو المعttالج ،386وال تttزال أحttدث المعالجttات من
عائلة x86بإمكانها العمل في الوضع الفعلي بالرغم من عدم استخدامه .يطّبق المعالج في الوضttع الفعلي شttكاًل
كان التقطيع أمًرا مهًم ا سابًق ا ،ولكن قّللت الذاكرة الوهمية من أهميتttه ،فللتقطيttع عيوبttه مثttل كونttه مربًك ا
للمبرمجين المبتدئين ،لذا ُاختِرعت أنظمة الذاكرة الوهمية لحل هذه المشاكل إىل حد كبير.
يوجد في التقطيع عدد من المسّtجالت الtتي تحتtوي عىل عنtوان يمثtل بدايtة المقطtع ،والطريقtة الوحيtدة
للوصول إىل عنوان في الذاكرة هي تحديده بوصفه إزاحة من أحttد هttذه المسttجالت .يمكن تحديttد حجم المقطttع
والحد األقصى لإلزاحة الذي يمكنك تحديده من خالل عدد البتات المتاحة لإلزاحة من مسجل المقطttع األساسttي.
الحد األقصى لإلزاحة في المعالج x86هttو 16بت أو 64كيلوبttايت فقttط ،ممttا يttؤدي إىل ظهttور جميttع أشttكال
ما استخدام عنوان يبعد أكثر من 64كيلوبايت ،فستتحول األمور من مجرد إزعttاج الفوضى ،حيث إذا أراد شخٌص
بسيط إىل فشل كامل عندما ينمو حجم الذاكرة إىل عدد من الميجابايتات أو الجيجابايتات.
لنفترض أن أقصى إزاحة هي 32بت ،وبالتالي يمكن الوصول إىل فضاء العناوين بالكامل بوصttفه إزاحttة من
مقطع عند العنوان 0x00000000وسيكون لديك تخطيط مسttطح ،ولكن ال تضttاهي هttذه الحالttة جttودة الttذاكرة
الوهمية .السبب الوحيد لكون اإلزاحة بمقدار 16بت هو أن معالجات إنتttل Intelاألصttلية كttانت محttدودة بهttذا
150
علوم الحاسوب من األلف إىل الياء الذاكرة الوهمية Virtual Memory
هناك ثالثة مسجالت مقاطع في الشكل السابق تؤّش ر جميعهttا إىل المقttاطع ،ويظهttر حttد اإلزاحttة األقصttى
الُم قَّيد بعدد البتات المتاحة في المنطقة الُم ظَّللة .إذا أراد البرنامج عنواًنا خارج هذا النطttاق ،فيجب إعttادة ضttبط
مسّج الت المقاطع الذي سرعان ما يصبح مصدر إزعاج كبير فيما بعد .بينما تسمح ذاكرة البرنامج الوهمية بتحديد
العنوان الذي تريده ويطّبق نظام التشغيل والعتاد عملية الترجمة إىل عنوان حقيقي.
المسؤول عن الذاكرة الوهمية ،وهttو ذاكttرٌة مخبئيttة لعمليttات ترجمtة الصttفحة الوهميttة إىل اإلطttار الحقيقي في
المعالج .يعمل نظام التشغيل والعتاد مع بعضهما البعض إلدارة مخزن TLBأثناء تشغيل النظام.
إذا ُط ِلب عنttوان وهمي من العتttاد باسttتخدام تعليمttة تحميttل loadمثاًل للحصttول عىل بعض البيانttات،
فسيبحث المعالج عن ترجمة العنوان الوهمي إىل العنوان الحقيقي في مخزن TLBالخاص به ،فttإن احتttوى عىل
ترجمة صالحة ،فيمكن عندئٍذ دمجه مع جزء اإلزاحة لالنتقال مباشرًة إىل العنوان الحقيقي وإكمttال التحميttل .لكن
إن لم يتمّكن المعttالج من العثttور عىل ترجمٍttة في مخttزن ،TLBفيجب عىل المعttالج إصttدار خطttأ الصttفحة
Page Faultالذي يشبه المقاطعة ،ويجب أن يعالجttه نظttام التشttغيل ،إذ يجب أن يسttتعرض نظttام التشttغيل
جدول الصفحات للعثور عىل الترجمة الصحيحة وإدخالها في مخزن TLBعند حدوث خطأ صفحة.
151
علوم الحاسوب من األلف إىل الياء الذاكرة الوهمية Virtual Memory
إن لم يتمّكن نظام التشغيل من العثور عىل ترجمٍttة في جttدول الصttفحات أو إن تحّق ق نظttام التشttغيل من
أذونات الصفحة المطلوبة ولم ُيسَم ح للعملية بالوصول إليها ،فيجب عىل نظام التشغيل إنهttاء العمليttة .إن رأيَت
مسبًق ا خطأ تقطيع أو ما ُيسَّم ى ،Segfaultفهذا يعني أن نظام التشغيل ينهي العمليtة الtتي تجtاوزت حtدودها.
بينما إن تمّكن نظام التشغيل من العثور عىل الترجمة وكان مخزن TLBممتلًئ ا حالًي ا ،فيجب إزالttة ترجمttة قبttل
إدخال ترجمٍة أخرى .ليست إزالة الترجمة التي ُيحتَم ل استخدامها الحًق ا أم ًtرا منطقًي ا ،إذ سttتتحمل عنttاء العثttور
تسtتخدم مخttازن TLBشttيًئا يشtبه خوارزميttة األقttل اسttتخداًم ا مtؤخًرا - Least Recently Usedأو LRU
اختصاًرا ،حيث ُتخَر ج أقدم ترجمة غير ُم ستخدَم ة إلدخال ترجمtة جديtدة .يمكن بعtد ذلtك محاولtة الوصtول مtرة
أخرى وسيسtير كttل شttيء عىل مtا يtرام ،حيث يجب العثttور عىل الترجمtة في مخttزن TLBوسttتحدث الترجمtة
بصورة صحيحة.
ال بد أنك تساءلت عن كيفية إيجاد نظام التشغيل للذاكرة التي تحتوي عىل جtدول الصtفحات page table
عندما قلنا أن نظام التشغيل يجد الترجمة في هذا الجدولُ .يحتَف ظ بقاعدة جدول الصttفحات في مسّtجل مرتبttط
بكل عملية ،حيث يسمى هذا المسجل بالمسجل األساسي لجدول الصفحات page-table base-registerأو
ما شابه ذلك .يمكن تحديد موقع المدخلة الصحيحة بوضع العنوان في هذا المسجل وإضافة رقم الصفحة إليه.
هناك عيبان مهمان آخران يمكن أن يوّلدهما مخttزن TLBويسttاعدان عىل إدارة الصttفحات المتسttخة dirty
pagesأي الصفحات التي جرى الوصول إليها مسtبًق ا ،حيث تحتtوي كtل صtفحة عىل سtمة ُم مَّثلtة ببٍت واحtد
تحّدد إذا جرى الوصول إىل الصفحة أي أنها أصبحت صفحة متسخة.
يمكن تمييز الصفحة عىل أنها صفحة جرى الوصول إليهtا مسtبًق ا عنtد تحميttل ترجمtة الصttفحة مبtدئًي ا في
مخزن .TLBإذا حّم لَت الصفحة دون وصول معَّل ق ،فيمكن تسttمية ذلttك بالتأّم ل Speculationمثttل تطttبيق
شيٍء ما مع التوّق ع بأنه سيؤتي ثماره ،حيث إذا قرأت الشيفرة البرمجية من الذاكرة خطًي ا مثاًل ،فيمكن أن يttؤدي
وضع ترجمة الصفحة التالية في مخزن TLBإىل توفير الوقت وتحسين األداء.
يعمل نظام التشغيل عىل تصفح جميع الصفحات دورًيا ويصّف ر بت الوصول ليمttيز الصttفحات الttتي تكttون
قيد االستخدام حالًي ا من غيرها .تكون الصفحات التي لم ُيعاد ضبط بت الوصول الخاص بها (بعttد تصttفيره) هي
أفضل المرشحين لإلزالة ألنها لم ُتستخَدم لفترة أطول عندما تمتلئ ذاكرة النظام ويحين الttوقت لنظttام التشttغيل
الصفحة المتسخة هي الصفحة التي تحتوي عىل بياناٍت مكتوبٍة عليهttا ،وبالتttالي ال تتطttابق مttع أّي بيانttات
موجودة فعلًي ا عىل القرص الصلب .إذا ُبِّدلت الصفحة من ثم كتبت فيهttا عمليttة مثاًل ،فيجب تحttديث نسttختها
152
علوم الحاسوب من األلف إىل الياء الذاكرة الوهمية Virtual Memory
الموجودة عىل القرص الصلب قبل تبديلها .ال ُتجَرى أّي تغيttيرات عىل الصttفحة النظيفttة ،لttذلك ال حاجttة لنسttخ
يتشابه هذان النوعان من الصفحات من حيث أنهمttا يسttاعدان نظttام التشttغيل في إدارة الصttفحات ،حيث
تحتوي الصفحة عىل بتين إضttافيين همtا :البت المتسtخ Dirty Bitوبت الوصttول ،Accessed Bitإذ ُيضَtبط
هذان البتان عند وضع الصفحة في مخزن TLBلإلشارة إىل أن وحدة المعالجة المركزية يجب أن تصدر خطأ.
يطّبق العتاُد عملية الترجمة المعتادة عندما تحاول إحدى العمليات الرجttوع إىل الttذاكرة ،ولكنttه يجttري أيًض ا
فحًص ا إضافًي ا للتأكد من عدم ضبط راية الوصول .إذا كان األمر كذلك ،فسيؤدي ذلك إىل حttدوث خطttأ في نظttام
التشغيل الذي يجب أن يضبط البت ويسمح للعملية باالستمرار .إذا اكتشف العتاد أنtه يكتب في صtفحٍة يكtون
بتها المتسخ غير مضبوط ،فسيؤدي ذلك إىل حدوث خطأ في نظام التشغيل لتمييز الصفحة بوصفها متسخة.
ُتسَّم ى عملية إزالة المدخالت من مخزن TLBباسم التفريغ ُ .Flushingيَع د تحديث مخزن TLBجttزًءا مهًم ا
من الحفاظ عىل فضاءات عناوين العمليات منفصلة ،ألن كل عملية يمكن أن تسttتخدم العنttوان الttوهمي نفسttه
دون تحديث مخزن ،TLBوهذا يعني أن العملية يمكن أن تكتب فوق ذاكttرة العمليttات األخttرى ،بينمttا تريttد في
حالة الخيوط Threadsمشtاركة فضttاء العنtاوين ،وبالتttالي ال ُيفَّtر غ مخttزن TLBعنtد التبtديل بين الخيttوط في
العملية نفسها.
ُيفَّر غ مخزن TLBبالكامل في بعض المعالجات في كل مرة يوجد فيها تبديل سياق ،ويمكن أن يكttون ذلttك
مرهًق ا للغاية ،ألنه يعني أن العملية الجديدة يجب أن تمر عبر المراحل كاملًة من أخذ خطأ الصفحة ثم العثttور عىل
بينما تطّبق المعالجات األخttرى معّtرف فضttاء عنttاوين إضttافي - Address Space IDأو ASIDاختصttاًرا-
ُيضاف إىل كل ترجمة في مخزن TLBلجعلها فريدة ،وبالتالي يحصل كل فضاء عناوين -أو كل عمليttة حيث تريttد
الخيوط مشاركة فضاء العناوين نفسه -عىل معّرفها الخاص الذي ُيخَّزن مtع الترجمtات في مخtزن ،TLBأي ليس
هناك داٍع لتفريغ مخزن TLBعند تبديل السياق ،ألن العملية التالية سيكون لها معّرف فضttاء عنttاوين مختلttف،
وسيختلف معّرف فضاء العناوين وسttتختلف الترجمttة إىل الصttفحة الحقيقيttة حttتى إن طلبت العمليttة العنttوان
الوهمي نفسه .يقلل هذا النظام من عمليttة التفريttغ ويزيttد من أداء النظttام ،ولكنttه يتطلب مزيًttدا من عتttاد TLB
153
علوم الحاسوب من األلف إىل الياء الذاكرة الوهمية Virtual Memory
يمكن تنفيذ ذلك من خالل وجود مسجل إضافي بوصفه جزًءا من حالة العملية التي تتضttمن معّtرف .ASID
ينظttر مخttزن TLBإىل هttذا المسّtجل عنttد ترجمttة الصttفحة الوهميttة إىل الصttفحة الحقيقيttة ،وسttيطابق فقttط
المدخالت التي لها معّرف ASIDالخاص بالعملية التي تكون قيد التشغيل حالًي ا .يحّttدد حجم هttذا المسttجل رقم
ُيَع د التحكم في مخزن TLBمن اختصاص نظام التشغيل ،ولكنها ليسttت القصttة كاملttة ،إذ تشttرح العمليttة
الموَّض حة في فقرة "أخطاء الصفحات" خطأ الصفحة الذي ُيرَف ع إىل نظام التشغيل ،ويمر عىل جttدول الصttفحات
للعثور عىل ترجمة الصفحة الوهمية إىل الحقيقية وتثبيتها في مخttزن .TLBيمكن أن يس َّtم ى ذلttك بمخttزن TLB
،Software-loadedوهنtttاك بtttديل آخtttر هtttو مخtttزن TLBالُم حَّم ل عتادًي ا الُم حَّم ل برمجًي ا TLB
.Hardware-loaded TLB
تحدد معمارية المعالج تخطيًط ا معيًنا لمعلومات جدول الصفحات في مخttزن TLBالُم حَّم ل عتادًي ا ،ويجب
اتباعها حتى ترجمة العنtوان الtوهمي .سtيمر المعtالج تلقائًي ا عىل جtداول الصtفحات لتحميtل مدخلtة الترجمtة
الصحيحة استجابًة للوصول إىل عنوان وهمي غير موجود في مخزن ،TLBوسيرفع المعالج استثناًء ليعttالج نظttام
يوّف ر التنفيذ الُم قَّtد م للمtرور عىل جtدول الصtفحات في العتtاد المتخصtص مزايtا السtرعة عنtد البحث عن
الترجمات ،ولكنه يزيل المرونة عن مطّبقي أنظمة التشغيل الttذين يرغبttون في تنفيttذ مخططttات بديلttة لجttداول
الصفحات.
يمكن تصنيف جميع المعماريات عىل نطاق واسع ضمن هاتين المنهجيتين السابقتين ،وسنّط لع تالًي ا عىل
المعالجات طرًق ا مختلفة إلدارة مخزن TLBمع مزايا وعيوب مختلفة .يشttار إىل جttزء المعttالج الttذي يتعامttل مttع
الذاكرة الوهمية باسم وحدة إدارة الذاكرة Memory Management Unitأو MMUاختصاًرا.
6.9.1معالج إيتانيوم
توفر وحدة MMUفي معالج إيتانيوم ميزات متعددة لنظtام التشtغيل للتعامtل مtع الtذاكرة الوهميtة والtتي
154
علوم الحاسوب من األلف إىل الياء الذاكرة الوهمية Virtual Memory
شرحنا سابًق ا مفهوم معّtرف فضtاء العنtاوين لتقليtل تكلفtة تفريtغ مخtزن TLBعنtد تبtديل السtياق ،ولكن
يستخدم المبرمجون في أغلب األحيان الخيوط Threadsللسماح لسياقات التنفيttذ بمشttاركة فضttاء العنttاوين،
حيث تحتوي جميع الخيوط عىل معّرف ASIDنفسه ،وبالتالي يتشاركون بمدخالت مخttزن ،TLBممttا يttؤدي إىل
زيادة األداء .لكن يمنع معّرف ASIDواحد مخزن TLBمن فttرض الحمايttة ،حيث تصttبح المشttاركة متمثلttة بنهج
"الكل أو ال شيء" ،إذ يجب أن تتخىل الخيوط عن الحماية من بعضها البعض لمشاركة بعض البايتات.
تttدرس وحttدة MMUفي معttالج إيتttانيوم المشttاكل السttابقة وتttوفر القttدرة عىل مشttاركة فضttاء العنttاوين
ومدخالت الترجمة بدقة أقل بكثير مع الحفاظ عىل الحماية داخل العتاد .يقسttم معttالج إيتttانيوم فضttاء العنttاوين
المؤلف من 64بت إىل 8مناطق كما هو موَّض ح في الشكل السابق .تحتtوي كtل عمليtة عىل ثمانيtة مسtجالت
مناطق بحجم 24بت بوصttفها جttزًءا من حالتهttا ،ويحتttوي كttل منهttا عىل معّtرف منطقttة - Region IDأو RID
اختصاًرا -لكل منطقة من المناطق الثمانية الخاصة بفضاء عناوين العمليةُ .توَس م ترجمttات مخttزن TLBبمع ّtرف
،RIDوبالتالي لن تتطابق إال إذا احتوت العملية عىل معّرف RIDنفسه كما هو موَّض ح في الشكل التالي:
155
علوم الحاسوب من األلف إىل الياء الذاكرة الوهمية Virtual Memory
ال ُتحتَس ب البتات الثالثة األوىل ( بتات المنطقة) في ترجمة العنوان الوهمي ،لذلك إذا كانت هناك عمليتان
تشتركان في معّرف RIDأي تحتفظان بالقيمة نفسها في أحد مسجالت المنطقة الخاصة بهما ،فسيكون لttديهما
اسم بديل لتلك المنطقة .إذا احتوت العملية Aعىل معّرف RIDقيمته 0x100في مسجل المنطقttة 3واحتttوت
العملية Bمعّرف RIDنفسه الذي قيمته 0x100في مسّجل المنطقttة ،5فسُتسَّtم ى المنطقttة 3من العمليttة A
باسم بديل هو .process-B, region 5تعني هذه المشاركة المحدودة أن كلتا العمليتين تتلقيان فوائد مدخالت
مخزن TLBالمشتركة دون الحاجة إىل منح إذن الوصول إىل كامل فضاء العناوين.
ُتوَس م كل مدخلة من مخزن TLBفي معالج إيتانيوم بمفتاح حماية للسماح بمشاركة أكttثر دقttة .تحتttوي كttل
عملية عىل عدد إضافي من مسجالت مفاتيح الحماية يحدده نظام التشغيل.
ُتوَس م كل صفحة بمفتاح فريد ويمنح نظام التشغيل العمليات المسموح بهttا للوصttول إىل الصttفحات الttتي
تستخدم هذا المفتاح عند مشاركة سلسلة من الصttفحات مثttل شttيفرة برمجيttة لمكتبtة نظttام مشtتركة .يفحص
مخزن TLBالمفتاح المرتبط بمدخلة الترجمة مقابtل المفttاتيح الtتي تحتفttظ بهtا العمليttة في مسtجالت مفتttاح
156
علوم الحاسوب من األلف إىل الياء الذاكرة الوهمية Virtual Memory
الحماية الخاصة بها عند اإلشارة إىل صفحة ما ،مما يسمح بالوصول إليها في حالة وجود المفتاح أو يؤدي إىل رفع
يمكن للمفتاح فرض األذونtات أيًض ا ،إذ يمكن أن تحتttوي إحtدى العمليttات مثاًل عىل مفتttاح يمنح أذونtات
الكتابة ويمكن أن تحتوي عملية أخرى عىل مفتاح للقراءة فقط ،مما يسمح بمشاركة مدخالت الترجمة بدقttة وفي
نطاق أوسع بكثttير وصttواًل إىل مسttتوى الصttفحة الواحttدة ،ويttؤدي ذلttك إىل تحسttينات محتملttة كبttيرة في أداء
مخزن .TLB
يؤدي تبديل السياق إىل نظttام التشttغيل عنttد حttل خطttأ في مخttزن TLBإىل إضttافة عبء كبttير إىل مسttار
معالجة الخطأ ،إذ يواجه معالج إيتانيوم ذلك العبء من خالل السماح بخيار استخدام العتاد الُم دَم ج لقراءة جttدول
الصفحات وتحميل ترجمات الصفحة الوهمية إىل الصفحة الحقيقية في مخزن TLBتلقائًي ا .تتجنب أداة المttرور
عىل جدول الصفحات العتادية - Hardware Page-table Walkerأو HPWاختصاًرا -عمليات االنتقال المكلفة
إىل نظام التشغيل ،ولكنه يتطلب أن تكون الترجمات بصيغة ثابتة ومناسبة للعتاد لتفهمه.
ُيشار إىل أداة HPWالخاصة بمعالج إيتانيوم في توثيق إنتل عىل أنها أداة ُم عَّم اة وهمًي ا للمttرور عىل جttدول
الصttفحات Virtually Hashed Page-table Walkerأو VHPT walkerاختصttاًرا .يمنح معttالج إيتttانيوم
المطورين خيارين من تقديمات HPWالحصرية تبادلًي ا ،إذ يعتمد أحدهما عىل جدول الصفحات الخطي الttوهمي
تجدر اإلشارة إىل أنه يمكن العمل بدون أداة عتادية للمرور عىل جدول الصفحات ،حيث يحل نظام التشttغيل
كل خطأ TLBويصبح المعالج معمارية محَّم لة برمجًي ا ،ولكن ُيَع د تtأثير تعطيttل HPWعىل األداء كبtيًرا جًtدا دون
يشار إىل تقديم جدول الصفحات الخطي الوهمي في التوثيقات عىل أنه الصيغة القصيرة لجدول الصفحات
الُم عَّم اة وهمًي ا - Short Format Virtually Hashed Page-tableأو SF-VHPTاختصاًرا ،وهو نموذج HPW
الحل المعتاد هو استخدام جدول صفحات متعدد المستويات أو هرمي ،حيث ُتستخَدم البتات الttتي تتكttون
من رقم الصفحة الوهمية بوصفها فهرًس ا إىل مستويات وسيطة من جttدول الصttفحات .ال توجttد منttاطق فضttاء
عناوين وهمية فارغة في جدول الصفحات الهرميُ .تهَttدر مسttاحة صttغيرة نسttبًي ا في الِح مttل اإلضttافي بالنسttبة
للحالة الواقعية لفضttاء العنttاوين الُم جَّم عttة Clusteredبإحكttام والمملttوءة بصttورة ضttئيلة بالموازنttة مttع جttدول
الصفحات الخطي ،ولكن العيب الرئيسي هو مراجع الذاكرة المتعددة المطلوبة للبحث.
157
علوم الحاسوب من األلف إىل الياء الذاكرة الوهمية Virtual Memory
يأخذ الجدول الخطي الذي حجمه 512جيبي بايت GiBمع فضاء عناوين 64بت ما مقداره %0.003فقttط
من 16إكسابايت المتاحة ،وبالتالي يمكن إنشاء جدول صفحات خطي وهمي Virtual Linear Page-table
يستخدم العتاد عند حدوث خطأ في مخزن TLBرقم الصفحة الوهمية لإلزاحة عن قاعttدة جttدول الصttفحات
تماًم ا كما هو الحال بالنسبة لجدول صفحات خطي حقيقي .إذا كانت هttذه المدخلttة صttحيحة ،فسُttتقَرأ الترجمttة
وُتدَر ج مباشرًة في مخزن ،TLBولكن يكون عنوان مدخلة الترجمة في حد ذاتهttا عنواًن ا وهمًي ا باسttتخدام جttدول
،VLPTوبالتالي هناك احتمال أن تكون الصفحة الوهمية التي توجد بها غttير موجttودة في مخttزن ،TLBوس ُtيرَف ع
خطأ متداخل Nested Faultإىل نظام التشغيل في هذه الحالة .يجب عىل البرمجيات بعttد ذلttك تصttحيح هttذا
الخطأ عن طريق ربط الصفحة التي تحتوي عىل مدخلة الترجمة مع جدول .VLPT
158
علوم الحاسوب من األلف إىل الياء الذاكرة الوهمية Virtual Memory
يمكن جعل هذه العملية مباشرًة إذا احتفظ نظام التشغيل بجدول صttفحات هttرمي ،حيث تحتttوي الصttفحة
التي تمثل ورقة من جدول صفحات هرمي عىل مدخالت ترجمة لمنطقة متجاورة وهمًي ا من العنttاوين ،وبالتttالي
يمكن ربطها باستخدام مخزن TLBإلنشاء جدول VLPTكما هو موضح في الشكل السابق.
تحدث ميزة جدول VLPTالرئيسية عندما يطلب أحد التطبيقات وصواًل متكرًرا أو متواصاًل إىل الttذاكرة .ضttع
في حساباتك أن الخطأ األول في عمليtة المtرور عىل الtذاكرة المتجtاورة وهمًي ا سtيؤدي إىل ربtط صtفحة مليئtة
159
علوم الحاسوب من األلف إىل الياء الذاكرة الوهمية Virtual Memory
بمدخالت الترجمة مع جدول الصفحات الخطي الوهمي .سيتطلب الوصول الالحق إىل الصفحة الوهميttة التاليttة
تحميَل مدخلة الترجمة التالية في مخزن ،TLBوالتي تتوفر اآلن في جدول ،VLPTوبالتالي ُتحَّم ل بسttرعة كبttيرة
دون استدعاء نظام التشغيل .سيكون ذلك ميزًة عند االستفادة من تكلفttة الخطttأ المتttداخل األولي عىل عمليttات
العيب الرئيسي هو أن جدول VLPTيتطلب اآلن مدخالت مخزن ،TLBمما يttؤدي إىل زيttادة الضttغط عليttه.
يتطلب كل فضاء عناوين جدول صفحاٍت خاص به ،لذلك تصبح التكلفة أكبر كلما أصبح النظام أكثر نشاًط ا ،ولكن
يجب أن تكون أّي زيادة في أخطاء الوصول إىل مخزن TLBأكبر من الفائدة الحاصلة عند انخفاض تكtاليف إعtادة
الملء من أداة المرور العتادية الفعالة .الحظ أن الحالة السيئة يمكن أن تتخطى مttدخالت بمقttداٍر يسttاوي نتيجttة
قسمة حجم الصفحة page_sizeعىل حجم الترجمة ،translation_sizeمما يتسبب في حttدوث أخطttاء
تتوقع أداة المرور العتادية hardware walkerأن تكون مدخالت الترجمة بصيغة معينة كما هو موضح عىل
يسار الشكل السابق ،حيث يتطلب جدول VLPTترجمات بصيغة قصيرة مؤَّلفة من 8بايتات .إذا استخدم نظttام
التشغيل جدول الصفحات الخاص به بوصفه دعًم ا لجدول VLPTكما في الشكل "تقttديم جttدول VHPTبصttيغة
قصيرة في معالج إيتانيوم" ،فيجب أن تستخدم هذه صيغة الترجمة القصيرة .تتجاهttل المعماريttة عttدًدا محttدوًدا
من البتات في هذه الصيغة وبالتالي تكون متاحة لتستخدمها البرمجيات مع عدم احتمال حدوث تعديالت كبيرة.
يعتمد جدول الصفحات الخطي linear page-tableعىل فكttرة حجم الصttفحة الثttابت ،وُيَع د دعم أحجttام
الصفحات المتعددة مشكلًة ألنه يعني أن ترجمة صفحة وهمية معينة لم َتُع د عنttد إزاحttة ثابتttة ،ولكن يمكن حttل
هذه المشكلة من خالل احتواء كل منطقة من المناطق الثمانية في فضاء العناوين -كمtا هtو موّض ح في الشtكل
"رسم توضيحي للمناطق ومفاتيح الحماية في معالج إيتttانيوم" -عىل جttدول VLPTمنفصttل يربttط عنttاوين تلttك
المنطقة فقط .يمكن إعطاء حجم الصفحة الوهمية لكل منطقttة ،حيث ُتخَّص ص منطقttة واحttدة لصttفحات أكttبر
(باسtttتخدام مخtttزن HugeTLBفي لينكس) ولكن ال يمكن اسtttتخدام أحجtttام صtttفحات متعtttددة ضtttمن
المنطقة الواحدة.
يمكن أن يكون استخدام مدخالت مخزن TLBلمحاولة تقليل تكاليف إعادة تعبئته -كما هو الحال مع جttدول
-SF-VHPTمقايضttة فّع الttة أو يمكن أاّل يكttون كttذلك .يطّب ق معttالج إيتttانيوم جttدول صttفحات ُم عَّم ى hashed
page-tableمع إمكانية خفض تكاليف مخزن ،TLBحيث يعّم ي المعالج في هذا المخطط عنواًنا وهمًي ا للعثttور
ُيَع د جدول الصفحات الخطي الذي ناقشناه سابًق ا جدول صفحات ُم عَّم ى باستخدام تعمية Hashمثاليttة لن
ينتج عنها تضارٌب أبًدا ،ولكن يتطلب ذلك مقايضة غير عملية لمناطق ضttخمة من الttذاكرة الحقيقيttة المتجttاورة.
يزيد تقييد متطلبات الذاكرة لجدول الصفحات من احتمال حtدوث تضtاربات عنtد تعميtة عنtوانين وهمtيين إىل
160
علوم الحاسوب من األلف إىل الياء الذاكرة الوهمية Virtual Memory
اإلزاحة نفسها .تتطلب الترجمات المتضاربة مؤشر سلسلة Chain Pointerإلنشاء قائمة مترابطة من المدخالت
البديلttة الممكنttة .يتطلب تميttيز المدخلttة الصttحيحة في القائمttة المترابطttة وسًttم ا Tagمشttتًق ا من العنttوان
الوهمي الوارد.
تؤدي المعلومات اإلضافية المطلوبة لكل مدخلة ترجمة إىل ظهور اسم بديل بصيغة جدول VHPTالطويلttة -
أو LF-VHPTاختصاًرا .تنمtو مtدخالت الترجمtة إىل 32بtايت كمtا هtو موضtح عىل الجtانب األيمن من الشtكل
الميزة الرئيسية لهذه الطريقة هي أن جدول التعمية العام يمكن تثبيته باستخدام مدخلة TLBواحدة .تشترك
جميع العمليات في الجدول ،لذا يجب أن ينمو حجمه بطريقة أفضل من صيغة ،SF-VHPTإذ تتطلب كل عملية
أعداًدا متزايدة من مدخالت صtفحات جtدول VLPTفي مخtزن .TLBلكن تكtون المtدخالت األكttبر أقtل مالءمtة
للذاكرة المخبئية ،إذ يمكننا مالءمة أربعة مدخالت ذات صيغة قصيرة بحجم 8بايتات مع كل مدخلttة ذات صttيغة
طويلة بحجم 32بايت .يمكن أن تساعد الذواكر المخبئية الكبيرة جًدا الموجودة عىل معttالج إيتttانيوم في تخفيttف
هذا التأثير.
تتمثل إحدى مزايا صيغة SF-VHPTفي أن نظام التشغيل يمكنه االحتفاظ بالترجمات في جttدول صttفحات
هرمي ،ويمكنه ربط الصفحات الورقية في هذه البنية الهرميttة مباشttرًة مttع جttدول VLPTمttع االحتفttاظ بصttيغة
الترجمة العتادية .بينما يجب عىل نظام التشغيل باستخدام صيغة LF-VHPTإما استخدام جدول التعمية بوصفه
مصدًرا أساسًي ا لمدخالت الترجمة أو االحتفاظ بجدول التعمية بوصفه ذاكرة مخبئية لمعلومات الترجمttة الخاصttة
بهُ .يَع د االحتفاظ بجدول التعمية بصيغة LF-VHPTبوصفه ذاكرة مخبئية دون المستوى األمثل إىل حد ما بسبب
زيادة الِح مل في مسارات األخطttاء األساسttية في الtوقت المناسttب ،ولكن ُتكتَس ب الفوائtد من الجttدول الtذي
161
.7سلسلة األدوات Toolchain
ناقشنا حتى اآلن كيفية تحميttل البرنttامج في تحميttل البرنttامج في الttذاكرة الوهميttة ،وسttوف نبttدأ في هttذا
الفصل بالتعرف عىل عملية يتعّق بها نظام التشttغيل ويتفاعttل معهttا باسttتخدام اسttتدعاءات النظttام وهي عمليttة
التصريف .Compilingكما سنتعّرف في هذا الفصل عىل الخطوات الثالث المتبعة إلنشاء ملف قابل للتنفيttذ،
ولكننا سنبدأ أواًل بالتعّرف عىل أهم الفروقات بين البرامج الُم صَّرفة Compiled Programsوالبرامج الُم فَّس رة
.Interpreted Programs
عملية تحويل الشيفرة البرمجية المكتوبة بلغٍة مثل لغة Cإىل ملف ثنائي جtاهز للتنفيttذ بعمليttة التصttريف الtتي
للبرامج الُم صَّرفة بعض العيوب في تطوير البرمجيttات الحديثttة ،إذ يجب اسttتدعاء المص ِّtرف إلعttادة إنشttاء
الملف القابل للتنفيذ في كل مرة ُيجري فيها المطور تعدياًل -لttو بسttطًي ا -وفي المقابttل يمكن منطقًي ا وبنttاًء عىل
ذلك تصميم برنامٍج ُم صَّرف يمكنه قراءة برنامج آخر وتنفيذ شيفرته البرمجية سطًرا سطًرا ،ونسمي هذا النttوع من
البرامج الُم صَّرفة بالبرامج الُم فَّس رة Interpreterألنها تفّس ر كل سطر من ملttف الttدخل وتنّف ذه بوصttفه شttيفرة
برمجية ،بحيث ال تكون هناك حاجة لتصريف البرنامج وستظهر أي تعديالت جديدة مضافة في المرة التالية الttتي
تعمل البرامج المفَّس رة عادًة بصورة أبطأ من نظيرتها الُم صَّرفة ،حيث يمكن مصادفة ِح مل البرنامج في قراءة
وتفسير الشيفرة البرمجية مرًة واحدة فقط في البرامج الُم صَّرفة ،بينما يصادف البرنامج المفَّس ر هttذا الِح مttل في
كل مرة ُيشَّغ ل فيها .لكن تمتلك اللغات المفَّس رة العديد من الجوانب اإليجابية ،حيث تعمttل العديttد من اللغttات
علوم الحاسوب من األلف إىل الياء سلسلة األدوات Toolchain
المفسرة فعلًي ا في آلttة افتراضttية ُ virtual machineم جَّtردة من العتttاد األساسttيُ .تَع د لغtة البرمجttة بttايثون
Pythonولغة Perl 6من اللغات التي تستخدم آلًة افتراضية تفّس ر الشيفرة البرمجية.
نسخ البرنامج في الذاكرة وتنفيذه ،حيث ُتَع د اآللة االفتراضية Virtual Machineتجريًدا برمجًي ا للعتاد.
تستخدم لغة جافا Javaمثاًل نهًجا هجيًنا يجمع بين التصريف والتفسttير ،فهي لغttة ُم ص َّtرفة جزئًي ا وُم فَّس رة
جزئًي اُ .تصَّرف شيفرة جافا في برنامج يعمل ضمن آلة جافا االفتراضttية Java Virtual Machineأو يشttار إليهttا
JVMاختصاًرا ،وبالتالي يمكن تشغيل البرنامج الُم صَّرف عىل أّي عتاد يحتوي عىل آلة JVMخاصة بtه ،أي يمكنtك
أن تكتب شيفرتك البرمجية مرة واحدة وتشّغ لها في أّي مكان.
الخطوات هي:
.1التصريف Compiling
.2التجميع Assembling
.3الربط Linking
تسَّم ى جميع المكونات المتضمنة في هttذه العمليttة بسلسttلة األدوات ،Toolchainإذ تكttون هttذه األدوات
عىل شكل سلسلة بحيث يكون خرج إحداها دخاًل لألخرى حتى الوصttول إىل الخttرج النهttائي .يأخttذ كttل رابttط في
السلسلة الشيفرَة البرمجية تدريجًي ا بحيث تكون أقرب إىل كونها شيفرة برمجية ثنائية مناسبًة للتنفيذ.
7.3الترصيف Compiling
تتمثل الخطوة األوىل لتصريف ملف مصدري إىل ملف قابل للتنفيذ في تحويل الشttيفرة البرمجيttة من لغttة
عالية المستوى يفهمها اإلنسان إىل شيفرة تجميع Assembly Codeتعمل مباشرًة مع التعليمات والمسجالت
ُتَع د عملية التصريف أكثر الخطوات تعقيًدا لعدة أسباب أولها أنه ال يمكن التنبttؤ بتصttرفات البشttر ،فلttديهم
شيفرتهم البرمجية الخاصة بأشكال مختلفة .يهتم المصِّرف بالشيفرة البرمجية الفعلية فقttط ،ولكن يحتttاج البشttر
ألشياء إضافية مثل التعليقات والمسافات البيضاء (الفراغات ومسافات الجدولة Tabوالمسttافات البادئttة ومttا
163
علوم الحاسوب من األلف إىل الياء سلسلة األدوات Toolchain
إىل ذلك) لفهم هذه الشيفرة البرمجية .تسمى العملية التي يتخttذها المص ِّtرف لتحويttل الشttيفرة البرمجيttة الttتي
هناك خطوة قبل تحليل الشيفرة البرمجية في الشيفرة المكتوبة بلغة ،Cحيث تسَّم ى هذه الخطوة بالمعالجة
الُم سَبقة أو التمهيدية يقوم بها المعالج المسبق ،Pre-processorوهو عبttارة عن برنttامج السttتبدال النصttوص،
حيث ُيستبَدل مثاًل المتغير variableالُم صَّر ح عنه بالشكل #define variable textبالنص ،textثم
7.3.1الصياغة
لكل لغة برمجة صياغة معينة تمّث ل قواعد اللغة ،بحيث يعرف المttبرمج والمص ِّtرف قواعttد الصttياغة ليفهمttا
بعضهما البعض ويسير كل شيء عىل ما يرام .ينسى البشر القواعد أو يكسttرونها في أغلب األحيttان ،ممttا يجعttل
المصِّرف غير قادر عىل فهم ما يقصده المبرمج ،فإن لم تضع قوس اإلغالق لشرط ifمثاًل ،فلن يعرف المصِّttرف
ُتوَص ف الصياغة في صيغة باكوس نور - Backus-Naur Formأو BNFاختصاًرا -في أغلب األحيttان ،وهي
لغة يمكنك من خاللها وصف اللغات ،والشكل األكttثر شttيوًعا منهttا هttو صttيغة بttاكور نttور الموَّس عة Extended
- Backus-Naur Formأو EBNFاختصاًرا -التي تسمح ببعض القواعد اإلضافية األكثر مالءمة للغات الحديثة.
للهدف من التصريف ،فلكل معمارية مجموعة تعليمات مختلفة وأعداد مختلفة من المسجالت وقواعttد مختلفttة
للتشغيل الصحيح.
ُتَع د محاذاة المتغيرات في الذاكرة أمًرا مهًم ا للمصِّtرفات ،إذ يحتtاج مtبرمجو األنظمtة أن يكونtوا عىل درايtة
بقيود المحاذاة لمساعدة المصِّرف عىل إنشاء أكثر شيفرة برمجية فّع الة ممكنة.
164
علوم الحاسوب من األلف إىل الياء سلسلة األدوات Toolchain
ال تستطيع وحدات المعالجة المركزية CPUتحميل قيمة في المسجل من موقع ذاكttرة عشttوائي ،إذ يتطلب
ذلtك أن تحttاذي المتغttيرات حtدوًدا معينtة .يمكننtا أن نtرى في الشtكل السtابق كيفيttة تحميttل قيمtة 32بت
( 4بايتات) في مسجل عىل آلة تتطلب محاذاة بمقدار 4بايتات للمتغيرات.
يمكن تحميل المتغير األول في المسجل مباشرًة ،حيث يقع بين حدود 4بايتات ،ولكن يجتاز المتغير الثtاني
حدود 4بايتات ،مما يعني أنه ستكون هنtاك حاجtة إىل عمليtتي تحميtل عىل األقtل للحصtول عىل المتغtير في
يمكن لبعض المعماريات مثل معمارية x86التعامَttل مttع عمليttات التحميttل الttتي تكttون دون محttاذاة في
العتاد مع انخفاض في األداء ،حيث يطّبق العتاد العمل اإلضttافي للحصttول عىل القيمttة في المسttجل ،بينمttا ال
يمكن أن يكون هناك انتهاك لقواعد المحاذاة في المعماريات األخرى وسترفع اسttتثناًء يكتشttفه نظttام التشttغيل
الذي يتعين عليه بعد ذلك تحميل المسجل يدوًيا عىل أجزاء ،مما يتسبب في مزيد من الِح مل.
يجب أن يأخذ المبرمجون المحاذاة في الحسبان خاصًة عند إنشاء البttنى ،structحيث يمكن للمttبرمجين
في بعض األحيttان أن يتسttببوا في سttلوك دون المسttتوى األمثttل ،بينمttا يعttرف المصِّttرف قواعttد المحttاذاة
للمعماريات التي يبنيهttا .ينص معيttار C99عىل أن البttنى سُttترَّتب في الttذاكرة بttالترتيب الُم حَّtد د في التصttريح
165
علوم الحاسوب من األلف إىل الياء Toolchain سلسلة األدوات
$ cat struct.c
#include <stdio.h>
struct a_struct {
char char_one;
char char_two;
int int_one;
};
int main(void)
{
struct a_struct s;
printf("%p : s.char_one\n" \
"%p : s.char_two\n" \
"%p : s.int_one\n", &s.char_one,
&s.char_two, &s.int_one);
return 0;
$ ./struct
0x7fdf6798 : s.char_one
0x7fdf6799 : s.char_two
0x7fdf679c : s.int_one
$ ./struct-packed
0x7fcd2778 : s.char_one
0x7fcd2779 : s.char_two
0x7fcd277a : s.int_one
166
علوم الحاسوب من األلف إىل الياء سلسلة األدوات Toolchain
أنشأنا في المثال السابق بنية تحتوي عىل بايتين من النوع charمتبوعين بعدد صحيح بحجم 4بايتات من
نوّجه في المثال السابق المصِّرف إىل عttدم حشtو البtنى ،وبالتttالي يمكننtا أن نtرى أن العttدد الصttحيح يبtدأ
تحدثنا سابًق ا عن استخدام األسماء البديلة في الذاكرة المخبئية ،وكيttف يمكن ربttط عttدة عنttاوين مttع سttطر
الذاكرة المخبئية نفسه .يجب أن يتأكد المبرمجون من أنهم ال يتسببون في ارتداد Bouncingفي خطوط الttذاكرة
يحدث هذا الموقف عنttدما يصttل البرنttامج باسttتمرار إىل منطقttتين من الttذاكرة ترتبطttان مttع خttط الttذاكرة
المخبئية نفسه ،مما يؤدي إىل هدر هذا الخط ،حيث ُيحَّم ل وُيستخَدم لفترة قصيرة ثم يجب إزالته وتحميttل خttط
الذاكرة المخبئية اآلخر في المكان نفسه من الذاكرة المخبئية .يؤدي تكtرار هtذا الموقtف إىل تقليtل األداء بصtورة
كبttيرة ،ولكن يمكن تخفيفttه من خالل تنظيم البيانttات المتعارضttة بطttرق مختلفttة لتجنب تعttارض خطttوط
الذاكرة المخبئية.
إحدى الطرق الممكنtة الكتشtاف هttذا النtوع من المواقttف هي التشtخيص Profilingالtذي يمّث ل مراقبtة
الشttيفرة البرمجيttة لتحليttل مسttاراتها الttتي يمكن اسttتخدامها والمttدة الُم سttتغرَق ة لتنفيttذها .يمكن للمص ِّtرف
باستخدام التحسttين الُم وَّج ه بالتشttخيص - Profile Guided Optimisationأو PGOاختصttاًرا -وضَttع بتttات
إضافية خاصة من الشيفرة البرمجية في أول ثنائية binaryيبنيها ويشّغ لها ويسّجل الفروع المأخوذة منهttا وغttير
ذلك .يمكنك بعد ذلك إعادة تصريفها مع المعلومات اإلضافية إلنشاء ثنائية binaryمع أداء أفضttل ،وإاّل فيمكن
للمبرمج أن ينظر إىل خرج عملية التشخيص ويكتشف مواقًف ا أخرى مثل ارتداد خط الذاكرة المخبئية.
يمكن المقايضة مع ما فعله المصِّرف سابًق ا باستخدام ذاكرة إضافية لتحسين السرعة عند تشttغيل شttيفرتنا
البرمجية .يعرف المصِّرف قواعد المعمارية ويمكنه اتخاذ قرارات بشأن أفضل طريقة لمحاذاة البيانات عن طريttق
مقايضة كميات صغيرة من الذاكرة المهدورة لزيادة األداء أو للوصول إىل األداء الصحيح فقط.
167
علوم الحاسوب من األلف إىل الياء سلسلة األدوات Toolchain
ال يجب أبًدا -بصفتك مبرمًجا -وضع افتراضات حول طريقة ترتيب المصِّرف للمتغttيرات والبيانttات ،ألنهttا ال
ُتَع د قابلًة للنقل ،إذ يكون للمعماريات المختلفة قواعٌد مختلفة ويمكن أن يتخذ الُم صِّرف قرارات مختلفة بناًء عىل
وضع االفرتاضات
يجب أن تكون -بصفتك مبرمًجا بلغة -Cعىل دراية بما يمكنttك افتراضttه بشttأن مttا سttيفعله المصِّtرف ومttا
يمكن أن يكون متغيًراُ .ذ ِكر بالتفصيل ما يمكنك أن تفترضttه بالضttبط ومttا ال يمكنttك افتراضttه في معيttار ،C99
حيث إذا كنت مبرمًجا بلغة ،Cفال بد أن يكون التعرف عىل القواعد جديًرا بالعناء لتجنب كتابة شيفرة برمجية غttير
قابلة للنقل.
{ struct a_struct
;int a
;int b
;}
)int main(void
{
;int i
;struct a_struct s
printf("%p\n%p\ndiff %ld\n", &i, &s, (unsigned long)&s - (unsigned
;)long)&i
;return 0
}
$ gcc-3.3 -Wall -o stack-3.3 ./stack.c
$ gcc-4.0 -o stack-4.0 stack.c
$ ./stack-3.3
0x60000fffffc2b510
0x60000fffffc2b520
diff 16
$ ./stack-4.0
168
علوم الحاسوب من األلف إىل الياء سلسلة األدوات Toolchain
0x60000fffff89b520
0x60000fffff89b524
diff 4
يمكننا أن نرى في المثال السابق المأخوذ من آلة إيتttانيوم Itaniumأن حاشttية ومحttاذاة المكttدس تغttيرت
بصورة كبيرة بين إصدارات المصِّرف ،gccوهذا أمر متوقع ويجب عىل المبرمج مراعاته .كمttا يجب عليttك التأكttد
هناك عدد من تسلسالت الشيفرة البرمجية الشtائعة الtتي تتعامtل مtع المحttاذاة ،ويجب أن تضttعها معظم
البرامج في حساباتها .يمكن أن ترى "مفاهيم الشيفرة البرمجية" في العديد من األماكن خارج النواة Kernelعنttد
التعامل مع البرامج التي تعالج أجزاًء من البيانات بصيغة أو بأخرى ،لذا فإن األمر يستحق البحث.
يمكننا أخذ بعض األمثلة من نواة لينكس Linux kernelالتي يتعين عليها في أغلب األحيان التعامttل مttع
محاذاة صفحات الذاكرة ضمن النظام .إليك مثال عن التعامل مع محاذاة الصفحات:
] [ include/asm-ia64/page.h
*/
يحّد د PAGE_SHIFTحجم صفحة النواة الفعلي *
*/
)#if defined(CONFIG_IA64_PAGE_SIZE_4KB
# define PAGE_SHIFT 12
)#elif defined(CONFIG_IA64_PAGE_SIZE_8KB
# define PAGE_SHIFT 13
)#elif defined(CONFIG_IA64_PAGE_SIZE_16KB
# define PAGE_SHIFT 14
)#elif defined(CONFIG_IA64_PAGE_SIZE_64KB
# define PAGE_SHIFT 16
#else
!# error Unsupported page size
#endif
169
علوم الحاسوب من األلف إىل الياء سلسلة األدوات Toolchain
يمكننا أن نرى في المثال السابق أن هناك عدًدا من الخيارات المختلفة ألحجام الصفحات داخل النواة الttتي
تttتراوح من 4كيلوبايتttات إىل 64كيلوبttايتَُ .يعttد المttاكرو PAGE_SIZEواض ًtحا إىل حttد مttا ،فهttو يعطي حجم
الصفحة الحالي المحّدد ضمن النظام عن طريق انزياح قيمته 1باستخدام رقم االنزياح الُم عَط ى ،ويعادل ذلttك 2n
لدينا بعد ذلك تعريف قناع الصفحة PAGE_MASKالذي يسمح لنtا بtالعثور عىل تلttك البتttات الموجtودة في
7.3.3التحسني Optimisation
يريد المصِّرف بمجرد الحصول عىل تمثيل داخلي للشيفرة البرمجية إيجاَد أفضل خtرج بلغtة التجميtع لtدخل
الشيفرة البرمجيtة الُم حَّtد د .هtذه مشtكلة كبtيرة ومتنوعtة وتتطلب معرفtة كtل شtيء من الخوارزميtات الفعالtة
المعتَم دة في علوم الحاسوب إىل المعرفة العميقة بالمعالج الذي ستعمل الشيفرة البرمجية عليه.
هناك بعض التحسينات الشائعة التي يمكن أن ينظر إليها المصِّرف عند توليد الخرج ،وهناك العديد والعديد
من االستراتيجيات إلنشاء الشيفرة البرمجية األفضل ،وُيَع د ذلك مجال بحث غttني .يمكن للمص ِّtرف أن يttرى في
كثير من األحيان أنه ال يمكن استخدام جttزء معين من الشttيفرة البرمجيttة ،لttذا يتركttه لتحسttين بنيttة لغttة معينttة
إذا احتوت الشيفرة البرمجية عىل حلقة مثل حلقة forأو whileوكان لدى الُم صِّرف فكرة عن عدد المرات
التي ستنّف ذ فيها ،فسيكون فك الحلقة أكثر فاعلية بحيث ُتنَّف ذ تسلسلًي ا ،إذ ُتكَّرر شيفرة الحلقة الداخلية لتنفيذها
عدد المرات ذاك أخرى بداًل من تنفيذ الجزء الداخلي من الحلقة ثم العودة إىل البداية لتكرار العملية.
تزيد هذه العملية من حجم الشيفرة البرمجية ،إذ يمكن أن تسمح للمعالج بتنفيttذ التعليمttات بفعاليttة ،حيث
يمكن أن تتسبب الفروع في تقليل كفاءة خط أنابيب التعليمات الواردة إىل المعالج.
يمكن وضع دوال ُم ضَّم نة الستدعائها ضمن المستدعي ،calleeويمكن للمبرمج تحديد ذلttك للمص ِّtرف من
خالل وضع الكلمة inlineفي تعريف الدالة ،ويمكنك مقايضة حجم الشtيفرة البرمجيttة بتسلسtل تنفيttذها من
خالل ذلك.
إذا صادف الحاسوب تعليمة ،ifفهناك نتيجتان محتملتان إما صحيحة أو خاطئttة .يريttد المعttالج االحتفttاظ
بأنابيبه الواردة ممتلئة قدر اإلمكان ،لذا ال يمكنtه انتظttار نتيجttة االختبtار قبtل وضttع الشtيفرة البرمجيttة في خtط
170
علوم الحاسوب من األلف إىل الياء سلسلة األدوات Toolchain
األنابيب ،وبالتالي يمكن للمصِّرف أن يتنبأ بالطريقة التي ُيحتَم ل أن يسttير بهttا االختبttار .وهنttاك بعض القواعttد
البسيطة التي يمكن أن يستخدمها المصِّرف لتخمين هذه األمور ،عىل سبيل المثال ال ُيحتَم ل أن تكون التعليمttة
) if (val == -1صحيحًة ،ألن القيمة -1تشير عادًة إىل رمز خطأ ونأمل أاّل ُتشَّغ ل هذه التعليمة كثيًرا.
يمكن لبعض المصِّرفات تصريف البرنامج ،وجعل المستخدم يشّغ له ليالحظ الطريق الtذي تسtير بtه الفtروع
في ظل ظروف واقعية ،ويمكنه بعد ذلك إعادة تصريفه بناًء عىل ما شاهده.
7.4المجمع Assembler
تبقى شttيفرة التجميttع الttتي أخرجهttا المص ِّtرف في صttيغة يمكن أن يقرأهttا اإلنسttان إذا كنت عىل معرفttة
بتفاصيل شيفرة التجميع الخاصة بالمعالجُ .يلقي المطورون في أغلب األحيان نظttرة خاطفttة عىل خttرج التجميttع
للتحقق يدوًيا من أن الشيفرة البرمجية هي األفضل أو الكتشاف أخطاء المصِّرف ،وُيَع د ذلك أكثر شيوًع ا ممttا هttو
المجِّم ع assemblyهو عملية آلية لتحويل شيفرة التجميع إىل صيغة ثنائية .يحتفttظ المجّم ع بجttدول كبttير
لكل تعليمة ممكنة ولنظيرها الثنائي الذي يسمى شيفرة العملية .Op Codeيدمج المجّم ع شttيفرات العمليttات
مع المسجالت المحَّد دة في شيفرة التجميع إلنتاج ملف ثنائي بوصفه خرًجا.
ُيطلق عىل هذه الشيفرة بشيفرة التعليمات الُم صَّرفة ،Object Codeوهي شيفرة غير قابلة للتنفيذ في هذه
المرحلة ،وُتعد مجرد تمثيل ثنائي للدخل الذي يمثل شيفرة برمجية مصدريةُ .يفَّض ل أاّل يضttع المttبرمج الشttيفرة
7.5الرابط Linker
سُتقَس م في أغلب األحيان الشيفرة البرمجية في برنامج كبير إىل ملفات متعددة لتكون الttدوال ذات الصttلة
مع بعضها بعًض ا .يمكن تصريف كل ملٍف من هذه الملفات إىل شيفرة تعليمtات ُم صَّtرفة ولكن هtدفك النهtائي
هو إنشاء ملف قابل للتنفيذ .يجب أن يكون هناك طريقة ما لدمجها في ملف واحد قابل للتنفيttذ ،حيث نسttمي
الحظ أنه ال يزال يجب ربط برنامجك بمكتبات نظام معينة للعمttل بصttورة صttحيحة حttتى إن كttان برنامجttك
مناسًبا لملٍف واحد ،إذ يكون االستدعاء printfمثاًل في مكتبة يجب دمجها مع ملفك القابل للتنفيttذ ليعمttل،
لذا ال تزال هناك بالتأكيد عملية ربط تحدث إلنشاء ملفك القابttل للتنفيttذ بttالرغم من أنttه ال داعي للقلttق صttراحًة
171
علوم الحاسوب من األلف إىل الياء سلسلة األدوات Toolchain
7.5.1الرموز Symbols
لجميع المتغيرات والدوال أسماء في الشيفرة المصدرية ،إذ نشير إليها باستخدام هذه األسماء .تتمثل إحدى
طرق التفكير في تعليمة التصريح عن متغير int aفي أنك تخبر المصِّtرف بttأن يحجttز حttيًزا من الttذاكرة بحجم
) ،sizeof(intوبالتالي كلما استخدمت اسم المتغير ،aفسيشير إىل هذه الذاكرة المخَّص صttة ،وكttذلك األمttر
بالنسبة للدالة التي تخبر المصِّرف بأن يحّزن هtذه الشtيفرة البرمجيtة في الtذاكرة ،ثم ينتقtل إليهtا وينّف ذها عنtد
استدعاء الدالة )( .functionوبالتالي نستدعي الرمttزين aو functionألنهمttا ُيَع دان تمttثياًل رمزًي ا لمنطقٍttة
من الذاكرة.
تساعد هذه الرموز البشر عىل فهم البرمجة .لكن يمكنك القول أن المهمttة األساسttية لعمليttة التصttريف هي
إزالة هذه الرموز ،إذ ال يعرف المعالج ما يمثله الرمز ،aفكل ما يعرفه هو أن لديه بعض البيانات في عنttوان ذاكttرة
معين .تحِّو ل عملية التصريف التعليمة a += 2إىل العبارة "زيادة القيمة الموجودة في العنوان 0xABCDEمن
ال بد أنك شاهدت في البرامج المكتوبة بلغة Cالمصطلحين staticو externمttع المتغttيرات ،إذ يمكن
الttدوال مشttاركَة متغttيٍر مttا .نريttد تعريًف ا لنفttترض أنttك قسttمت برنامجttك إىل ملفين ،ولكن تريttد بعُض
Definitionأو موقًع ا واحًدا فقط في الذاكرة للمتغير المشترك وإال فال يمكن مشttاركته ،ولكن يجب أن يشttير كال
الملفين إليه .يمكن ذلك من خالل التصريح عن المتغير في ملف واحttد ،ثم نصّtر ح في الملttف اآلخttر عن متغttير
باالسم نفسه مع البادئة externالتي ترمز إىل أنه خارجي Externalوترمز للمبرمج بأن هذا المتغير ُم صَّر ٌح عنه
تخبر الكلمة externالمصِّرف أنه ال ينبغي تخصيص أي مساحة في الذاكرة لهذا المتغير ،ويجب تttرك هttذا
الرمز في التعليمات الُم صَّرفة إلصالحه الحًق ا .ال يمكن للمصِّرف أن يعرف مكان تعريف الرمز فعلًي ا ولكن الرابttط
Linkerيمكنه ذلك ،فوظيفته هي النظر في جميع ملفات التعليمttات الُم صَّtرفة ودمجهttا في ملttف واحttد قابttل
للتنفيذ .لذا سيرى الرابط هذا الرمز في الملف الثاني ،وسيقول" :رأيت هذا الرمز مسبًق ا في الملف ،1وأعلم أنttه
يشير إىل موقع الذاكرة ،"0x12345وبالتالي يمكن تعديل قيمة الرمز لتكون قيمtة الtذاكرة للمتغtير الموجtود في
الملف األول.
ُتَع د الكلمة ساكن staticعكس خttارجي externتقريًب ا ،ألنهttا تضttع قيttوًدا عىل رؤيttة الرمttز الttذي نريttد
تعديله .إذا صّرحَت عن متغير بأنه ساكن ،staticفهذا يعttني للمصّtرف بttأال يttترك أّي رمttوز لهttذا المتغttير في
شيفرة التعليمات المصَّرفة ،وبالتالي لن يرى الرابط هذا الرمز أبًدا عندما يربط ملفttات التعليمttات الُم ص َّtرفة مttع
بعضها البعض ،أي ال يمكنه القول بأنه رأى هذا الرمز سابًق اُ .يَع د اسtتخدام الكلمtة staticمفيًtدا للفصtل بين
172
علوم الحاسوب من األلف إىل الياء سلسلة األدوات Toolchain
الرموز وتقليل التعارضات بينها ،إذ يمكنك إعادة استخدام اسم المتغير الُم صَّtر ح عنttه بأنttه staticفي ملفttات
أخرى دون وجود تعارضات بين الرموز .يمكن القول بأننا نقّي د رؤية الرمز ،ألننا ال نسمح للرابط برؤيته بعكس الرمز
7.5.2عملية الربط
تتكون عملية الربط من خطوتين هما :دمج جميع ملفات التعليمات الُم صَّرفة في ملف واحttد قابttل للتنفيttذ
ثم االنتقال إىل كل ملف لتحليل الرموز .يتطلب ذلك تمريرين ،أحttدهما لقttراءة جميttع تعريفttات الرمttوز وتttدوين
الرموز التي لم ُتحَّلل والثاني إلصالح تلك الرموز التي لم ُتحَّلل في المكان الصحيح.
يجب أن يكون الملف القابل للتنفيذ النهائي بدون رموز غير ُم حَّللة ،إذ سيفشل الرابط مع وجود خطأ بسبب
هذه الرموز .نسمي ذلك بالربط الساكن ،Static Linkingفالربط الtديناميكي هttو مفهtوم مشtابه ُيطَّب ق ضttمن
الملف القابل للتنفيذ في وقت التشغيل ،حيث سنتطرق إليه الحًق ا.
تعّرفنttا الفقttرات السttابقة عىل الخطttوات الثالث لبنttاء ملttف قابttل للتنفيttذ هي :التصttريف Compiling
والتجميttع Assemblingوالربttط ،Linkingوسttنطّبق في الفقttرات التاليttة هttذه الخطttوات عملًي ا لبنttاء ملttف
Assemblingوالربط ،Linkingوسنطّبق اآلن هذه الخطوات عملًي ا لبناء ملف قابل للتنفيذ.
تابع فيما الخطوات المتبعة لبناء تطبيق بسيط خطوة بخطttوة .الحtظ أن األمtر gccيشّtغ ل برنttامج تشtغيٍل
driver programيخفي معظم الخطوات عنك ،وهذا هو ما تريده بالضبط في ظل الظروف العادية ،ألن األوامر
والخيارات الدقيقة للحصول عىل ملف قابٍل للتنفيذ عىل نظام حقيقي يمكن أن تكون معقدة للغاية وخاصًة بكttل
سنشرح عملية التصريف في المثttالين التttاليين ،حيث سنسttتخدم ملفين مصttدريين مكتttوبين بلغttة ،Cإذ
يعّرف أحدهما الدالة الرئيسية )( mainالتي ُتَع د نقطة الدخول األولية ،ويصّر ح الملف اآلخttر عن دالttة مسttاعدة،
>#include <stdio.h
173
علوم الحاسوب من األلف إىل الياء سلسلة األدوات Toolchain
*/بما أن هذا المتغير ساكن ،staticفيمكننا تعريفه في كٍّل من الملفين hello.cو/* function.c
)int main(void
{
*/يجب أن تعيد الدالة )( functionقيمة المتغير العام /* global
;)"!int ret = function("Hello, World
;)exit(ret
}
>#include <stdio.h
ُ */م صّرح عنه بأنه خارجي externألنه ُم عَّرف في الملف /* hello.c
;extern int global
7.6.1الترص يف Compiling
لكل المصِّرفات خياٌر لتنفيذ الخطوة األوىل من التصريف فقtط مثtل اسtتخدام الرايtة -Sلوضtع الخtرج في
ملف يحمل اسم ملف الدخل نفسه ولكن مع الالحقة ،.sوبالتالي يمكننا عرض الخطttوة األوىل باسttتخدام األمttر
174
علوم الحاسوب من األلف إىل الياء Toolchain سلسلة األدوات
.file "function.c"
.pred.safe_across_calls p1-p5,p16-p63
.section .sdata,"aw",@progbits
.align 4
.type i#, @object
.size i#, 4
i:
data4 100
.section .rodata
.align 8
.LC0:
stringz "%s\n"
.text
.align 16
.global function#
.proc function#
function:
.prologue 14, 33
.save ar.pfs, r34
alloc r34 = ar.pfs, 1, 4, 2, 0
.vframe r35
mov r35 = r12
adds r12 = -16, r12
mov r36 = r1
.save rp, r33
mov r33 = b0
.body
;;
st8 [r35] = r32
addl r14 = @ltoffx(.LC0), r1
;;
ld8.mov r37 = [r14], .LC0
ld8 r38 = [r35]
br.call.sptk.many b0 = printf#
mov r1 = r36
;;
175
علوم الحاسوب من األلف إىل الياء سلسلة األدوات Toolchain
ُتَع د عملية التجميع Assemblyمعقدة قلياًل ،ولكن يجب أن تكون قادًرا عىل معرفة مكان تعريف المتغttير i
بوصtttفه data4أي 4بايتtttات أو 32بت بحجم النtttوع ،intومكtttان تعريtttف الدالtttة ( functionبالشtttكل
أصبح لدينا اآلن ملفا تجميع جاهزين لتجميعهما في شيفرة اآللة البرمجية .machine code
7.6.2التجميع Assembly
التجميع هو عملية مباشرة إىل حد ما ،وُيطَلق عىل المجّم ع asويأخذ وسائًط ا بطريقة مماثلة لألمر .gcc
تنتج عن عملية التجميع التعليمات الُم صَّرفة ،Object Codeحيث تكون هذه الشيفرة جttاهزًة لربطهttا مttع
بعضها البعض في الملف النهائي القابل للتنفيذ .يمكنك تخطي االضطرار إىل استخدام الُم جِّم ع يttدوًيا من خالل
استدعاء المصِّرف مع الراية -cالتي تحّو ل ملف الدخل مباشرًة إىل شيفرة كttائن ،وتضttعها في ملttف لttه البادئttة
176
علوم الحاسوب من األلف إىل الياء Toolchain سلسلة األدوات
تخدام بعضttا اسtt ولكن يمكنن،ةttيغة ثنائيttا في صttرًة ألنهttال يمكننا فحص شيفرة التعليمات الُم صَّرفة مباش
وزttتعرض الرمttتي سtt الreadelf --symbols ل األداةttَّرفة مثttات الُم صttات التعليمttاألدوات لفحص ملف
177
علوم الحاسوب من األلف إىل الياء سلسلة األدوات Toolchain
ُيَع د هذا الخرج معقًدا للغاية ،ولكن يجب أن تكون قادًرا عىل فهم الكثير منه مثل:
الحظ الرمز الذي يحمل االسم iفي الخرج ،hello.oحيث ُيسَtبق هttذا الرمttز بالكلمtة LOCALأي أنtه •
محلي ،ألننا صّرحنا عنه بأنه ساكن ،staticوبالتالي ُيمَّيز عىل أنه محلي لملف الكائن.
الحظ المتغير globalفي الخرج نفسه الُم عَّرف عىل أنه متغير عام ،GLOBALمما يعني أنه مرئي خارج •
الحttظ أن الرمttز functionلttه النttوع UNDأو غttير ُم عَّttرف Undefinedمن أجttل اسttتدعاء الدالttة •
7.6.3الربط Linking
ُيَع د استدعاء الرابط الُم سَّم ى ldعمليًة معقدة للغاية عىل نظام حقيقي ،لذلك نترك عملية الربط لألمttر ،gcc
ولكن يمكننا التعّرف عىل ما يفعله داخلًي ا باستخدام الراية -vالتي ترمز إىل Verboseأي ُم فَّص لة.
/usr/lib/gcc-lib/ia64-linux/3.3.5/collect2 -static
/usr/lib/gcc-lib/ia64-linux/3.3.5/../../../crt1.o
/usr/lib/gcc-lib/ia64-linux/3.3.5/../../../crti.o
/usr/lib/gcc-lib/ia64-linux/3.3.5/crtbegin.o
-L/usr/lib/gcc-lib/ia64-linux/3.3.5
-L/usr/lib/gcc-lib/ia64-linux/3.3.5/../../..
hello.o
function.o
--start-group
-lgcc
-lgcc_eh
-lunwind
178
علوم الحاسوب من األلف إىل الياء سلسلة األدوات Toolchain
-lc
--end-group
/usr/lib/gcc-lib/ia64-linux/3.3.5/crtend.o
/usr/lib/gcc-lib/ia64-linux/3.3.5/../../../crtn.o
أول شيء تالحظه هو استدعاء برنامج باالسم collect2وهو عبارة عن ُم غِّلف للرابط ،ldويسttتخدم األمttر
gccداخلًي ا .الشيء اآلخر الذي ستالحظه هو ملفات الكائنات التي تبدأ بالرمز crtأي أنهtا ُم حَّtد دة للرابtطُ .ي وّف ر
األمر gccومكتبات النظام هذه الدوال التي تحتوي عىل الشيفرة البرمجية المطلوبة لبدء البرنttامج .ال ُتَع د الدالttة
الرئيسية )( mainأول دالة ُم سttتدعاة عنttد تشttغيل البرنttامج ،بttل ُتسttتدَع ى أواًل الدالttة _startالموجttودة في
ملفttات الكائنttات ،crtحيث تضttبط هttذه الدالttة بعض اإلعttدادات العامttة الttتي ال يجب أن يقلttق مttبرمجو
التطبيقات بشأنها.
ُيَع د تسلسل المسار الهرمي معقًدا للغاية ،ولكن يمكننا أن نttرى أن الخطttوة األخttيرة هي ربttط بعض ملفttات
:crt1.oتوفره مكتبات النظام ،libcويحتوي عىل الدالtة _startالtتي ُتَع د أول شttيء ُيسtتدَع ى في •
البرنامج.
crtbegin.o •
crtsaveres.o •
crtend.o •
crtn.o •
يمكنك أن ترى بعد ذلك أننا نربttط ملفي الكائنttات hello.oو ،function.oثم نحّttدد بعض المكتبttات
اإلضافية باستخدام رايات ،-lحيث ُتَع د هذه المكتبات خاصًة بالنظام ومطلوبة لكل برنامج .الرايttة الرئيسttية هي
الراية -lcالتي تجلب مكتبة Cالتي تحتوي عىل جميع الدوال المشتركة مثل الدالة )( .printfنربط بعد ذلttك
مرة أخرى بعض ملفات كائنات النظام التي تطّبق بعض عمليات التنظيف بعد انتهاء البرامجُ .تَع د هذه التفاصيل
سنربط بعدها جميع ملفات التعليمات الُم صَّرفة مع بعضها بملف واحد قابل للتنفيذ وجاهز للتشغيل.
179
علوم الحاسوب من األلف إىل الياء Toolchain سلسلة األدوات
180
علوم الحاسوب من األلف إىل الياء Toolchain سلسلة األدوات
181
علوم الحاسوب من األلف إىل الياء Toolchain سلسلة األدوات
182
علوم الحاسوب من األلف إىل الياء Toolchain سلسلة األدوات
dynsym وزttل رمttة عمttرح كيفيtt سنش.symtab وdynsym :اttوز همttالحظ وجود نوعين من جداول الرم •
183
علوم الحاسوب من األلف إىل الياء سلسلة األدوات Toolchain
الحظ الرموز العديدة الُم ضَّم نة من ملفات الكائنات اإلضافية ،حيث يبدأ الكثttير منهttا بttالرمز __ لتجنب •
التعارض مع األسماء التي يختارها المبرمج .اقرأ واختر الرموز التي ذكرناهttا سttابًق ا من ملفttات الكائنttات
184
.8ما وراء العملية
باسم النص Textوالبيانات .Dataال يبقى الملف القابل للتنفيذ في الذاكرة ،ولكنه يقضttي معظم وقتttه بوصttفه
ملًف ا عىل القرص الصلب ينتظر تحميله عند التشغيلُ .يَع د الملف مجرد مصفوفة متجاورة من البتات ،لذا تبتكttر
جميع األنظمة طرًق ا لتنظيم الشيفرة البرمجية والبيانات ضمن الملفات للتنفيذ عند الطلب ،حيث يشار إىل هttذه
الصيغة من الملفات باسم ملف ثنائي Binaryأو ملف قابttل للتنفيttذ ،Executableوتكttون البتttات والبايتttات
الخاصة بالملف بصيغة جاهزة لوضعها في الذاكرة وتفسيرها مباشرًة بواسطة عتاد المعالج.
ضمن الملف الثنائي ،حيث ُتَع د الشيفرة البرمجية والبيانات القسtمين األساسtيين لملٍtف قابtل للتنفيtذ ،وأحtد
المكونtttات اإلضtttافية الtttتي لم نtttذكرها حtttتى اآلن هtttو مسtttاحة تخtttزين المتغtttيرات العامtttة غtttير الُم هَّي أة
إذا صّرحنا عن متغير وأعطيناه قيمة أولية ،فيجب تخزين هذه القيمttة في ملttف قابttل للتنفيttذ بحيث يمكن
تهيئته بالقيمة الصحيحة عند بدء البرنامج ،ولكن هناك العديد من المتغيرات غير المهيttأة أو الttتي قيمتهttا صttفر
عند تنفيذ البرنامج ألول مرةُ .يَع د حجز مساحة لهذه المتغيرات في الملف القابل للتنفيذ ثم تخزين قيم صفرية أو
فارغة NULLهدًرا للمساحة ،مما يtؤدي إىل تضّttخم حجم الملttف القابtل للتنفيttذ عىل القttرص الصttلب دون داع
لذلكُ .تعِّرف معظم الصيغ الثنائيttة مفهttوم القسttم BSSاإلضttافي بوصttفه حجًم ا بttدياًل للبيانttات الصttفرية غttير
الُم هَّيأة .يمكن تخصيص الذاكرة اإلضtافية الtتي يحّtددها القسtم BSSوضtبطها عىل القيمtة صtفر عنtد تحميtل
علوم الحاسوب من األلف إىل الياء ما وراء العملية
البرنامج .يرمز االختصار BSSإىل العبارة ،Block Started by Symbolوهو أمttر بلغttة تجميttع حاسttوب IBM
الملف بصيغة محttددة وواضttحة بحيث يمكن للمصِّtرف إنشttاؤه ويمكن لنظttام التشttغيل تحديttده وتحميلttه في
الذاكرة وتحويله إىل عملية ُم شَّغ لة يمكن لنظام التشغيل إدارتها .يمكن أن تكون هذه الصيغة من الملفات القابلttة
للتنفيذ خاصة بنظام التشغيل ،إذ ال نتوقع تنفيذ برنامج ُم صَّرف لنظtاٍم مtا عىل نظtام آخtر مثtل أن تعمtل بtرامج
ويندوز عىل نظام لينكس أو أن تعمل برامج لينكس عىل نظام .macOS
لكن خيط المعالجة Threadالمشترك بين جميع صيغ الملفات القابلttة للتنفيttذ هttو أنهttا تتضttمن ترويسttة
معيارية ُم عَّرفة مسبًق ا توّض ح كيفية تخزين شيفرة وبيانات البرنامج في بقية الملف ،حيث يمكن أن تشttرح ذلttك
بالكلمات مثل أن نقول" :تبدأ شيفرة البرنامج من 20بايت في هذا الملف ،ويبلغ طولها 50كيلوبttايت ،وتتبعهttا
هناك صيغة معينة أصبحت في اآلونة األخيرة معياًرا لتمثيل الملفات القابلttة للتنفيttذ في األنظمtة الحديثttة
القائمة عىل نظام يونكس ،ويطَلق عىل هذا التنسيق بصيغة الرابط والملفات القابلة للتنفيttذ Executable and
- Linker Formatأو ELFاختصاًرا ،حيث سنشرحها بمزيد من التفصيل الحًق ا.
اa.out .
لم تكن صيغة ملفات ELFالمعيار دائًم ا ،إذ استخدمت أنظمة يونكس األصلية صيغة ملف باالسttم .a.out
يمكننا أن نرى آثار ذلك عند تصريف برنامج بtدون الخيttار -oلتحديtد اسttم ملttف الخttرج ،حيث سينشtأ الملttف
القابل للتنفيذ باالسم االفتراضttي a.outالttذي ُيَع د اسttم ملttف الخttرج االفتراضttي النttاتج عن الرابttط .Linker
يستخدم المصِّtرف Compilerأسttماء الملفttات الُم نَش أة عشttوائًي ا بوصttفها ملفttات وسttيطة لشttيفرة التجميttع
a.outهو صيغة ترويسة بسيطة تسمح فقط بقسم واحد للبيانات والشيفرة و ،BSSوهذا غير كاٍف لألنظمة
186
علوم الحاسوب من األلف إىل الياء ما وراء العملية
بCOFF .
كانت صيغة ملف التعليمات الُم صَّرفة المشترك - Common Object File Formatأو COFFاختصttاًرا-
مقدمة لظهور صيغة ملفات ،ELFحيث كانت صيغة ترويستها أكثر مرونة ،مما يسمح بمزيد -ولكن محttدود -من
األقسام في الملف.
تواجttه صttيغة COFFصttعوبات في دعم المكتبttات المشttتركة ،لttذا اختttيرت صttيغة ELFبوصttفها تقttديًم ا
Implementationبدياًل عىل نظام لينكس .لكن توجد صيغة COFFفي مايكروسوفت وينttدوز بوصttفها صttيغة
ملفات قابلة للتنفيذ والنقل - Portable Executableأو PEاختصاًرا -التي ُتَع د بالنسبة إىل ويندوز مثل صttيغة
تمثيل النواة Kernelثنائًي ا بسهولة مثل تمثيل ملف قابل للتنفيذ أو مكتبة نظام عادية .يمكن اسttتخدام األدوات
نفسها لفحص وتشغيل جميع ملفات ELFويمكن للمطورين الذين يفهمttون صttيغة ملفttات ELFاالسttتفادة من
توّس ع الصيغة ELFصيغة الملفات COFFوتمنح الترويسة مرونة كافية لتحديد عttدد عشttوائي من األقسttام،
بحيث يكون لكل منها خاصياته الخاصة ،مما يسّه ل الربط الديناميكي وتنقيح األخطاء .Debugging
187
علوم الحاسوب من األلف إىل الياء ما وراء العملية
ةtة برمجtق واجهtوارد في توثيtو الtف عىل النحtالي الوصtال التtح المثt يوض.فtا الملtون منهtاألقسام التي يتك
:ELF الذي يعّرف ترويسةC وهو تخطيط لبنية لغة،)ELF بت من صيغة ملفات32 (نموذجELF32 تطبيقات
typedef struct {
unsigned char e_ident[EI_NIDENT];
Elf32_Half e_type;
Elf32_Half e_machine;
Elf32_Word e_version;
Elf32_Addr e_entry;
Elf32_Off e_phoff;
Elf32_Off e_shoff;
Elf32_Word e_flags;
Elf32_Half e_ehsize;
Elf32_Half e_phentsize;
Elf32_Half e_phnum;
Elf32_Half e_shentsize;
Elf32_Half e_shnum;
Elf32_Half e_shstrndx;
} Elf32_Ehdr;
ELF Header:
Magic: 7f 45 4c 46 01 02 01 00 00 00 00 00 00 00 00 00
Class: ELF32
Data: 2's complement, big endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: PowerPC
Version: 0x1
188
علوم الحاسوب من األلف إىل الياء ما وراء العملية
][...
يوّض ح المثال السابق نموذًجا سهل القراءة عىل اإلنسان كما مولًدا باسttتخدام برنttامج ،readelfوهttو جttزء
ُتوَجد المصفوفة e_identفي بداية أّي ملف ،ELFوتبدأ دائًم ا بمجموعة بايتات سحرية .البttايت األول هttو
0x7Fثم الثالثttة بايتttات التاليttة هي " ."ELFيمكنtك فحص ملttف ELFالثنtائي لtترى ذلtك بنفسtك باسttتخدام
األمر .hexdump
الحظ وجود البت 0x7Fفي البداية ثم سلسلة آسكي ASCIIالُم شَّف رة " ."ELFألِق نظرة عىل المعيار وشttاهد
ما تعّرفه بقية المصفوفة وما هي القيم الموجودة في الملف الثنائي .لدينا بعtد ذلtك بعض الرايtات Flagsلنtوع
الجهاز الذي ُانِش ئ هذا الملف الثنائي من أجله .الحظ أن صيغة ELFتعّرف إصttدارات مختلفttة من األحجttام مثttل
إصدارات 32بت و 64بت ،حيث سنشرح هنا اإلصدار 32بت .يكمن االختالف في أنه يجب االحتفاظ بالعناوين
عىل أجهزة 64بت في متغيرات بحجم 64بت .يمكننا أن نرى أن الملttف الثنttائي ُأنِش ئ للجهttاز الttذي يسttتخدم
صيغة ( Big Endianتخزين البتات األقل أهمية أواًل ) ،حيث يستخدم هذا الجهاز المكمل الثنائي لتمثيل األعداد
189
علوم الحاسوب من األلف إىل الياء ما وراء العملية
يبدو أن عنوان نقطة الدخول واضح وصريح بدرجة كافية ،وهو العنtوان الموجtود في الtذاكرة الtذي تبtدأ منtه
شيفرة البرنtامجُ .يقtال لمtبرمجي لغtة Cالمبتtدئين أن الدالtة الرئيسtية )( mainهي أول برنtامج ُيسtتدَع ى في
برامجهم ،ولكن يمكننا التحقق من أنه ليس كذلك باستخدام عنوان نقطة الدخول كما يلي:
)int main(void
{
;)printf("main is : %p\n", &main
;return 0
}
$ ./test
main is : 0x10000430
الحظ في المثال السابق أنه يمكننا أن نرى أن نقطة الدخول هي دالttة تسttمى ._startلم يع ّtرف برنامجنttا
هذه الدالة عىل اإلطالق ،ويشير الخط السفلي في بداية اسم الدالة إىل أنها موجttودة في فضttاء أسttماء منفصttل.
تحتوي الترويسة بعد ذلك عىل مؤشtرات إىل المكtان الموجtود في الملtف الtذي تبtدأ فيtه األجtزاء المهمtة
190
علوم الحاسوب من األلف إىل الياء ما وراء العملية
الملفُ .تَع د الرمtوز مطلوبtة للربtط ،Linkingفمثاًل يمكن أن يتطلب إسtناُد قيمtة للمتغtير fooالُم صَّtر ح عنtه
بالشكل extern int fooرابًط ا للعثور عىل عنوان المتغير ،fooوالذي يمكن أن يتضمن البحث عن الكلمة
ترتبط المنقوالت Relocationsارتباًط ا وثيًق ا بtالرموز ،حيث ُيَع د االنتقtال مسtاحًة فارغtة ُت تَرك إلصtالحها
الحًق ا ،إذ ال يمكن استخدام المتغير fooفي المثال السابق حتى معرفة عنوانttه ،ولكن نعلم في نظttام 32بت أن
عنوان المتغير fooيجب أن يكون بقيمة 4بايتات ،لذلك يمكن للمصِّرف ببساطة ترك مساحة فارغttة بمقttدار 4
بايتات واالحتفاظ بانتقاٍل Relocationيخبر الرابط بأن يضع القيمة الحقيقيttة للمتغttير fooفي هttذه المسttاحة
التي مقدارها 4بايتات في هذا العنوان في أّي وقت يحتاج فيه المصِّtرف اسttتخداَم هttذا العنttوان إلسttناد قيمttة
لمصممي األنظمة .سنتحدث عن األقسام الموجودة في شيفرة الكائن التي تنتظر أن ُترَبط بملف قابttل للتنفيttذ،
من األسهل في بعض األحيان النظر إىل المستوى األعىل من التجريد abstractionالمتمثل بالمقاطع قبل
فحص الطبقات السفلية .يحتوي ملف ELFعىل ترويسة تصف تخطيط الملف العام ،حيث تشttير ترويسttة ELF
إىل مجموعة أخرى من الترويسات تسمى ترويسات البرامج ،Program Headersحيث تِص ف هذه الترويسات
لنظام التشغيل أّي شيء يمكن أن يكون مطلوًبا لتحميل الملف الثنائي في الذاكرة وتنفيذه .كما تصف ترويسات
البرامج المقاطع ،ولكن هناك بعض األشياء األخرى المطلوبة لتشغيل الملف القابل للتنفيذ.
{ typedef struct
;Elf32_Word p_type
Elf32_Off ;p_offset
;Elf32_Addr p_vaddr
;Elf32_Addr p_paddr
;Elf32_Word p_filesz
;Elf32_Word p_memsz
191
علوم الحاسوب من األلف إىل الياء ما وراء العملية
;Elf32_Word p_flags
;Elf32_Word p_align
}
يوضح المثال السابق تعريف ترويسtة البرنtامج .ال بtد أنtك الحظت من تعريtف ترويسtة ELFسtابًق ا وجtود
الحقول e_phoffو e_phnumو e_phentsizeالتي تمّثل اإلزاحttة في الملttف حيث تبttدأ ترويسttات الttبرامج
وعدد ترويسات البرامج الموجttودة وحجم كttل ترويسttة برنttامج ،وبالتttالي يمكنttك العثttور عىل ترويسttات الttبرامج
ُتَع د ترويسات البرامج أكثر من مجرد مقاطع ،حيث يعّرف الحقل p_typeما تعّرفه ترويسة البرنttامج ،فمثاًل
إذا كttان هttذا الحقttل هttو ،PT_INTERPفسُttتعَّرف الترويسttة بأنهttا مؤشttر سلسttلة نصttية يؤّش ر إىل مفّس ر
Interpreterالملttف الثنttائي .ناقشttنا سttابًق ا الفttرق بين اللغttات الُم صَّttرفة Compiledواللغttات الُم فَّس رة
Interpretedومّي زنا الُم صِّرف بأنه ينشئ ملًف ا ثنائًي ا يمكن تشغيله بطريقة مستقلة .لكن ال بد أنttك تتسttاءل عن
سبب حاجتنا لمفّس ر! حسًنا ،ترغب األنظمة الحديثة في المرونة عند تحميل الملفات القابلة للتنفيذ ،لذا ال يمكن
الحصول عىل بعض المعلومات بصورة كافية إاّل في الوقت الفعلي الذي ُيَع د فيttه البرنttامج للتشttغيل ،وهttذا مttا
يسمى بالربط الديناميكي Dynamic Linkingالذي سنتحدث عنه الحًق ا ،وبالتالي يجب إجراء بعض التغيttيرات
الطفيفة عىل البرنامج الثنائي للسماح له بالعمttل بصttورة صttحيحة في وقت التشttغيل .لttذا ُيَع د مفّس ر الملttف
الثنائي المعتاد هو المحّم ل الديناميكي ،Dynamic Loaderألنه يأخذ الخطوات النهائية إلكمال تحميل الملttف
تصف القيمة PT_LOADفي الحقل p_typeالمقاطع ،ثم تصف الحقول األخرى في ترويسttة البرنttامج كّttل
مقطع منها .يخبرك الحقل p_offsetبمقدار ُبعد بيانات المقطttع عن الملttف الموجttود عىل القttرص الصttلب.
بينمttا يخttبرك الحقttل p_vaddrبttالعنوان الttذي يجب أن توجttد عنttده البيانttات في الttذاكرة الوهميttة Virtual
،Memoryحيث يصttف الحقttل p_addrالعنttوان الحقيقي Physical Addressالttذي ُيَع د مفيًttدا لألنظمttة
المدَم جttة الصttغيرة الttتي ال تطّب ق الttذاكرة الوهميttة .تخttبرك الرايتttان p_fileszو p_memszبحجم المقطttع
الموجود عىل القرص الصلب وكم يجب أن يكون حجمه في الذاكرة .إذا كttان حجم الtذاكرة أكttبر من حجم القttرص
الصلب ،فيجب ملء التداخل بينهما باألصفار ،وبالتالي يمكنك توفير مساحة كبيرة في ملفاتك الثنائية من خالل
عدم االضطرار إىل هدر مساحة للمتغيرات العامة الفارغة .أخttيًرا ،يشttير الحقttل p_flagsإىل أذونttات المقطttع،
حيث يمكن تحديد أذونات التنفيذ والقراءة والكتابة ،فمثاًل يجب تميttيز مقttاطع الشttيفرة البرمجيttة بأنهttا للقttراءة
والتنفيذ فقط ،وتمييز أقسام البيانات للقراءة والكتابة فقط بدون تنفيذ.
هناك عدد من أنواع المقاطع األخرى الُم عَّرفة في ترويسات البرامج الموصوفة كاملًة في مواصفات المعايير.
192
علوم الحاسوب من األلف إىل الياء ما وراء العملية
تشّكل األقسام مقاطًع ا ،حيث ُتَع د األقسttام طريقttة لتنظيم الملttف الثنttائي في منttاطق منطقيttة لتوصttيل
المعلومttات بين المصِّtرف والرابttطُ .تسttتخَدم األقسttام في بعض الملفttات الثنائيttة الخاصttة مثttل نttواة لينكس
رأينا كيف تصل المقاطع في النهاية إىل كتلة بيانات في ملف عىل القرص الصttلب مttع بعض المواصttفات
حول المكان الذي يجب تحميلها فيه واألذونات التي تمتلكها .تمتلك األقسام ترويسًة مماثلtة لترويسtة المقtاطع
{ typedef struct
;Elf32_Word sh_name
;Elf32_Word sh_type
;Elf32_Word sh_flags
;Elf32_Addr sh_addr
Elf32_Off ;sh_offset
;Elf32_Word sh_size
;Elf32_Word sh_link
;Elf32_Word sh_info
;Elf32_Word sh_addralign
;Elf32_Word sh_entsize
}
تحتtttوي األقسtttام عىل عtttدد من األنtttواع الُم عَّرفtttة للحقtttل sh_typeمثtttل تعريtttف قسtttم من النtttوع
SH_PROGBITSبوصفه قسًم ا يحتوي عىل بيانات ثنائية يستخدمها البرنttامج .تشttير الرايttات األخttرى إىل مttا إذا
كان هذا القسم جدوَل رموز يستخدمه الرابط أو منقح األخطttاء مثاًل أو يمكن أن يكttون شttيًئا مttا خاًص ا بالمحّم ل
الديناميكي .كما توجد سمات إضافية مثل سمة التخصيص Allocateالتي تشير إىل أن هذا القسم سيحتاج إىل
>#include <stdio.h
;]int big_big_array[10*1024*1024
193
علوم الحاسوب من األلف إىل الياء ما وراء العملية
int main(void)
{
big_big_array[0] = 100;
printf("%s\n", a_string);
a_var_with_value += 20;
}
رجttذا الخttتخدام هttا باسt حيث يمكنن،رىtزاء األخtع بعض األجt مreadelf يوضح المثال التالي خرج األداة
:تحليل كل جزء من برنامجنا البسيط السابق ومعرفة ما سيحدث به في خرج الملف الثنائي النهائي
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf
Al
[ 0] NULL 00000000 000000 000000 00 0 0
0
[ 1] .interp PROGBITS 10000114 000114 00000d 00 A 0 0
1
[ 2] .note.ABI-tag NOTE 10000124 000124 000020 00 A 0 0
4
[ 3] .hash HASH 10000144 000144 00002c 04 A 4 0
4
[ 4] .dynsym DYNSYM 10000170 000170 000060 10 A 5 1
4
[ 5] .dynstr STRTAB 100001d0 0001d0 00005e 00 A 0 0
1
[ 6] .gnu.version VERSYM 1000022e 00022e 00000c 02 A 4 0
2
[ 7] .gnu.version_r VERNEED 1000023c 00023c 000020 00 A 5 1
4
[ 8] .rela.dyn RELA 1000025c 00025c 00000c 0c A 4 0
4
[ 9] .rela.plt RELA 10000268 000268 000018 0c A 4 25
4
194
علوم الحاسوب من األلف إىل الياء ما وراء العملية
195
علوم الحاسوب من األلف إىل الياء ما وراء العملية
[33] .debug_str PROGBITS 00000000 0010ce 0000f0 01 MS 0 0
1
[34] .shstrtab STRTAB 00000000 0011be 00013b 00 0 0
1
[35] .symtab SYMTAB 00000000 0018c4 000c90 10 36 65
4
[36] .strtab STRTAB 00000000 002554 000909 00 0 0
1
Key to Flags:
)W (write), A (alloc), X (execute), M (merge), S (strings
)I (info), L (link order), G (group), x (unknown
)O (extra OS processing required) o (OS specific), p (processor specific
لنلِق أواًل نظرة عىل المتغير big_big_arrayالذي -كما يوحي االسم -هو مصفوفة عامة كبيرة إىل حد مttا،
وإذا انتقلنا إىل جدول الرموز ،فيمكننا أن نرى أن هttذا المتغttير موجttود في الموقttع 0x100109ccالttذي يمكننttا
ربطه بالقسم .bssفي قائمة األقسام ألنه يبدأ تحته مباشرًة عند الموقع ،0x100109c8والحظ حجمه الكبير.
ذكرنا أن القسم BSSهو جزء معياري من صورة ثنائية ،ألنttه ليس منطقًي ا أن تطلب أن يكttون لملٍttف ثنttائي
عىل القرص الصلب 10ميجابايتات من المساحة المخَّص صة له عندما تكtون كttل هttذه المسtاحة قيًم ا صttفرية.
الحظ أن هذا القسم يحتوي عىل النوع ،NOBITSمما يعني أنه ال يحتوي عىل أّي بايت عىل القرص الصttلب .لttذا
ُيعَّرف القسم .bssللمتغيرات العامة التي يجب أن تكون قيمتها صفًرا عند بttدء البرنttامج .رأينttا كيttف يمكن أن
يختلف حجم الذاكرة عن حجم القرص الصلب عند مناقشتنا للمقاطع ،فوجود المتغيرات في القسم .bssدليttل
يوجد المتغير a_stringفي القسم .sdataالذي يمّث ل البيانات الصغيرة ،Small Dataحيث ُيَع د هttذا
القسttم وقسttم .sbssالمقابttل لttه أقسttاًم ا متttوفرة في بعض المعماريttات حيث يمكن الوصttول إىل البيانttات
196
علوم الحاسوب من األلف إىل الياء ما وراء العملية
،يttوان األساسtt وهذا يعني أنه يمكن إضافة قيمة ثابتة إىل العن،باستخدام اإلزاحة عن بعض المؤشرات المعروفة
لttافية وتحميttة إضttات بحث مطلوبttود عمليttدم وجttًرا لعtمما يجعل الوصول إىل البيانات في األقسام أسر ع نظ
كttتي يمكنtt الImmediate Value ةttات عىل حجم القيم الفوريttر معظم المعماريtt تقتص.ذاكرةttاوين في الttللعن
عt عىل عكس جمr1 = add r2, 70; ةt عند تطبيق التعليم70 إضافتها إىل المسجل مثل القيمة الفورية
وبالتالي يمكن تطبيق إزاحة بمقدار مسافة صغيرة معينة،r1 = add r2,r3 قيمتين مخزنتين في مسجلين
تخَدمانtt" ُيسCode " و"الشيفرةText تذكر أن "النص..text في القسمmain بينما توجد الدالة الرئيسية
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
PHDR 0x000034 0x10000034 0x10000034 0x00100 0x00100 R E 0x4
INTERP 0x000154 0x10000154 0x10000154 0x0000d 0x0000d R 0x1
[Requesting program interpreter: /lib/ld.so.1]
LOAD 0x000000 0x10000000 0x10000000 0x14d5c 0x14d5c R E 0x10000
LOAD 0x014d60 0x10024d60 0x10024d60 0x002b0 0x00b7c RWE 0x10000
DYNAMIC 0x014f00 0x10024f00 0x10024f00 0x000d8 0x000d8 RW 0x4
NOTE 0x000164 0x10000164 0x10000164 0x00020 0x00020 R 0x4
GNU_EH_FRAME 0x014d30 0x10014d30 0x10014d30 0x0002c 0x0002c R 0x4
GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RWE 0x4
197
علوم الحاسوب من األلف إىل الياء ما وراء العملية
.dynamic
.note.ABI-tag
.eh_frame_hdr
07
يوضح المثال السابق كيف تظِه ر األداة readelfربط المقاطع واألقسام في ملف ELFمع الملف الثنttائي
./bin/ls
انتقل إىل نهاية الخرج حيث يمكننا أن نرى األقسام المنقولة إىل المقttاطع ،فمثاًل ُيوَض ع القسttم .interp
في المقطع الذي له الرايtة .INTERPالحtظ أن األداة readelfتخبرنtا بطلب المفّس ر ،/lib/ld.so.1وهtو
يمكننا أن نرى الفرق بين النص والبيانات بالنظر إىل مقطعي .LOADالحttظ أن المقطttع األول لديttه أذونttات
القراءة والتنفيذ فقط ،بينما يكون للمقطع اآلخر أذونات القراءة والكتابة والتنفيذ ،أي أن مقطع الشيفرة له أذونات
القراءة والكتابة ( )r/wومقطع البيانtات لtه أذونtات القttراءة والكتابtة والتنفيttذ ( ،)r/w/eولكن ال يجب أن تكtون
البيانات قابلة للتنفيذ .لن ُيمَّيز قسم البيانات في معظم المعماريات مثل المعمارية x86األكttثر شttيوًع ا عىل أنttه
يحتوي عىل قسم بيانات قابل للتنفيذ .لكن المثال السابق مأخوذ من معمارية PowerPCالتي لها نموذج برمجة
مختلف قلياًل وهو واجهة التطبيق الثنائية - Application Binary Interfaceأو ABIاختصاًرا -التي تتطلب أن
يكون قسم البيانات قاباًل للتنفيذ .هذه هي حياة مبرمج األنظمة ،إذ ُو ِض عت القواعد لكسرها.
مباشرًة في جدول اإلزاحة العام - Global Offset Tableأو GOTاختصاًرا -بttداًل من جعلهttا ترتttد بين مttدخالت
منفصلة من جدول ،PLTوبالتالي يحتاج المعالج إىل أذونات تنفيذ للقسم GOTالذي يمكنك رؤيتttه مض َّtم ًنا في
مقطع البيانات.
الشيء اآلخر الذي يجب مالحظته هو أن حجم الملف هttو حجم الttذاكرة نفسttه لمقطttع الشttيفرة ،ولكن حجم
الذاكرة أكبر من حجم ملف مقطع البيانات ،ويأتي ذلك من القسم BSSالذي يحتوي عىل متغيرات عامة صفرية.
8.4واجهات ABI
ُتَع د واجهة ABIمصطلًحا ستسمع عنه كثيًرا عند العمل مع برمجة األنظمة ،وهو مختلttف عن مصttطلح API
الذي ُيَع د واجهات يراها المبرمج في شيفرته البرمجية .تشttير ABIإىل واجهttات المسttتوى األدنى الttتي يجب أن
يتفق عليها المصِّرف ونظام التشغيل والمعالج إىل حد ما للتواصل مع بعضها بعًض ا .سنقدم فيمttا يلي عttدًدا من
198
علوم الحاسوب من األلف إىل الياء ما وراء العملية
8.4.1ترتيب البايتات
ُترَّتب البايتات باستخدام ترتيب Endianessالذي يحتوي عىل نوعين هما Big-endian :أي تخزين البتttات
األقل أهمية أواًل ،و Little-endianأي تخزين البتات األكثر أهمية أواًل .
stackوواصفات الدوال.
بخصوص واصفات الدوال ،ال ُتستدَع ى الدالة في العديد من المعماريات مباشرًة ،بل ُتستدَع ى عttبر واصttف
دالة .Function Descriptorيتكون واصف الدالttة في المعماريttة IA64مثاًل من مكttونين همttا :عنttوان الدالttة
(ُيمَّثل بقيمة مقدارها 64بت أو 8بايتات) وعنوان المؤشر العام Global Pointerأو gpاختصاًرا .تحدد واجهttة
ABIأن المسttجل r1يجب أن يحتttوي دائًم ا عىل قيمttة المؤشttر gpالخttاص بالدالttة ،وهttذا يعttني أن مهمttة
المستدعي عند استدعاء دالة هي حفظ قيمة المؤشر gpالخاصة به وضبط المسجل r1عىل القيمة الجديدة من
ُتَع د واصفات الدوال مفيدة للغاية كما سترى الحًق ا .يمكن أن تأخtذ تعليمtة الجمtع addفي المعtالج IA64
قيمة فورية ذات حجم بحد أقصى 22بت بسبب الطريقة التي يحُزم بها المعالج IA64التعليمttات ،حيث ُتوَض ع
ثالثة تعليمات في كل حزمة ،وال يوجد سttوى مسtاحة كافيttة لالحتفttاظ بقيمttة 22بت للحفttاظ عىل الحزمttة مttع
بعضها البعض .القيمة الفورية Immediate Valueهي القيمة المحttددة مباشttرًة وليس القيمtة الموجtودة في
المسجل ،إذ ُتَع د القيمة 100في التعليمة add r1 + 100هي القيمة الفورية.
يمكن أن تتمكن 22بت من تمثيل 4194304بايت أو 4ميجابايتات ،وبالتالي يمكن إزاحة كل دالة مباشttرة
في حّي ز ذاكرة كبير مقttداره 4ميجابايتttات دون الحاجtة إىل تحمtل عنtاء تحميttل أّي قيٍم في المسtجل .إذا اتفttق
المصِّرف والرابط والمحِّم ل عىل ما يشير إليه المؤشر العام كما هو محدد في واجهttة ،ABIفيمكن تحسttين األداء
8.5المكتبات
لقد سئم المطورون من االضطرار إىل كتابة كل شيء من البداية ،لذلك كانت المكتبttات من أوىل اختراعttات
علوم الحاسوب ،فالمكتبة هي ببساطة مجموعة من الدوال التي يمكنك استدعاؤها من برنامجك .تتمتع المكتبttة
بالعديد من المزايا مثل أنه يمكنك توفير الكثير من الوقت عن طريق إعادة استخدام العمل الttذي أنجttزه شttخص
آخر ،وتكون أكثر ثقة في أنها تحتوي عىل أخطاء أقل بسttبب وجttود أشttخاص آخttرين اسttتخدموا هttذه المكتبttات
مسبًق ا ،وبالتالي ستستفيد من عثttورهم عىل األخطttاء وإصttالحها .تشttبه المكتبttة الملttف القابttل للتنفيttذ تماًم ا
باستثناء استدعاء دوال المكتبة باستخدام معامالت من ملفك القابل للتنفيذ بداًل من تشغيلها مباشرًة .
199
علوم الحاسوب من األلف إىل الياء ما وراء العملية
القابل للتنفيذ كما هو الحال مع تلك الملفات التي صَّرفتها بنفسك ،وعندها ُتسَّم ى المكتبttة مكتبttة سttاكنة ،ألن
المكتبة ستبقى دون تغيير ما لم ُيعاد تصريف البرنامجُ .تَع د هذه الطريقة الستخدام مكتبttة الطريقttة األسttهل ألن
ُتَع د المكتبة الساكنة مجموعًة من ملفات الكائنttات ،حيث ُيحتَف ظ بملفttات الكائنttات في سttجل ،Archive
مما يؤدي إىل استخدام الحقتها المعتادة ..aيمكنك التفكير في هttذه السttجالت بوصttفها ملًف ا مضttغوًط ا ولكن
بدون ضغط .يوّض ح المثال التالي كيفية إنشاء مكتبة ساكنة بسيطة ويقtدم بعض األدوات الشtائعة للتعامtل مtع
المكتبات:
)int main(void
{
;)int d = function(100
;)printf("%d\n", d
}
200
علوم الحاسوب من األلف إىل الياء ما وراء العملية
Archive index:
function in library.o
library.o:
T function
$ ./program
110
أواًل ،نصّرف مكتبتنا إىل ملف كائن كما رأينا سابًق ا .الحظ أننا نحدد واجهة APIالخاصة بالمكتبة في ترويسttة
الملف ،حيث تتكون واجهة APIمن تعريفات الدوال الموجودة في المكتبة حتى يعttرف الُم ص ِّtرف أنttواع الttدوال
عند إنشاء ملفات الكائنات التي تشير إىل المكتبة مثل الملف program.cالذي ُيضَّم ن باستخدام #include
ننشئ سجل مكتبة باستخدام األمر arالذي يمثل اختصاًرا للكلمة "سجل ُ ."Archiveتسَبق أسttماء ملفttات
المكتبة الساكنة بالبادئة libويكون لها الالحقة .aحسب العرف المَّتبع .يخبر الوسيُط cالبرنامَج بإنشاء السجل
،Archiveويخبر aالسttجل بإضttافة ملفttات الكائنttات المحttددة في ملttف المكتبttة .تنبثttق السttجالت الُم نَش أة
باسttتخدام األمttر arفي أمttاكن مختلفttة من أنظمttة لينكس بخالف إنشttاء مكتبttات سttاكنة .أحttد التطبيقttات
المستخدمة عىل نطاق واسttع هي التطبيقttات الُم سttتخَدمة في صttيغة حttزم .debمttع أنظمttة دبيttان Debian
وأوبنتو Ubuntuوبعض أنظمة لينكس األخرى ،حيث تستخدم ملفات debالسجالت لالحتفاظ بجميttع ملفttات
التطبيق مع بعضها بعًض ا في ملف حزمttة واحttد .تسttتخدم حttزم RedHat RPMصttيغًة بديلًtة ولكنهttا مشttابهة
لصيغة debوُتسَّم ى ُ .cpioيَع د ملف tarالتطبيَق األساسي لحفttظ الملفttات مttع بعضttها بعًض ا ،وهttو صttيغة
نستخدم بعد ذلك تطبيق ranlibإلنشاء ترويسة في المكتبة باستخدام رموز محتويات ملف الكttائن ،ممttا
يساعد المصِّرف عىل اإلشارة إىل الرموز بسرعة ،إذ يمكن أن تبدو هذه الخطوة زائدة في حالة وجود رمز واحد فقط
،ولكن يمكن أن تحتوي مكتبة كبيرة عىل آالف الرموز مما يعني أن الفهttرس يمكن أن يسttار ع بصttورة كبttيرة في
العثور عىل المراجع .نفحص هذه الترويسة الجديدة باستخدام تطبيق .nmالحظ وجود الرمttز functionالخttاص
201
علوم الحاسوب من األلف إىل الياء ما وراء العملية
يمكنك بعد ذلك تحديد المكتبttة للمصِّtرف باسttتخدام الخيttار -lnameحيث يكttون االسttم هttو اسttم ملttف
المكتبة بدون البادئة .libكما نوفر مجلد بحث إضافي للمكتبttات وهttو المجلttد الحttالي ( ،)-L .ألنttه ال يمكن
البحث عن المكتبttات في المجلttد الحttالي افتراضًttي ا .النتيجttة النهائيttة هي ملttف قابttل للتنفيttذ مttع المكتبttة
ُيَع د الربط الساكن أمًرا سهاًل للغاية ،ولكن له عدد من العيوب ،فهناك نوعان من العيوب الرئيسية أولهما أنttه
يجب عليك إعادة تصريف برنامجك إىل ملف تنفيttذي جديttد عنttد تحttديث شttيفرة المكتبttة إلصttالح خطttأ مثاًل ،
وثانيهما احتواء كل برنامج يستخدم تلك المكتبة في النظام عىل نسخة في ملفه القابttل للتنفيttذُ .يَع د ذلttك غttير
ُتضَّم ن مكتبة Cالتي هي glibcمثاًل في جميع البرامج ،وتوفر جميع الدوال الشائعة مثل .printf
8.5.2المكتبات المشرتكة
ُتَع د المكتبات المشتركة طريق ًtة للتغلب عىل المشttاكل الttتي تشّtكلها المكتبttات السttاكنةُ .تحَّم ل المكتبttة
المشتركة ديناميكًي ا في وقت التشغيل لكل تطبيق يحتاجها ،حيث يسttتخدم التطttبيق مؤشttرات تتطلب مكتبttة
معينة ،وُتحَّم ل المكتبة في الذاكرة وُتنَّف ذ عند استدعاء الدالttة .إن ُحِّم لت المكتبttة لتطttبيق آخttر ،فيمكن مشttاركة
الشيفرة البرمجية بين التطبيقين ،مما يوفر موارد كبيرة مع المكتبات شائعة االستخدام.
ُيَع د الربط الديناميكي الذي تحدثنا عنه سابًق ا أحد األجزاء األكثر تعقيًدا في نظام التشغيل الحديث.
وسنوضح في الفقرات التالية بعض المفاهيم المتعلقة بصttيغة ملفttات ELFمثttل تنقيح األخطttاء Debugging
وكيفية إنشاء أقسام مخصصة فيها وسكربتات الرابط Linker Scriptsالttتي يسttتخدمها الرابttط لبنttاء األقسttام
Sectionsالُم كِّو نة للمقاطع ،Segmentsولكن لنتعّرف أواًل عىل مفهوم ملفات ELFالقابلة للتنفيذ.
ُتَع د الملفات القابلة للتنفيذ أحد االستخدامات األساسية لصيغة .ELFيحتوي الملف الثنائي عىل كل ما هttو
مطلوب لنظام التشغيل لتنفيذ الشيفرة البرمجية بالطريقة المطلوبة ،حيث ُص ِّم م الملف التنفيttذي لتشttغيله في
عملية ذات فضاء عناوين فريد ،لذا يمكن للشيفرة البرمجية وضع افتراضات حول مكttان تحميttل أجttزاء البرنttامج
المختلفة في الذاكرة.
يوضح المثال اآلتي اختبttار أجttزاء ملٍttف قابttل للتنفيttذ باسttتخدام أداة .readelfيمكننttا أن نttرى العنttاوين
للشttيفرة الوهمية التي يجب وضع مقاطع LOADفيهttا ،حيث يمكننttا أن نttرى أّن أحttد هttذه المقttاطع مخصٌtص
202
علوم الحاسوب من األلف إىل الياء ما وراء العملية
راءةtات القtه أذونtات ولديtللبيان ٌصtر مخصtع آخtاك مقطt وهن،طtالبرمجية ويمتلك أذونات القراءة والتنفيذ فق
فبدونها لن ُتمَّيز الصفحات التي تدعم خطأ ما بأن لها أذونات التنفيذ حتى إن،والكتابة دون وجود أذونات التنفيذ
يفرةttذ للشttأّي تنفيttات بttمح معظم المعالجttالي لن تسtt وبالت،وائيةttسمح هذا الخطأ للمهاجم بإدخال بيانات عش
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
PHDR 0x0000000000000040 0x0000000000400040 0x0000000000400040
0x00000000000001c0 0x00000000000001c0 R E 8
INTERP 0x0000000000000200 0x0000000000400200 0x0000000000400200
0x000000000000001c 0x000000000000001c R 1
[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000
0x0000000000019ef4 0x0000000000019ef4 R E 200000
LOAD 0x000000000001a000 0x000000000061a000 0x000000000061a000
0x000000000000077c 0x0000000000001500 RW 200000
DYNAMIC 0x000000000001a028 0x000000000061a028 0x000000000061a028
0x00000000000001d0 0x00000000000001d0 RW 8
NOTE 0x000000000000021c 0x000000000040021c 0x000000000040021c
0x0000000000000044 0x0000000000000044 R 4
GNU_EH_FRAME 0x0000000000017768 0x0000000000417768 0x0000000000417768
0x00000000000006fc 0x00000000000006fc R 4
GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 RW 8
.interp
.interp .note.ABI-tag .note.gnu.build-id .hash .gnu.hash .dynsym .dynstr
.gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .text .fini .rodata
.eh_frame_hdr .eh_frame
203
علوم الحاسوب من األلف إىل الياء ما وراء العملية
يجب تحميل مقاطع البرنامج عىل هذه العنtاوين ،حيث تتمثttل الخطttوة األخtيرة للرابtط Linkerفي تحليttل
معظم المنقوالت Relocationsوتصحيحها باستخدام العناوين المطلقttة الُم فتَرضttة ،ثم تجاهttل البيانttات الttتي
تصف االنتقال في الملف الثنائي النهائي دون وجود طريقة إليجاد هذه المعلومات بعد اآلن.
تحتtوي الملفtات القابلtة للتنفيtذ عموًم ا عىل اعتماديtات Dependenciesخارجيtة للمكتبtات المشtتركة
Shared Librariesأو أجزاء من الشيفرة البرمجية المشتركة يمكن تجريدها ومشاركتها بين أجزاء النظام بأكمله،
حيث تتعلق جميع األجزاء الغريبة في المثال السابق باستخدام المكتبات المشتركة التي سنوّض حها الحًق ا.
حيث يأتي مصطلح األساسي Coreمن الخصttائص الفيزيائيttة األصttلية للttذاكرة المغناطيسttية األساسttية الttتي
تستخدم اتجاه الحلقات المغناطيسية الصغيرة لتخزين الحالةُ .يَع د التفريغ األساسttي لقطttة كاملttة للبرنttامج عنttد
عمله في وقت معين ،ويمكن بعد ذلك استخدام منقح أخطاء Debuggerلفحص هذا التفريغ وإعادة بنttاء حالttة
البرنامج .يوضح المثال التالي نموذًجا لبرنامج يكتب في موقع ذاكرة عشttوائية لغttرض التعطttل ،حيث سttتتوقف
;return 0
}
$ ./coredump
)Segmentation fault (core dumped
204
علوم الحاسوب من األلف إىل الياء ما وراء العملية
./core: ELF 32-bit LSB core file Intel 80386, version 1 (SYSV), SVR4-
'style, from './coredump
وبالتالي فإن ملف التفريغ األساسي هو مجرد ملttف ELFيحتttوي عىل مجموعttة من األقسttام الttتي يفهمهttا
تنقيح األخطاء .الحظ أن الملف القابل للتنفيذ األصلي ُأنشئ باستخدام الراية -gالتي توجه المصِّرف Compiler
لتضمين جميع معلومات األخطاء ،حيث يجري االحتفاظ بالمعلومات المتعلقة بعمليttة تنقيح األخطttاء اإلضttافية
في أقسام خاصة من ملف ،ELFإذ تصف هذه المعلومات بالتفصيل أشياًء مثل قيم المسّجل التي تحتوي حالًي ا
عىل المتغيرات المستخدمة في الشيفرة البرمجية وحجم المتغيرات وطول المصفوفات وغttير ذلttك .تكttون هttذه
يمكن أن يؤدي تضمين معلومات تنقيح األخطاء إىل جعل الملفات والمكتبات القابلة للتنفيذ كبيرة جًttدا ،إذ
ال تزال تشغل مساحة كبيرة عىل القرص الصلب بالرغم من أنهttا ليسttت مطلوبttة في الttذاكرة للتشttغيل الفعلي،
وبالتالي يجب إزالة هذه المعلومات من ملف .ELFيمكن نقل كل من الملفات التي ًأزيلت منها هذه المعلومات
والملفات التي لم ًُتزال منها هذه المعلومtات ،ولكن تtوّف ر معظم طtرق توزيtع أو نشtر الملفtات الثنائيtة binary
distributionالحالية معلومات لتنقيح األخطاء في ملفات منفصttلة .يمكن اسttتخدام أداة objcopyالسttتخراج
معلومات تنقيح األخطاء ( )--only-keep-debugثم إضافة رابط في الملف القابل للتنفيذ األصttلي إىل هttذه
المعلومات الُم زاَلة ( ،)--add-gnu-debuglinkثم سيكون هناك قسttم خttاص باالسttم .gnu_debuglink
موجود في الملف القابل للتنفيذ األصلي ويحتوي عىل قيمة فريدة بحيث يمكن لمنقح األخطاء عند بدء جلسttات
تنقيح األخطاء التأكَد من أنه يربط معلومات تنقيح األخطاء الصحيحة بالملف التنفيذي الصحيح.
يوضح المثال التالي إزالة معلومات تنقيح األخطاء إىل ملفات منفصلة باستخدام األداة :objcopy
205
علوم الحاسوب من األلف إىل الياء ما وراء العملية
اءttة لبقttاك حاجttون هنtt إذ لن تك، ولكنها ُتَع د هدًف ا لإلزالة من الخرج النهائي،تشغل الرموز مساحة أقل بكثير
ُتَع د.ةttورة النهائيttذ بالصtt لملف قابل للتنفيobject files معظم الرموز بمجرد ربط ملفات التعليمات الُم صَّرفة
غيلtt ولكن لن تكون الرموز بعد ذلك ضرورية تماًم ا لتش،Relocation الرموز مطلوبة إلصالح مدخالت المنقوالت
هttظ أنtt الح.وزttة الرمttاراٍت إلزالttام لينكس خيttامج في نظtt لتجريد البرنGNU توفر سلسلة أدوات.البرنامج النهائي
ولكنها ُتوَض ع في جداول،)Dynamic Linking يجب تحليل بعض الرموز في وقت التشغيل (للربط الديناميكي
.رموز ديناميكية منفصلة حتى ال ُتزال وتجعل الخرج النهائي عديم الفائدة
206
علوم الحاسوب من األلف إىل الياء ما وراء العملية
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
NOTE 0x000214 0x00000000 0x00000000 0x0022c 0x00000 0
LOAD 0x001000 0x08048000 0x00000000 0x01000 0x01000 R E 0x1000
LOAD 0x002000 0x08049000 0x00000000 0x01000 0x01000 RW 0x1000
LOAD 0x003000 0x489fc000 0x00000000 0x01000 0x1b000 R E 0x1000
LOAD 0x004000 0x48a17000 0x00000000 0x01000 0x01000 R 0x1000
LOAD 0x005000 0x48a18000 0x00000000 0x01000 0x01000 RW 0x1000
LOAD 0x006000 0x48a1f000 0x00000000 0x01000 0x153000 R E 0x1000
LOAD 0x007000 0x48b72000 0x00000000 0x00000 0x01000 0x1000
LOAD 0x007000 0x48b73000 0x00000000 0x02000 0x02000 R 0x1000
LOAD 0x009000 0x48b75000 0x00000000 0x01000 0x01000 RW 0x1000
LOAD 0x00a000 0x48b76000 0x00000000 0x03000 0x03000 RW 0x1000
LOAD 0x00d000 0xb771c000 0x00000000 0x01000 0x01000 RW 0x1000
LOAD 0x00e000 0xb774d000 0x00000000 0x02000 0x02000 RW 0x1000
LOAD 0x010000 0xb774f000 0x00000000 0x01000 0x01000 R E 0x1000
LOAD 0x011000 0xbfeac000 0x00000000 0x22000 0x22000 RW 0x1000
207
علوم الحاسوب من األلف إىل الياء ما وراء العملية
$ eu-readelf -n ./core
208
علوم الحاسوب من األلف إىل الياء ما وراء العملية
GID: 1000
EGID: 1000
SECURE: 0
RANDOM: 0xbfecba1b
EXECFN: 0xbfecdff1
PLATFORM: 0xbfecba2b
NULL
LINUX 48 386_TLS
index: 6, base: 0xb771c8d0, limit: 0x000fffff, flags: 0x00000051
index: 7, base: 0x00000000, limit: 0x00000000, flags: 0x00000028
index: 8, base: 0x00000000, limit: 0x00000000, flags: 0x00000028
يمكننا أن نرى في المثال السابق اختباًرا باسttتخدام أداة readelfأواًل للملttف األساسttي النttاتج عن مثttال
إنشاء ملف تفريغ أساسي واستخدامه باستخدام منقح األخطtاء .gdbال توجtد أقسtام أو منقtوالت أو معلومtات
أخرى غريبة في هذا الملف يمكن أن تكون مطلوبة لتحميل ملف قابل للتنفيttذ أو مكتبttة ،إذ يتكttون من سلسttلة
من ترويسات البرامج التي تمثل مقاطع LOADالttتي هي عمليttات تفريttغ بيانttات أوليttة أنشttأتها النttواة Kernel
المكون اآلخر لملف التفريغ األساسي هttو أقسtام المالحظttات NOTEالtتي تحتttوي عىل البيانtات الضttرورية
لتنقيح األخطtttاء ولكن ليس بالضtttرورة أن ُتلتَق ط في لقطtttة مباشtttرة لتخصيصtttات الtttذاكرة .يtttوّف ر برنtttامج
eu-readelfالُم ستخَدم في الجزء الثاني من المثال السابق رؤيًة أكثر اكتمااًل للبيانات من خالل فك تشفيرها.
تقّدم المالحظة PRSTATUSمجموعة من المعلومات حول العملية أثناء تشttغيلها ،فمثاًل يمكننttا أن نttرى من
قيمة cursigأن البرنtامج تلقى إشtارة قيمتهtا 11تمثtل خطtأ تقطيtع .segmentation faultكمtا تتضtمن
باإلضافة إىل معلومات رقم العملية ملف تفريغ لجميع المسجالت الحالية .يمكن لمنقح األخطttاء بttالنظر إىل قيم
المسّttجل إعttادة بنttاء حالttة المكttدس وبالتttالي توفttير تعقب خلفي ،Backtraceحيث يمكن لمنقح األخطttاء
جنًبا إىل جنب مع الرمز ومعلومات تنقيح األخطاء من الملف الثنائي األصttلي إظهttار كيفيttة الوصttول إىل نقطttة
التنفيذ الحالية.
من المخرجات األخرى المتجه المسttاعد الحttالي Auxiliary Vectorأو AUXVاختصttاًرا .تصttف TLS_386
مدخالت جدول الواصفات العام المستخدمة في تقttديم Implementationمعماريttة x86لمخttزن محلي قttائم
عىل الخيوط .thread-local storageسيكون هناك مدخالت مكررة لكل خيط قيد التشغيل بالنسtبة للتطtبيق
متعدد الخيوط ،حيث سيفهم منقح األخطاء ذلك وهذه هي الطريقttة الttتي يطّب ق بهttا منقح األخطttاء gdbاألمttر
209
علوم الحاسوب من األلف إىل الياء ما وراء العملية
تنشئ النواة ملف التفريغ األساسي ضمن حدود إعدادات ulimitالحالية ،إذ يمكن أن يؤدي البرنامج الذي
يستخدم قدًرا كبيًرا من الذاكرة إىل وجود ملف تفريغ كبير جًدا ،وُيحتَم ل أن يمأل القرص الصttلب ويزيttد المشttاكل
سوًءا ،وُيضَبط ulimitعىل مستوى منخفض أو حتى عىل القيمttة الصttفر ألن معظم األشttخاص الttذين ليسttوا
مطورين لديهم استخدام ضئيل لملف التفريغ األساسي .لكن يظل التفريttغ األساسttي هttو الطريقttة األكttثر فائttدة
ولكن يمكن في بعض األحيان توسيع أو تخصيص األقسام ومحتوياتها مثل وحدات نttواة لينكس الttتي ُتسttتخَدم
لتحميل المشّغ الت والميزات األخرى ديناميكًي ا في نواة التشغيلُ .تَع د هذه الوحدات غير قابلة للنقل ،فهي تعمل
فقط مع إصدار بناء نواة ثابت واحد ،لذلك يمكن أن تكون الواجهة بين الوحدات والنواة مرنًة وغير مرتبطة بمعايير
معينة ،وبالتالي يمكن تعريف طرق تخزين مثل تخزين معلومات الترخيص والتأليف واالعتماديttات والمعttامالت
يمكن ألداة modinfoفحص هذه المعلومttات في وحttدٍة مttا وتقttديمها للمسttتخدم .يوضttح المثttال التttالي
استخدام مثttال عن وحttدة نttواة لينكس fuseالttتي تسttمح لمكتبttات مسttاحة المسttتخدم Userspaceبتوفttير
210
علوم الحاسوب من األلف إىل الياء ما وراء العملية
يوضح المثال التالي. الُم ضَّم ن في ملف الوحدة لتقديم تفاصيلها.modinfo القسمmodinfo تحّلل األداة
ة غالًب ا منtttيفرة البرمجيtttذه الشtttأتي هttt حيث ت،دةttt" في الوحAuthor فtttل "المؤلtttع حقtttة وضtttكيفي
:include/linux/module.h
211
علوم الحاسوب من األلف إىل الياء ما وراء العملية
/*
* ابدأ من األسفل ثم انتقل إلى األعلى
*/
/* __LINE__ * وحدات الماكرو غير المباشرة مطلوبة للصق الوسيط الُم وَّس ع مثل الماكرو/
#define ___PASTE(a,b) a##b
#define __PASTE(a,b) ___PASTE(a,b)
/* يسمح إنشاء مستويين للمعامل بأن يكون.تحويل الماكرو غير المباشر إلى سلسلة نصية
" عند التصريفbar" مثاًل إلى السلسلةstringify(FOO)__ حيث يتحّول،ماكرو بحد ذاته
-DFOO=bar * باستخدام/
#define __stringify_1(x...) #x
#define __stringify(x...) __stringify_1(x)
/*
* " فقطName" " أوName <email>" استخدم للمؤلفين المتعددين الذين يستخدمون
* متعددةMODULE_AUTHOR() تعليمات أو سطور
*/
#define MODULE_AUTHOR(_author) MODULE_INFO(author, _author)
/* ---- */
،__MODULE_INFO اكرو األعمtt تغّل ف المMODULE_AUTHOR لنبدأ من الجزء السفلي حيث نرى أن الوحدة
يةttلة النصttوي عىل السلسtt ليحتstatic const char [] وعttيًرا من النttني متغttا نبttرى أننttا أن نttويمكنن
212
علوم الحاسوب من األلف إىل الياء ما وراء العملية
)))" (section(".modinfoيخبر المصّرف بعدم وضع هذا المتغير في قسم البيانات dataمع المتغيرات
األخرى ،ولكن يمكن إخفاؤه في قسم ELFالخاص به الذي اسمه ..modinfoتوِقف المعامالت األخرى المتغttير
الذي يجري تحسينه ألنه يبدو غير ُم ستخَدم وللتأكد من أننا نضع المتغيرات بجانب بعضها بعًض ا من خالل تحديد
المحاذاة.
هناك استخدام واسع النطاق لتحويل وحدات الماكرو إىل سالسtل نصtية ،Stringification Macrosوهي
حيل ُتستخَدم في معالج لغة Cالمسttبق لضttمان أن السالسttل النصttية والتعttاريف يمكن أن تكttون مttع بعضttها
بعًض ا .يوّف ر المصِّرف gccالتعريف الخاص __ __COUNTERالذي يوفر قيمة فريttدة ومتزايttدة في كttل اسttتدعاء،
مما يسمح باستدعاءات وحدة MODULE_AUTHORمتعددة في ملف واحد دون استخدام اسم المتغير نفسه.
يمكننا فحص الرموز الموضوعة في الوحدة النهائية لمعرفة النتيجة النهائية كما يلي:
الرابط الذي يِص ف مكان بدء المقاطع واألقسام الموجودة فيها ويحدد المعامالت األخرى.
213
علوم الحاسوب من األلف إىل الياء ما وراء العملية
يوضح المثال اآلتي مقتطًف ا من سكربت الرابط االفتراضي الذي سيعرضه الرابط عند إعطاء الراية التفصيلية
السكربت االفتراضي ُم ضَّم ٌن في الرابط ويعتمد عىل.gcc مع--verbose و-Wl باستخدام الرايتينVerbose
. المعيارية إلنشاء برامج عاملة لمساحة مستخدٍم خاصة بمنصة البناءAPI تعريفات واجهة
يمكنك أن ترى في المثال السابق كيف يحدد سكربت الرابط أموًرا متعددة مثل مواقع البدء واألقسام المراد
يرtt إذ يمكن توف،gcc إىل الرابط عبر--verbose لتمرير الراية-Wl ُتستخَدم الراية.تجميعها في مقاطع مختلفة
214
علوم الحاسوب من األلف إىل الياء ما وراء العملية
سكربتات مخصصة للرابط باسttتخدام الرايtات .ليس ُم حتًم اًل أن يحتttاج مطttورو مسtاحة المسtتخدم العاديtة إىل
تجاوز سكربت الرابط االفتراضي ،ولكن تتطلب التطبيقات المخَّص صة جًدا مثttل عمليttات بنttاء النttواة سttكربتات
8.7بدء العمليات
ذكرنا سابًق ا أن البرنامج ال يبدأ بالدالة الرئيسية )( ،mainوسنختبر في هذا الفصل ما يحدث لبرنامج مرتبط
ديناميكًي ا عند تحميله وتشغيله ونشرح العمليات التي تسبق بدء تنفيذ برنامج في نظام التشغيل.
تخّص ص النواة أواًل البنى لعملية جديدة وتقttرأ ملttف ELFالُم حَّtد د من القttرص الصttلب اسttتجابًة السttتدعاء
النظام .execذكرنا أن صيغة ELFلديها حقل لمفّس ر Interpreterالبرنامج هو PT_INTERPالذي يمكن ضبطه
لتفسير البرنامج ،حيث يكون المفِّس ر بالنسبة للتطبيقات المرتبطة ديناميكًي ا هttو الرابttط الttديناميكي Dynamic
- Linkerأو -ld.soالذي يسمح بإجراء بعض عمليات الربط مباشرًة قبل بدء البرنامج.
كما تقرأ النواة شيفرة الرابط الديناميكي ،وتبدأ البرنامج من عنوان نقطة الدخول entry pointالذي تحدده.
سنختبر دور الرابط الديناميكي بالتفصيل الحًق ا ،ولكن يكفي أن نقول أنه يضttبط بعض اإلعttدادات مثttل تحميttل
المكتبات التي يتطلبها التطبيق كما هو محدد في القسم الديناميكي من الملف الثنائي ،ثم يبدأ تنفيttذ البرنttامج
ومتغيرات البيئة الحالية environment variablesوبنية خاصة اسمها المتجه المساعد Auxiliary Vectorأو
auxvاختصاًرا .يمكنك أن تطلب من الرابط الديناميكي أن ُيظهر لtك بعًض ا من خtرج تنقيح األخطtاء من البنيtة
auxvمن خالل تحديد قيمة البيئة كما يلي .LD_SHOW_AUXV=1تسمح الوسttائط والبيئttة واألشttكال المختلفttة
من اسttتدعاء النظttام execبتحديttد هttذه األشttياء للبرنttامج الttتي يمكن للنttواة توصttيلها من خالل وضttع جميttع
المعلومات المطلوبة في المكدس ليلتقطها البرنامج الُم نَش أ حtديًث ا ،وبالتttالي يمكن للبرنttامج عنtد بtدء تشtغيله
استخدام مؤشر المكدس الخاص به للعثور عىل جميع معلومات بدء التشغيل المطلوبة.
المتجه المساعد هو بنية خاصة لنقل المعلومات مباشرًة من النttواة إىل البرنttامج الُم شَّtغ ل حttديًث ا ،ويحتttوي
عىل معلومات خاصة بالنظام يمكن أن تكون مطلوبة مثل الحجم االفتراضي لصفحة الذاكرة الوهمية عىل النظttام
أو إمكانات العتttاد ،وهttذه هي المttيزات الttتي تحttددها النttواة للعتttاد األساسttي ويمكن أن تسttتفيد منهttا بttرامج
مساحة المستخدمين.
215
علوم الحاسوب من األلف إىل الياء ما وراء العملية
ذكرنا سابًق ا أن استدعاءات النظام بطيئة وأن األنظمة الحديثة لديها آليات لتجنب الِح مل الناتج عن استدعاء
مصttيدة Trapللمعttالج ،حيث يمكن تنفيttذ ذلttك في نظttام لينكس من خالل اسttتخدام حيلttة بين المحمttل
الديناميكي والنواة المتصَلين ببنية ،AUXVإذ تضيف النواة مكتبة مشتركة صغيرة إىل فضاء العناوين لكل عملية
ُم نَش أة حديًث ا وتحتوي عىل دالة تجري استدعاءات النظttام نيابttة عنttك .يكمن جمttال هttذا النظttام في أنttه إذا دعم
العتاد األساسي آليttة اسttتدعاء نظttام سttريعة ،فيمكن للنttواة اسttتخدامها لكونهttا منشttئة المكتبttة ،وإاّل فيمكنهttا
اسttتخدام النظttام القttديم إلنشttاء مصttيدة .تسttمى هttذه المكتبttة linux-gate.so.1ألنهttا بوابttة إىل عمttل
النواة الداخلي.
تضيف النواة مدخلًة إىل البنية auxvتسَّم ى AT_SYSINFO_EHDRعندما تبttدأ الرابttط الttديناميكي ،وهttذه
المدخلة هي العنوان الموجود في الذاكرة الذي توجد فيه مكتبة النttواة الخاصttة .يمكن للرابttط الttديناميكي عنttدما
يبدأ البحَث عن المؤشر ،AT_SYSINFO_EHDRفإن ُو جد ،فسُتحَّم ل تلك المكتبة للبرنامج .ال يملttك البرنttامج أّي
فكرة عن وجود هذه المكتبة ،ألنها ُتَع د ترتيًبا خاًص ا بين الرابط الديناميكي والنواة.
ذكرنا أن المبرمجين يجرون استدعاءات النظام بطريقة غير مباشttرة من خالل اسttتدعاء الttدوال في مكتبttات
النظام libcالتي يمكنها التحقق مما إذا كان ملف النواة الثنائي الخاص محَّم اًل أم ال ،فإذا كان األمر كذلك ،فيجب
استخدام الدوال الموجودة ضمنه إلجراء استدعاءات النظام .إذا حددت النواة أن العتاد يمتلttك القttدرة المطلوبttة،
8.7.2بدء الربنامج
تمّرر النواُة المفّس َر بعد تحميله إىل نقطة الدخول كما هو مذكور في ملف المفّس ر (الحظ عtدم اختبtار كيفيtة
بدء الرابط الديناميكي) .سيقفز الرابط الديناميكي إىل عنوان نقطة الدخول كما هو مذكور في ملف ELFالثنائي.
)int main(void
{
;return 0
}
216
علوم الحاسوب من األلف إىل الياء ما وراء العملية
[...]
080482b0 <_start>:
80482b0: 31 ed xor %ebp,%ebp
80482b2: 5e pop %esi
80482b3: 89 e1 mov %esp,%ecx
80482b5: 83 e4 f0 and $0xfffffff0,%esp
80482b8: 50 push %eax
80482b9: 54 push %esp
80482ba: 52 push %edx
80482bb: 68 00 84 04 08 push $0x8048400
80482c0: 68 90 83 04 08 push $0x8048390
80482c5: 51 push %ecx
80482c6: 56 push %esi
80482c7: 68 68 83 04 08 push $0x8048368
80482cc: e8 b3 ff ff ff call 8048284
<__libc_start_main@plt>
80482d1: f4 hlt
80482d2: 90 nop
80482d3: 90 nop
<main>:
push %ebp
e5 mov %esp,%ebp
804836b: 83 ec 08 sub $0x8,%esp
804836e: 83 e4 f0 and $0xfffffff0,%esp
b8 00 00 00 00 mov $0x0,%eax
c0 0f add $0xf,%eax
c0 0f add $0xf,%eax
804837c: c1 e8 04 shr $0x4,%eax
804837f: c1 e0 04 shl $0x4,%eax
c4 sub %eax,%esp
b8 00 00 00 00 mov $0x0,%eax
c9 leave
804838a: c3 ret
217
علوم الحاسوب من األلف إىل الياء ما وراء العملية
<__libc_csu_init>:
push %ebp
e5 mov %esp,%ebp
][...
<__libc_csu_fini>:
push %ebp
][...
يمكننا أن نرى في المثال البسيط السابق باسttتخدام أداة readelfأن نقطttة الtدخول هي الدالtة _start
في الملف الثنtائي ،ويمكننtا أن نtرى في عمليtة التفكيtك دفtع بعض القيم إىل المكtدس .تمثtل القيمtة األوىل
إن الدالttة ُ __libc_start_mainم عَّرفttة ضttمن مصttادر المكتبttة glibcوالموجttودة ضttمن المسttار
،sysdeps/generic/libc-start.cوُتَع د معقدًة جًدا ومخفيًة بين عدد كبير من التعريفttات ،حيث يجب
أن تكون قابلة للنقل عبر عدد كبير جًدا من األنظمة والمعماريات التي يمكن لمكتبtة glibcالعمtل عليهtا .تطّب ق
هذه الدالة عدًدا من األشياء المحَّد دة المتعلقة بإعداد مكتبة Cوالttتي ال يحتttاج المttبرمج العttادي للقلttق بشttأنها.
النقطة التالية التي تستدعي فيها المكتبُة البرنامَج هي عند التعامل مع شيفرة .init
تعttد الttدالتان initو finiمفهttومين خاصttين يسttتدعيان أجttزاًء من الشttيفرة البرمجيttة الموجttودة في
المكتبات المشتركة والتي يمكن أن تحتاج الستدعائها قبtل أن تبtدأ المكتبtة أو عنtد إلغttاء تحميttل المكتبtة عىل
التوالي .يمكنك أن ترى كيف يمكن أن يكون ذلك مفيًدا لمبرمجي المكتبات إلعداد المتغيرات عند بttدء تشttغيل
المكتبة أو لتنظيفها في النهاية .كان البحث عن الدالتين _initو _finiفي المكتبة ممكًنا سابًق ا ،ولكن أصبح
ذلك مقيًدا إىل حد ما حيث كان كل شيء مطلوًبا في هاتين الدالتين .سنوضttح فيمttا يلي كيفيttة عمttل الttدالتين
initو finiفقط.
،stackإذ سيكون بإمكانها أواًل الوصول إىل وسائط البرنامج ومتغttيرات البيئttة والمتجttه المسttاعد من النttواة ،ثم
ستدفع دالة التهيئة إىل عناوين المكدس الخاصة بالدوال للتعامل مttع الttدالتين initأو finiثم عنttوان الدالttة
الرئيسية نفسها.
218
علوم الحاسوب من األلف إىل الياء ما وراء العملية
.دريةttيفرة المصtt في الشfini أوinit تخدامttا باسttٍة مttنحتاج طريقًة ما لإلشارة إىل أنه يجب استدعاء دال
Destructors انtt أو هادمتConstructors انttا بانيتtt لتمييز دالتين بأنهمAttributes سماتgcc نستخدم مع
ف دوراتttه لوصttة التوجttات كائنيttع اللغtt ُتستخَدم هذه المصطلحات بصورة أكثر شيوًع ا م.في برنامجنا الرئيسي
.حياة الكائن
$ cat test.c
#include <stdio.h>
int main(void)
{
return 0;
}
$ ./test
init
fini
[...]
<_init>:
219
علوم الحاسوب من األلف إىل الياء ما وراء العملية
push %ebp
e5 mov %esp,%ebp
ec 08 sub $0x8,%esp
e8 79 00 00 00 call 8048304 <call_gmon_start>
804828b: e8 e0 00 00 00 call 8048370 <frame_dummy>
e8 2b 02 00 00 call 80484c0 <__do_global_ctors_aux>
c9 leave
c3 ret
[...]
080484c0 <__do_global_ctors_aux>:
80484c0: 55 push %ebp
80484c1: 89 e5 mov %esp,%ebp
80484c3: 53 push %ebx
80484c4: 52 push %edx
80484c5: a1 2c 95 04 08 mov 0x804952c,%eax
80484ca: 83 f8 ff cmp $0xffffffff,%eax
80484cd: 74 1e je 80484ed
<__do_global_ctors_aux+0x2d>
80484cf: bb 2c 95 04 08 mov $0x804952c,%ebx
80484d4: 8d b6 00 00 00 00 lea 0x0(%esi),%esi
80484da: 8d bf 00 00 00 00 lea 0x0(%edi),%edi
80484e0: ff d0 call *%eax
80484e2: 8b 43 fc mov 0xfffffffc(%ebx),%eax
80484e5: 83 eb 04 sub $0x4,%ebx
80484e8: 83 f8 ff cmp $0xffffffff,%eax
80484eb: 75 f3 jne 80484e0
<__do_global_ctors_aux+0x20>
80484ed: 58 pop %eax
80484ee: 5b pop %ebx
80484ef: 5d pop %ebp
80484f0: c3 ret
80484f1: 90 nop
80484f2: 90 nop
80484f3: 90 nop
Section Headers:
220
علوم الحاسوب من األلف إىل الياء ما وراء العملية
221
علوم الحاسوب من األلف إىل الياء ما وراء العملية
ةttل الدالttدس من أجttة إىل المكttيرة المدفوعttة األخtt__ هي القيمlibc_csu_init ةttة التهيئttانت دالttك
ةtt_ في النهايinit تستدعي الدالة._ في الملف القابل للتنفيذinit تجري بعض اإلعدادات ثم تستدعي الدالة
دأ منtt فيمكننا أن نرى أنها تب، حيث إذا نظرنا إىل تفكيك هذه الدالة،__do_global_ctors_aux دالة تسمى
مtود في القسtة موجtل البدايtذي يمثtوان الtذا العنt ه.تدعيهاtة وتسtرأ قيمtرر وتقt ثم تتك0x804952c العنوان
222
علوم الحاسوب من األلف إىل الياء ما وراء العملية
.ctorsمن الملف ،حيث إذا ألقينا نظرة عليه ،فسنرى أنه يحتوي عىل القيمة األوىل -1وعنttوان الدالttة بصttيغة
العنوان بصيغة Big Endianهو 0x08048398أو عنوان الدالة ،program_initلذا فإن صttيغة القسttم
.ctorsهي -1أواًل ثم عنوان الدوال المطلوب استدعاؤها عند التهيئة ،وأخيًرا القيمة صفر لإلشttارة إىل اكتمttال
أخيًرا ،تستدعي الدالة __libc_start_mainالدالَة الرئيسية )( mainبمجttرد اكتمالهttا باسttتدعاء الدالttة
._initتذكر أن هذه الدالة تمتلك إعداد المكدس األولي باستخدام الوسائط ومؤشرات البيئttة من النttواة ،وهttذه
هي الطريقة التي تحصل بها الدالة الرئيسية عىل الوسائط ][ .argc, argv[], envpستعمل العملية بعد
تحدث عملية مماثلة مttع القسttم .dtorsللهttادمين Destructorsعنttد إنهttاء البرنttامج ،حيث تسttتدعيها
الحظ تطبيق الكثير من العمل قبل أن يبدأ البرنامج وحتى بعد أن تعتقد أنه انتهى بقليل.
223
.9مفهوم الربط الديناميكي
9.1مشاركة الشيفرة
يشرح هذا الفصل طريقة مشtاركة الشtيفرة بين التطبيقtات والمكتبtات المشtتركة ،ويشtرح مفهtوم الربtط
الديناميكي في تنفيذ التطبيقات حيث ُتَع د شيفرة نظام التشttغيل البرمجيttة للقttراءة فقttط وتكttون منفصttلة عن
البيانات ،لذا إن لم تتمكن البرامج من تعديل الشيفرة البرمجية مع وجود كميات كبيرة من الشيفرة البرمجية فمن
المنطقي مشاركة هذه الشيفرة بين العديد من الملفات القابلة للتنفيذ بداًل من تكرارها لكل ملف منها.
يمكن القيام بذلك بسهولة بين التطبيقات والمكتبات المشتركة باستخدام الذاكرة الوهمية ،إذ يمكن الرجttوع
بسهولة إىل صفحات الttذاكرة الحقيقيttة الttتي جttرى تحميttل شttيفرة المكتبttة البرمجيttة إليهttا من خالل عttدد من
الصفحات الوهمية في عدٍد من فضاءات العناوين .لذا يمكن لكل عملية الوصttول إىل شttيفرة المكتبttة البرمجيttة
باستخدام أّي عنوان وهمي تريده ،بينما يكون لديك نسخة حقيقية واحدة فقط من هذه الشيفرة في ذاكرة النظام.
وبذلك توصل المبرمجون بسرعة إىل فكرة المكتبة المشttتركة Shared Libraryالttتي -كمttا يttوحي االسttم-
يمكن مشاركتها بين العديد من الملفات القابلة للتنفيذ .يحتوي كل ملف قابل للتنفيذ عىل مرجع يقttول" :أحتttاج
مكتبة Fooمثاًل " ،حيث ُيتَرك األمر للنظام عند تحميل البرنامج للتحقق من وجود برنامج آخر حّم ل شttيفرة هttذه
المكتبة في الذاكرة ثم مشاركتها من خالل ربط صفحات الملف القابtل للتنفيtذ مtع الtذاكرة الحقيقيtة ،أو يمكنtه
تحميل المكتبة في ذاكرة الملف القابل للتنفيذ .تسمى هttذه العمليttة بالربttط الttديناميكي ،Dynamic Linking
ألنها تطّبق جزًءا من عملية الربط مباشرًة عند تنفيذ البرامج في النظام.
والمتغيرات) تماًم ا مثل الملفttات القابtل للتنفيttذ ،ولكن ال يمكن تشtغيلها ،فهي تtوفر فقttط مكتبtة من الtدوال
علوم الحاسوب من األلف إىل الياء مفهوم الربط الديناميكي
للمطورين الستدعائها .لذا يمكن ملف ELFأن يمثل مكتبة ديناميكية تماًم ا كما يمثل ملًف ا قاباًل للتنفيذ مع وجود
بعض االختالفات األساسية مثل عدم وجود مؤشttر للمكttان الttذي يجب أن يبttدأ فيttه التنفيttذ ،ولكن ُتَع د جميttع
المكتبات المشتركة مجرد كائنات بصيغة ELFمثل أي ملف آخر قابل للتنفيذ.
تحتوي ترويسة ملف ELFعىل رايتين حصريتين هما ET_EXECو ET_DYNلتمييز ملف ELFبوصttفه ملًف ا
تمتلك ملفات الكائنات مراجًع ا إىل دوال المكتبة تماًم ا كما هو الحال مع أّي مرجع خارجي آخر عندما تصِّرف
برنامجك الذي يستخدم مكتبة ديناميكية .يجب تضمين ترويسة المكتبة ليعرف المصِّرف األنواع المحددة للدوال
التي تستدعيها ،إذ يحتاج المصِّرف فقط معرفَة األنواع المرتبطة بالدالة (مثttل أن تأخtذ الدالtة النtوع intوتعيttد
لم يكن هذا هو الحال دائًم ا مع معايير لغة ،Cإذ افترضت المِّص رفات سttابًق ا أن أّي دالttة غttير معروفttة تعيttد
قيمة من النوع .intيكون لحجم المؤشر في نظttام 32بت حجم النtوع intنفسtه ،لtذلك ال توجtد مشtكلة في
ذلك ،ولكن يكون حجم المؤشر ضttعف حجم intفي نظttام 64بت ،لttذلك إذا أعttادت الدالttة مؤش ًtرا ،فسُttتدَّم ر
قيمتهاُ .يَع د ذلك األمر غير مقبول ،ألن المؤشر لن يؤّش ر إىل ذاكرة صالحة ،ولكن تغّي ر معيttار C99بحيث ُيطَلب
يطّبق الرابط الديناميكي الكثير من العمل للمكتبttات المشttتركة ،ولكن ال يttزال الرابttط التقليttدي يلعب دوًرا
هاًم ا في إنشاء الملف القابل للتنفيذ ،إذ يجب أن يضع الرابط التقليدي مؤشًرا في الملttف القابttل للتنفيttذ حttتى
يعttرف الرابtط الtديناميكي المكتبtة الtتي سttتحّق ق االعتماديtات Dependenciesفي وقت التشtغيل .يتطلب
القسم dynamicمن الملف القابل للتنفيذ مدخلة مطلوبة NEEDEDلكل مكتبtة مشtتركة يعتمtد عليهtا الملtف
القابل للتنفيذ.
يمكننا فحص هذه الحقول باستخدام برنامج .readelfسنلقي فيمttا يلي نظttرة عىل ملttف ثنttائي معيttاري
225
علوم الحاسوب من األلف إىل الياء مفهوم الربط الديناميكي
يمكنك أن ترى أن المثال السابق يحدد ثالث مكتبات .المكتبة األكثر شيوًع ا التي تتشارك بها أغلبية الttبرامج
الموجودة عىل النظttام -إن لم تكن جميعهttا -هي مكتبttة ،libcوهنttاك بعض المكتبttات األخttرى الttتي يحتاجهttا
تكون قراءة ملف ELFالمباشرة مفيدًة أحياًن ا ،ولكن الطريقttة المعتttادة لفحص ملttف قابttل للتنفيttذ مرتبttط
ديناميكًي ا هي باستخدام أداة lddالttتي تمttر عىل اعتماديttات المكتبttات ،حيث سttتظِه ر لttك إذا كttانت المكتبttة
يمكننا أن نرى في المثال السابق أن مكتبttة libpthreadمطلوبttة من مكttان مttا ،حيث إذا تعّم قنttا قلياًل ،
ويعمل عىل تحميل المكتبات في الذاكرة وتعديل البرنttامج في وقت التشttغيل السttتدعاء الttدوال الموجttودة في
226
علوم الحاسوب من األلف إىل الياء مفهوم الربط الديناميكي
المكتبttة .يسttمح ملttف ELFللملفttات القابلttة للتنفيttذ بتحديttد المفّس ر Interpreterالttذي هttو برنttامج يجب
استخدامه لتشغيل الملف القابل للتنفيذ .يضبط المصِّرف Compilerوالرابttط السttاكن Static Linkerمفّس ر
الملفات القابلة للتنفيذ الذي يعتمد عىل المكتبات الديناميكية ليكون الرابط الديناميكي.
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
PHDR 0x0000000000000040 0x4000000000000040 0x4000000000000040
0x0000000000000188 0x0000000000000188 R E 8
INTERP 0x00000000000001c8 0x40000000000001c8 0x40000000000001c8
0x0000000000000018 0x0000000000000018 R 1
][Requesting program interpreter: /lib/ld-linux-ia64.so.2
LOAD 0x0000000000000000 0x4000000000000000 0x4000000000000000
0x0000000000022e40 0x0000000000022e40 R E 10000
LOAD 0x0000000000022e40 0x6000000000002e40 0x6000000000002e40
0x0000000000001138 0x00000000000017b8 RW 10000
DYNAMIC 0x0000000000022f78 0x6000000000002f78 0x6000000000002f78
0x0000000000000200 0x0000000000000200 RW 8
NOTE 0x00000000000001e0 0x40000000000001e0 0x40000000000001e0
0x0000000000000020 0x0000000000000020 R 4
IA_64_UNWIND 0x0000000000022018 0x4000000000022018 0x4000000000022018
0x0000000000000e28 0x0000000000000e28 R 8
يمكنك أن ترى في المثال السابق أن المفّس ر مضttبوط ليكttون /lib/ld-linux-ia64.so.2أي يمثttل الرابttط
الديناميكي .تتحقق النواة Kernelعندما تحّم ل الملف الثنائي للتنفيذ مما إذا كان الحقل PT_INTERPموجوًدا،
حيث إذا كان موجوًدا ،فيجب تحميل ما يؤّش ر إليه في الذاكرة وتشغيله.
ذكرنا أن للملفات القابلة للتنفيذ المرتبطة ديناميكًي ا مراجع يجب إصالحها باستخدام معلومttات غttير متttوفرة
حtttتى وقت التشtttغيل مثtttل عنtttوان دالtttة موجtttودة في مكتبtttة مشtttتركة ،وتسَّtttم ى هtttذه المراجtttع
باالنتقاالت .Relocations
9.2.1االنتقاالت Relocations
يتمثل الجزء األساسي من الرابط الديناميكي في إصالح العناوين في وقت التشغيل الذي ُيَع د المرة الوحيدة
التي يمكنك أن تعرف فيها مكان تحميلك في الttذاكرة بالضttبط .يمكن التفكttير في االنتقttاالت بأنهttا مالحظttة أن
227
علوم الحاسوب من األلف إىل الياء مفهوم الربط الديناميكي
عنواًنا معيًنا يجب إصالحه في وقت التحميل ،حيث يجب قراءة جميع االنتقاالت وإصttالح العنttاوين الttتي تشttير
إليها لإلشارة إىل المكان الصحيح قبل أن تصبح الشيفرة البرمجية جاهزة للتشغيل .إليك مثال عن عملية انتقال:
الحدث العنوان
عنوان الرمز ""x 0x123456
هناك العديد من أنواع االنتقاالت لكل معماريtة ،حيث ُيوَّث ق السtلوك الtدقيق لكtل نtوع بوصttفه جtزًءا من
واجهة ABIالخاصة بالنظامُ .يَع د تعريف االنتقاالت واضًحا وسهاًل كما في المثال التالي:
{ typedef struct
Elf32_Addr ;r_offset <--- address to fix
Elf32_Word ;r_info <--- symbol table pointer and relocation
type
}
{ typedef struct
Elf32_Addr ;r_offset
Elf32_Word ;r_info
Elf32_Sword ;r_addend
} Elf32_Rela
يشير الحقل r_offsetإىل اإلزاحة في الملف التي يجب إصالحها ،ويحدد الحقttل r_infoنttوع االنتقttال
الذي يِص ف بالضبط ما يجب تطبيقه إلصالح هذه الشيفرة البرمجيةُ .تَع د قيمة الرمز أبسط عملية انتقttال ُم عَّرفttة
لمعماريٍة ما ،حيث يمكنك ببساطة في هذه الحالة استبدال عنوان الرمز في الموقع المحttدد ،وبالتttالي سttيكتمل
يوجد نوعان من الطرق الُم ستخدمة لتشغيل االنتقاالت أحttدهما مttع قيمttة ُم ضttافة واآلخttر بttدونها .القيمttة
المضافة هي ببساطة شيء يجب إضافته إىل العنوان الذي جرى إصالحه للعثور عىل العنوان الصttحيح ،فمثاًل إذا
كان االنتقال للرمز iمثاًل ،فسُتضَبط القيمة الُم ضافة عىل القيمة 8ألن الشيفرة البرمجيttة األصttلية تطّب ق شttيًئا
مثل ] ،i[8وهذا يعني العثور عىل عنوان iثم تجاوزه بمقدار .8
يجب تخزين هذه القيمة المضافة في مكان مtا ،فمثاًل ُتخَّtزن في صttيغة RELالقيمtة الُم ضttافة في شttيفرة
البرنامج ضمن المكان الذي يجب أن يكون فيه العنtوان الtذي جtرى إصttالحه .لtذا يجب إصttالح العنtوان بصttورة
صحيحة من خالل قراءة الذاكرة التي تريد إصالحها للحصول عىل أّي قيمة مضافة وتخزينها والعثttور عىل العنttوان
228
علوم الحاسوب من األلف إىل الياء مفهوم الربط الديناميكي
الحقيقي وإضافة القيمة المضافة إليه ثم كتابته مرة أخرى فوق القيمة المضافة .بينما تحدد الصيغة RELAمكان
هناك بعض المقايضات لكٍل من هاتين الصيغتين ،إذ يجب في صيغة RELاستخدام مرجttع ذاكttرة إضttافي
للعثور عىل القيمة المضافة قبل اإلصالح ،لكنك ال تهدر مساحًة في الملف الثنائي ألنك تستخدم الttذاكرة الهttدف
لعملية االنتقال .بينما يمكنttك في صttيغة RELAاالحتفttاظ بالقيمttة المضttافة مttع االنتقttال ،ولكنttك تهttدر هttذه
المساحة في ملف القرص الصلب الثنائي .تستخدم معظم األنظمة الحديثة انتقاالت .RELA
يوضح المثال التالي كيفية عمل االنتقاالت ،حيث سننشئ مكتبتين مشتركتين بسيطتين ونشير إىل إحttدى
لدينا عملية انتقال واحدة في addendtest.soمن النوع R_IA64_DIR64LSBالذي إن بحثت عنttه في
:LSB .3بمttا أن IA64يمكنهttا أن تعمttل في أنمttاط ( Big Endianتخttزين البتttات األقttل أهميttة أواًل )
و ( Little Endianتخزين البتات األكثر أهميtة أواًل ) ،فسtيكون هtذا االنتقtال ( Little Endianالبtايت
229
علوم الحاسوب من األلف إىل الياء مفهوم الربط الديناميكي
تقول واجهة ABIأن هذا االنتقال يمثل قيمة الرمز الذي يؤّش ر إليه مع أّي قيمttة مضttافة .يمكننttا أن نttرى أن
لدينا قيمة مضافة مقدارها ، 8حيث أن حجم النوع intيساوي 4أو ،sizeof(int) == 4ونقلنا قيمttتين
من النوع intفي المصفوفة أي .*j = i + 2لذا يمكن إصالح هذا االنتقال في وقت التشttغيل من خالل
العثور عىل عنوان الرمز iووضع قيمته باإلضافة إىل القيمة 8في .0x104f8
9.2.2استقالل المواقع
ُيعَط ى مقطع الشيفرة البرمجية ومقطع البيانات في ملف قابtل للتنفيtذ عنواًن ا أساسًtي ا محtدًدا في الtذاكرة
الوهمية ،لذا ال يمكن مشاركة شيفرة الملف القابل للتنفيذ الذي يحصل عىل فضاء عناوين جديد خاص به ،وهttذا
يعني أن المصِّرف يعرف بالضبط مكان قسم البيانات ويمكنه الرجوع إليه مباشرًة .
ال تمتلك المكتبات مثل هذا الضمان ،إذ يمكنها معرفة أن قسم البيانات الخاص بها سttيكون إزاحttة محttددة
عن العنوان األساسي ،ولكن ال يمكن بالضبط معرفة مكttان ذلttك العنttوان األساسttي إاّل في وقت التشttغيل .لttذا
يجب إنشاء جميع المكتبات باستخدام شيفرة برمجية يمكن تنفيذها بغض النظر عن مكttان وضttعها في الttذاكرة،
وُيعَرف ذلك باسم الشيفرة المستقلة عن الموقع ،Position Independent Codeأو PICاختصاًرا .الحttظ أن
قسم البيانات ال يزال يمثل إزاحة ثابتة عن قسم الشيفرة البرمجية ،ولكن يجب إضافة اإلزاحة إىل عنوان التحميل
حيث ذكرنا سابًق ا أن ميزة المكتبttة المشttتركة مttع الttذاكرة الوهميttة هي أن الttبرامج المتعttددة يمكنهttا اسttتخدام
تنبع هذه المشكلة من حقيقة أن المكتبttات ليس لttديها أي ضttمان حttول مكttان وضttعها في الttذاكرة ،حيث
سيجد الرابط الديناميكي المكان األكثر مالءمة في الذاكرة الوهمية لكل مكتبة مطلوبة ويضttعها هنtاك .لكن إن لم
يحدث ذلك ،فستطلب كل مكتبة في النظttام جزءهttا الخttاص من الttذاكرة الوهميttة حttتى ال تتttداخل مttع غيرهttا.
تتطلب كل مكتبة جديدة في النظام تخصيًص ا لها عند إضافتها ،ويمكن كتابة مكتبة ضخمة ال تترك مساحَة كافية
للمكتبات األخرى مع احتمالية أاّل يرغب برنامجك أبًدا في استخدام هذه المكتبة عىل أّي حال .إذا عّttدلَت شttيفرة
مكتبة مشتركة لديها انتقال ،فلن تصبح هذه الشيفرة البرمجية قابلًة للمشاركة ،وسنفقد ميزة المكتبة المشتركة.
لنفترض أننا نأخذ قيمة رمٍز ما ،حيث سيكون لدينا باسttتخدام االنتقttاالت فقttط رابttط دينttاميكي يبحث عن
عنوان الذاكرة لهذا الرمز ويعيد كتابة الشيفرة البرمجية لتحميل هذا العنوان .يمكن تحسين هذا الموقف من خالل
تخصيص مساحة في الملف الثنائي لالحتفاظ بعنوان هذا الرمز وجعل الرابط الديناميكي يضع العنوان هناك بداًل
من وضttعه في الشttيفرة البرمجيttة مباشttرًة ،وبالتttالي ال نحتttاج أبًttدا إىل تعttديل جttزء الشttيفرة البرمجيttة في
الملف الثنائي.
230
علوم الحاسوب من األلف إىل الياء مفهوم الربط الديناميكي
تسمى المنطقة المخصصة لهذه العناوين بجدول اإلزاحة العام - Global Offset Tableأو GOTاختصttاًرا-
وهو يتواجد في القسم .gotمن ملف ،ELFويوضح الشكل التالي الوصول للذاكرة باستخدام جدول :GOT
ُيَع د جدول GOTخاًص ا بكل عملية ،ويجب أن يكون للعملية أذونات للكتابة خاصة بها .بينمttا يمكن مشttاركة
شيفرة المكتبة ويجب أن يكون للعملية فقط أذونات قراءة وتنفيذ الشيفرة البرمجيttة ،وإال فسttيكون هنttاك خttرق
)void test(void
{
;i = 100
}
231
علوم الحاسوب من األلف إىل الياء مفهوم الربط الديناميكي
<test>:
0d 10 00 18 00 21 [MFI] mov r2=r12
00 00 02 00 c0 nop.f 0x0
41c: 81 09 00 90 addl r14=24,r1;;
0d 78 00 1c 18 10 [MFI] ld8 r15=[r14]
00 00 02 00 c0 nop.f 0x0
42c: 41 06 00 90 mov r14=100;;
00 38 1e 90 11 [MIB] st4 [r15]=r14
c0 00 08 00 42 80 mov r12=r2
43c: 08 00 84 00 br.ret.sptk.many b0;;
Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000
0 0 0
[ 1] .hash HASH 0000000000000120 00000120
00000000000000a0 0000000000000004 A 2 0 8
[ 2] .dynsym DYNSYM 00000000000001c0 000001c0
00000000000001f8 0000000000000018 A 3 e 8
[ 3] .dynstr STRTAB 00000000000003b8 000003b8
000000000000003f 0000000000000000 A 0 0 1
[ 4] .rela.dyn RELA 00000000000003f8 000003f8
A 2 0 8
[ 5] .text PROGBITS 0000000000000410 00000410
AX 0 0 16
[ 6] .IA_64.unwind_inf PROGBITS 0000000000000440 00000440
A 0 0 8
[ 7] .IA_64.unwind IA_64_UNWIND 0000000000000458 00000458
AL 5 5 8
[ 8] .data PROGBITS 0000000000010470 00000470
232
علوم الحاسوب من األلف إىل الياء مفهوم الربط الديناميكي
يوّض ح المثال السابق كيفية إنشاء مكتبة مشتركة بسيطة تشير إىل رمز خارجي .ال نعttرف عنttوان هttذا الرمttز
في وقت التصريف ،لذلك نتركه للرابط الttديناميكي إلصttالحه في وقت التشttغيل .لكننttا نريttد أن تبقى الشttيفرة
البرمجية قابلًة للمشاركة في حالة رغبttة العمليttات األخttرى في اسttتخدام هttذه الشttيفرة ،حيث يوّض ح التفكيttك
Disassemblyكيفية تطبيق ذلك باستخدام القسم ُ ..gotيعرف المسّجل r1في معمارية IA64التي ُص ِّرفت
المكتبة من أجلها بالمؤشر العام Global Pointerالذي يؤّش ر دائًم ا إىل مكان تحميل القسم .gotفي الذاكرة.
إذا ألقينا نظرة عىل خرج األداة ،readelfفيمكننا أن نرى أن القسttم .gotيبttدأ عنttد عنttوان يبعttد بمقttدار
0x10570بttايت عن مكttان تحميttل المكتبttة في الttذاكرة .لttذا إذا ُحِّم لت المكتبttة في الttذاكرة عنttد العنttوان
كما يمكننا أن نرى أننا نخزن القيمة 100في عنوان الttذاكرة الموجttود في المس ّtجل r15الttذي يحتttوي عىل
قيمttة عنttوان الttذاكرة المخttزن في المسttجل ،r14حيث حّم لنttا هttذا العنttوان من خالل إضttافة عttدد صttغير إىل
المسّجل ُ .1يَع د جدول GOTمجرد قائمة طويلة من المدخالت ،حيث تكون كل مدخلttة خاص ًtة بمتغttير خttارجي،
مما يعني أن مدخلة جدول GOTالخاصة بالمتغير الخارجي iتخّزن 24بايت أو 3عناوين بحجم 64بت.
233
علوم الحاسوب من األلف إىل الياء مفهوم الربط الديناميكي
كما يمكننا التحقق من انتقال مدخلة جدول ،GOTحيث يمثل االنتقاُل استبداَل القيمة عند اإلزاحة 10588
يبدأ القسم .gotعند اإلزاحة 0x10570عن الخرج السابق ،حيث رأينا كيف تحّم ل الشيفرة البرمجية عنواًنا
يبعد عن القسم .gotبمقدار ( 0x18أو 24في النظام العشري) ،مما يمنحنا عنواًنا مقداره 0x10570 + 0x18
= 0x10588يمّث ل العنوان الذي ُط ِّبق االنتقال ألجله .لذا يجب أن يصلح الرابط الديناميكي االنتقال قبل أن يبدأ
البرنامج للتأكد من أن قيمة الذاكرة عند اإلزاحة 0x10588هي عنوان المتغير العام .i
إلنجاز عمله .يستخدم البرنامج دالة أو دالتين فقttط من كttل مكتبttة من المكتبttات المتعttددة المتاحttة ،ويمكن أن
تستخدم الشيفرة البرمجية بعض الدوال دون غيرها اعتماًدا عىل مسار وقت التشغيل.
تحتوي عملية الربط الttديناميكي Dynamic Linkingالttتي شttرحناها في الفصttل السttابق عىل الكثttير من
العمليات الحسابية ،ألنها تتضمن النظر والبحث عبر العديد من الجداول ،لttذا يمكن تحسttين األداء عنttد تطttبيق
تقنيttات تسttاعد في تقليttل هttذا الِح مttل النttاتج عن هttذه العمليttات الحسttابية الكثttيرة وهttو مttا سنشttرحه
الكسول Lazy Bindingفي البرامج ،حيث ُيَع د االرتباط Bindingمرادًف ا لعملية إصttالح المتغttيرات الموجttودة
في جدول GOTالموضحة سابًق ا ،إذ ُيقال أن المدخلة مرتبطة بعنوانها الفعلي عند إصالحها.
يتضمن البرنامج في بعض األحيان دالًة من مكتبة ،ولكنه ال يسtتدعيها أبًtدا اعتمtاًدا عىل دخtل المسtتخدم.
تحتوي عملية االرتباط الخاصة بهذه الدالة الكثير من العمليات لتطبيقها ،ألنها تتضمن تحميل الشيفرة البرمجيttة
والبحث في الجداول والكتابة في الذاكرة ،لذا ُتَع د المتابعة في عملية ارتباط دالة غير ُم ستخَدمة مضيعة للttوقت،
حيث يؤِّجل االرتباط الكسول هذه العملية حتى يستدعي جدوُل PLTالدالَة الفعلية.
234
علوم الحاسوب من األلف إىل الياء مفهوم الربط الديناميكي
لكttل دالttة في مكتبٍttة مدخل ٌtة في جttدول PLTتؤّش ر في البدايttة إىل بعض الشttيفرات البرمجيttة الوهميttة
Dummy Codeالخاصة .إن استدعى البرنامج الدالة ،فهذا يعني أنه يستدعي مدخلة من جدول PLTباسtتخدام
تحّم ل هذه الدالة الوهمية بعض المعامالت الttتي تريttد تمريرهttا إىل الرابttط الttديناميكي لتتمكن من تحليttل
الدالة ثم استدعاء دالة بحث خاصة بالرابط الديناميكي .يجد الرابط الديناميكي عنوان الدالttة الفعلي ،ويكتب هttذا
الموقع في استدعاء الملف الثنائي في أعىل استدعاء الدالة الوهمية ،وبالتالي يمكن تحميل العنوان دون الحاجttة
إىل العودة إىل المحمل الديناميكي مرة أخرى في المرة التالية الستدعاء الدالة .إن لم ُتسtتدَع ى دالٌtة مطلًق ا ،فلن
ُتعَّد ل مدخلة جدول PLTأبًدا ولكن لن يكون هناك وقت تشغيل إضافي.
يجب أن تبدأ اآلن في إدراك أن هناك قttدًرا ال بttأس بttه من العمttل في تحليttل رمttز دينttاميكي .لنطلttع عىل
تطبيق " "hello Worldالبسيط الذي يجري استدعاء مكتبtة واحtد فقtط هtو اسtتدعاء الدالtة printfلعtرض
)int main(void
{
;)"printf("Hello, World!\n
;return 0
}
235
علوم الحاسوب من األلف إىل الياء مفهوم الربط الديناميكي
عttل وضttذي يمثtt الprintf زtt للرمR_IA64_IPLTLSB يمكننا أن نرى في المثال السابق أن لدينا االنتقال
يجب أن نبدأ في البحث بصورة أعمق للعثور.0x6000000000000f10 عنوان رمز هذه الدالة في عنوان الذاكرة
: الخاصة بالبرنامجmain() سنلقي في المثال التالي نظرة عىل تفكيك الدالة الرئيسية
<main>:
00 08 15 08 80 05 [MII] alloc r33=ar.pfs,5,4,0
20 02 30 00 42 60 mov r34=r12
400000000000079c: 04 08 00 84 mov r35=r1
40000000000007a0: 01 00 00 00 01 00 [MII] nop.m 0x0
40000000000007a6: 00 02 00 62 00 c0 mov r32=b0
40000000000007ac: 81 0c 00 90 addl r14=72,r1;;
40000000000007b0: 1c 20 01 1c 18 10 [MFB] ld8 r36=[r14]
40000000000007b6: 00 00 00 02 00 00 nop.f 0x0
40000000000007bc: 78 fd ff 58 br.call.sptk.many
b0=4000000000000520 <_init+0xb0>
40000000000007c0: 02 08 00 46 00 21 [MII] mov r1=r35
40000000000007c6: e0 00 00 00 42 00 mov r14=r0;;
40000000000007cc: 01 70 00 84 mov r8=r14
40000000000007d0: 00 00 00 00 01 00 [MII] nop.m 0x0
40000000000007d6: 00 08 01 55 00 00 mov.i ar.pfs=r33
40000000000007dc: 00 0a 00 07 mov b0=r32
40000000000007e0: 1d 60 00 44 00 21 [MFB] mov r12=r34
40000000000007e6: 00 00 00 02 00 80 nop.f 0x0
40000000000007ec: 08 00 84 00 br.ret.sptk.many
b0;;
اtt حيث يمكنن،printf ةttتدعاء الدالttو اسtt ه0x4000000000000520 وانttتدعاء العنttون اسttيجب أن يك
: كما يليreadelf باستخدام األداةSections معرفة مكان هذا العنوان من خالل االطالع األقسام
236
علوم الحاسوب من األلف إىل الياء مفهوم الربط الديناميكي
Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000
0000000000000000 0 0 0
...
[11] .plt PROGBITS 40000000000004c0 000004c0
00000000000000c0 0000000000000000 AX 0 0 32
[12] .text PROGBITS 4000000000000580 00000580
00000000000004a0 0000000000000000 AX 0 0 32
[13] .fini PROGBITS 4000000000000a20 00000a20
0000000000000000 AX 0 0 16
[14] .rodata PROGBITS 4000000000000a60 00000a60
000000000000000f 0000000000000000 A 0 0 8
[15] .opd PROGBITS 4000000000000a70 00000a70
0000000000000000 A 0 0 16
[16] .IA_64.unwind_inf PROGBITS 4000000000000ae0 00000ae0
00000000000000f0 0000000000000000 A 0 0 8
[17] .IA_64.unwind IA_64_UNWIND 4000000000000bd0 00000bd0
00000000000000c0 0000000000000000 AL 12 c 8
[18] .init_array INIT_ARRAY 6000000000000c90 00000c90
0000000000000000 WA 0 0 8
[19] .fini_array FINI_ARRAY 6000000000000ca8 00000ca8
0000000000000000 WA 0 0 8
[20] .data PROGBITS 6000000000000cb0 00000cb0
0000000000000000 WA 0 0 4
[21] .dynamic DYNAMIC 6000000000000cb8 00000cb8
00000000000001e0 0000000000000010 WA 5 0 8
[22] .ctors PROGBITS 6000000000000e98 00000e98
0000000000000000 WA 0 0 8
[23] .dtors PROGBITS 6000000000000ea8 00000ea8
0000000000000000 WA 0 0 8
[24] .jcr PROGBITS 6000000000000eb8 00000eb8
0000000000000000 WA 0 0 8
[25] .got PROGBITS 6000000000000ec0 00000ec0
0000000000000000 WAp 0 0 8
[26] .IA_64.pltoff PROGBITS 6000000000000f10 00000f10
0000000000000000 WAp 0 0 16
237
علوم الحاسوب من األلف إىل الياء مفهوم الربط الديناميكي
لtt لكن لنواص.PLT دولttتدعاؤها في جttد اسttع حيث يوجttو متوقtt كما ه.plt يوجد هذا العنوان في القسم
: لنرى ما يفعله هذا االستدعاء كما يلي.plt البحث أكثر ولنفكك القسم
40000000000004c0 <.plt>:
40000000000004c0: 0b 10 00 1c 00 21 [MMI] mov r2=r14;;
40000000000004c6: e0 00 08 00 48 00 addl r14=0,r2
40000000000004cc: 00 00 04 00 nop.i 0x0;;
40000000000004d0: 0b 80 20 1c 18 14 [MMI] ld8 r16=[r14],8;;
238
علوم الحاسوب من األلف إىل الياء مفهوم الربط الديناميكي
ا فيtt وخّزناه،r1 ّجلt ودة في المسttة الموجtt إىل القيم80 ةtt حيث أضفنا أواًل القيم،إًذ ا لنمر عىل التعليمات
وي عىلttذي يحتtt الr15 جلttزين المسttني تخttا يعtt مم،GOT دولtt إىل جr1 جلtt سيؤّش ر المس.r15 ّجلt المس
ثم،r16 ّجلt إىل المسGOT دولttع من جttذا الموقtt حّم لنا القيمة المخزنة في ه، ثانًي ا.GOT بايت في جدول80
في-GOT دولtع جttأو موق- r1 ّجلtا المسtt خّزن، ثالًث ا.اتtt بايت8 بمقدارr15 زدنا القيمة الموجودة في المسجل
239
علوم الحاسوب من األلف إىل الياء مفهوم الربط الديناميكي
المسّجل r14وضبطنا القيمة الموجودة في المسجل r1لتكون القيمة الموجودة في 8بايتات التاليttة للمسّtجل
ناقشنا سابًق ا كيفية استدعاء الدوال باسttتخدام واصttف الدالttة Function Descriptorالttذي يحتttوي عىل
عنوان الدالة وعنوان المؤشر العام .يمكننا أن نرى أن مدخلة جدول PLTتحّم ل أواًل قيمttة الدالttة ،ممttا يttؤدي إىل
االنتقttال بمقttدار 8بايتttات إىل الجttزء الثttاني من واصttف الدالttة ثم تحميttل تلttك القيمttة في مس ّtجل العمليttة
نعلم أن المسttجل r1سيؤّش ر إىل جttدول ،GOTثم سttنذهب بمقttدار 80بttايت بعttد جttدول GOTأي
بمقدار (.)0x50
6000000000000ec0 <.got>:
...
6000000000000ee8: 80 0a 00 00 00 00 data8 0x02a000000
6000000000000eee: 00 40 90 0a dep r0=r0,r0,63,1
6000000000000ef2: 00 00 00 00 00 40 [MIB] (p20) break.m 0x1
6000000000000ef8: a0 0a 00 00 00 00 data8 0x02a810000
6000000000000efe: 00 40 50 0f br.few
>6000000000000ef0 <_GLOBAL_OFFSET_TABLE_+0x30
6000000000000f02: 00 00 00 00 00 60 [MIB] (p58) break.m 0x1
6000000000000f08: 60 0a 00 00 00 00 data8 0x029818000
6000000000000f0e: 00 40 90 06 br.few
>6000000000000f00 <_GLOBAL_OFFSET_TABLE_+0x40
Disassembly of section .IA_64.pltoff:
6000000000000f10 <.IA_64.pltoff>:
6000000000000f10: f0 04 00 00 00 00 [MIB] (p39) break.m 0x0
6000000000000f16: 00 40 c0 0e 00 00 data8 0x03b010000
6000000000000f1c: 00 00 00 60 data8 0xc000000000
6000000000000f20: 00 05 00 00 00 00 [MII] (p40) break.m 0x0
6000000000000f26: 00 40 c0 0e 00 00 data8 0x03b010000
6000000000000f2c: 00 00 00 60 data8 0xc000000000
6000000000000f30: 10 05 00 00 00 00 [MIB] (p40) break.m 0x0
6000000000000f36: 00 40 c0 0e 00 00 data8 0x03b010000
6000000000000f3c: 00 00 00 60 data8 0xc000000000
240
علوم الحاسوب من األلف إىل الياء مفهوم الربط الديناميكي
يمكننا فك شيفرة خرج البرنامج objdumpلنتمّكن من رؤية ما جرى تحميله بالضبط .يttؤدي تبttديل تttرتيب
إذ يبttدو هttذا العنttوان مألوًف ا ،حيث إذا نظرنttا إىل الttوراء في نttاتج التجميttع الخttاص بجttدول ، PLTفسttنرى
ذلك العنوان.
أواًل تضع الشيفرة البرمجية الموجودة عند العنوان 0x4000000000004f0قيمة صفرية في المسجل ،r15
ثم تتفر ع مرة أخرى إىل العنوان ،0x40000000000004c0ولكن ُيَع د هtذا العنtوان بدايtة القسtم .PLTيمكننtا
تتّب ع هttذه الشttيفرة البرمجيttة ،إذ نحفttظ أواًل قيمttة المؤشttر العttام في المسttجل ،r2ثم نحمttل ثالث قيم بحجم
8بايتات في المسجالت r16و r17و ،r1ثم نتفر ع إىل العنوان الموجود في المسجل ،r17حيث يمّث ل تلttك
يجب أن نتعمق قلياًل في فهم واجهة ABIالتي تعطينا مفهومين لنفهم بالضبط ما يجري تحميله اآلن ،وهذا
المفهومtttان همtttا أنtttه يجب أن تحتtttوي الtttبرامج المرتبطtttة ديناميكًي ا عىل قسtttم خtttاص يسtttمى القسtttم
DT_IA_64_PLT_RESERVEالذي يمكنه االحتفاظ بثالث قيم بحجم 8بايتات ،ويوجttد مؤشttر في مكttان وجttود
هذه المنطقة المحجوزة في المقطع الديناميكي للملف الثنائي الموّض ح في المثال التالي:
241
علوم الحاسوب من األلف إىل الياء مفهوم الربط الديناميكي
الحظ أننا حصلنا عىل قيمة جدول GOTنفسه ،وهذا يعني أن أول ثالث مدخالت بحجم 8بايتات في جدول
GOTتمثل المنطقة المحجوزة ،وبالتالي سُيؤَّش ر إليها دائًم ا باستخدام المؤشر العام.
يجب أن يمأل الرابط الديناميكي هذه القيم عند بدء تشغيله ،حيث تحّدد واجهttة ABIأنttه يجب ملء القيمttة
األوىل بواسطة الرابط الديناميكي الذي يمنح هذه الوحدة معرًف ا فريًtدا ،والقيمtة الثانيttة هي قيمtة المؤشttر العttام
للرابط الديناميكي ،والقيمة الثالثة هي عنوان الدالة التي تبحث عن الرمز وتصلحه.
يوّض ح المثttال التttالي شttيفرة برمجيttة في الرابttط الttديناميكي إلعttداد قيم خاصttة من المكتبttة libcأو من
:sysdeps/ia64/dl-machine.h
*/
إعداد الكائن المحَّم ل الموصوف باستخدام المتغير Lحتى تقفز مدخالت جدول PLTالتي *
ليس لها انتقاالت إلى شيفرة اإلصالح البرمجية عند الطلب في ملف * .dl-runtime.c
*/
))static inline int __attribute__ ((unused, always_inline
)elf_machine_runtime_setup (struct link_map *l, int lazy, int profile
{
;)extern void _dl_runtime_resolve (void
;)extern void _dl_runtime_profile (void
)if (lazy
{
;)"register Elf64_Addr gp __asm__ ("gp
;Elf64_Addr *reserve, doit
*/
احذر من تبديل األنواع Typecastهنا أو ستُضاف عناصر مؤشر * l-l_addr
*/
242
علوم الحاسوب من األلف إىل الياء مفهوم الربط الديناميكي
;reserve[1] = doit
;reserve[2] = gp
}
;return lazy
}
يمكننا أن نرى كيفية إعداد هذه القيم بواسطة الرابط الديناميكي من خالل النظر في الدالة التي تطّب ق ذلttك
للملف الثنائيُ .يضَبط المتغير reserveمن مؤشر القسم PLT_RESERVEفي الملttف الثنttائي .تمثttل القيمttة
الفريدة الموضوعة في ] reserve[0عنوان خارطttة الربttط Link Mapلهttذا الكttائن ،حيث ُتَع د خارطttة الربttط
التمثيtttل الtttداخلي ضtttمن مكتبtttة glibcللكائنtttات المشtttتركة.ثم نضtttع بعtttد ذلtttك عنtttوان الدالtttة
_dl_runtime_resolveفي القيمة الثانية بافتراض أننا ال نستخدم عملية التشخيص ،Profilingثم ُتضبط
قيمة ] reserve[2عىل gpالتي يمكن العثور عليها في المسجل r2باستخدام االستدعاء __.__asm
243
علوم الحاسوب من األلف إىل الياء مفهوم الربط الديناميكي
إذا عدنا إىل الوراء في واجهة ،ABIفسنرى أنه يجب وضع فهرس انتقال للمدخلة في المسttجل r15ويجب
تمرير المعّرف الفريد في المسجل ُ .r16ض ِبط المسجل r15مسبًق ا في الشttيفرة االختباريttة Stub Codeقبttل
العودة إىل بداية جدول .PLTألِق نظرة عىل المدخالت ،والحظ كيف تحِّم ل كل مدخلttة في جttدول PLTالمسttجل
r15مع قيمة متزايدة ،إذ ال ينبغي أن يكون ذلك مفاجًئا إذا نظttرت إىل عمليttات االنتقttال ،حيث يكttون النتقttال
نحّم ل المسجل r16من القيم التي هّي أها الرابط الديناميكي ،ثم يمكننا تحميل عنوان الدالة والمؤشttر العttام
والفttر ع في الدالttة ،ثم نشّtغ ل دالttة الرابttط الttديناميكي _dl_runtime_resolveالttتي تعttثر عىل االنتقttال.
يستخدم االنتقال اسم الرمز الذي حّدده للعثور عىل الدالة الصحيحة ،حيث يمكن يتضttمن ذلttك تحميttل المكتبttة
من القرص الصلب إن لم تكن موجودة في الذاكرة ،وإاّل فيجب مشاركة الشيفرة البرمجية.
يوفر سجُل االنتقال للرابط الديناميكي العنواَن الذي يجب إصالحه ،حيث كان هذا العنوان موجوًدا في جدول
GOTثم حّم لته شيفرة PLTاالختبارية ،وهذا يعني أنه يمكن الحصttول عىل عنttوان الدالttة المباشttر أو مttا يسttمى
بتقصير دورة الرابط الديناميكي بعد المرة األوىل التي ُتستدَع ى فيها الدالة أي في المرة الثانية لتحميلها.
رأينا اآلليttة الدقيقttة لعمttل جttدول PLTوالعمttل الttداخلي للرابttط الttديناميكي .النقttاط المهمttة الttتي يجب
تذكرها هي:
تستدعي استدعاءات المكتبة في برنامجك الشيفرة االختبارية في جدول PLTالخاص بالملف الثنائي. •
يؤّش ر هذا العنوان إىل دالٍة في الرابط الديناميكي قادرٍة عىل البحث عن الدالة الحقيقيttة من خالل النظttر •
يعيد الرابط الديناميكي كتابة العنوان الذي تقttرأه الشttيفرة االختباريttة ،بحيث تنتقttل الدالttة مباشttرة إىل •
9.5.1إصدارات المكتبات
إحدى المشاكل الُم حتَم لة هي وجtود إصtدارات مختلفtة للمكتبtات .لكن هنtاك احتمtال أقtل بكثtير لوجtود
مشاكل عند استخدام المكتبات الساكنة ،حيث ُتدَم ج شيفرة المكتبة البرمجية مباشرًة في الملف الثنائي الخاص
بالتطبيق .إن أردَت استخدام إصدار جديد من المكتبة ،فيجب إعادة تصريفها في ملف ثنائي جديttد لتحttل محttل
244
علوم الحاسوب من األلف إىل الياء مفهوم الربط الديناميكي
اإلصدار القديمُ .يَع د ذلtك أمًtرا غtير عملي إىل حtد مtا بالنسtبة للمكتبtات الشtائعة وأكثرهtا شtيوًع ا مكتبtه libc
والُم ضَّم نة في معظم التطبيقات .إذا كانت المكتبة متوفرة فقط بوصفها مكتبttة سttاكنة ،فيجب إعttادة بنttاء كttل
يمكن أن تسّبب التعديالت في طريقة عمل المكتبة الديناميكيttة مشttكالت متعttددة .تكttون التعttديالت في
أحسن األحوال متوافقة تماًم ا دون تغيير أّي شيء مرئي خارجًي ا ،ولكن يمكن أن تتسttبب التعttديالت في تعطttل
التطبيق مثل تغير الدالة التي تأخذ النوع intلتأخذ النوع * .intاألسوأ من ذلك هو أن يغّي ر إصttداُر المكتبttة
الجديد الدالالت ويعيد قيًم ا مختلفة وخاطئة فجأًة .يمكن أن يكون هذا خطًأ يصعب تعّق به ،حيث إن تعطttل أحttد
التطبيقات ،فيمكنك استخدام منّق ح أخطاء Debuggerلعزل مكان حدوث الخطttأ ،بينمttا يمكن أن يظهttر تلttف
يتطلب الرابط الديناميكي طريقة لتحديد إصدار المكتبات في النظttام بحيث يمكن التع ّtرف عىل التعttديالت
األحدث .هناك عtدد من األنظمtة الtتي يمكن للرابtط الtديناميكي الحtديث اسtتخدامها للعثtور عىل اإلصttدارات
ُيستخَدم نظام sonamesإلضافة بعض المعلومات اإلضافية إىل مكتبة للمسttاعدة في تحديttد اإلصttدارات.
يسرد التطبيق المكتبات التي يريدها في الحقول DT_NEEDEDضمن القسم الديناميكي للملف الثنائي ،وتوجttد
المكتبttة الفعليttة في ملttف عىل القttرص الصttلب ضttمن المجلttد /libلمكتبttات النظttام األساسttية أو المجلttد
/usr/libللمكتبات االختيارية.
يتطلب وجوُد إصدارات متعtددة من المكتبtة عىل القtرص الصtلب اسtتخداَم أسtماء ملفtات مختلفtة .لtذا
يستخدم نظام sonamesمجموعة من األسماء وروابًط ا إىل نظام الملفات لبناء تسلسل هرمي من المكتبات من
خالل تقديم مفهوم التعديالت الرئيسية Majorوالثانوية Minorللمكتبةُ .يَع د التعديل الثانوي تعدياًل متوافًق ا مع
إصدار سابق من المكتبة ،ويتكون من إصالحاٍت لألخطاء فقط .بينما ُيَع د التعديل الرئيسي أي تعديل غير متوافق
تشّكل الحاجة إىل االحتفاظ بكل تعtديل مكتبtة رئيسtي أو ثtانوي في ملtف منفصtل عىل القtرص الصtلب
أساَس تسلسtل المكتبtات الهtرمي .يكtون اسttم المكتبtة هttو libNAME.so.MAJOR.MINORحسtب الِع رف
المتبع ،حيث يمكنك اختيارًيا الحصول عىل إطالق Releaseبوصفه معرًف ا نهائًي ا بعد العدد الثانوي ،ويكفي ذلك
لتمييز جميع إصدارات المكتبة المختلفة .لكن إذا ُرِبط كل تطبيق بهذا الملف مباشرًة ،فسنواجه المشكلة نفسttها
التي واجهناها مع المكتبة الساكنة ،إذ يجب إعادة بناء التطبيق لإلشارة إىل المكتبة الجديدة في كttل مttرة يحttدث
فيها تعديل ثانوي .ما نريده هو أن نشير إىل ما يمثله العدد الرئيسttي Majorمن المكتبttة الttذي إن تغttير ،فيجب
إعادة تصريف Recompileتطبيقنا ،ألننا نحتاج إىل التأكد من أن برنامجنا ال يزال متوافًق ا مع المكتبة الجديدة.
245
علوم الحاسوب من األلف إىل الياء مفهوم الربط الديناميكي
الديناميكي لمكتبة مشتركة ،حيث يمكن لمؤلف المكتبة تحديد هذا اإلصدار عند إنشاء المكتبة.
يمكن أن يحّدد كل ملف مكتبة لإلصدار الثانوي عىل القرص الصلب رقَم اإلصدار الرئيسي نفسه في الحقttل
،DT_SONAMEمما يسمح للرابط الديناميكي بمعرفة أن ملف المكتبة يطّبق تعدياًل رئيسًtي ا معيًن ا لواجهttتي API
و ABIالخاصتين بالمكتبة.
لذا ُيشَّغ ل تطبيق اسمه ldconfigإلنشاء روابط رمزية لإلصدار الرئيسي إىل أحدث إصدار ثانوي عىل النظام.
يعمtل تطtبيق ldconfigمن خالل تشtغيل جميtع المكتبtات الtتي تطّب ق رقم إصtدار رئيسtي معين ،ثم يختtار
المكتبة التي تحتوي عىل أعىل رقم تعديل ثttانوي ،ثم ينِش ئ رابًط ا رمزًي ا من libNAME.so.MAJORإىل ملttف
الجزء األخير من التسلسل الهرمي هو اسم تصريف Compile Nameالمكتبة .إن أردت تصريف برنامجك
لربطه بمكتبة ،فيمكنك استخدام الراية -lNAMEالتي تبحث عن الملف libNAME.soفي مسار بحث المكتبة.
الحظ أننا لم نحدد أي رقم إصدار ،ألننا نريد فقط الربط بأحدث مكتبة عىل النظام .يعود األمttر إىل إجttراء التثttبيت
الخاص بالمكتبة إلنشاء رابط رمزي بين اسم التصريف libNAME.soوأحدث شيفرة مكتبة عىل النظام ،ويمكن
التعامل مع ذلك باستخدام نظام الحزم dpkgأو .rpmال ُيَع د ذلك عملية آلية ،إذ ُيحتَم ل أاّل تكون أحttدث مكتبttة
عىل النظام هي المكتبة التي ترغب في تصريفها دائًم ا ،فمثاًل يمكن أن تكون أحدث مكتبة ُم ثَّبتة إصداًرا تطويرًي ا
غير مناسب لالستخدام العام ،ويوضح الشكل التالي العملية العامة لنظام :sonames
246
علوم الحاسوب من األلف إىل الياء مفهوم الربط الديناميكي
يبحث الرابط الديناميكي في الحقل DT_NEEDEDللعثور عىل المكتبات المطلوبة عند بدء تشغيل التطبيق،
حيث يحتوي هذا الحقل عىل اسم sonameالخاص بالمكتبة ،لذا فالخطوة التالية هي أن يمر الرابttط الttديناميكي
تتضttمن هttذه العمليttة من الناحيttة النظريttة خطttوتين .أواًل ،يجب أن يبحث الرابttط الttديناميكي في جميttع
المكتبات للعثور عىل تلك المكتبات الttتي تطّب ق نظttام sonameالمحttدد .ثانًي ا ،يجب موازنttة أسttماء الملفttات
الخاصة بالتعديالت الثانوية للعثور عىل أحدث إصدار والذي يكون جاهًزا للتحميل الحًق ا.
ذكرنا سابًق ا أن هنttاك رابًط ا رمزًي ا أعّttده برنttامج ldconfigبين اسttم sonameالخttاص بالمكتبttة والتعttديل
الثانوي األخير ،وبالتالي يجب أن يتبع الرابط الtديناميكي هttذا الرابtط فقttط للعثttور عىل الملttف الصttحيح المtراد
تحميله بداًل من االضطرار إىل فتح جميع المكتبات الممكنة وتحديد المكتبات التي تريد استخدامها في كttل مttرة
ُيَع د الوصول إىل نظام الملفات بطيًئا جًدا ،لذا ينشئ برنامج ldconfigذاكرة مخبئيtة للمكتبtات الُم ثَّبتtة في
النظام ،حيث تكون هذه الذاكرة المخبئية ببسttاطة قائمًtة بأسttماء sonameالخاصttة بالمكتبttات المتاحttة للرابttط
الديناميكي ومؤشًرا لرابط اإلصدار الرئيسي عىل القرص الصلب ،مما يوفر عىل الرابط الديناميكي قراءة مجلttدات
كاملة مليئة بالملفات لتحديد الرابط الصحيح .يمكنك تحليل ذلك باستخدام /sbin/ldconfig -pالموجttود
ضمن الملف ./etc/ldconfig.so.cacheإن لم ُيعَث ر عىل المكتبة في الttذاكرة المخبئيttة ،فسttيعود الرابttط
الttديناميكي إىل الخيttار األبطttأ المتمثttل في المttرور عىل نظttام الملفttات ،وبالتttالي يجب إعttادة تشttغيل برنttامج
9.5.2البحث عن الرموز
ناقشنا كيف حصل الرابط الديناميكي عىل عنوان دالة المكتبة ووضعه في جدول PLTليسttتخدمه البرنttامج،
ولكننا لم نناقش حتى اآلن كيف يجد الرابط الديناميكي عنوان الدالةُ .تس َّtم ى هttذه العمليttة باالرتبttاط ،Binding
يحتوي الرابط الديناميكي عىل أجزاء من المعلومات مثل الرمز الذي يبحث عنه وقائمة المكتبات التي يمكن
أن يكون هذا الرمز فيها كما هو محَّtد د باسttتخدام حقttول DT_NEEDEDفي الملttف الثنttائي .تحتttوي كttل مكتبttة
كائنات مشتركة عىل قسم يسمى .dynsymممَّيز عىل أنه ،SHT_DYNSYMحيث ُيَع د هttذا القسttم الحttد األدنى
من مجموعة الرموز المطلوبة للربط الديناميكي ،وهو أّي رمز في المكتبة يمكن أن يستدعيه برنامج خارجي.
247
علوم الحاسوب من األلف إىل الياء مفهوم الربط الديناميكي
هناك ثالثة أقسام تلعب جميعها دوًرا في وصف الرمttوز الديناميكيttة .لنلِtق أواًل نظttرة عىل تعريttف رمttز من
{ typedef struct
Elf32_Word ;st_name
Elf32_Addr ;st_value
Elf32_Word ;st_size
;unsigned char st_info
;unsigned char st_other
Elf32_Half ;st_shndx
;} Elf32_Sym
القيمة الحقل
القيمة الموجودة في كائن مشترك قابل للنقل ،حيث تحتفظ هذه القيمة باإلزاحة عن قسم
st_value
الفهرس المعطى في الحقل
معلومات عن ارتباط Bindingالرمز الذي سنشرحه الحًق ا ويكون نوع هذا الرمز دالة أو كائن
st_info
أو غير ذلك
فهرس القسم الذي يوجد فيه الرمز (اّط لع عىل الحقل )st_value st_shndx
تكون السلسلة النصية الفعلية السم الرمز ضمن قسم منفصل هو القسم .dynstrحيث تحتوي المدخلttة
في هذا القسم فهرًس ا إىل قسم السالسل النصttية فقttط ،ممtا يtؤدي إىل ظهtور مسtتًو ى معين من الِح مtل عىل
الرابط الديناميكي ،إذ يجب أن يقرأ الرابط الديناميكي جميع مدخالت الرموز في القسم .dynstrثم يتبع مؤشر
248
علوم الحاسوب من األلف إىل الياء مفهوم الربط الديناميكي
يمكن تسريع هذه العمليtة من خالل تقtديم قسtم ثtالث يسtمى .hashيحتtوي عىل جtدول تعميtة Hash
Tableألسماء رموز مدخالت جدول الرموزُ .يحَس ب جدول التعميtة مسtبًق ا عنtد إنشtاء المكتبtة ويسtمح للرابtط
الديناميكي بالعثور عىل مدخلة الرمز بصورة أسر ع باستخدام عملية بحث واحدة أو اثنتين فقط.
تشير عملية العثور عىل عنوان رمز إىل عملية ارتباط هذا الرمز ،ولكن ارتبttاط الرمttوز Symbol Bindingلttه
معًنى منفصل ،إذ تفرض عملية ارتباط الرموز رؤيتها خارجًي ا أثنttاء عمليttة الربttط الttديناميكيُ .يَع د الرمttز المحلي
Local Symbolغير مرئي خارج ملف الكائن الُم عَّرف ضttمنه ،بينمtا ُيَع د الرمttز العttام Global Symbolمرئًي ا
لملفات الكائنات األخرى ويمكن أن يحِّق ق المراجَع غير الُم عَّرفة في كائنات أخرى .يكون المرجع الضttعيف Weak
Referenceنوًع ا خاًص ا من المراجع العامة ذات األولوية المنخفضة ،مما يعني أنttه ُم صَّtم م لتجttاوزه كمttا سttنرى
الحًق ا.
;)int external_function(void
)int function(void
{
;)(return external_function
}
249
علوم الحاسوب من األلف إىل الياء مفهوم الربط الديناميكي
SYMBOL TABLE:
l *df *ABS 00000000 test.c
l d .text 00000000 .text
l d .data 00000000 .data
l d .bss 00000000 .bss
l F .text 00000024 static_function
l d .sbss 00000000 .sbss
l O .sbss 00000004 static_variable
l d .note.GNU-stack 00000000 .note.GNU-stack
l d .comment 00000000 .comment
g F .text 00000038 function
**UND 00000000 external_function
0000005c w F .text 00000024 weak_function
$ nm test.o
U external_function
T function
t static_function
s static_variable
0000005c W weak_function
الحظ استخدام #pragmaلتعريف الرمز الضعيف ،حيث ُيَع د pragmaطريقttة إليصttال معلومttات إضttافية
إىل المصِّرف Compilerواستخدامه غttير شttائع ،ولكن يكttون في بعض األحيttان مطلوًب ا إلخttراج المص ِّtرف من
العمليات المعتادة.
يمكن فحص الرموز باستخدام أداتين مختلفتين كما هو موَّض ح في المثال السابق ،حيث يظهر االرتبttاط في
العمود الثاني في كلتا الحالتين ،ويجب أن تكون الشيفرات البرمجية واضحة تماًم ا.
يجب أن يكون المبرمج قادًرا عىل تجاوز رمز في مكتبة ،مما يعني تخttريب الرمttز العttادي بتعريٍttف مختلttف.
ذكرنا أن تtرتيب البحث في المكتبtات ُم حَّtد ٌد حسtب تtرتيب حقtول DT_NEEDEDداخtل المكتبtة ،ولكن يمكن
250
علوم الحاسوب من األلف إىل الياء مفهوم الربط الديناميكي
منهاtوز ضtُيعَث ر عىل أّي رمtه سtني أنtذا يعt وه،اtإدخال مكتبات لتكون المكتبات األخيرة التي يجري البحث عنه
يحدد المكتبات التي يجبLD_PRELOAD يمكن تحقيق ذلك باستخدام متغير بيئة يسمى.بوصفها مرجًع ا نهائًي ا
$ cat override.c
#define _GNU_SOURCE 1
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <dlfcn.h>
pid_t getpid(void)
{
pid_t (*orig_getpid)(void) = dlsym(RTLD_NEXT, "getpid");
printf("Calling GETPID\n");
return orig_getpid();
}
$ cat test.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
printf("%d\n", getpid());
}
251
علوم الحاسوب من األلف إىل الياء مفهوم الربط الديناميكي
تجاوزنا في المثال السابق الدالة getpidلطباعة عبارة صغيرة عند استدعائها .نستخدم الدالة dlysmالttتي
توفرها مكتبة libcمع وسيط يخبرها باالستمرار والعثور عىل الرمز التالي المسَّم ى .getpid
الرموز الضعيفة
الرمز الضعيف هو الرمز الُم مَّيز بأنه لttه أولويttة أقttل ويمكن تجttاوزه برمttز آخttر ،حيث إن لم ُيعَث ر عىل تقttديم
Implementationآخttر أبًttدا ،فسttيكون الرمttز الضttعيف هttو الرمttز الُم سttتخَدم .لttذا يجب أن يحّم ل المحمttل
الديناميكي جميttع المكتبttات ويتجاهttل الرمttوز الضttعيفة الموجttودة في تلttك المكتبttات لصttالح الرمttوز العاديttة
الموجودة في مكتبات أخرى ،حيث كانت هذه هي الطريقة المتبعة لتقديم معالجttة الرمttوز الضttعيفة في لينكس
لكن كان ذلك غير صحيح بالنسبة لنص معيار يونكس في ذلك الوقت ( )SysVr4الذي يفرض أنttه يجب أن
يتعامل الرابط الساكن مع الرموز الضعيفة التي يجب أن تظل بعيدة عن الرابط الديناميكي .تطابق تقديم لينكس
الخاص بجعل الرابط الديناميكي يتجاوز الرموز الضعيفة مع منصة IRIXالخاصة بشركة SGIواختلف عن األنظمة
األخرى مثل Solarisو AIXفي ذلك الوقت .لذا لغى المطورون هذا السلوك عنtدما أدركtوا أنtه ينتهtك المعيtار،
رأينا كيف يمكننا تجاوز دالة في مكتبة من خالل التحميل المسبق لمكتبة مشتركة أخرى لهttا الرمttز المحttدد
نفسهُ .يَع د الرمز الذي ُيحَّلل بوصفه الرمز األخير بأن له المرتبة األخttيرة في تttرتيب تحميttل المحمttل الttديناميكي
للمكتبات ،حيث ُتحَّم ل المكتبات بالترتيب المحَّد د في الراية DT_NEEDEDالخاصة بالملف االثنائي ،وُيحَّد د هtذا
الترتيب بدوره من خالل ترتيب تمرير المكتبات في سطر األوامر عنtد بنtاء الكtائن .يبtدأ الرابtط الtديناميكي عنtد
تحديد موقع الرموز بآخر مكتبة ُم حَّم لة ويعمل بصورة عكسية حتى العثور عىل الرمز المطلوب.
لكن تحتاج بعض المكتبات المشتركة إىل طريقة لتجاوز هذا السلوك ،إذ يجب أن تخبر هذه المكتبات الرابط
الديناميكي بأنه يجب أن ينظر أواًل بداخلها عن هذه الرموز بداًل من العمل بصورة عكسية من آخttر مكتبttة ُم حَّم لttة.
يمكن للمكتبات ضبط الرايtة DT_SYMBOLICفي ترويسtة القسtم الtديناميكي للحصtول عىل هtذا السtلوك ،إذ
يمكن ضبط هذه الراية من خالل تمرير الراية -Bsymbolicعبر سطر أوامtر الروابtط السtاكنة عنtد بنtاء المكتبtة
المشتركة ،حيث تتحكم هذه الراية برؤية الرمز .Symbol Visibilityال يمكن تجاوز الرموز الموجودة في المكتبttة،
لكن يؤدي ذلك إىل فقدان قدر كبير من التفاصيل نظًtرا لتميttيز المكتبttة بهttذا السttلوك أو عttدم تمييزهttا ،إذ
سيسمح النظام األفضل بجعل بعض الرموز خاصة وبعض الرموز عامة.
252
علوم الحاسوب من األلف إىل الياء مفهوم الربط الديناميكي
افيةtt حيث يمكننا تحديد بعض المدخالت اإلض،يأتي النظام األفضل من خالل استخدام تحديد إصدار الرموز
:للرابط الساكن لمنحه بعض المعلومات اإلضافية حول الرموز في المكتبة المشتركة كما يلي
$ cat Makefile
all: test testsym
clean:
rm -f *.so test testsym
liboverride.so : override.c
$(CC) -shared -fPIC -o liboverride.so override.c
libtest.so : libtest.c
$(CC) -shared -fPIC -o libtest.so libtest.c
libtestsym.so : libtest.c
$(CC) -shared -fPIC -Wl,-Bsymbolic -o libtestsym.so libtest.c
$ cat libtest.c
#include <stdio.h>
int foo(void) {
printf("libtest foo called\n");
return 1;
}
int test_foo(void)
{
return foo();
253
علوم الحاسوب من األلف إىل الياء مفهوم الربط الديناميكي
$ cat override.c
#include <stdio.h>
int foo(void)
{
printf("override foo called\n");
return 0;
}
$ cat test.c
#include <stdio.h>
int main(void)
{
printf("%d\n", test_foo());
}
$ cat Versions
{global: test_foo; local: *; };
تكون الدالة.يمكننا ذكر ما إذا كان الرمز عاًم ا أم محلًي ا في أبسط الحاالت عىل النحو الوارد في المثال السابق
ولكن إن،test_foo ةttة للدالttة الكليttاوز الوظيفtt ويمكن أن نكون سعداء بتج،test_foo دالة دعم للدالةfoo
. إذ ال ينبغي ألحٍد تعديل دالة الدعم، فيجب الوصول إليها دون تعديل،استخدمنا إصدار المكتبة المشتركة
254
علوم الحاسوب من األلف إىل الياء مفهوم الربط الديناميكي
يسمح ذلك بالحفاظ عىل فضاء أسمائنا منظًم ا بطريقة أفضل ،إذ يمكن أن ترغب العديُttد من المكتبttات في
تقديم شtيء يمكن تسtميته باسtم دالtة شtائعة مثtل readأو ،writeولكن إن فعلت ذلtك ،فيمكن أن يكtون
اإلصدار الفعلي الممنوح للبرنامج خاطًئا تماًم ا .يمكن للمطور من خالل تحديد الرموز بأنها محلية التأكُttد من عttدم
تعارض أي شيء مع هذا االسم الداخلي دون أن يؤثر االسم الذي يختاره عىل أّي برنامج آخر.
جاء مفهوم تحديد إصدار الرمttوز Symbol Versioningمن تلttك الفكttرة ،حيث يمكنttك تحديttد إصttدارات
متعددة من الرمز نفسه ضمن المكتبة نفسهاُ .يلِح ق الرابط الساكن بعض معلومات اإلصدار بعد اسم الرمز مثttل
إن قّدم المطور دالة لها االسم نفسttه تقttديًم ا ثنائًي ا أو برمجًي ا مختلًف ا ،فيمكنttه زيttادة رقم اإلصttدار .تلتقttط
التطبيقات الجديدة أحدث إصدار من الرمز عند بنائها بمقابل المكتبة المشتركة .لكن ستطلب التطبيقات المبنية
بمقابل اإلصدارات السابقة من المكتبة نفسها إصدارات أقدم ،فمثاًل سيكون لهttا سالسttل @VERأقttدم في اسttم
255
.10قائمة المصطلحات
واجهة التطبيق الثنائية Application Binary Interfaceأو ABIاختصاًر ا :وصف تقني لكيفية •
لغTTة التوصTTيف الموَّس عة :Extensible Markup Languageتع ّtرف عىل هttذه اللغttة أكttثر في •
أكاديمية حسوب.
لغة التوصيف المعياري العام ُ :Standardised Generalised Markup Languageتَع د األب •
اإلقصاء المتبادل :Mutually Exclusiveيمكن أن يكون عنصر واحد فقط صالًحا في كل مttرة عنttد •
تطبيق اإلقصاء المتبادل عىل عtدد من األشtياء ،إذ يtؤدي كtون أحtد األشtياء صtالًحا إىل جعtل األشtياء
:MMUمكون وحدة إدارة الذاكرة Memory Management Unitفي معمارية العتاد. •
المصدر المفتوح ُ :Open Sourceتوَّز ع البرمجيttات بصttيغة مصttدر بمttوجب تttراخيص تضttمن ألي •