Download as pdf or txt
Download as pdf or txt
You are on page 1of 269

‫علوم الحاسوب من األلف إىل الياء‬

‫مرجع شامل لتعلم علوم الحاسوب‬


‫‪Book Title: Computer Science from the‬‬ ‫اسم الكتاب‪ :‬علوم الحاسوب من األلف إىل الياء‬
‫‪Bottom Up‬‬

‫‪Author: Ian Wienand‬‬ ‫المؤلف‪ :‬إيان ويناند‬

‫‪Translator: Ola Abbas - Zainab Al Zaeem‬‬ ‫المترجم‪ :‬عال عباس ‪ -‬زينب الزعيم‬

‫‪Editor: Ayat Alyatakan - Jamil Bailony‬‬ ‫المحرر‪ :‬آيات اليطقان ‪ -‬جميل بيلوني‬

‫‪Cover Design: Ahmed Emara‬‬ ‫تصميم الغالف‪ :‬أحمد عمارة‬

‫‪Publication Year:‬‬ ‫‪2024‬‬ ‫سنة النشر‪:‬‬

‫‪Edition:‬‬ ‫‪1.0‬‬ ‫رقم اإلصدار‪:‬‬

‫بعض الحقوق محفوظة ‪ -‬أكاديمية حسوب‪.‬‬

‫أكاديمية حسوب أحد مشاريع شركة حسوب محدودة المسؤولية‪.‬‬

‫مسجلة في المملكة المتحدة برقم ‪.07571594‬‬

‫‪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).

You are free to: :‫لك مطلق الحرية في‬


• Share — copy and redistribute the ‫• المشاركة — نسخ وتوزيع ونقل العمل ألي‬
material in any medium or format .‫وسط أو شكل‬
• Adapt — remix, transform, and build ‫ واإلضافة عىل‬،‫ التحويل‬،‫• التعديل — المزج‬
upon the material .‫العمل‬

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‬‬ ‫تمهيد‬

‫‪24‬‬ ‫‪ .1‬نظرة متقدمة عىل يونكس ولغة سي‬

‫‪36‬‬ ‫‪ .2‬تمثيل األعداد والنظام الثنائي‬

‫‪67‬‬ ‫‪ .3‬معمارية الحاسوب‬

‫‪97‬‬ ‫‪ .4‬نظام التشغيل‬

‫‪120‬‬ ‫‪ .5‬العمليات في نظام تشغيل الحاسوب‬

‫‪139‬‬ ‫‪ .6‬الذاكرة الوهمية ‪Virtual Memory‬‬

‫‪162‬‬ ‫‪ .7‬سلسلة األدوات ‪Toolchain‬‬

‫‪185‬‬ ‫‪ .8‬ما وراء العملية‬

‫‪224‬‬ ‫‪ .9‬مفهوم الربط الديناميكي‬

‫‪256‬‬ ‫‪ .10‬قائمة المصطلحات‬


‫علوم الحاسوب من األلف إىل الياء‬ ‫جدول المحتويات‬

‫جدول المحتويات‬
‫‪20‬‬ ‫تمهيد‬

‫‪20‬‬ ‫حول الكتاب‬

‫‪22‬‬ ‫استخدام الشيفرة‬

‫‪23‬‬ ‫المساهمة‬

‫‪24‬‬ ‫‪ .1‬نظرة متقدمة عىل يونكس ولغة سي‬

‫‪24‬‬ ‫‪ 1.1‬كل شيء عبارة عن ملف‬

‫‪25‬‬ ‫‪ 1.2‬تطبيق التجريد‬

‫‪26‬‬ ‫‪ 1.2.1‬تطبيق التجريد بلغة البرمجة ‪C‬‬

‫‪29‬‬ ‫‪ 1.2.2‬المكتبات‬

‫‪30‬‬ ‫‪ 1.3‬مفهوم واصفات الملفات ‪File Descriptors‬‬

‫‪33‬‬ ‫‪ 1.3.1‬الصدفة ‪Shell‬‬

‫‪33‬‬ ‫ا‪ .‬إعادة التوجيه ‪Redirection‬‬

‫‪34‬‬ ‫ب‪ .‬تنفيذ عملية التمرير ‪pipe‬‬

‫‪36‬‬ ‫‪ .2‬تمثيل األعداد والنظام الثنائي‬

‫‪36‬‬ ‫‪ 2.1‬تعرف عىل نظام العد الثنائي ‪ Binary‬أساس الحوسبة‬

‫‪36‬‬ ‫‪ 2.1.1‬نظرية النظام الثنائي‬

‫‪37‬‬ ‫ا‪ .‬أسس الحوسبة‬

‫‪38‬‬ ‫ب‪ .‬البتات ‪ Bits‬والبايتات ‪Bytes‬‬

‫‪38‬‬ ‫‪ .2‬أسكي ‪ASCII‬‬

‫‪39‬‬ ‫‪ .2‬التكافؤ ‪Parity‬‬

‫‪39‬‬ ‫‪ .2‬الحواسيب ذات أنظمة ‪ 16‬و ‪ 32‬و ‪ 64‬بت‬

‫‪40‬‬ ‫ج‪ .‬كيلوبايت وميغابايت وغيغابايت‬

‫‪41‬‬ ‫‪ .2‬كيلوبت وميغابت وغيغابت‬

‫‪41‬‬ ‫‪ .2‬التحويل‬

‫‪42‬‬ ‫د‪ .‬العمليات البوليانية ‪Boolean Operations‬‬

‫‪42‬‬ ‫‪Not .2‬‬

‫‪42‬‬ ‫‪And .2‬‬

‫‪7‬‬
‫علوم الحاسوب من األلف إىل الياء‬ ‫جدول المحتويات‬

‫‪43‬‬ ‫‪Or .2‬‬

‫‪43‬‬ ‫‪ .2‬معامل أو الحصرية ‪Exclusive Or‬‬

‫‪43‬‬ ‫ه‪ .‬استخدام العمليات البوليانية في الحواسيب‬

‫‪44‬‬ ‫و‪ .‬العمل بالنظام الثنائي في اللغة ‪C‬‬

‫‪44‬‬ ‫‪ 2.1.2‬النظام الست عشري ‪Hexadecimal‬‬

‫‪46‬‬ ‫‪ 2.1.3‬االستخدام العملي لألنظمة العددية‬

‫‪46‬‬ ‫ا‪ .‬استخدام النظام الثنائي في الشيفرات البرمجية‬

‫‪46‬‬ ‫ب‪ .‬التقنع والرايات‬

‫‪46‬‬ ‫‪ .2‬التقنع ‪Masking‬‬

‫‪48‬‬ ‫‪ .2‬الرايات ‪flags‬‬

‫‪49‬‬ ‫‪ 2.2‬تمثيل األنواع واألعداد في األنظمة الحاسوبية‬

‫‪49‬‬ ‫‪ 2.2.1‬معايير اللغة ‪C‬‬

‫‪50‬‬ ‫‪ 2.2.2‬جنو سي ‪GNU C‬‬

‫‪50‬‬ ‫‪ 2.2.3‬األنواع‬

‫‪52‬‬ ‫ا‪ 64 .‬بت‬

‫‪53‬‬ ‫ب‪ .‬مؤهالت األنواع‬

‫‪54‬‬ ‫ج‪ .‬األنواع المعيارية‬

‫‪54‬‬ ‫د‪ .‬التطبيق العملي لألنواع‬

‫‪56‬‬ ‫‪ 2.2.4‬تمثيل األعداد‬

‫‪56‬‬ ‫ا‪ .‬القيم السلبية‬

‫‪56‬‬ ‫ب‪ .‬بت اإلشارة ‪Sign Bit‬‬

‫‪57‬‬ ‫ج‪ .‬المتمم األحادي ‪One's complement‬‬

‫‪57‬‬ ‫‪ .2‬المتمم الثنائي ‪Two's Complement‬‬

‫‪58‬‬ ‫‪ .2‬امتداد اإلشارة ‪Sign-extension‬‬

‫‪58‬‬ ‫د‪ .‬األعداد العشرية ‪Floating Point‬‬

‫‪61‬‬ ‫‪ .2‬القيم الموحدة ‪Normalised Values‬‬

‫‪62‬‬ ‫‪ .2‬مهارات التوحيد ‪Normalisation‬‬

‫‪63‬‬ ‫‪ .2‬خالصة األفكار السابقة‬

‫‪67‬‬ ‫‪ .3‬معمارية الحاسوب‬

‫‪8‬‬
‫علوم الحاسوب من األلف إىل الياء‬ ‫جدول المحتويات‬

‫‪67‬‬ ‫‪ 3.1‬تعرف عىل وحدة المعالجة المركزية وعملياتها‬

‫‪68‬‬ ‫‪ 3.1.1‬التفريع ‪Branching‬‬

‫‪68‬‬ ‫‪ 3.1.2‬الدورات‬

‫‪68‬‬ ‫‪ 3.1.3‬جلب التعليمة وفك تشفيرها وتنفيذها وتخزين نتيجتها‬

‫‪69‬‬ ‫ا‪ .‬نظرة داخلية إىل وحدة المعالجة المركزية‬

‫‪70‬‬ ‫ب‪ .‬استخدام خط األنابيب ‪Pipelining‬‬

‫‪71‬‬ ‫ج‪ .‬إعادة الترتيب‬

‫‪72‬‬ ‫‪ 3.1.4‬معمارية ‪ CISC‬ومعمارية ‪RISC‬‬

‫‪73‬‬ ‫ا‪ .‬معمارية ‪EPIC‬‬

‫‪74‬‬ ‫‪ 3.2‬تعرف عىل ذاكرة الحاسوب‬

‫‪74‬‬ ‫‪ 3.2.1‬تسلسل الذواكر الهرمي‬

‫‪75‬‬ ‫‪ 3.2.2‬نظرة معمقة عىل الذاكرة المخبئية‬

‫‪78‬‬ ‫ا‪ .‬عنونة الذاكرة المخبئية‬

‫‪79‬‬ ‫‪ 3.3‬األجهزة الطرفية ‪ Peripherals‬ونواقلها ‪Buses‬‬

‫‪79‬‬ ‫‪ 3.3.1‬المفاهيم الخاصة بنواقل األجهزة الطرفية‬

‫‪80‬‬ ‫ا‪ .‬المقاطعات ‪Interrupts‬‬

‫‪81‬‬ ‫‪ .3‬حفظ الحالة‬

‫‪81‬‬ ‫‪ .3‬المقاطعات ‪ Interrupts‬والمصائد ‪ Traps‬واالستثناءات ‪Exceptions‬‬

‫‪81‬‬ ‫‪ .3‬أنواع المقاطعات‬

‫‪82‬‬ ‫‪ .3‬المقاطعات غير القابلة للتقنع أو اإلخفاء ‪Non-maskable Interrupts‬‬

‫‪82‬‬ ‫ب‪ .‬فضاء اإلدخال واإلخراج ‪IO‬‬

‫‪82‬‬ ‫‪ 3.3.2‬الوصول المباشر للذاكرة ‪DMA‬‬

‫‪83‬‬ ‫‪ 3.3.3‬نواقل أخرى‬

‫‪83‬‬ ‫ا‪USB .‬‬

‫‪84‬‬ ‫‪ 3.4‬أنظمة المعالجات في معمارية الحاسوب‬

‫‪84‬‬ ‫‪ 3.4.1‬المعالجة المتعددة المتماثلة ‪Symmetric Multi-Processing‬‬

‫‪85‬‬ ‫ا‪ .‬ترابط الذواكر المخبئية ‪Cache Coherency‬‬

‫‪86‬‬ ‫‪ .3‬حصرية الذاكرة المخبئية في أنظمة ‪SMP‬‬

‫‪87‬‬ ‫ب‪ .‬تقنية خيوط المعالجة الفائقة ‪Hyperthreading‬‬

‫‪9‬‬
‫علوم الحاسوب من األلف إىل الياء‬ ‫جدول المحتويات‬

‫‪87‬‬ ‫ج‪ .‬األنوية المتعددة ‪Multi Core‬‬

‫‪88‬‬ ‫‪ 3.4.2‬العناقيد ‪Clusters‬‬

‫‪89‬‬ ‫‪ 3.4.3‬الوصول غير الموحد للذاكرة ‪Non-Uniform Memory Access‬‬

‫‪89‬‬ ‫ا‪ .‬تخطيط نظام ‪NUMA‬‬

‫‪90‬‬ ‫ب‪ .‬ترابط الذاكرة المخبئية ‪Cache Coherency‬‬

‫‪91‬‬ ‫ج‪ .‬تطبيقات ‪NUMA‬‬

‫‪91‬‬ ‫‪ 3.4.4‬ترتيب الذاكرة وقفلها‬

‫‪94‬‬ ‫ا‪ .‬المعالجات ونماذج الذاكرة‬

‫‪94‬‬ ‫ب‪ .‬القفل‬

‫‪94‬‬ ‫‪ .3‬صعوبات األقفال‬

‫‪95‬‬ ‫‪ .3‬استراتيجيات القفل‬

‫‪97‬‬ ‫‪ .4‬نظام التشغيل‬

‫‪97‬‬ ‫‪ 4.1‬دور نظام التشغيل وتنظيمه في معمارية الحاسوب‬

‫‪97‬‬ ‫‪ 4.1.1‬تجريد العتاد‬

‫‪97‬‬ ‫‪ 4.1.2‬تعدد المهام ‪Multitasking‬‬

‫‪98‬‬ ‫‪ 4.1.3‬الواجهات الموحدة ‪Standardised Interfaces‬‬

‫‪98‬‬ ‫‪ 4.1.4‬األمن‬

‫‪99‬‬ ‫‪ 4.1.5‬األداء‬

‫‪99‬‬ ‫‪ 4.2‬تنظيم نظام التشغيل‬

‫‪100‬‬ ‫‪ 4.2.1‬النواة ‪Kernel‬‬

‫‪100‬‬ ‫‪ 4.2.2‬النواة األحادية ‪ Monolithic‬والنواة الدقيقة ‪Microkernel‬‬

‫‪101‬‬ ‫ا‪ .‬الوحدات ‪Modules‬‬

‫‪101‬‬ ‫‪ 4.2.3‬االفتراضية ‪Virtualisation‬‬

‫‪103‬‬ ‫ا‪ .‬القنوات السرية ‪Covert Channels‬‬

‫‪104‬‬ ‫‪ 4.2.4‬مجال المستخدم‬

‫‪104‬‬ ‫‪ 4.3‬استدعاءات النظام ‪System Calls‬‬

‫‪104‬‬ ‫‪ 4.3.1‬أرقام استدعاءات النظام‬

‫‪105‬‬ ‫‪ 4.3.2‬الوسائط ‪Arguments‬‬

‫‪105‬‬ ‫‪ 4.3.3‬المصيدة ‪Trap‬‬

‫‪10‬‬
‫علوم الحاسوب من األلف إىل الياء‬ ‫جدول المحتويات‬

‫‪105‬‬ ‫‪ 4.3.4‬مكتبة ‪libc‬‬

‫‪105‬‬ ‫‪ 4.3.5‬تحليل استدعاء النظام‬

‫‪106‬‬ ‫ا‪ .‬معمارية ‪PowerPC‬‬

‫‪111‬‬ ‫ب‪ .‬استدعاءات نظام ‪x86‬‬

‫‪115‬‬ ‫‪ 4.4‬الصالحيات في نظام التشغيل‬

‫‪115‬‬ ‫‪ 4.4.1‬مستويات الصالحيات‬

‫‪116‬‬ ‫ا‪ .‬نموذج الحماية ‪386‬‬

‫‪116‬‬ ‫ب‪ .‬رفع مستوى الصالحيات‬

‫‪116‬‬ ‫ج‪ .‬استدعاءات النظام السريعة‬

‫‪120‬‬ ‫‪ .5‬العمليات في نظام تشغيل الحاسوب‬

‫‪120‬‬ ‫‪ 5.1‬ما هي العملية؟‬

‫‪120‬‬ ‫‪ 5.2‬عناصر العملية‬

‫‪121‬‬ ‫‪ 5.2.1‬معرف العملية‬

‫‪121‬‬ ‫‪ 5.2.2‬الذاكرة‬

‫‪121‬‬ ‫ا‪ .‬الشيفرة والبيانات‬

‫‪121‬‬ ‫ب‪ .‬المكدس ‪Stack‬‬

‫‪125‬‬ ‫ج‪ .‬الكومة ‪Heap‬‬

‫‪125‬‬ ‫د‪ .‬تخطيط الذاكرة‬

‫‪126‬‬ ‫‪ 5.2.3‬واصفات الملف ‪File Descriptors‬‬

‫‪126‬‬ ‫‪ 5.2.4‬المسجالت ‪Registers‬‬

‫‪126‬‬ ‫‪ 5.2.5‬حالة النواة‬

‫‪126‬‬ ‫ا‪ .‬حالة العملية‬

‫‪127‬‬ ‫ب‪ .‬األولوية ‪Priority‬‬

‫‪127‬‬ ‫ج‪ .‬اإلحصائيات‬

‫‪127‬‬ ‫‪ 5.3‬تسلسل العمليات الهرمي‬

‫‪128‬‬ ‫‪ 5.4‬استدعاءات النظام ‪ Fork‬و ‪Exec‬‬

‫‪128‬‬ ‫‪ 5.4.1‬استدعاءات ‪Fork‬‬

‫‪128‬‬ ‫‪ 5.4.2‬استدعاءات ‪Exec‬‬

‫‪128‬‬ ‫‪ 5.4.3‬كيفية تعامل لينكس مع ‪ fork‬و ‪exec‬‬

‫‪11‬‬
‫علوم الحاسوب من األلف إىل الياء‬ ‫جدول المحتويات‬

‫‪128‬‬ ‫ا‪ .‬النسخ‬

‫‪128‬‬ ‫‪ .5‬الخيوط ‪Threads‬‬

‫‪130‬‬ ‫‪ .5‬النسخ عند الكتابة‬

‫‪130‬‬ ‫‪ 5.4.4‬العملية األولية ‪Init Process‬‬

‫‪131‬‬ ‫ا‪ .‬مثال عملية شبه ميتة ‪Zombie‬‬

‫‪132‬‬ ‫‪ 5.5‬الجدولة ‪Scheduling‬‬

‫‪132‬‬ ‫‪ 5.5.1‬الجدولة ذات األولوية ‪ Preemptive‬والجدولة التعاونية ‪Co-operative‬‬

‫‪133‬‬ ‫‪ 5.5.2‬الوقت الفعلي ‪Realtime‬‬

‫‪133‬‬ ‫‪ 5.5.3‬القيمة اللطيفة ‪Nice Value‬‬

‫‪133‬‬ ‫‪ 5.5.4‬مجدول لينكس‬

‫‪134‬‬ ‫‪ 5.6‬الصدفة ‪Shell‬‬

‫‪135‬‬ ‫‪ 5.7‬اإلشارات ‪Signals‬‬

‫‪139‬‬ ‫‪ .6‬الذاكرة الوهمية ‪Virtual Memory‬‬

‫‪139‬‬ ‫‪ 6.1‬ما هي الذاكرة الوهمية ‪Virtual Memory‬؟‬

‫‪140‬‬ ‫‪ 6.1.1‬المعالجات ذات ‪ 64‬بت‬

‫‪140‬‬ ‫ا‪ .‬العناوين المعيارية ‪Canonical Addresses‬‬

‫‪141‬‬ ‫‪ 6.1.2‬استخدام فضاء العناوين‬

‫‪141‬‬ ‫‪ 6.2‬الصفحات ‪Pages‬‬

‫‪142‬‬ ‫‪ 6.3‬الذاكرة الحقيقية ‪Physical Memory‬‬

‫‪142‬‬ ‫‪ 6.4‬جداول الصفحات‬

‫‪143‬‬ ‫‪ 6.5‬العناوين الوهمية ‪Virtual Address‬‬

‫‪143‬‬ ‫‪ 6.5.1‬الصفحة‬

‫‪143‬‬ ‫‪ 6.5.2‬اإلزاحة ‪Offset‬‬

‫‪143‬‬ ‫‪ 6.5.3‬ترجمة العنوان الوهمي ‪Virtual Address‬‬

‫‪144‬‬ ‫‪ 6.6‬مفاهيم متعلقة بالعناوين الوهمية والصفحات وجداول الصفحات‬

‫‪145‬‬ ‫‪ 6.6.1‬فضاءات العناوين المفردة‬

‫‪145‬‬ ‫‪ 6.6.2‬الحماية‬

‫‪146‬‬ ‫‪ 6.6.3‬التبديل ‪Swap‬‬

‫‪146‬‬ ‫ا‪mmap .‬‬

‫‪12‬‬
‫علوم الحاسوب من األلف إىل الياء‬ ‫جدول المحتويات‬

‫‪146‬‬ ‫‪ 6.6.4‬مشاركة الذاكرة‬

‫‪147‬‬ ‫‪ 6.6.5‬ذاكرة القرص الصلب المخبئة ‪Cache‬‬

‫‪147‬‬ ‫ا‪ .‬ذاكرة الصفحة المخبئة ‪Page Cache‬‬

‫‪147‬‬ ‫‪ 6.7‬مواصفات الذاكرة الوهمية في لينكس‬

‫‪147‬‬ ‫‪ 6.7.1‬مخطط فضاء العناوين‬

‫‪148‬‬ ‫‪ 6.7.2‬جدول الصفحات المكون من المستويات الثالثة‬

‫‪150‬‬ ‫‪ 6.8‬دعم العتاد للذاكرة الوهمية في معمارية الحاسوب‬

‫‪150‬‬ ‫‪ 6.8.1‬الوضع الحقيقي والوضع الوهمي‬

‫‪150‬‬ ‫ا‪ .‬مشاكل التقطيع‬

‫‪151‬‬ ‫‪ 6.8.2‬مخزن الترجمة المؤقت ‪TLB‬‬

‫‪151‬‬ ‫ا‪ .‬أخطاء الصفحات‬

‫‪152‬‬ ‫‪ .6‬العثور عىل جدول الصفحات‬

‫‪152‬‬ ‫ب‪ .‬أخطاء أخرى متعلقة بالصفحات‬

‫‪153‬‬ ‫‪ 6.8.3‬إدارة مخزن ‪TLB‬‬

‫‪153‬‬ ‫ا‪ .‬تفريغ مخزن ‪TLB‬‬

‫‪154‬‬ ‫ب‪ .‬مخزن ‪ TLB‬المحمل برمجيا وعتادًيا‬

‫‪154‬‬ ‫‪ 6.9‬دعم العتاد للذاكرة الوهمية‬

‫‪154‬‬ ‫‪ 6.9.1‬معالج إيتانيوم‬

‫‪155‬‬ ‫ا‪ .‬فضاءات العناوين ‪Address spaces‬‬

‫‪156‬‬ ‫‪ .6‬مفاتيح الحماية ‪Protection Keys‬‬

‫‪157‬‬ ‫ب‪ .‬أداة إيتانيوم العتادية للمرور عىل جدول الصفحات ‪Page-Table‬‬

‫‪157‬‬ ‫‪ .6‬جدول الصفحات الخطي الوهمي ‪Virtual Linear Page-Table‬‬

‫‪160‬‬ ‫‪ .6‬جدول التعمية الوهمي ‪Virtual Hash Table‬‬

‫‪162‬‬ ‫‪ .7‬سلسلة األدوات ‪Toolchain‬‬

‫‪162‬‬ ‫‪ 7.1‬البرامج المصرفة ‪ Compiled‬والبرامج المفسرة ‪Interpreted‬‬

‫‪163‬‬ ‫‪ 7.1.1‬اآلالت االفتراضية ‪Virtual Machines‬‬

‫‪163‬‬ ‫‪ 7.2‬بناء ملف قابل للتنفيذ‬

‫‪163‬‬ ‫‪ 7.3‬التصريف ‪Compiling‬‬

‫‪164‬‬ ‫‪ 7.3.1‬الصياغة‬

‫‪13‬‬
‫علوم الحاسوب من األلف إىل الياء‬ ‫جدول المحتويات‬

‫‪164‬‬ ‫‪ 7.3.2‬توليد شيفرة التجميع ‪Assembly Generation‬‬

‫‪164‬‬ ‫ا‪ .‬المحاذاة ‪Alignment‬‬

‫‪165‬‬ ‫‪ .7‬حاشية البنية ‪Structure Padding‬‬

‫‪167‬‬ ‫‪ .7‬محاذاة خط الذاكرة المخبئية ‪Cache line alignment‬‬

‫‪167‬‬ ‫‪ .7‬المقايضة بين المساحة والسرعة‬

‫‪168‬‬ ‫‪ .7‬وضع االفتراضات‬

‫‪169‬‬ ‫‪ .7‬مفاهيم لغة ‪ C‬الخاصة بالمحاذاة‬

‫‪170‬‬ ‫‪ 7.3.3‬التحسين ‪Optimisation‬‬

‫‪170‬‬ ‫ا‪ .‬فك الحلقات ‪Unrolling Loops‬‬

‫‪170‬‬ ‫ب‪ .‬الدوال المضمنة ‪Inlining Functions‬‬

‫‪170‬‬ ‫ج‪ .‬توقع الفر ع ‪Branch Prediction‬‬

‫‪171‬‬ ‫‪ 7.4‬المجمع ‪Assembler‬‬

‫‪171‬‬ ‫‪ 7.5‬الرابط ‪Linker‬‬

‫‪172‬‬ ‫‪ 7.5.1‬الرموز ‪Symbols‬‬

‫‪172‬‬ ‫ا‪ .‬إمكانية رؤية الرموز ‪Symbol Visibility‬‬

‫‪173‬‬ ‫‪ 7.5.2‬عملية الربط‬

‫‪173‬‬ ‫‪ 7.6‬تطبيق عملي لبناء برنامج تنفيذي من شيفرة مصدرية بلغة ‪C‬‬

‫‪174‬‬ ‫‪ 7.6.1‬التصريف ‪Compiling‬‬

‫‪176‬‬ ‫‪ 7.6.2‬التجميع ‪Assembly‬‬

‫‪178‬‬ ‫‪ 7.6.3‬الربط ‪Linking‬‬

‫‪179‬‬ ‫‪ 7.6.4‬الملف القابل للتنفيذ ‪Executable‬‬

‫‪185‬‬ ‫‪ .8‬ما وراء العملية‬

‫‪185‬‬ ‫‪ 8.1‬نظرة عىل الملفات القابلة للتنفيذ‬

‫‪185‬‬ ‫‪ 8.2‬تمثيل الملفات القابلة للتنفيذ‬

‫‪186‬‬ ‫‪ 8.2.1‬الصيغة الثنائية ‪Binary Format‬‬

‫‪186‬‬ ‫‪ 8.2.2‬تاريخ الصيغة الثنائية‬

‫‪186‬‬ ‫ا‪a.out .‬‬

‫‪187‬‬ ‫ب‪COFF .‬‬

‫‪187‬‬ ‫‪ 8.3‬صيغة ملفات ‪ELF‬‬

‫‪14‬‬
‫علوم الحاسوب من األلف إىل الياء‬ ‫جدول المحتويات‬

‫‪188‬‬ ‫‪ 8.3.1‬ترويسة ملفات ‪ELF‬‬

‫‪191‬‬ ‫‪ 8.3.2‬الرموز ‪ Symbols‬والمنقوالت ‪Relocation‬‬

‫‪191‬‬ ‫‪ 8.3.3‬المقاطع ‪ Segments‬واألقسام ‪Sections‬‬

‫‪191‬‬ ‫ا‪ .‬المقاطع ‪Segments‬‬

‫‪193‬‬ ‫ب‪ .‬األقسام ‪Sections‬‬

‫‪197‬‬ ‫ج‪ .‬األقسام والمقاطع مع بعضها بعًض ا‬

‫‪198‬‬ ‫‪ 8.4‬واجهات ‪ABI‬‬

‫‪199‬‬ ‫‪ 8.4.1‬ترتيب البايتات‬

‫‪199‬‬ ‫‪ 8.4.2‬العرف المتبع في االستدعاءات‬

‫‪199‬‬ ‫‪ 8.5‬المكتبات‬

‫‪200‬‬ ‫‪ 8.5.1‬المكتبات الساكنة ‪Static Libraries‬‬

‫‪202‬‬ ‫ا‪ .‬عيوب الربط الساكن‬

‫‪202‬‬ ‫‪ 8.5.2‬المكتبات المشتركة‬

‫‪202‬‬ ‫‪ 8.6‬مفاهيم متقدمة متعلقة بصيغة ملفات ‪ELF‬‬

‫‪204‬‬ ‫‪ 8.6.1‬تنقيح األخطاء ‪Debugging‬‬

‫‪205‬‬ ‫‪ 8.6.2‬الرموز ومعلومات تنقيح األخطاء‬

‫‪206‬‬ ‫‪ 8.6.3‬التفريغ األساسي ‪Coredump‬‬

‫‪210‬‬ ‫‪ 8.6.4‬إنشاء أقسام مخصصة‬

‫‪213‬‬ ‫‪ 8.6.5‬سكربتات الرابط ‪Linker Scripts‬‬

‫‪215‬‬ ‫‪ 8.7‬بدء العمليات‬

‫‪215‬‬ ‫‪ 8.7.1‬اتصال النواة بالبرامج‬

‫‪216‬‬ ‫ا‪ .‬مكتبة النواة ‪Kernel Library‬‬

‫‪216‬‬ ‫‪ 8.7.2‬بدء البرنامج‬

‫‪224‬‬ ‫‪ .9‬مفهوم الربط الديناميكي‬

‫‪224‬‬ ‫‪ 9.1‬مشاركة الشيفرة‬

‫‪224‬‬ ‫‪ 9.1.1‬تفاصيل المكتبة الديناميكية‬

‫‪225‬‬ ‫‪ 9.1.2‬تضمين المكتبات في ملف قابل للتنفيذ‬

‫‪225‬‬ ‫ا‪ .‬التصريف ‪Compilation‬‬

‫‪225‬‬ ‫ب‪ .‬الربط ‪Linking‬‬

‫‪15‬‬
‫علوم الحاسوب من األلف إىل الياء‬ ‫جدول المحتويات‬

‫‪226‬‬ ‫‪ 9.2‬الرابط الديناميكي ‪Dynamic Linker‬‬

‫‪227‬‬ ‫‪ 9.2.1‬االنتقاالت ‪Relocations‬‬

‫‪229‬‬ ‫ا‪ .‬كيفية عمل االنتقاالت‬

‫‪230‬‬ ‫‪ 9.2.2‬استقالل المواقع‬

‫‪230‬‬ ‫‪ 9.3‬جدول اإلزاحة العام ‪Global Offset Table‬‬

‫‪231‬‬ ‫‪ 9.3.1‬كيفية عمل جدول ‪GOT‬‬

‫‪234‬‬ ‫‪ 9.4‬المزيد حول المكتبات‬

‫‪234‬‬ ‫‪ 9.4.1‬جدول البحث عن اإلجراءات ‪Procedure Lookup Table‬‬

‫‪235‬‬ ‫ا‪ .‬كيفية عمل جدول ‪PLT‬‬

‫‪244‬‬ ‫‪ 9.5‬عمل الرابط الديناميكي مع المكتبات‬

‫‪244‬‬ ‫‪ 9.5.1‬إصدارات المكتبات‬

‫‪245‬‬ ‫ا‪ .‬نظام ‪sonames‬‬

‫‪247‬‬ ‫‪ 9.5.2‬البحث عن الرموز‬

‫‪248‬‬ ‫ا‪ .‬جدول الرموز الديناميكي‬

‫‪249‬‬ ‫ب‪ .‬ارتباط الرموز ‪Symbol Binding‬‬

‫‪250‬‬ ‫‪ .9‬تجاوز الرموز ‪Overriding Symbols‬‬

‫‪252‬‬ ‫‪ .1‬الرموز الضعيفة‬

‫‪252‬‬ ‫‪ .9‬تحديد ترتيب االرتباط‬

‫‪253‬‬ ‫‪ .9‬تحديد إصدار الرموز ‪Symbol Versioning‬‬

‫‪256‬‬ ‫‪ .10‬قائمة المصطلحات‬

‫‪16‬‬
‫علوم الحاسوب من األلف إىل الياء‬ ‫جدول المحتويات‬

‫فهرس األشكال‬

‫‪25‬‬ ‫شكل ‪ :1‬صورة توضح مفهوم التجريد‬

‫‪30‬‬ ‫شكل ‪ :2‬ملفات يونيكس االفتراضية‬

‫‪31‬‬ ‫شكل ‪ :3‬فائدة واصفات الملفات في عملية التجريد‬

‫‪34‬‬ ‫شكل ‪ :4‬األنبوب‬

‫‪47‬‬ ‫شكل ‪ :5‬التقُّنع‬

‫‪51‬‬ ‫شكل ‪ :6‬األنواع‬

‫‪67‬‬ ‫شكل ‪ :7‬وحدة المعالجة المركزية ‪CPU‬‬

‫‪69‬‬ ‫شكل ‪ :8‬داخل ‪CPU‬‬

‫‪76‬‬ ‫شكل ‪ :9‬ترابط الذاكرة المخبئية‪.‬‬

‫‪78‬‬ ‫شكل ‪ :10‬وسوم الذاكرة المخبئية ‪Cache Tags‬‬

‫‪80‬‬ ‫شكل ‪ :11‬نظرة عامة عىل معالجة المقاطعة‬

‫‪83‬‬ ‫شكل ‪ :12‬نظرة عامة عىل متحكم ‪( UCHI‬مأخوذة من توثيق إنتل ‪)Intel‬‬

‫‪90‬‬ ‫شكل ‪ :13‬المكعب الفائق ‪Hypercube‬‬

‫‪93‬‬ ‫شكل ‪ :14‬اكتساب وإطالق الدالالت‬

‫‪99‬‬ ‫شكل ‪ :15‬نظام التشغيل‬

‫‪102‬‬ ‫شكل ‪ :16‬بعض طرق تطبيق االفتراضية المختلفة‬

‫‪115‬‬ ‫شكل ‪ :17‬مستويات الصالحيات في معمارية ‪x86‬‬

‫‪117‬‬ ‫شكل ‪ :18‬تقطيع العنونة ‪ Segmentation Addressing‬في معمارية ‪x86‬‬

‫‪118‬‬ ‫شكل ‪ :19‬مقاطع ‪x86‬‬

‫‪120‬‬ ‫شكل ‪ :20‬عناصر العملية‬

‫‪122‬‬ ‫شكل ‪ :21‬المكدس‬

‫‪125‬‬ ‫شكل ‪ :22‬تخطيط ذاكرة العملية‬

‫‪129‬‬ ‫شكل ‪ :23‬الخيوط ‪Threads‬‬

‫‪134‬‬ ‫شكل ‪ :24‬المجدول )‪O(1‬‬

‫‪140‬‬ ‫شكل ‪ :25‬توضيح للعناوين المعيارية ‪canonical addresses‬‬

‫‪141‬‬ ‫شكل ‪ :26‬صفحات الذاكرة الوهمية‬

‫‪144‬‬ ‫شكل ‪ :27‬ترجمة العنوان الوهمي‬

‫‪17‬‬
‫علوم الحاسوب من األلف إىل الياء‬ ‫جدول المحتويات‬

‫‪148‬‬ ‫شكل ‪ :28‬مخطط فضاء العناوين في لينكس‬

‫‪149‬‬ ‫شكل ‪ :29‬جدول صفحات لينكس المكون من ثالثة مستويات‬

‫‪151‬‬ ‫شكل ‪ :30‬التقطيع ‪Segmentation‬‬

‫‪155‬‬ ‫شكل ‪ :31‬رسم توضيحي للمناطق ومفاتيح الحماية في معالج إيتانيوم‬

‫‪156‬‬ ‫شكل ‪ :32‬رسم توضيحي لترجمة مخزن ‪ TLB‬في معالج إيتانيوم‬

‫‪158‬‬ ‫شكل ‪ :33‬رسم توضيحي لجدول صفحات هرمي‬

‫‪159‬‬ ‫شكل ‪ :34‬تقديم جدول ‪ VHPT‬بصيغة قصيرة في معالج إيتانيوم‬

‫‪159‬‬ ‫شكل ‪ :35‬صيغ مدخلة ‪ PTE‬في معالج إيتانيوم‬

‫‪165‬‬ ‫شكل ‪ :36‬المحاذاة في الذاكرة‬

‫‪167‬‬ ‫شكل ‪ :37‬محاذاة المتغيرات‬

‫‪187‬‬ ‫شكل ‪ :38‬نظرة عامة عىل ‪ELF‬‬

‫‪231‬‬ ‫شكل ‪ :39‬الوصول إىل الذاكرة عبر ‪GOT‬‬

‫‪246‬‬ ‫شكل ‪ :40‬نظام ‪sonames‬‬

‫‪18‬‬
‫علوم الحاسوب من األلف إىل الياء‬ ‫جدول المحتويات‬

‫فهرس الجداول‬

‫‪30‬‬ ‫جدول ‪ :1‬الملفات االفتراضية التي يوفرها نظام يونكس‬

‫‪33‬‬ ‫جدول ‪ :2‬إعادة التوجيه في الصدفة‬

‫‪36‬‬ ‫جدول ‪ :3‬النظام الثنائي‬

‫‪37‬‬ ‫جدول ‪ :4‬تمثيل العدد ‪ 203‬باألساس العشري‬

‫‪37‬‬ ‫جدول ‪ :5‬تمثيل العدد ‪ 203‬بالنظام الثنائي‬

‫‪40‬‬ ‫جدول ‪ :6‬معامالت النظام الثنائي والعشري المتعلقة بالبايتات‬

‫‪41‬‬ ‫جدول ‪ :7‬تحويل العدد ‪ 203‬للنظام الثنائي‬

‫‪42‬‬ ‫جدول ‪ :8‬جدول الحقيقة لـ ‪NOT‬‬

‫‪43‬‬ ‫جدول ‪ :9‬جدول الحقيقة لـ ‪AND‬‬

‫‪43‬‬ ‫جدول ‪ :10‬جدول الحقيقة لـ ‪OR‬‬

‫‪43‬‬ ‫جدول ‪ :11‬جدول الحقيقة لـ ‪Xor‬‬

‫‪44‬‬ ‫جدول ‪ :12‬العمليات المنطقية في ‪C‬‬

‫‪45‬‬ ‫جدول ‪ :13‬النظام الست عشري والثنائي والعشري‬

‫‪46‬‬ ‫جدول ‪ :14‬تحويل العدد ‪ 203‬إىل النظام الست عشري‬

‫‪52‬‬ ‫جدول ‪ :15‬أنواع وأحجام األعداد الصحيحة القياسية‬

‫‪52‬‬ ‫جدول ‪ :16‬أنواع وأحجام أنواع البيانات القياسية التي تتضمن قيمة مفردة‬

‫‪57‬‬ ‫جدول ‪ :17‬إضافة بت الِح ْم ل‬

‫‪58‬‬ ‫جدول ‪ :18‬إضافة المتمم الثنائي‬

‫‪59‬‬ ‫جدول ‪:19‬نموذج العدد العشري ‪IEEE‬‬

‫‪59‬‬ ‫جدول ‪ :20‬الصيغة العلمية للقيمة ‪1.98765x106‬‬

‫‪60‬‬ ‫جدول ‪ :21‬التمثيل الثنائي للدقة‬

‫‪61‬‬ ‫جدول ‪ :22‬مثال عىل توحيد القيمة ‪0.375‬‬

‫‪74‬‬ ‫جدول ‪ :23‬تسلسل الذواكر الهرمي‬

‫‪228‬‬ ‫جدول ‪ :24‬مثال عىل الترحيل‬

‫‪248‬‬ ‫جدول ‪ :25‬حقول رمز ‪ELF‬‬

‫‪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‬ع‪،‬‬

‫والربط وآلية تحويل شيفرة مصدرية بلغة ‪ C‬إىل برنامج تنفيذي‪.‬‬

‫ويتوسع الفصل الثامن في شرح طريقة تمثيل الملف‪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‬ة‬ ‫•‬

‫تغييراتك مرة أخرى عىل ‪.GitHub‬‬

‫إذا كنت ال تريد استخدام ‪ Git‬عىل اإلطالق‪ ،‬فيمكنك تنزيل الملفات في مل‪tt‬ف مض‪tt‬غوط ‪ zip‬باس‪tt‬تخدام‬ ‫•‬

‫الزر الموجود في الزاوية اليمنى السفلية من صفحة ‪.GitHub‬‬

‫المساهمة‬
‫يرجى إرسال بريد إلك‪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.1‬كل يشء عبارة عن ملف‬


‫ُتَع ّد مقولة كل شيء عبارة عن ملف مبدًأ ُيستَم د غالًب ا من أنظم‪tt‬ة ي‪tt‬ونكس ‪ Unix‬وم‪tt‬ا يش‪tt‬ابهها مث‪tt‬ل نظ‪tt‬ام‬

‫لينكس ‪ Linux‬وبي إس دي ‪.BSD‬‬

‫لنتخيل ملًف ا في إطار مألوف مثل معالج النصوص‪ ،‬إذ تكون العمليتان األساسيتان اللتان نستطيع تنفيذهما‬

‫عىل ملف في معالج النصوص التخيلي هذا كما يلي‪:‬‬

‫‪ .1‬قراءته‪ ،‬أي قراءة معالج النصوص البيانات الحالية المحفوظة‪.‬‬

‫‪ .2‬الكتابة ضمنه‪ ،‬أي كتابة المستخِدم بيانات جديدًة‪.‬‬

‫لنستعرض بعض الطرفيات الشائعة الموصولة بالحاسوب‪ ،‬وما ارتباطها بالعمليات األساسية عىل الملفات‪:‬‬

‫‪ .1‬الشاشة‪.‬‬

‫‪ .2‬لوحة المفاتيح‪.‬‬

‫‪ .3‬الطابعة‪.‬‬

‫‪ .4‬القرص المدَم ج ‪.CD-ROM‬‬


‫علوم الحاسوب من األلف إىل الياء‬ ‫نظرة متقدمة عىل يونكس ولغة سي‬

‫تشبه كل من الشاشة والطابعة ملًف ا للكتابة فقط‪ ،‬إذ ُتعَرض المعلومات بشكل نقاط عىل الشاشة أو خط‪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‬صورة توضح مفهوم التجريد‬

‫‪ 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‬دة‬

‫‪ remote procedure calls‬واس‪tt‬تعالمات قاع‪tt‬دة البيان‪tt‬ات ‪ database queries‬وعملي‪tt‬ات نق‪tt‬ل البيان‪tt‬ات‬

‫‪ ،data transfers‬وتكون جميعها غير مرئية بالنسبة للمستخِدم النهائي الذي يتلقى البيانات المقتضبة ببساطة‪.‬‬

‫سيألف الذين هم عىل دراية باللغات البرمجية كائنية التوجه ‪ object-oriented‬مث‪tt‬ل جاف‪tt‬ا ‪ Java‬أو ب‪tt‬ايثون‬

‫‪ Python‬أو ‪ C++‬مفهوم التجريد في األص‪tt‬ناف ‪ ،classes‬إذ ت‪ِّtt‬زود التواب‪tt‬ع ‪ methods‬الص‪tt‬نف بالواجه‪tt‬ة لكنه‪tt‬ا‬

‫تجِّرد التنفيذ‪.‬‬

‫‪ 1.2.1‬تطبيق التجريد بلغة الربمجة ‪C‬‬


‫ُتَع ّد مؤشرات الدالة ‪ function pointers‬منهجيًة شائعًة ُتستخَدم في نواة نظام تشغيل لينكس وغيرها من‬

‫الشيفرات البرمجية األساسية المكتوبة بلغة ‪ C‬والتي ال يكون مفهوم كائنية التوجه مدمًجا فيها‪ ،‬كما ُيَع ّد فهم هذا‬

‫المصطلح أمًرا رئيسًي ا لقراءة معظم الشيفرات البرمجية األساسية المكتوبة بلغة ‪ ،C‬إذ يمّكن‪tt‬ك فهم طريق‪tt‬ة ق‪tt‬راءة‬

‫التجريدات الموجودة ضمن الشيفرة البرمجية من تكوين فكرة عن تصاميم واجهات ‪ API‬الداخلية‪.‬‬

‫>‪#include <stdio.h‬‬

‫‪ */‬الواجهة البرمجية التي سننفذها *‪/‬‬


‫‪struct greet_api‬‬
‫{‬
‫;)‪int (*say_hello)(char *name‬‬
‫;)‪int (*say_goodbye)(void‬‬
‫;}‬

‫‪ */‬تطبيق دالة ‪/* hello‬‬


‫)‪int say_hello_fn(char *name‬‬
‫{‬
‫;)‪printf("Hello %s\n", name‬‬
‫;‪return 0‬‬
‫}‬

‫‪ */‬تطبيق دالة ‪/* goodbye‬‬


‫)‪int say_goodbye_fn(void‬‬
‫{‬
‫;)"‪printf("Goodbye\n‬‬

‫‪26‬‬
‫علوم الحاسوب من األلف إىل الياء‬ ‫نظرة متقدمة عىل يونكس ولغة سي‬

‫;‪return 0‬‬
‫}‬

‫‪*/‬بنية لتنفيذ الواجهة البرمجية *‪/‬‬


‫= ‪struct greet_api greet_api‬‬
‫{‬
‫‪.say_hello = say_hello_fn,‬‬
‫‪.say_goodbye = say_goodbye_fn‬‬
‫;}‬

‫‪*/‬ال تحتاج الدالة )(‪ main‬معرفة أّي شيء عن آلية عمل‬


‫* ‪ ،say_hello/goodbye‬فهي ال تعلم إال أنها تعمل*‪/‬‬
‫)][‪int main(int argc, char *argv‬‬
‫{‬
‫;)]‪greet_api.say_hello(argv[1‬‬
‫;)(‪greet_api.say_goodbye‬‬

‫‪printf("%p, %p, %p\n", greet_api.say_hello, say_hello_fn,‬‬


‫;)‪&say_hello_fn‬‬

‫;)‪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 ‫ لذا ال حاجة ألخذ عنوان الدالة مثل‬،‫الدالة مؤشًرا‬

.main ‫ضمن بنية‬

‫ك‬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
‫علوم الحاسوب من األلف إىل الياء‬ ‫نظرة متقدمة عىل يونكس ولغة سي‬

‫;)‪void (*scan)(struct virtio_device *dev‬‬


‫;)‪void (*remove)(struct virtio_device *dev‬‬
‫;)‪void (*config_changed)(struct virtio_device *dev‬‬
‫‪#ifdef CONFIG_PM‬‬
‫;)‪int (*freeze)(struct virtio_device *dev‬‬
‫;)‪int (*restore)(struct virtio_device *dev‬‬
‫‪#endif‬‬
‫;}‬

‫كل المطلوب هو أن نفهم فهًم ا سطحًي ا أّن هذه البنية هي وصف لجه‪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‬تي‬

‫تؤلف واجهة ‪ API‬القياسية في نظام يونكس‪.‬‬

‫تتبع معظم منصات يونكس عموًم ا معايير بوسيكس‪ ،‬مع وجود بعض الفروقات الطفيفة التي تك‪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‬ل‬

‫المكتبات بالتفصيل‪.‬‬

‫‪ 1.3‬مفهوم واصفات الملفات ‪File Descriptors‬‬


‫إحدى أوىل المفاهيم التي يتعلمها مبرمج أنظمة يونكس هي أّن عمل كل برن‪tt‬امج يب‪tt‬دأ بثالث ملف‪tt‬ات تك‪tt‬ون‬

‫مفتوحًة مسبًق ا‪:‬‬

‫الشرح‬ ‫رقم الملف‬ ‫االسم المختصر‬ ‫االسم الوصفي‬


‫الدخل من لوحة المفاتيح‬ ‫‪0‬‬ ‫‪stdin‬‬ ‫مجرى الدخل القياسي‬

‫الخرج الظاهر عىل الطرفية‬ ‫‪1‬‬ ‫‪stdout‬‬ ‫مجرى الخرج القياسي‬

‫خرج رسائل الخطأ عىل الطرفية‬ ‫‪2‬‬ ‫‪stderr‬‬ ‫مجرى الخطأ القياسي‬

‫جدول ‪ :1‬الملفات االفتراضية التي يوفرها نظام يونكس‬

‫شكل ‪ :2‬ملفات يونيكس االفتراضية‬

‫‪30‬‬
‫علوم الحاسوب من األلف إىل الياء‬ ‫نظرة متقدمة عىل يونكس ولغة سي‬

‫يستحضر هذا إىل أذهاننا السؤال عّم ا يمثله الملف المفتوح وكيفية فتحه‪ ،‬إذ تسمى القيمة التي يعيدها استدعاء‬

‫‪ open‬لفتح الملف اصطالًحا بواص‪tt‬ف المل‪tt‬ف ‪ ،file descriptor‬وهي أساًس ا فه‪tt‬رس لمص‪tt‬فوفة من الملف‪tt‬ات‬

‫المفتوحة المخَّزنة في النواة‪.‬‬

‫شكل ‪ :3‬فائدة واصفات الملفات في عملية التجريد‬

‫ُتَع ّد واصفات الملفات فهرًس ا لجدول واصفات الملف‪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‬كما هو موضح في المثال التالي‪:‬‬

‫‪$ ls -l /dev/null /dev/zero /dev/tty‬‬


‫‪crw-rw-rw- 1 root root 1, 3 Aug 26 13:12 /dev/null‬‬
‫‪crw-rw-rw- 1 root root 5, 0 Sep‬‬ ‫‪2 15:06 /dev/tty‬‬
‫‪crw-rw-rw- 1 root root 1, 5 Aug 26 13:12 /dev/zero‬‬

‫ينقلنا هذا إىل واصف الملف‪ ،‬وهو األداة التي تستخِدمها مساحة المستخِدم للتواصل مع الجهاز األساس‪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‬ذا‬

‫كله يعيدنا إىل مقولة كل شيء هو عبارة عن ملف‪.‬‬

‫ا‪ .‬إعادة التوجيه ‪Redirection‬‬

‫ال نريد في معظم األحيان أن تشير واصفات الملفات القياسية التي تحدثنا عنها في فقرة سابقة إىل مواضع‬

‫محددة افتراضًي ا‪ ،‬فقد ترغب مثاًل في تسجيل كامل خ‪tt‬رج البرن‪tt‬امج عىل مل‪tt‬ف تح‪tt‬دده عىل الق‪tt‬رص أو في جعل‪tt‬ه‬

‫يتلقى أوامره من ملف أعددته مسبًق ا‪ ،‬وقد ترغب في تمرير خرج برنامج ليكون دخل برنامج آخر‪ ،‬إذ تيّس ر الصدفة‬

‫ذلك وأكثر بالعمل مع نظام التشغيل‪.‬‬

‫مثال‬ ‫الوصف‬ ‫األمر‬ ‫االسم‬


‫أخذ كامل الخرج الناتج عن‬
‫‪ Standard Out‬وتسجيله في‬
‫الملف ‪( filename‬استبدل‬
‫‪ls > filename‬‬ ‫‪ filename‬باسم الملف)‪.‬‬ ‫‪< filename‬‬ ‫إعادة التوجيه إىل ملف‬
‫مالحظة‪ :‬استخدم << لُتلحق الخرج‬
‫بنهاية محتوى الملف بداًل من‬
‫استبدال محتواه‬

‫نسخ كافة البيانات من الملف إىل‬


‫‪cat < filename‬‬ ‫دخل البرنامج القياسي ‪standard‬‬ ‫‪> filename‬‬ ‫القراءة من ملف‬
‫‪input‬‬

‫أخذ كامل خرج ‪standard out‬‬


‫البرنامج األول ‪ program1‬وتمريره‬ ‫| ‪Program1‬‬
‫‪ls | more‬‬ ‫التمرير ‪Pipe‬‬
‫إىل دخل ‪standard input‬‬ ‫‪program2‬‬
‫البرنامج الثاني ‪program2‬‬

‫جدول ‪ :2‬إعادة التوجيه في الصدفة‬

‫‪33‬‬
‫علوم الحاسوب من األلف إىل الياء‬ ‫نظرة متقدمة عىل يونكس ولغة سي‬

‫ب‪ .‬تنفيذ عملية التمرير ‪pipe‬‬

‫ُيَع ّد تنفيذ األمر ‪ 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‬تمثيل األعداد والنظام الثنائي‬

‫‪ 2.1‬تعرف عىل نظام العد الثنائي ‪ Binary‬أساس الحوسبة‬


‫النظام الثنائي ‪ Binary‬هو نظام عددي يكون أساس العدد فيه ‪ ،2‬ويمثل المعلوم‪tt‬ات بح‪tt‬التين متن‪tt‬افيتين ال‬

‫ثالث لهما‪ ،‬ويتكون العدد الثنائي من عناصر تسمى ِبتات ‪ bits‬بحيث يمكن أن يك‪t‬ون ك‪t‬ل بت بإح‪t‬دى الح‪t‬التين‬

‫المحتَم لَتين‪ ،‬واللتين نمثلهما عموًم ا بالرقمين ‪ 1‬و ‪.0‬‬

‫‪ 2.1.1‬نظرية النظام الثنائي‬


‫النظام الثنائي هو نظام يكون فيه األساس هو العدد ‪ 2‬ليمثل المعلومات بحالتين متنافيتين‪ ،‬ويتكون الع‪tt‬دد‬

‫الثنائي من عناصر تسمى ِبّت ات ‪ ،bits‬إذ يمكن أن يكون كل ِبّت بإحدى الح‪tt‬التين المحتَم ل‪tt‬تين والل‪tt‬تين نمِّثلهم‪tt‬ا‬

‫عموًم ا بالرقمين ‪ 1‬و ‪ ،0‬ويمكننا القول أنهما تمِّثالن القيمَتين الصحيحة والخاطئة؛ أما من الناحية الكهربائية‪ ،‬فقد‬

‫تمَّث ل الحالتين بجهد كهربائي مرتفع ومنخفض أو مثل زر التشغيل واإليقاف‪.‬‬

‫نبني األعداد الثنائية بالطريقة نفسها التي نبني بها األعداد في نظامن‪tt‬ا التقلي‪tt‬دي العش‪tt‬ري ال‪tt‬ذي يك‪tt‬ون في‪tt‬ه‬

‫األساس هو العدد ‪ ،10‬لكن بداًل من منزلة اآلح‪tt‬اد ومنزل‪tt‬ة العش‪tt‬رات ومنزل‪tt‬ة المئ‪tt‬ات‪… ،‬إلخ‪ .‬ل‪tt‬دينا منزل‪tt‬ة الواح‪tt‬د‬

‫ومنزلة االثنين ومنزلة األربعة ومنزلة الثمانية‪… ،‬إلخ‪ .‬أي كما هو موضح في الجدول التالي‪:‬‬

‫‪2...‬‬ ‫‪26‬‬ ‫‪25‬‬ ‫‪24‬‬ ‫‪23‬‬ ‫‪22‬‬ ‫‪21‬‬ ‫‪20‬‬


‫…‬ ‫‪64‬‬ ‫‪32‬‬ ‫‪16‬‬ ‫‪8‬‬ ‫‪4‬‬ ‫‪2‬‬ ‫‪1‬‬

‫جدول ‪ :3‬النظام الثنائي‬


‫علوم الحاسوب من األلف إىل الياء‬ ‫تمثيل األعداد والنظام الثنائي‬

‫لنمِّثل العدد ‪ 203‬عىل سبيل المثال في األساس العشري‪ ،‬إذ نعلم أننا نضع الرقم ‪ 3‬في منزلة اآلحاد‪ ،‬والرقم‬

‫‪ 0‬في منزل‪tt‬ة العش‪tt‬رات وال‪tt‬رقم ‪ 2‬في منزل‪tt‬ة المئ‪tt‬ات‪ ،‬ويمَّث ل ه‪tt‬ذا من خالل اُألس‪tt‬س ‪ exponents‬كم‪tt‬ا في‬

‫الجدول التالي‪:‬‬

‫‪102‬‬ ‫‪101‬‬ ‫‪100‬‬


‫‪2‬‬ ‫‪0‬‬ ‫‪3‬‬

‫جدول ‪ :4‬تمثيل العدد ‪ 203‬باألساس العشري‬

‫أو نمثلها بطريقة أخرى‪:‬‬

‫‪2 x 102 + 3 x 100 = 200 + 3 = 203‬‬

‫لنمِّث ل العدد نفسه بالنظام الثنائي‪ ،‬إذ سيكون لدينا الجدول التالي‪:‬‬

‫‪27‬‬ ‫‪26‬‬ ‫‪25‬‬ ‫‪24‬‬ ‫‪23‬‬ ‫‪22‬‬ ‫‪21‬‬ ‫‪20‬‬


‫‪1‬‬ ‫‪1‬‬ ‫‪0‬‬ ‫‪0‬‬ ‫‪1‬‬ ‫‪0‬‬ ‫‪1‬‬ ‫‪1‬‬

‫جدول ‪ :5‬تمثيل العدد ‪ 203‬بالنظام الثنائي‬

‫ويكافئ هذا‪:‬‬

‫‪27 + 26 + 23 + 21 + 20 = 128 + 64 + 8 + 2 + 1 = 203‬‬

‫ا‪ .‬أسس الحوسبة‬

‫قد تتساءل كيف لعدد بسيط أن يكون األساس الذي بنَي ت عليه كل األمور المذهلة التي يستطيع الحاسوب‬

‫تنفيذها‪ ،‬ورغم صعوبة تصديق ذلك إال أنه‪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‬ة أو‬

‫من األلف إىل الياء إذا صح التعبير‪.‬‬

‫ب‪ .‬البتات ‪ Bits‬والبايتات ‪Bytes‬‬

‫يمكننا بصورة أساسية تمثيل أّي شيء بعدد كم‪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‬‬

‫أو ‪ 128‬شيفرًة متاحًة ‪.‬‬

‫‪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‬التي تستخِدم ما يصل‬

‫إىل ‪ 4‬بايتات لتمثل محرًف ا‪ ،‬وهذا يفسح مجااًل أكبر بكثير‪.‬‬

‫التكافؤ ‪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‬وبهذه الطريقة سينتج عن تغّي ر ِبّت واحد خطأ تكافؤ يمكن اكتشافه‪.‬‬

‫الحواسيب ذات أنظمة ‪ 16‬و ‪ 32‬و ‪ 64‬بت‬

‫ال تتسع جميع األعداد في بايت أو مجموعة محددة من البايتات‪ ،‬فبفرض أن كان رص‪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‬اعف حجم‬

‫عمل المعالج إىل ‪ 8‬بايت بداًل من ‪ 4‬في معماريات ‪ 32‬بت‪.‬‬

‫‪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‬‬

‫‪1.000.000‬‬ ‫‪106‬‬ ‫‪1.048.576‬‬ ‫‪ 1‬ميغابايت ‪220‬‬

‫‪1.000.000.000‬‬ ‫‪109‬‬ ‫‪1.073.741.824‬‬ ‫‪ 1‬غيغابايت ‪230‬‬

‫‪1.000.000.000.000‬‬ ‫‪1012‬‬ ‫‪1.099.511.627.776‬‬ ‫‪240‬‬ ‫‪ 1‬تيرابايت‬

‫‪1.000.000.000.000.000‬‬ ‫‪1015‬‬ ‫‪1.125.899.906.842.624‬‬ ‫‪250‬‬ ‫‪ 1‬بيتابايت‬

‫‪1.000.000.000.000.000.000‬‬ ‫‪1018 1.152.921.504.606.846.976‬‬ ‫‪ 1‬إكسابايت ‪260‬‬

‫جدول ‪ :6‬معامالت النظام الثنائي والعشري المتعلقة بالبايتات‬

‫قد يفيدك ترسيخ معاِم الت النظام الثنائي في ذاكرتك كثيًرا في الربط السريع للعالقة بين ع‪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‬و ل‬

‫إىل النظام الثنائي‪.‬‬

‫اتجاه قراءة الباقي‬ ‫الباقي‬ ‫النتيجة‬ ‫عملية القسمة‬


‫‪1‬‬ ‫‪101‬‬ ‫‪20310 ÷ 2‬‬

‫↑‬ ‫‪1‬‬ ‫‪50‬‬ ‫‪10110 ÷ 2‬‬

‫↑‬ ‫‪0‬‬ ‫‪25‬‬ ‫‪5010 ÷ 2‬‬

‫↑‬ ‫‪1‬‬ ‫‪12‬‬ ‫‪2510 ÷ 2‬‬

‫↑‬ ‫‪0‬‬ ‫‪6‬‬ ‫‪1210 ÷ 2‬‬

‫↑‬ ‫‪0‬‬ ‫‪3‬‬ ‫‪610 ÷ 2‬‬

‫↑‬ ‫‪1‬‬ ‫‪1‬‬ ‫‪310 ÷ 2‬‬

‫↑‬ ‫‪1‬‬ ‫‪0‬‬ ‫‪110 ÷ 2‬‬

‫جدول ‪ :7‬تحويل العدد ‪ 203‬للنظام الثنائي‬

‫‪41‬‬
‫علوم الحاسوب من األلف إىل الياء‬ ‫تمثيل األعداد والنظام الثنائي‬

‫ابدأ بقراءة الباقي من األسفل وأضف كل عدد منه إىل اليمين لتحصل عىل النتيجة ‪ ،11001011‬وقد وج‪tt‬دنا‬

‫فعاًل أّن هذه القيمة في النظام الثنائي هي ‪ 203‬في النظام العشري‪.‬‬

‫د‪ .‬العمليات البوليانية ‪Boolean Operations‬‬

‫اكتشف جورج بول عالم الرياضيات مجااًل كاماًل في الرياضيات يسمى جبر ُبول ‪ ،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‬ة‪،‬‬

‫ويقابل مصطلح حقيقي ‪ true‬القيمة ‪ 1‬في النظام الثنائي‪.‬‬

‫‪Not‬‬

‫تمَّثل عادًة بالرمز !‪ ،‬وهي تعكس قيمة الدخل فتحول ‪ 0‬إىل ‪ 1‬و ‪ 1‬إىل ‪.0‬‬

‫الخرج‬ ‫الدخل‬
‫‪0‬‬ ‫‪1‬‬

‫‪1‬‬ ‫‪0‬‬

‫جدول ‪ :8‬جدول الحقيقة لـ ‪NOT‬‬

‫‪And‬‬

‫تذَّك ر العبارة التالية‪" :‬تكون النتيجة حقيقيًة إذا كان الدخل األول حقيقًي ا و الدخل الثاني حقيقًي ا" لكي يس‪tt‬هل‬

‫عليك تذُّكر آلية عمل معامل ‪.and‬‬

‫‪42‬‬
‫علوم الحاسوب من األلف إىل الياء‬ ‫تمثيل األعداد والنظام الثنائي‬

‫الخرج‬ ‫الدخل الثاني‬ ‫الدخل األول‬


‫‪0‬‬ ‫‪0‬‬ ‫‪0‬‬

‫‪0‬‬ ‫‪0‬‬ ‫‪1‬‬

‫‪0‬‬ ‫‪1‬‬ ‫‪0‬‬

‫‪1‬‬ ‫‪1‬‬ ‫‪1‬‬

‫جدول ‪ :9‬جدول الحقيقة لـ ‪AND‬‬

‫‪Or‬‬

‫تذَّك ر العبارة التالية‪" :‬تكون النتيجة حقيقيًة إذا كان الدخل األول حقيقًي ا أو الدخل الثاني حقيقًي ا" لكي يس‪tt‬هل‬

‫عليك تذُّكر آلية عمل معامل ‪. or‬‬

‫الخرج‬ ‫الدخل الثاني‬ ‫الدخل األول‬


‫‪0‬‬ ‫‪0‬‬ ‫‪0‬‬

‫‪1‬‬ ‫‪0‬‬ ‫‪1‬‬

‫‪1‬‬ ‫‪1‬‬ ‫‪0‬‬

‫‪1‬‬ ‫‪1‬‬ ‫‪1‬‬

‫جدول ‪ :10‬جدول الحقيقة لـ ‪OR‬‬

‫معامل أو الحرصية ‪Exclusive Or‬‬

‫تختَص ر عبارة معام‪tt‬ل أو الحص‪tt‬رية ‪ Exclusive Or‬بـ ‪ xor‬وهي حال‪tt‬ة خاص‪tt‬ة من معاِم ل ‪ ،or‬بحيث يك‪tt‬ون‬

‫الخرج حقيقًي ا عندما يكون أحد المدَخلين فقط حقيقًي ا‪ ،‬وستدهشك الحيل المميزة ال‪t‬تي يس‪t‬تطيع ه‪t‬ذا المعاِم ل‬

‫تنفيذها‪ ،‬لكنها ليست مستخَدمًة كثيًرا في النواة‪.‬‬

‫الخرج‬ ‫الدخل الثاني‬ ‫الدخل األول‬


‫‪0‬‬ ‫‪0‬‬ ‫‪0‬‬

‫‪1‬‬ ‫‪0‬‬ ‫‪1‬‬

‫‪1‬‬ ‫‪1‬‬ ‫‪0‬‬

‫‪0‬‬ ‫‪1‬‬ ‫‪1‬‬

‫جدول ‪ :11‬جدول الحقيقة لـ ‪Xor‬‬

‫ه‪ .‬استخدام العمليات البوليانية في الحواسيب‬

‫قد يصعب عليك تصديق أّن أساس كل ما ينِّف ذه حاسوبك هو تلك المعاِم الت التي تح‪tt‬دثنا عنه‪tt‬ا‪ ،‬فالج‪tt‬امع‬

‫النصفي ‪ half adder‬مثاًل هو أحد أنواع الدارات التي تتكون من العمليات البولياني‪t‬ة ال‪t‬تي تجم‪t‬ع الِبّت ات‪ ،‬وق‪t‬د‬

‫‪43‬‬
‫علوم الحاسوب من األلف إىل الياء‬ ‫تمثيل األعداد والنظام الثنائي‬

‫ُس ّم ي الجامع النصفي ألنه ال يعالج البتات الفائضة‪ ،‬وستبدأ في بن‪tt‬اء كي‪tt‬ان يجم‪tt‬ع أع‪tt‬داد ثنائي‪tt‬ة طويل‪tt‬ة من خالل‬

‫وضع أكثر من جامع نصفي مًع ا‪ ،‬ثم أضف إليه بعض الذواكر الخارجية وستكون قد بنيت حاسوًبا‪.‬‬

‫تنَّف ذ العملي‪ttt‬ات البولياني‪ttt‬ة من الناحي‪ttt‬ة اإللكتروني‪ttt‬ة في بواب‪ttt‬ات ‪ gates‬مص‪ttt‬نوعة من الترانزس‪ttt‬تورات‬

‫‪ ،transistors‬لذا ال بد أنك سمعت عن عدد الترانزستورات ‪ transistor counts‬وقانون مور وغيرها‪ ،‬وكلما زاد‬

‫عدد الترانزستورات زاد عدد البوابات وزاد عدد األشياء التي يمكنك جمعها‪ ،‬كما ستحتاج لبناء الحاسوب الح‪tt‬ديث‬

‫إىل عدد هائل من البوابات وعدد هائ‪tt‬ل من الترانزس‪tt‬تورات‪ ،‬إذ تحت‪tt‬وي بعض معاِلج‪tt‬ات إيت‪tt‬انيوم ‪ Itanium‬عىل‬

‫حوالي ‪ 460‬مليون ترانزستور‪.‬‬

‫و‪ .‬العمل بالنظام الثنائي في اللغة ‪C‬‬

‫توجد واجهة مباشرة لجميع المعاِم الت التي ذكرناها في اللغة ‪ ،C‬ويشرح الجدول التالي هذه المعاِم الت‪:‬‬

‫اصطالحه في اللغة ‪C‬‬ ‫المعامل‬


‫!‬ ‫‪not‬‬
‫&‬ ‫‪and‬‬
‫\‬ ‫‪or‬‬
‫^‬ ‫‪xor‬‬

‫جدول ‪ :12‬العمليات المنطقية في ‪C‬‬

‫نطّبق هذه المعاِم الت عىل المتغيرات لتعديل الِبّتات ضمن المتغير‪ ،‬ولكن يجب علينا أواًل أن نتن‪tt‬اول ش‪tt‬رًحا‬

‫للترميز الست العشري قبل أن نستعرض أمثلًة عن ذلك‪.‬‬

‫‪ 2.1.2‬النظام الست عرشي ‪Hexadecimal‬‬


‫يشير النظام الست عشري إىل نظام أساسه العدد ‪ ،16‬والسبب الوحي‪tt‬د الس‪tt‬تخدامنا ه‪tt‬ذا النظ‪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‬الي‬

‫عن ظهر قلب‪.‬‬

‫النظام العشري‬ ‫النظام الثنائي‬ ‫النظام الست عشري‬

‫‪0‬‬ ‫‪0000‬‬ ‫‪0‬‬

‫‪1‬‬ ‫‪0001‬‬ ‫‪1‬‬

‫‪2‬‬ ‫‪0010‬‬ ‫‪2‬‬

‫‪3‬‬ ‫‪0011‬‬ ‫‪3‬‬

‫‪4‬‬ ‫‪0100‬‬ ‫‪4‬‬

‫‪5‬‬ ‫‪0101‬‬ ‫‪5‬‬

‫‪6‬‬ ‫‪0110‬‬ ‫‪6‬‬

‫‪7‬‬ ‫‪0111‬‬ ‫‪7‬‬

‫‪8‬‬ ‫‪1000‬‬ ‫‪8‬‬

‫‪9‬‬ ‫‪1001‬‬ ‫‪9‬‬

‫‪10‬‬ ‫‪1010‬‬ ‫‪A‬‬

‫‪11‬‬ ‫‪1011‬‬ ‫‪B‬‬

‫‪12‬‬ ‫‪1100‬‬ ‫‪C‬‬

‫‪13‬‬ ‫‪1101‬‬ ‫‪D‬‬

‫‪14‬‬ ‫‪1110‬‬ ‫‪E‬‬

‫‪15‬‬ ‫‪1111‬‬ ‫‪F‬‬

‫جدول ‪ :13‬النظام الست عشري والثنائي والعشري‬

‫بالطبع ال يوجد سبب للتوقف عن متابعة النمط (مثل تحدي‪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‬‬
‫علوم الحاسوب من األلف إىل الياء‬ ‫تمثيل األعداد والنظام الثنائي‬

‫اتجاه قراءة الباقي‬ ‫الباقي‬ ‫النتيجة‬ ‫عملية القسمة‬


‫‪)0xB( 11‬‬ ‫‪12‬‬ ‫‪20310 ÷ 16‬‬

‫↑‬ ‫‪)0xC( 12‬‬ ‫‪0‬‬ ‫‪1210 ÷ 16‬‬

‫جدول ‪ :14‬تحويل العدد ‪ 203‬إىل النظام الست عشري‬

‫لذا تكون قيمة ‪ 203‬في النظام الست عشري هي ‪0xCB‬‬

‫‪ 2.1.3‬االستخدام العميل لألنظمة العددية‬


‫سنطلع فيما يلي عىل االستخدام العملي لألنظمة العددية وما النتائج العملية التي ممكن أن نحصل عليها‪.‬‬

‫ا‪ .‬استخدام النظام الثنائي في الشيفرات الربمجية‬

‫ُتَع ّد برمجة حاسوب بلغات عالية المستوى ‪ 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‬ا‬

‫القيمة النهائية ‪.0000 1010‬‬

‫>‪#include <stdio.h‬‬

‫‪#define LOWER_MASK 0x0F‬‬


‫‪#define UPPER_MASK 0xF0‬‬

‫)][‪int main(int argc, char* argv‬‬


‫{‬
‫‪ */‬قيمتان بحجم ‪ 4‬بتات مخزنتان في متغير بحجم ‪ 8‬بتات *‪/‬‬
‫;‪char value = 0xA5‬‬
‫;‪char lower = value & LOWER_MASK‬‬
‫;‪char upper = (value & UPPER_MASK) >> 4‬‬

‫;)‪printf("Lower: %x\n", lower‬‬


‫;)‪printf("Upper: %x\n", upper‬‬
‫}‬

‫يتطلب ضبط الِبّتات المعاِم ل ‪ 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 */‬‬

‫)][‪int main(int argc, char *argv‬‬


‫{‬
‫‪ */‬متغير بحجم ‪ 8‬بتات *‪char flags = 0; /‬‬
‫‪ */‬ضبط الرايات بمعامل ‪ or‬المنطقي *‪/‬‬
‫‪ */‬ضبط الراية األولى *‪flags = flags | FLAG1; /‬‬
‫ضبط الراية الثالثة *‪flags = flags | FLAG3; /‬‬

‫تحقق من الرايات بالمعامل ‪ and‬المنطقي‪ .‬إذا كانت الراية مضبوطة بالقيمة ‪/* 1‬‬
‫سيرجع المعامل ‪ and‬قيمة ‪* 1‬‬
‫مما سيحقق الشرط الوارد في ‪* if‬‬ ‫‪*/‬‬
‫)‪if (flags & FLAG1‬‬

‫‪48‬‬
‫علوم الحاسوب من األلف إىل الياء‬ ‫تمثيل األعداد والنظام الثنائي‬

‫;)"‪printf("FLAG1 set!\n‬‬

‫‪ */‬سيكون هذا بالطبع غير صحيح *‪/‬‬


‫)‪if (flags & FLAG8‬‬
‫;)"‪printf("FLAG8 set!\n‬‬

‫تحقق من عدة رايات بواسطة ‪ or‬المنطقي *‪/‬‬


‫‪ */‬سيمرر هذا ألن الراية األولى مضبوطة *‬
‫))‪if (flags & (FLAG1|FLAG4‬‬
‫;)"‪printf("FLAG1 or FLAG4 set!\n‬‬

‫;‪return 0‬‬
‫}‬

‫‪ 2.2‬تمثيل األنواع واألعداد في األنظمة الحاسوبية‬


‫يجب التصريح عن نوع كل متغير في اللغة التي يحَّد د فيها نوع المتغير ‪ typed language‬مثل اللغة ‪ ،C‬إذ‬

‫ُيعِلم النوع المصِّرف مالذي يتوقع تخزينه في المتغير‪ ،‬وبالتالي يستطيع المصّرف تخصيص مس‪tt‬احة كافي‪tt‬ة له‪tt‬ذا‬

‫االستخدام‪ ،‬والتحقق من أن المبرمج ال ينتهك قيود النوع المحَّد د‬

‫‪ 2.2.1‬معايري اللغة ‪C‬‬


‫من الضروري االطالع قلياًل عىل تاريخ اللغة البرمجي‪tt‬ة ‪ C‬عىل ال‪tt‬رغم من االختالف الطفي‪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‬‬

‫‪ 2.2.2‬جنو يس ‪GNU C‬‬


‫ينِّف ذ مصِّرف ‪- GNU C‬والذي يشار إلي‪t‬ه ع‪t‬ادًة باالختص‪t‬ار ‪ -gcc‬معي‪t‬ار ‪ C99‬بالكام‪t‬ل تقريًب ا‪ ،‬ويطِّب ق أيًض ا‬

‫مجموعة إضافات للمعيار سيستخدمها المبرمجون غالًبا للحصول عىل خص‪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‬بواحدة الِب ّت‬ ‫النوع‬
‫‪ِ 32‬بّت‬
‫‪8‬‬ ‫‪8‬‬ ‫‪char‬‬

‫‪16‬‬ ‫‪16‬‬ ‫‪short‬‬

‫‪32‬‬ ‫‪16‬‬ ‫‪int‬‬

‫‪32‬‬ ‫‪32‬‬ ‫‪long‬‬

‫‪64‬‬ ‫‪64‬‬ ‫‪long long‬‬

‫‪32‬‬ ‫حسب التنفيذ‬ ‫المؤشرات ‪Pointers‬‬

‫جدول ‪ :15‬أنواع وأحجام األعداد الصحيحة القياسية‬

‫نالحظ في مثالنا السابق أّن االختالف الوحيد عن المعيار ‪ C99‬هو أن حجم المتغير من نوع ‪ int‬ه‪tt‬و ‪ِ 32‬بت‬

‫عادًة ‪ ،‬وهو ضعف الحد األدنى الصارم لحجم ‪ 16‬بت الذي يتطلبه المعيار ‪ ،C99‬كما أّن المؤش‪tt‬رات ‪ Pointers‬هي‬

‫فعلًي ا عنوان فقط‪ ،‬أي أّن قيمتها تكون عنواًنا وبالتالي "تشير" إىل موقع آخر في الذاكرة‪ ،‬لذا يجب تخصيص حجم‬

‫كاٍف للمؤشر حتى يتمكن من عنونة أّي موقع في ذاكرة النظام‪.‬‬

‫ا‪ 64 .‬بت‬

‫إحدى النواحي المرِبكة هي إدراج حوسبة ‪ 64‬بت‪ ،‬إذ يعني هذا أّن المعالج يمكنه معالجة العناوين التي تخَّزن‬

‫عىل ‪ 64‬بت وتحديًدا تكون سعة السجالت ‪ِ 64‬بّت ‪ ،‬وهو موضوع سنتناوله في فصل معمارية الحاسوب الحًق ا‪.‬‬

‫يعني هذا أواًل أّن جميع المؤشرات يجب أن تكون بحجم ‪ِ 64‬بّت حتى تتمكن من تمثي‪tt‬ل أّي عن‪tt‬وان محتم‪tt‬ل‬

‫في النظام‪ ،‬لكن عندها يجب عىل منّف ِذي النظام ‪ system implementers‬تحديد حجم األنواع األخرى‪ ،‬في حين‬

‫ينتشر استخدام نموذَجين شائَع ين عىل نطاق واسع كما هو موضح في الجدول التالي‪:‬‬

‫الحجم الشائع في نظام‬ ‫الحجم األدنى وفق معيار‬


‫الحجم الشائع ‪LP64‬‬ ‫النوع‬
‫التشغيل ويندوز‬ ‫‪ C99‬بواحدة الِب ّت‬
‫‪8‬‬ ‫‪8‬‬ ‫‪8‬‬ ‫‪char‬‬

‫‪16‬‬ ‫‪16‬‬ ‫‪16‬‬ ‫‪short‬‬

‫‪32‬‬ ‫‪32‬‬ ‫‪16‬‬ ‫‪int‬‬

‫‪32‬‬ ‫‪64‬‬ ‫‪32‬‬ ‫‪long‬‬

‫‪64‬‬ ‫‪64‬‬ ‫‪64‬‬ ‫‪long long‬‬

‫‪64‬‬ ‫‪64‬‬ ‫حسب التنفيذ‬ ‫المؤشرات ‪Pointers‬‬

‫جدول ‪ :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‬ذا‬

‫النموذج في نظام ويندوز الذي يدعم ‪ِ 64‬بّت ‪.‬‬

‫تكمن أسباب وجيهة خلف عدم زيادة حجم المتغير من نوع ‪ 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‬فوفة‬

‫عدد صحيح ‪ integer‬يتضاعف بذلك أيًض ا‪.‬‬

‫يعني هذا أّن البرامج ستستهلك حجًم ا أكبر من ذاكرة النظام دون أّي تحسن يذكر في أدائ‪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‬ة‬

‫كامل واجهة ‪.API‬‬

‫إذا جربنا البديل المق‪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‬هو امتداد يشير إىل أنك تستخدم األنواع‬

‫المعَّرفة في معيار ‪.C99‬‬

‫تشير الصيغة ‪ 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‬غ‬

‫‪ 4‬بايت فقط‪ ،‬وبالطبع لن تتسع ‪ 8‬بايت في ‪ 4‬بايت‪.‬‬

‫يمكننا محاولة خداع المصرف بتحويل القيمة قب‪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‬‬

‫نقل متغير كبير إلى متغير أصغر منه ‪//‬‬


‫;‪i = 0x12341234‬‬
‫;‪a = i‬‬
‫;‪i = a‬‬
‫;)‪printf("i is %d\n", i‬‬

‫نقل المؤشر ليشير إلى متغير من نوع ‪// integer‬‬


‫;)‪printf("p is %p\n", p‬‬
‫;‪i = p‬‬
‫الخداع بإجراء التحويالت ‪//‬‬
‫;‪i = (int)p‬‬
‫;‪p = (char*)i‬‬
‫;)‪printf("p is %p\n", p‬‬

‫;‪return 0‬‬
‫}‬
‫‪$ uname -m‬‬
‫‪i686‬‬

‫‪55‬‬
‫علوم الحاسوب من األلف إىل الياء‬ ‫تمثيل األعداد والنظام الثنائي‬

‫‪$ gcc -Wall -o types types.c‬‬


‫‪types.c: In function 'main':‬‬
‫‪types.c:19: warning: assignment makes integer from pointer without‬‬
‫‪a cast‬‬
‫‪$ ./types‬‬
‫‪i is 52‬‬
‫‪p is 0x80484e8‬‬
‫‪p is 0x80484e8‬‬
‫‪$ uname -m‬‬
‫‪ia64‬‬
‫‪$ gcc -Wall‬‬ ‫‪-o types types.c‬‬
‫‪types.c: In function 'main':‬‬
‫‪types.c:19: warning: assignment makes integer from pointer without a‬‬
‫‪cast‬‬
‫‪types.c:21: warning: cast from pointer to integer of different size‬‬
‫‪types.c:22: warning: cast to pointer from integer of different size‬‬
‫‪$ ./types‬‬
‫‪i is 52‬‬
‫‪p is 0x40000000000009e0‬‬
‫‪p is 0x9e0‬‬

‫‪ 2.2.4‬تمثيل األعداد‬
‫سنشرح كيفية تمثيل األعداد بمختلف مجاالتها مثل األعداد السالبة واألعداد العشرية وغيرهما‪.‬‬

‫ا‪ .‬القيم السلبية‬

‫نمِّيز العدد السالب في نظامنا العشري الح‪t‬ديث بوض‪t‬ع عالم‪t‬ة الط‪t‬رح ‪ -‬قبل‪t‬ه؛ أم‪t‬ا عن‪t‬دما نس‪t‬تخِدم النظ‪t‬ام‬

‫الثنائي‪ ،‬فعلينا اتباع أسلوب مختلف عند اإلشارة إىل األرقام السالبة‪ ،‬إذ يوج‪tt‬د نظ‪tt‬ام وحي‪tt‬د ش‪tt‬ائع اس‪tt‬تخدامه في‬

‫العتاد الصلب الحديث‪ ،‬لكن معيار ‪ C99‬يحِّدد ثالثة أساليب مقبولة لتمثيل القيمة السلبية‪.‬‬

‫ب‪ .‬بت اإلشارة ‪Sign Bit‬‬

‫أبسط طريقة هي تخصيص بت واحد من العدد يشير إىل قيمة سالبة أو موجبة حسب هل ه‪tt‬و مح ‪َّt‬د د أم ال‪،‬‬

‫وهذا مشابه للنهج الرياضي الذي يبين قيمة العدد بإشارتي ‪ +‬و ‪ ،-‬إذ ُيَع ّد هذا منطقًي ا نوًع ا ما‪ ،‬وق‪tt‬د مّثلت بعض‬

‫أجهزة الحاسوب األولية أعداًدا سالبًة بهذه الطريقة‪ ،‬لكن يتيح استخدام األعداد الثنائية بعض االحتم‪tt‬االت األخ‪tt‬رى‬

‫التي تسِّهل عمل مصممي العتاد الصلب‪.‬‬

‫‪56‬‬
‫علوم الحاسوب من األلف إىل الياء‬ ‫تمثيل األعداد والنظام الثنائي‬

‫الحظ أّن القيمة ‪ 0‬قد أصبح لها اآلن قيمتان مكافئتان‪ ،‬واحدة ُحِّدد فيها ِبّت إشارة وواحدة دون تحديده‪ ،‬وقد‬

‫ُيشار أحياًنا إىل هذه القيم بـ ‪ +0‬و ‪ -0‬عىل التوالي‪.‬‬

‫ج‪ .‬المتمم األحادي ‪One's complement‬‬

‫يطِّبق نهج المتِّم م األحادي العملية ‪ not‬عىل العدد الموجب من أجل تمثيل العدد السالب‪ ،‬لذا تمَّثل القيمة‬

‫‪ )-0x5A(-90‬مثاًل بـ ‪.~01011010 = 10100101‬‬

‫الحظ أن العاِم ل ~ هو عاِم ل في اللغة ‪ C‬الذي يطبق عاِم ل ‪ NOT‬عىل القيمة‪ ،‬كما يدعى أحياًنا بعاِم ل المتمم‬

‫األحادي ألسباب صارت معروفة لدينا اآلن‪.‬‬

‫الميزة األكبر في هذا النظام هي أنه ال يشترط تطبيق منطق خاص عند إضافة عدد سالب إىل ع‪tt‬دد م‪tt‬وجب‪،‬‬

‫باستثناء أنه يجب إضافة أّي حمل ‪ carry‬إضافي متبقي إىل القيمة النهائية‪ ،‬لذا تأمل الجدول التالي‪:‬‬

‫العملية‬ ‫النظام الثنائي‬ ‫النظام العشري‬


‫‪+‬‬ ‫‪10100101‬‬ ‫‪-90‬‬

‫‪01100100‬‬ ‫‪100‬‬

‫‪--------‬‬ ‫‪---‬‬
‫‪1‬‬
‫‪9‬‬ ‫‪00001001‬‬ ‫‪10‬‬

‫‪10‬‬ ‫‪00001010‬‬

‫جدول ‪ :17‬إضافة بت الِح ْم ل‬

‫إذا أضفت البتات الواحد تلو اآلخر‪ ،‬فستجد أنه سينتج لديك في النهاي‪t‬ة ِبّت حم‪t‬ل ‪ carry bit‬الموَّض ح في‬

‫الجدول‪ ،‬وستنتج لدينا القيمة الصحيحة ‪ 10‬بإضافته مجدًدا إىل العدد األصلي‪.‬‬

‫مجدًدا ال تزال لدينا مشكلة تمثيل الِص فرين‪ ،‬وال يوجد حاسوب حديث يس‪tt‬تخِدم المتمم األح‪tt‬ادي‪ ،‬والس‪tt‬بب‬

‫الرئيسي في ذلك وجود نظام أفضل‪.‬‬

‫المتمم الثنائي ‪Two's Complement‬‬

‫يتشابه المتمم الثنائي تماًم ا مع المتمم األحادي‪ ،‬باستثناء أّن التمثيل السالب يضاف إليه واحد ونتجاهل أّي‬
‫ِبّتات حمل متبقية‪ ،‬فإذا طبقناه عىل المثال السابق‪ ،‬فسنمِّثل العدد ‪ -90‬وفق ما يلي‪:‬‬

‫‪~01011010+1=10100101+1 = 10100110‬‬

‫يعني هذا أّن هناك تماثاًل غريًبا بعض الشيء في األعداد التي يمكن تمثيلها؛ ففي الع‪t‬دد الص‪t‬حيح ‪integer‬‬

‫مثاًل الذي يخَّزن عىل ‪ِ 8‬بت لدينا ‪ 256 = 82‬قيمة ممكنة‪ ،‬كما يمكننا تمثيل ‪ -127‬في نهج تمثي‪tt‬ل ِبت اإلش‪tt‬ارة‬

‫بواسطة ‪ ،127‬لكن يمكننا تمثيل ‪ -127‬في نظام المتمم الثنائي بواسطة ‪ 128‬ألننا أزلنا مش‪tt‬كلة وج‪tt‬ود ص‪tt‬فرين‪،‬‬

‫‪57‬‬
‫علوم الحاسوب من األلف إىل الياء‬ ‫تمثيل األعداد والنظام الثنائي‬

‫وَض ع في الحسبان أّن الصفر السالب هو (‪،00000000 = )11111111 + 1( = )~00000000 + 1‬‬

‫والحظ تجاهل ِبّت الحمل‪.‬‬

‫العملية ‪OP‬‬ ‫النظام الثنائي‬ ‫النظام العشري‬


‫‪+‬‬ ‫‪10100110‬‬ ‫‪-90‬‬

‫‪01100100‬‬ ‫‪100‬‬

‫‪--------‬‬ ‫‪---‬‬

‫‪00001010‬‬ ‫‪10‬‬

‫جدول ‪ :18‬إضافة المتمم الثنائي‬

‫ال بّد أنك الحظت أّن تطبيق المتمم الثنائي لن ُيحيج مصممي العتاد الصلب إال إىل توفير عمليات منطقي‪tt‬ة‬

‫لدارات اإلضافة‪ ،‬إذ يمكن إجراء عملية الطرح عن طريق متمم ثنائي ينفي القيمة المراد طرحها ثم يضيف القيم‪tt‬ة‬

‫الجديدة‪ ،‬وبالمثل يمكنك تنفيذ عملية الضرب بالجمع المتكرر وعملية القسمة بالطرح المتك‪tt‬رر‪ .‬وبالت‪tt‬الي يخ‪tt‬تزل‬

‫المتمم الثنائي جمي‪t‬ع العملي‪t‬ات الحس‪t‬ابية البس‪t‬يطة بعملي‪t‬ة الجم‪t‬ع‪ ،‬ومن الج‪t‬دير بال‪t‬ذكر أن جمي‪t‬ع الحواس‪t‬يب‬

‫الحديثة تستخِدم تمثيل المتمم الثنائي‪.‬‬

‫امتداد اإلشارة ‪Sign-extension‬‬

‫بناًء عىل صيغة المتمم الثنائي‪ ،‬عند زيادة حجم القيمة المؤَّش رة ‪ ،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‬االت‬

‫المحَّد دة في معيار اللغة‪ ،‬مع توفير المعالج عموًم ا تعليمات خاصة ألخذ قيمة وتمديد إشارتها إىل قيمة أكبر‪.‬‬

‫د‪ .‬األعداد العرشية ‪Floating Point‬‬

‫تحدثنا حتى اآلن عن األعداد الصحيحة ‪ 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‬بت فقط‪ ،‬وكلما زاد عدد البتات حظينا بدقة أكبر‪.‬‬

‫الجزء المعنوي‪/‬الجزء العشري‬ ‫األس‬ ‫اإلشارة‬


‫‪MMMMMMMMMMMMMMMMMMMMMMM‬‬ ‫‪EEEEEEEE‬‬ ‫‪S‬‬

‫جدول ‪:19‬نموذج العدد العشري ‪IEEE‬‬

‫العامل المهم اآلخر هو انحياز ‪ bias‬األس‪ ،‬إذ يجب أن يمِّثل األس القيم الموجب‪tt‬ة والس‪tt‬البة‪ ،‬وبالت‪tt‬الي ُتط‪َt‬ر ح‬

‫القيمة الضمنية للعدد ‪ 127‬من األس‪ ،‬إذ يحتوي األس ‪ 0‬مثاًل عىل حقل أس يس‪tt‬اوي ‪ ،127‬في حين يمِّث ل ‪128‬‬

‫العدد ‪ 1‬ويمثل ‪ 126‬العدد ‪.-1‬‬

‫يضيف كل ِبّت من الجزء المعنوي مزيًدا من الدقة إىل القيم التي يمكننا تمثيلها‪ ،‬وَض ع في الحسبان تمثي‪tt‬ل‬

‫الصيغة العلمية للقيمة ‪ ،198765‬إذ يمكننا كتابة هذا بالصيغة ‪ ،1.98765x106‬الذي يقابل التمثيل التالي‪:‬‬

‫‪100‬‬ ‫‪.‬‬ ‫‪10-1‬‬ ‫‪10-2‬‬ ‫‪10-3‬‬ ‫‪10-4‬‬ ‫‪10-5‬‬


‫‪1‬‬ ‫‪.‬‬ ‫‪9‬‬ ‫‪8‬‬ ‫‪7‬‬ ‫‪6‬‬ ‫‪5‬‬

‫جدول ‪ :20‬الصيغة العلمية للقيمة ‪1.98765x106‬‬

‫يتيح كل رقم إضافي مجااًل أكبر من القيم العشرية التي يمكننا تمثيلها‪ ،‬إذ يزيد كل رقم بعد الفاصلة العشرية‬

‫من دقة العدد بمقدار ‪ 10‬مرات في النظام العشري‪ ،‬فيمكننا مثاًل تمثيل ‪ 0.0‬بـ ‪- 0.9‬أي ‪ 10‬قيم‪ -‬برقم واحد بعد‬

‫الفاصلة عشرية‪ ،‬و‪ 0.00‬بـ ‪- 0.99‬أي ‪ 100‬قيمة‪ -‬برقمين‪ ،‬وهكذا؛ أما في النظام الثنائي‪ ،‬فبداًل من أن يمنحنا كل‬

‫رقم إضافي دقة أكبر بعشر أضعاف‪ ،‬ال نحظى إال بضعَف ي الدقة كما هو موَّض ح في الجدول التالي‪ ،‬ويعني ه‪tt‬ذا أّن‬

‫التمثيل الثنائي الخاص ال يوّجهنا دائًم ا بطريقة مباشرة إىل التمثيل العشري‪.‬‬

‫‪20‬‬ ‫‪.‬‬ ‫‪2-1‬‬ ‫‪2-2‬‬ ‫‪2-3‬‬ ‫‪2-4‬‬ ‫‪2-5‬‬


‫‪1‬‬ ‫‪.‬‬ ‫‪1/2‬‬ ‫‪1/4‬‬ ‫‪1/8‬‬ ‫‪1/16‬‬ ‫‪1/32‬‬

‫‪59‬‬
‫علوم الحاسوب من األلف إىل الياء‬ ‫تمثيل األعداد والنظام الثنائي‬

‫‪1‬‬ ‫‪.‬‬ ‫‪0.5‬‬ ‫‪0.25‬‬ ‫‪0.125‬‬ ‫‪0.0625‬‬ ‫‪0.03125‬‬

‫جدول ‪ :21‬التمثيل الثنائي للدقة‬

‫ال تكون دقة كسورنا كب‪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‬ل‬

‫هذا كاٍف لتمثيل طول أقل من حجم الذرة؟)‪.‬‬

‫‪$ cat float.c‬‬


‫>‪#include <stdio.h‬‬

‫)‪int main(void‬‬
‫{‬
‫;‪float a = 0.45‬‬
‫;‪float b = 8.0‬‬

‫;‪double ad = 0.45‬‬
‫;‪double bd = 8.0‬‬

‫‪printf("float+float, 6dp‬‬ ‫;)‪: %f\n", a+b‬‬


‫‪printf("double+double, 6dp‬‬ ‫;)‪: %f\n", ad+bd‬‬
‫‪printf("float+float, 20dp‬‬ ‫;)‪: %10.20f\n", a+b‬‬
‫;)‪printf("dobule+double, 20dp : %10.20f\n", ad+bd‬‬

‫;‪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‬ال يزالون يواجهون مشاكل في دقة المتغيرات‪.‬‬

‫القيم الموحدة ‪Normalised Values‬‬

‫يمكننا تمثي‪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‬ون البت‬

‫األيسر ‪ 1‬يكون ضمنًي ا‪.‬‬

‫‪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‬‬

‫‪0‬‬ ‫‪.‬‬ ‫‪1‬‬ ‫‪1‬‬ ‫‪0‬‬ ‫‪0‬‬ ‫‪0‬‬ ‫‪1-‬‬


‫)‪2 0.375= 5. * (0.5+0.25‬‬

‫‪1‬‬ ‫‪.‬‬ ‫‪1‬‬ ‫‪0‬‬ ‫‪0‬‬ ‫‪0‬‬ ‫‪0‬‬ ‫‪2-‬‬


‫)‪2 0.375= 0.25 * (1+0.5‬‬

‫جدول ‪ :22‬مثال عىل توحيد القيمة ‪0.375‬‬

‫‪61‬‬
‫علوم الحاسوب من األلف إىل الياء‬ ‫تمثيل األعداد والنظام الثنائي‬

‫كما ترى في المثال السابق‪ ،‬يمكننا جعل القيمة قيم‪tt‬ة موَّح دة من خالل تحري‪tt‬ك الِبّت ات لألم‪tt‬ام طالم‪tt‬ا أنن‪tt‬ا‬

‫نعِّو ض عن ذلك بزيادة األس‪.‬‬

‫مهارات التوحيد ‪Normalisation‬‬

‫من المش‪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‬‬
‫علوم الحاسوب من األلف إىل الياء‬ ‫تمثيل األعداد والنظام الثنائي‬

‫توَّح د هذه القيمة عندما ُتحَّم ل ‪//‬‬


‫;‪long double d = 0x8000UL‬‬
‫;‪long exp‬‬

‫تعليمات "الحصول على أس العدد العشري" في معالج إتانيوم ‪//‬‬


‫;))‪asm ("getf.exp %0=%1" : "=r"(exp) : "f"(d‬‬

‫األس متضمًن ا االنزياح ‪//‬‬


‫;)‪printf("The first non-zero (fast) is %d\n", exp - 65535‬‬

‫}‬

‫خالصة األفكار السابقة‬

‫نستخرج مكونات العدد العشري ونطبع القيمة التي يمثلها في نموذج الشيفرة البرمجي‪tt‬ة التالي‪tt‬ة‪ ،‬إذ س‪tt‬نحرز‬

‫نتيج ‪ًt‬ة فق‪tt‬ط عن‪tt‬دما تك‪tt‬ون القيم‪tt‬ة ع‪tt‬دًدا عش‪tt‬رًيا بحجم ‪ِ 32‬بّت بص‪tt‬يغة المعي‪tt‬ار ‪ ،IEEE‬وه‪tt‬ذا ش‪tt‬ائع في معظم‬

‫المعماريات من نوع ‪ float‬أي عدد عشري‪.‬‬

‫>‪#include <stdio.h‬‬
‫>‪#include <string.h‬‬
‫>‪#include <stdlib.h‬‬

‫‪ */‬إرجاع ‪/* 2^n‬‬


‫)‪int two_to_pos(int n‬‬
‫{‬
‫)‪if (n == 0‬‬
‫;‪return 1‬‬
‫;)‪return 2 * two_to_pos(n - 1‬‬
‫}‬

‫)‪double two_to_neg(int n‬‬


‫{‬
‫)‪if (n == 0‬‬
‫;‪return 1‬‬
‫;)))‪return 1.0 / (two_to_pos(abs(n‬‬
‫}‬

‫‪63‬‬
‫علوم الحاسوب من األلف إىل الياء‬ ‫تمثيل األعداد والنظام الثنائي‬

double two_to(int n)
{
if (n >= 0)
return two_to_pos(n);
if (n < 0)
return two_to_neg(n);
return 0;
}

/* ‫" الذي هو الجزء المعنوي‬m" ‫مراجعة بعض أجزاء الذاكرة للمتغير‬


‫ نبدأ بالمقلوب من البتات في أقصى اليمين‬،‫ بت‬24 ‫للعدد العشري بحجم‬
‫* دون أي سبب معين‬/
double calc_float(int m, int bit)
{
/* 23 ‫* بت؛ هذا ينهي العودية‬/
if (bit > 23)
return 0;

/* ‫^بت‬2/1 ‫ فهو يمثل القيمة‬،‫إذا كان البت مضبوًطا‬ */


if ((m >> bit) & 1)
return 1.0L/two_to(23 - bit) + calc_float(m, bit + 1);

/* ‫* وإال انتقل إلى البت التالي‬/


return calc_float(m, bit + 1);
}

int main(int argc, char *argv[])


{
float f;
int m,i,sign,exponent,significand;

if (argc != 2)
{
printf("usage: float 123.456\n");
exit(1);
}

64
‫علوم الحاسوب من األلف إىل الياء‬ ‫تمثيل األعداد والنظام الثنائي‬

‫)‪if (sscanf(argv[1], "%f", &f) != 1‬‬


‫{‬
‫;)"‪printf("invalid input\n‬‬
‫;)‪exit(1‬‬
‫}‬

‫سنحتاج إلى خداع المصرف‪ ،‬كأننا بدأنا استخدام التحويالت *‪/‬‬


‫فمثاًل (‪ )f()int‬ستجري تحوياًل فعلًيا لنا‬
‫نريد الوصول إلى البتات األولية‪ ،‬لذا ننسخها إلى متغير‬
‫‪. */‬بنفس الحجم‬
‫;)‪memcpy(&m, &f, 4‬‬

‫‪ */‬بت اإلشارة هو أول بت *‪/‬‬


‫;‪sign = (m >> 31) & 0x1‬‬

‫‪ */‬األس هو البتات الثمانية التي تلي بت اإلشارة *‪/‬‬


‫;‪exponent = ((m >> 23) & 0xFF) - 127‬‬

‫الجزء المعنوي يمأل المنازل العشرية‪ ،‬ويكون أول بت ضمنًيا ‪/* 1‬‬
‫‪. */‬بت ‪ OR 24‬وبالتالي هو قيمة المعامل‬
‫;‪significand = (m & 0x7FFFFF) | 0x800000‬‬

‫‪ */‬اطبع قيمًة تمثل األس *‪/‬‬


‫;)‪printf("%f = %d * (", f, sign ? -1 : 1‬‬
‫)‪for(i = 23 ; i >= 0 ; i--‬‬
‫{‬
‫)‪if ((significand >> i) & 1‬‬
‫‪printf("%s1/2^%d", (i == 23) ? "" : " + ",‬‬
‫;)‪23-i‬‬
‫}‬
‫;)‪printf(") * 2^%d\n", exponent‬‬

‫‪ */‬اطبع تمثياًل كسرًيا *‪/‬‬


‫;)‪printf("%f = %d * (", f, sign ? -1 : 1‬‬
‫)‪for(i = 23 ; i >= 0 ; i--‬‬

‫‪65‬‬
‫علوم الحاسوب من األلف إىل الياء‬ ‫تمثيل األعداد والنظام الثنائي‬

‫{‬
‫)‪if ((significand >> i) & 1‬‬
‫‪printf("%s1/%d", (i == 23) ? "" : " + ",‬‬
‫;))‪(int)two_to(23-i‬‬
‫}‬
‫;)‪printf(") * 2^%d\n", exponent‬‬

‫‪ */‬حول هذا إلى قيمة عشرية واطبعه *‪/‬‬


‫‪printf("%f = %d * %.12g * %f\n",‬‬
‫‪f,‬‬
‫‪(sign ? -1 : 1),‬‬
‫‪calc_float(significand, 0),‬‬
‫;))‪two_to(exponent‬‬

‫‪ */‬اجِر العملية الحسابية اآلن *‪/‬‬


‫‪printf("%f = %.12g\n",‬‬
‫‪f,‬‬
‫* )‪(sign ? -1 : 1‬‬
‫* )‪calc_float(significand, 0‬‬
‫)‪two_to(exponent‬‬
‫;)‬

‫;‪return 0‬‬
‫}‬

‫وفيما يلي نموذج خرج القيمة ‪ 8.45‬الذي درسناه سابًق ا‪:‬‬

‫‪$ ./float 8.45‬‬


‫‪8.450000 = 1 * (1/2^0 + 1/2^5 + 1/2^6 + 1/2^7 + 1/2^10 + 1/2^11 +‬‬
‫‪1/2^14 + 1/2^15 + 1/2^18 + 1/2^19 + 1/2^22 + 1/2^23) * 2^3‬‬
‫‪8.450000 = 1 * (1/1 + 1/32 + 1/64 + 1/128 + 1/1024 + 1/2048 + 1/16384‬‬
‫‪+ 1/32768 + 1/262144 + 1/524288 + 1/4194304 + 1/8388608) * 2^3‬‬
‫‪8.450000 = 1 * 1.05624997616 * 8.000000‬‬
‫‪8.450000 = 8.44999980927‬‬

‫نستخلص من هذا المثال فكرًة عن تسلل عدم الدقة إىل أعدادنا العشرية‪.‬‬

‫‪66‬‬
‫‪ .3‬معمارية الحاسوب‬

‫‪ 3.1‬تعرف عىل وحدة المعالجة المركزية وعملياتها‬


‫تنّف ذ وحدة المعالجة المركزية التعليمات عىل القيم الموجودة في المسّجالت ‪ ،Registers‬إذ يوّض ح المث‪tt‬ال‬

‫اآلتي أواًل ضبط ‪ R1‬عىل القيمة ‪ 100‬وتحميل القيمة من موقع الذاكرة ‪ 0x100‬إىل ‪ R2‬وجمع القيمتين‪ ،‬ثم وضع‬

‫النتيجة في ‪ ،R3‬وأخيًرا تخزين القيمة الجديدة ‪ 110‬في ‪.R4‬‬

‫شكل ‪ :7‬وحدة المعالجة المركزية ‪CPU‬‬

‫يتكون الحاسوب من وحدة معالجة مركزية ‪- Central Processing Unit‬أو ‪ CPU‬اختصاًرا‪ -‬متصلة بالذاكرة‪،‬‬

‫إذ توّض ح الصورة السابقة المبدأ العام لجميع عمليات الحاسوب‪ ،‬كما تنّف ذ وح‪t‬دة المعالج‪tt‬ة المركزي‪t‬ة التعليم‪t‬ات‬

‫المقروءة من الذاكرة‪ ،‬وهناك نوعان من هذه التعليمات هما‪:‬‬


‫علوم الحاسوب من األلف إىل الياء‬ ‫معمارية الحاسوب‬

‫التعليمات التي تحّم ل القيم من الذاكرة إىل المسجالت وتخّزن القيم من المسجالت إىل الذاكرة‪.‬‬ ‫‪.1‬‬

‫التعليمات التي ُتشَّغ ل عىل القيم المخَّزنة في المسّجالت مثل جمع أو طرح أو ضرب أو قسمة قيم‪tt‬تين‬ ‫‪.2‬‬

‫موجودتين في مسجلين‪ ،‬أو إجراء العمليات الثنائية ‪ and‬و ‪ or‬و ‪ xor‬وغيرها‪ ،‬أو إجراء عمليات حس‪tt‬ابية‬

‫أخرى‪ ،‬مثل الجذر التربيعي و ‪ sin‬و ‪ cos‬و ‪ tan‬وغيرها‪.‬‬

‫لذا نجمع في مثالنا ببساطة العدد ‪ 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‬ه متزامًن ا داخلًي ا‪ ،‬ويمكن‬

‫البدء بعملية أخرى في كل لحظة أو نبضة‪.‬‬

‫‪ 3.1.3‬جلب التعليمة وفك تشفريها وتنفيذها وتخزين نتيجتها‬


‫يتكون تنفيذ تعليمة واحدة من دورة معين‪tt‬ة من األح‪tt‬داث‪ ،‬وهي الجلب وف‪tt‬ك التش‪tt‬فير والتنفي‪tt‬ذ والتخ‪tt‬زين‪،‬‬

‫إذ يجب عىل وحدة المعالجة المركزية تطبيق الخطوات التالية لتنفيذ تعليمة ‪ add‬السابقة مثاًل ‪:‬‬

‫‪ .1‬الجلب ‪ :Fetch‬الحصول عىل التعليمات من الذاكرة إىل المعالج‪.‬‬

‫‪ .2‬فك التشفير ‪ :Decode‬فك تشفير ما يجب أن تفعله داخلًي ا‪ ،‬أي الجمع في هذه الحالة‪.‬‬

‫‪ .3‬التنفيذ ‪ :Execute‬أخذ القيم من المسجالت وجمعها‪.‬‬

‫‪ .4‬التخزين ‪ :Store‬تخزين النتيجة في مسجل آخر‪ ،‬كما يمكن رؤية مصطلح انتهاء ‪ Retiring‬التعليمة‪.‬‬

‫‪68‬‬
‫علوم الحاسوب من األلف إىل الياء‬ ‫معمارية الحاسوب‬

‫ا‪ .‬نظرة داخلية إىل وحدة المعالجة المركزية‬

‫تحتوي وحدة المعالج‪tt‬ة المركزي‪tt‬ة داخلًي ا عىل العدي‪tt‬د من المكون‪tt‬ات الفرعي‪tt‬ة المختلف‪tt‬ة ال‪tt‬تي تطّب ق كاًل من‬

‫الخطوات المذكورة سابًق ا‪ ،‬كما يمكن أن تحدث جميعها بصورة مستقلة عن بعضها البعض‪ ،‬وهي مش‪tt‬ابهة لخ‪tt‬ط‬

‫اإلنتاج في المصانع‪ ،‬حيث توجد العديد من المحطات ولكل خطوة َم همة معينة ألدائها‪ ،‬ثم يمكن‪tt‬ه تمري‪tt‬ر النت‪tt‬ائج‬

‫إىل المحطة التالية وأخذ مدخالت جديدة للعمل عليها‪.‬‬

‫شكل ‪ :8‬داخل ‪CPU‬‬

‫تتكون وحدة المعالجة المركزية من العديد من المكونات الفرعية المختلفة‪ ،‬وتطّبق كل منها مهمًة ُم خَّص صًة ‪،‬‬

‫وتوِّض ح الصورة السابقة مخطًط ا بسيًط ا لبعض األجزاء الرئيسية لوحدة المعالجة المركزية الحديث‪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‬‬

‫‪ )Instruction Multiple Data‬أو ‪ Altivec‬مسجالت مماثلة للمسجالت الخاصة األعداد العشرية‪.‬‬

‫‪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‬تخِدم‬

‫مصطلحات مختلفًة قلياًل لمكوناتها‪.‬‬

‫ب‪ .‬استخدام خط األنابيب ‪Pipelining‬‬

‫ُتَع ّد عملية وحدة ‪ 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‬ادة‬

‫ترتيب المخزن المؤقت ‪:Buffer‬‬

‫‪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‬برمجين إىل‬

‫القلق بشأنها عىل الرغم من أنك قد تصادفها‪.‬‬

‫‪ 3.1.4‬معمارية ‪ CISC‬ومعمارية ‪RISC‬‬


‫يمكن تقس‪tt‬يم معماري‪tt‬ات الحاس‪tt‬وب إىل معماري‪tt‬ة حاس‪tt‬وب مجموع‪tt‬ة التعليم‪tt‬ات المعق‪tt‬دة ‪Complex‬‬

‫‪- Instruction Set Computer‬أو ‪ CISC‬اختصاًرا‪ -‬ومعمارية حاسوب مجموعة التعليمات الُم خَّف ضة ‪Reduced‬‬

‫‪ Instruction Set Computer‬أو ‪ RISC‬اختصاًرا‪.‬‬

‫الحظ أننا في المثال األول من مقالنا حّم لن‪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‬دا |‬

‫ليكون فّع ااًل ‪.‬‬

‫ا‪ .‬معمارية ‪EPIC‬‬

‫ُيَع ّد مع‪t‬الج إيت‪t‬انيوم ‪ Itanium‬مث‪t‬ااًل عىل معماري‪t‬ة معَّد ل‪t‬ة تس‪t‬مى الحوس‪t‬بة الص‪t‬ريحة للتعليم‪t‬ات الفرعي‪t‬ة‬

‫‪.Explicitly Parallel Instruction Computing‬‬

‫ناقشنا سابًق ا كيف أّن المعالجات الفائقة لها خطوط أن‪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‬‬
‫علوم الحاسوب من األلف إىل الياء‬ ‫معمارية الحاسوب‬

‫‪ 3.2‬تعرف عىل ذاكرة الحاسوب‬


‫يمكن لوحدة المعالجة المركزية جلب التعليمات والبيانات مباشرًة من ال‪t‬ذاكرة المخبئي‪tt‬ة ‪Cache Memory‬‬

‫الموجودة عىل شريحة المعالج فقط‪ ،‬ل‪tt‬ذا يجب تحمي‪tt‬ل ال‪tt‬ذاكرة المخبئي‪tt‬ة من ذاك‪tt‬رة النظ‪tt‬ام الرئيس‪tt‬ية‪ ،‬أي ذاك‪tt‬رة‬

‫الوصول العشوائي ‪- Random Access Memory‬أو ‪ RAM‬اختصاًرا‪ ،-‬ولكن تحتفظ ال‪tt‬ذاكرة ‪ RAM‬بمحتوياته‪tt‬ا‬

‫فقط عند الوصل بمصدر طاقة‪ ،‬لذلك يجب تخزينها عىل مساحة تخزين دائمة وغير متطايرة‪.‬‬

‫‪ 3.2.1‬تسلسل الذواكر الهرمي‬


‫نطلق عىل طبقات الذواكر التالية اسم تسلسل الذواكر الهرمي ‪:Memory Hierarchy‬‬

‫الوصف‬ ‫الذاكرة‬ ‫السرعة‬


‫الذاكرة المخبئية هي ذاكرة مضَّم نة في وحدة المعالجة‬
‫المركزية‪ ،‬وهي ذاكرة سريعة جًدا وتستغرق دورة واحدة فقط‬
‫للوصول إليها‪ ،‬ولكن هناك حد لحجمها ألنها ُم دَم جة مباشرًة‬
‫الذاكرة المخبئية ‪Cache‬‬ ‫األسر ع‬
‫في وحدة المعالجة المركزية‪ ،‬كما توجد هناك عدة مستويات‬
‫فرعية من الذاكرة المخبئية تسمى ‪ L1‬و ‪ L2‬و ‪ L3‬بسرعات‬
‫متزايدة قلياًل عن بعضها البعض‪.‬‬

‫يجب أن تأتي جميع التعليمات وعناوين التخزين الخاصة‬


‫بالمعالج من الذاكرة ‪ ،RAM‬وتستغرق وحدة المعالجة‬
‫المركزية بعض الوقت للوصول إىل الذاكرة ‪ RAM‬يسمى زمن‬
‫التأخير ‪ Latency‬بالرغم من أنها ذاكرة سريعة جًدا‪ ،‬كما ُتخَّزن‬ ‫الذاكرة ‪RAM‬‬
‫الذاكرة ‪ RAM‬في شرائح منفصلة ومخصصة متصلة باللوحة‬
‫األم‪ ،‬مما يعني أنها أكبر بكثير من الذاكرة المخبئية‪.‬‬

‫جميعنا عىل دراية بالبرامج التي تصلنا عىل قرص مرن‬


‫‪ floppy disk‬أو قرص مضغوط‪ ،‬ونعلم كيفية حفظ ملفاتنا‬
‫عىل القرص الصلب‪ ،‬ونعلم الوقت الطويل الذي يمكن أن‬
‫يستغرقه البرنامج للتحميل من القرص الصلب‪ ،‬إذ يعني‬ ‫القرص الصلب ‪Disk‬‬ ‫األبطأ‬
‫وجود آليات فيزيائية مثل األقراص الدوارة والرؤوس‬
‫المتحركة أن األقراص الصلبة هي أبطأ وسيلة من وسائل‬
‫التخزين‪ ،‬ولكنها أكبرها حجًم ا‪.‬‬

‫جدول ‪ :23‬تسلسل الذواكر الهرمي‬

‫تجدر اإلشارة ألننا نستخدم مصطلح القرص الصلب ‪ HDD‬في سياق هذا الكتاب مع العلم بأن قرص الحالة‬

‫الصلبة ‪ SSD‬يستخدم اليوم في معظم الحواسيب وهو أحدث وأسر ع بكثير‪.‬‬

‫النقطة المهمة التي يجب معرفتها حول تسلسل الذواكر الهرمي هي المقايض‪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‬ذاكرة أي‬

‫المحلية الزمانية وتخزين كتل صغيرة من المعلومات ذات الصلة أي المحلية المكانية‪.‬‬

‫‪ 3.2.2‬نظرة معمقة عىل الذاكرة المخبئية‬


‫ُتَع ّد الذاكرة المخبئية أحد أهم عناصر معمارية وحدة المعالج‪t‬ة المركزي‪t‬ة‪ ،‬إذ يجب عىل المط‪t‬ورين فهم كيفي‪t‬ة‬

‫عمل الذاكرة المخبئية في أنظمتهم لكتابة شيفرة برمجية فعال‪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‬‬

‫بعد أن قدمها حاسوب ‪ Harvard Mark-1‬القائم عىل الُم رّحالت ‪.Relay‬‬

‫تساعد الذواكر المخبئية المقس‪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‬راءة‬

‫الكتل الفرعية ألن مجرى التعليمات يستخِدم عموًم ا عمليات وصول ذات أحجام أكثر انتظاًم ا‪.‬‬

‫شكل ‪ :9‬ترابط الذاكرة المخبئية‪.‬‬

‫يوضح الشكل السابق ترابط الذاكرة المخبيئة حيث يمكن أن يجد خط ذاكرة مخبئية معّين مكاًن ا ص‪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‬ة‬

‫ذات المستوى األعىل التي تحتوي عىل خط معّين إمكانيَة احتواء ذاكرة مخبئية ذات مستوى أقل عىل هذا الخط‪،‬‬

‫فإننا نقول أنها حصرية ‪ Exclusive‬وسنناقش ذلك الحًق ا‪.‬‬

‫ا‪ .‬عنونة الذاكرة المخبئية‬

‫لم نناقش حتى اآلن كيف تقرر الذاكرة المخبئية ما إذا كان عنوان معّين موجوًدا في الذاكرة المخبئية أم ال‪ ،‬إذ‬

‫يجب أن تحتفظ الذواكر المخبئية بمجلد للبيانات الموجودة حالًي ا في خطوط الذاكرة المخبئية‪ ،‬ويمكن وضع مجلد‬

‫وبيانات الذاكرة المخبئية عىل المعالج مًع ا‪ ،‬ولكن يمكن أن يكونا منفصَلين أيًض ا كما في حالة المعالج ‪POWER5‬‬

‫الذي يحتوي عىل مجلد ذاكرة ‪ L3‬عىل المعالج‪ ،‬ولكن يتطلب الوصول إىل البيانات اجتي‪tt‬از ناق‪tt‬ل ‪ L3‬للوص‪tt‬ول إىل‬

‫ذاكرة خارجية ليست عىل المعالج‪ ،‬ويمكن أن يسّه ل هذا الترتيب معالجة عمليات الوصول الصحيحة أو الخاطئ‪tt‬ة‬

‫بصورة أسر ع دون التكاليف األخرى لالحتفاظ بالذاكرة المخبئية بالكامل عىل المعالج‪.‬‬

‫شكل ‪ :10‬وسوم الذاكرة المخبئية ‪Cache Tags‬‬

‫وسوم الذاكرة المخبئية ‪ :Cache Tags‬يجب التحقق من الوسوم عىل التوازي للحفاظ عىل وقت االس‪tt‬تجابة‬

‫منخفًض ا‪ ،‬إذ يتطلب المزيُد من بتات الوسوم (أي ارتباطات مجموعات أق‪tt‬ل) عت‪tt‬اًدا أك‪tt‬ثر تعقي‪ًtt‬دا لتحقي‪tt‬ق ذل‪tt‬ك‪.‬‬

‫بينما تعني ارتباطاُت المجموعات األكثر وسوًم ا أقل‪ ،‬ولكن يحتاج المعالج اآلن إىل عت‪tt‬اد لمض‪tt‬اعفة خ‪tt‬رج العدي‪tt‬د‬

‫من المجموعات التي يمكن أن تضيف زمن تأخير أيًض ا‪.‬‬

‫يمكن تحديد ما إذا كان العنوان موج‪tt‬وًدا في ال‪tt‬ذاكرة المخبئي‪tt‬ة بس‪tt‬رعة من خالل فص‪tt‬له إىل ثالث‪tt‬ة أج‪tt‬زاء هي‬

‫الوسم ‪ Tag‬والفهرس ‪ Index‬واإلزاحة ‪.Offset‬‬

‫تعتمد بتات اإلزاحة عىل حجم خط الذاكرة المخبئية‪ ،‬إذ يمكن استخدام خط بحجم ‪ 32‬بايت مثاًل آخر ‪ 5‬بت‪tt‬ات‬

‫أي ‪ 25‬من العنوان بوصفه إزاحًة في الخط‪ ،‬وُيَع ّد الفهرس خط ذاكرة مخبئية معّي ن يمكن أن يتواجد في‪t‬ه اإلدخ‪t‬ال‪،‬‬

‫‪78‬‬
‫علوم الحاسوب من األلف إىل الياء‬ ‫معمارية الحاسوب‬

‫فلنفترض أنه لدينا ذاكرة مخبئية تحتوي عىل ‪ 256‬إدخااًل مثاًل ‪ ،‬فإذا كانت هذه الذاكرة هي ذاك‪t‬رة مخبئي‪t‬ة مربوط‪t‬ة‬

‫مباشرًة ‪ ،‬فيمكن أن تكون البيانات موجودة في خط واحد محتَم ل فقط‪ ،‬لذا تصف ‪ 8‬بتات التالية (‪ )28‬بعد اإلزاحة‬

‫الخط المراد التحقق منه بين ‪ 0‬و ‪.255‬‬

‫لنفترض اآلن أّن ال‪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‬وط ‪-‬أي‬

‫حجمها‪ -‬التي يمكن أن تنمو إليها الذاكرة المخبئية‪.‬‬

‫‪ 3.3‬األجهزة الطرفية ‪ Peripherals‬ونواقلها ‪Buses‬‬


‫األجه‪t‬زة الطرفي‪t‬ة ‪ peripherals‬هي مجموع‪t‬ة األجه‪t‬زة الخارجي‪t‬ة ال‪t‬تي تتص‪t‬ل بحاس‪t‬وبك‪ ،‬ويجب أن يك‪t‬ون‬

‫للمعالج طريقة ما للتواصل مع هذه األجهزة الطرفية لجعلها مفيدة‪ ،‬وتسمى قناة االتص‪tt‬ال بين المع‪tt‬الج واألجه‪tt‬زة‬

‫الطرفية بالناقل ‪.Bus‬‬

‫‪ 3.3.1‬المفاهيم الخاصة بنواقل األجهزة الطرفية‬


‫يتطلب الجهاز عمليات إدخال وإخ‪t‬راج ليك‪t‬ون مفي‪ًt‬دا‪ ،‬ويوج‪t‬د هن‪t‬اك ع‪t‬دد من المف‪t‬اهيم الش‪t‬ائعة المطلوب‪t‬ة‬

‫للتواصل المفيد مع األجهزة الطرفية التي سنستعرضها فيما يلي‪.‬‬

‫‪79‬‬
‫علوم الحاسوب من األلف إىل الياء‬ ‫معمارية الحاسوب‬

‫ا‪ .‬المقاطعات ‪Interrupts‬‬

‫تسمح المقاطعة للجهاز بمقاطعة المعالج حرفًي ا بما تعنيه الكلمة لإلشارة إىل بعض المعلوم‪tt‬ات‪ ،‬فمثاًل ُتنَش أ‬

‫مقاطعة لتسليم حدث الض‪tt‬غط عىل مفت‪tt‬اح إىل نظ‪tt‬ام التش‪tt‬غيل عن‪tt‬د الض‪tt‬غط علي‪tt‬ه‪ ،‬إذ تس‪ِtt‬ند تركيب‪tt‬ة من نظ‪tt‬ام‬

‫التشغيل وبيوس ‪ BIOS‬مقاطعًة لكل جهاز‪.‬‬

‫ترتبط األجهزة عموًم ا بمتحكم المقاطعة القابل للبرمجة ‪ 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‬ام‬

‫التشغيل‪.‬‬

‫شكل ‪ :11‬نظرة عامة عىل معالجة المقاطعة‬

‫توضح الصورة السابقة نظرة عامة عىل معالجة المقاطع‪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‬حًة تماًم ا‬

‫في كل ما يحدث في ذلك الوقت بغض النظر عن الوقت الضائع‪.‬‬

‫المقاطعات ‪ Interrupts‬والمصائد ‪ Traps‬واالستثناءات ‪Exceptions‬‬

‫ترتبط المقاطعة عموًم ا بحدث خارجي من جهاز فيزيائي‪ ،‬ولكن ُتَع ّد اآللية نفسها مفيدًة للتعامل مع عمليات‬

‫النظام الداخلية‪ ،‬فإذا اكتشف المعالج مثاًل حاالت مثل الوص‪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‬ه أو‬

‫يمكن أن يؤدي التشويش البيئي أو غيره إىل حدوث مقاطعة زائفة يجب تجاهلها‪.‬‬

‫المقاطعات غري القابلة للتقنع أو اإلخفاء ‪Non-maskable Interrupts‬‬

‫يجب أن يكون النظام قادًرا عىل إخفاء المقاطع‪t‬ات أو منعه‪t‬ا في أوق‪t‬ات معين‪t‬ة‪ ،‬ويمكن وض‪t‬ع المقاطع‪t‬ات‬

‫لتكون قيد االنتظار‪ ،‬لكن هناك صنف معّين من المقاطعات يس‪tt‬مى المقاطع‪tt‬ات غ‪tt‬ير القابل‪tt‬ة للتقّن ع أو اإلخف‪tt‬اء‬

‫‪ Non-maskable Interrupts‬أو ‪ NMI‬اختصاًرا‪ ،‬إذ ُتَع ّد هذه المقاطعات استثناًء من هذه القاعدة مثل مقاطعة‬

‫إعادة الضبط ‪.reset‬‬

‫يمكن أن تكون مقاطعات ‪ NMI‬مفيدًة لتطبيق أشياء مثل مراقبة النظ‪tt‬ام‪ ،‬حيث ُترَف ع مقاطع‪tt‬ة ‪ NMI‬دورًي ا‬

‫وتضِب ط بعض الرايات التي يجب أن يقّر بها نظام التشغيل‪ ،‬فإذا لم يظهر هذا اإلقرار قبل مقاطع‪tt‬ة ‪ NMI‬الدوري‪tt‬ة‬

‫التالية‪ ،‬فيمكن َع ّد النظام أنه ال يحرز أّي تقدم‪ ،‬كما يمكن استخدام مقاطعات ‪ NMI‬لتشخيص ‪ Profiling‬النظام‪،‬‬

‫إذ يمكن رفع مقاطعات ‪ NMI‬الدورية واستخدامها لتقييم الشيفرة البرمجية التي يعمل به‪tt‬ا المع‪tt‬الج حالًي ا‪ ،‬مم‪tt‬ا‬

‫يؤدي بمرور الوقت إىل إنشاء ملف تعريف للشيفرة البرمجية التي تعمل والحصول عىل رؤية مفيدة للغاي‪tt‬ة ح‪tt‬ول‬

‫أداء النظام‪.‬‬

‫ب‪ .‬فضاء اإلدخال واإلخراج ‪IO‬‬

‫يجب أن يتصل المعالج بالجهاز الطرفي عبر عمليات اإلدخال واإلخراج ‪ ،IO‬وُيطَلق عىل الشكل األكثر ش‪tt‬يوًع ا‬

‫من عمليات ‪ IO‬عمليات اإلدخال واإلخراج المرتبطة بالذاكرة ‪ ،Memory Mapped IO‬إذ ترتبط مسجالت الجهاز‬

‫مع الذاكرة‪ ،‬وما عليك سوى القراءة أو الكتابة في عنوان محدد من الذاكرة للتواصل مع الجهاز‪.‬‬

‫‪ 3.3.2‬الوصول المبارش للذاكرة ‪DMA‬‬


‫بما أن سرعة األجهزة أقل بكثير من سرعة المعالجات‪ ،‬فيجب أن يكون هناك طريقة ما لتجنب انتظار وح‪tt‬دة‬

‫المعالجة المركزية للبيانات من األجهزة‪.‬‬

‫ُيَع ّد الوصول المباشر للذاكرة ‪- 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‬من‬

‫إعدادات ‪ ،Configurations‬ولكن معظم األجهزة لها إعداد واحد فقط‪.‬‬

‫شكل ‪ :12‬نظرة عامة عىل متحكم ‪( UCHI‬مأخوذة من توثيق إنتل ‪)Intel‬‬

‫‪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‬ز‬

‫‪ ،USB‬ثم ُتشَّغ ل دالة رد النداء‪.‬‬

‫‪ 3.4‬أنظمة المعالجات في معمارية الحاسوب‬


‫َنمت قوة الحوسبة بوتيرة سريعة دون ظهور أّي عالمات عىل التباطؤ كما توّق ع ق‪tt‬انون م‪tt‬ور ‪ ،Moore‬فليس‬

‫مألوًف ا أن تحتوي أّي خوادم عالية الجودة عىل وحدة معالجة مركزية واحدة فقط مع إمكانية تحقيق ذلك باستخدام‬

‫عدد من األساليب المختلفة‪.‬‬

‫‪ 3.4.1‬المعالجة المتعددة المتماثلة ‪Symmetric Multi-Processing‬‬


‫ُتَع ّد المعالجة المتعددة المتماثلة ‪- Symmetric Multi-Processing‬أو ‪ SMP‬اختصاًرا‪ -‬اإلعداد األكثر شيوًع ا‬

‫حالًي ا لتضمين وحدات المعالجة المركزية ‪ CPU‬المتعددة في نظام واحد‪ ،‬ويشير المصطلح متماث‪tt‬ل ‪Symmetric‬‬

‫إىل حقيقة أّن جميع وحدات المعالجة المركزية في النظام هي نفسها من حيث المعماري‪tt‬ة وس‪tt‬رعة الس‪tt‬اعة مثاًل ‪،‬‬

‫كما توجد في نظام ‪ SMP‬معالجات متعددة تشترك في جميع موارد النظام األخرى مثل الذاكرة والقرص الص‪tt‬لب‬

‫وغير ذلك‪.‬‬

‫‪84‬‬
‫علوم الحاسوب من األلف إىل الياء‬ ‫معمارية الحاسوب‬

‫ا‪ .‬ترابط الذواكر المخبئية ‪Cache Coherency‬‬

‫تعمل وحدات المعالجة المركزية في النظام بصورة مستقلة عن بعضها بعًض ا‪ ،‬فلكل منها مجموعته الخاص‪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‬د ل‬

‫‪ Modified‬ومالك ‪ Owner‬وحصري ‪ Exclusive‬ومشاَرك ‪ Shared‬وغير صالح ‪ Invalid‬التي تمّثل الحال‪tt‬ة ال‪tt‬تي‬

‫يمكن أن يكون فيها خ‪t‬ط ال‪t‬ذاكرة المخبئي‪t‬ة عىل مع‪t‬الج في النظ‪t‬ام‪ ،‬كم‪t‬ا توج‪t‬د بروتوك‪t‬والت أخ‪t‬رى ل‪t‬ذلك‪ ،‬ولكن‬

‫تشترك جميعها في مفاهيم متشابهة‪ ،‬وسنوضح فيما يلي بروتوكول ‪.MOESI‬‬

‫إذا طلب المع‪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‬وا عىل‬

‫دراية بما يطّبقه العتاد استجابًة للبرمجيات التي يصّم مونها لزيادة األداء إىل الحد األقصى‪.‬‬

‫حرص ية الذاكرة المخبئية في أنظمة ‪SMP‬‬

‫ش‪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‬ا‬

‫في المستقبل في تلوث مورد ‪ L1‬المحدود ألن أحجام ‪ L1‬صغيرة‪.‬‬

‫ليس هناك داع للقلق بش‪tt‬أن الكتاب‪tt‬ة في ‪ L1‬من الن‪tt‬وع ‪ Write-through‬إذا احت‪tt‬وت عىل بيان‪tt‬ات متس‪tt‬خة‬

‫معَّلق‪tt‬ة‪ ،‬وبالت‪tt‬الي يمكن أن تم‪ّt‬رر منط‪tt‬ق التراب‪t‬ط اإلض‪tt‬افي إىل ذاك‪tt‬رة ‪ L2‬ال‪t‬تي ل‪t‬ديها دور أك‪tt‬بر تلعب‪t‬ه في تراب‪t‬ط‬

‫الذاكرة المخبئية‪.‬‬

‫ب‪ .‬تقنية خيوط المعالجة الفائقة ‪Hyperthreading‬‬

‫يمكن أن يقضي المعالج الحديث كثيًرا من وقته في انتظار أجهزة أبط‪tt‬أ بكث‪tt‬ير في تسلس‪tt‬ل ال‪tt‬ذواكر اله‪tt‬رمي‬

‫لتقديم البيانات للمعالجة‪ ،‬وبالتالي فإن إستراتيجيات الحفاظ عىل خط أنابيب المعالج ممتلًئا لها أهمية قص‪tt‬وى‪،‬‬

‫وتتمثل إحدى اإلستراتيجيات في تضمين عدد كاف من المسجالت ومنطق الحالة بحيث يمكن معالجة مجريين‬

‫من التعليمات في الوقت نفسه‪ ،‬مما يجع‪tt‬ل وح‪tt‬دة معالج‪tt‬ة مركزي‪tt‬ة واح‪tt‬دة تبحث عن جمي‪tt‬ع النواي‪tt‬ا واأله‪tt‬داف‬

‫المطلوبة كأنها وحدتان ‪.CPU‬‬

‫تحتوي كل وحدة معالجة مركزية عىل مسجالتها الخاصة‪ ،‬ولكن يجب عليها مشاركة منطق المعالج األساسي‬

‫والذاكرة المخبئية وحيز النطاق التراسلي لإلدخال واإلخراج من وحدة المعالجة المركزي‪t‬ة إىل ال‪t‬ذاكرة‪ ،‬ل‪t‬ذا يمكن أن‬
‫يحافظ مجريان من التعليمات عىل المنطق األساسي للمع‪tt‬الج أك‪tt‬ثر انش‪tt‬غااًل ‪ ،‬ولكن لن تك‪tt‬ون زي‪tt‬ادة األداء كب‪tt‬يرًة‬

‫بسبب وجود وحدَتي ‪ CPU‬منفصلتين فيزيائًي ا‪ ،‬ويكون تحسين األداء أقل من ‪ ،%20‬ولكن يمكن أن يكون أفضل‬

‫أو أسوأ كثيًرا اعتماًدا عىل الِح مل‪.‬‬

‫ج‪ .‬األنوية المتعددة ‪Multi Core‬‬

‫أصبح وضع معاِلَجين أو أك‪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‬ده‪ ،‬إذ ُيَع ّد‬

‫‪ SETI@Home‬عنقوًدا مخَّص ًص ا وكبيًرا جًدا‪.‬‬

‫يوجد تطبيق آخر هو تطبيق تصيير الصور ‪ Rendering of Images‬الذي ُيستخَدم خاصًة للتأثيرات الخاصة‬

‫في األفالم‪ ،‬إذ ُيسَّلم كل حاسوب إطاًرا واحًدا من الفيلم يحتوي عىل نم‪tt‬اذج إط‪tt‬ارات ش‪tt‬بكية وخام‪tt‬ات ‪Textures‬‬

‫ومصادر إضاءة يجب دمجها أو تصييرها في التأثيرات الخاصة المذهلة ال‪t‬تي نحص‪tt‬ل عليه‪t‬ا‪ ،‬كم‪t‬ا ُيَع ّد ك‪tt‬ل إط‪tt‬ار‬

‫‪88‬‬
‫علوم الحاسوب من األلف إىل الياء‬ ‫معمارية الحاسوب‬

‫ساكًنا‪ ،‬لذلك ال يحتاج الحاسوب بمجرد حصوله عىل الدخل األولي لمزيد من االتصال حتى يصبح اإلط‪tt‬ار النه‪tt‬ائي‬

‫جاهًزا إلرساله ودمجه في الحركة‪ ،‬فقد كان لفيلم سيد الخواتم مثاًل تأثيرات خاصة مصَّيرة عىل عنقود ضخم يعمل‬

‫بنظام لينكس‪.‬‬

‫‪ 3.4.3‬الوصول غري الموحد للذاكرة ‪Non-Uniform Memory Access‬‬


‫ُيَع ّد الوصول غير الموح‪tt‬د لل‪tt‬ذاكرة ‪- Non-Uniform Memory Access‬أو ‪ NUMA‬اختص‪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‬رًة ‪،‬‬

‫ويستغرق ذلك دائًم ا وقًتا ثابًتا أو موّحًدا‪.‬‬

‫ا‪ .‬تخطيط نظام ‪NUMA‬‬

‫ُيَع ّد تقليل المسافة بين العقد أمًرا بالغ األهمية مع وج‪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‬‬
‫علوم الحاسوب من األلف إىل الياء‬ ‫معمارية الحاسوب‬

‫شكل ‪ :13‬المكعب الفائق ‪Hypercube‬‬

‫يوضح الشكل السابق مثااًل عن المكعب الفائق ‪ Hypercube‬الذي ي‪tt‬وفر مقايض ‪ًt‬ة جي‪tt‬دًة بين المس‪tt‬افة بين‬

‫العقد وعدد الوصالت المطلوب‪ .‬يمكننا أن نرى في هذا الشكل أن المكعب الخارجي يحتوي عىل ‪ 8‬عق‪tt‬د‪ ،‬والح‪tt‬د‬

‫األقصى لعدد المسارات المطلوبة ألي عقدة للتواصل مع عقدة أخرى هو ‪ ،3‬ف‪t‬إذا وض‪t‬عنا مكعًب ا آخ‪t‬ر داخ‪t‬ل ه‪t‬ذا‬

‫المكعب‪ ،‬فسيكون لدينا ضعف عدد المعالجات ولكن زادت التكلفة القصوى للمسار إىل ‪ ،4‬مما يعني نمو تكلفة‬

‫المسار القصوى خطًي ا فقط عند نمو عدد المعالجات بمقدار ‪.2n‬‬

‫ب‪ .‬ترابط الذاكرة المخبئية ‪Cache Coherency‬‬

‫ال يزال الحفاظ عىل ترابط الذاكرة المخبئية في نظام ‪ NUMA‬ممكًنا‪ ،‬إذ يش‪tt‬ار إىل ذل‪tt‬ك باس‪tt‬م نظ‪tt‬ام ‪NUMA‬‬

‫مع ترابط الذاكرة المخبئية ‪ Cache Coherent NUMA System‬أو ‪ ccNUMA‬اختصاًرا‪ ،‬وال يتوّس ع المخط‪tt‬ط‬

‫القائم عىل البث اإلذاعي الُم ستخَدم للحفاظ عىل ترابط ذاكرة المعالج المخبئية في نظام ‪ SMP‬إىل مئات أو ح‪tt‬تى‬

‫آالف المعالجات في نظام ‪ NUMA‬كبير‪.‬‬

‫يشار إىل أحد المخططات الشائعة لترابط ال‪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‬‬

‫ُتَع ّد أنظمة ‪ 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‬ل‬

‫مسارات الشيفرة البرمجية المّتَبعة والعواقب التي تسببها الشيفرة البرمجية للنظام الستخراج أفضل أداء‪.‬‬

‫‪ 3.4.4‬ترتيب الذاكرة وقفلها‬


‫تجلب الذاكرة المخبئية متعددة المستويات والمعمارية متعددة المعالجات الفائقة بعض المشاكل المتعلقة‬

‫بكيفية رؤية المبرمج لشيفرة المعالج البرمجية التي تكون قيد التشغيل‪.‬‬

‫‪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‬‬

‫ال نهتم بترتيب التعليمتين التاليتين *‪/‬‬


‫‪ */‬اللتين سُت نَّف ذان في النهاية *‬
‫;‪p->a = 100‬‬
‫;‪p->b = 150‬‬

‫لكن يجب أن ُتنَّف ذا قبل التعليمة التالية‪/* .‬‬


‫وإاّل فسيتمكن معالج آخر ينظر إلى قيمة ‪* p‬‬
‫من أن يجدها تؤّش ر إلى بنية قيمها غير مملوءة‪* .‬‬
‫‪*/‬‬

‫‪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‬ة‬

‫تحميل أو تخزين سابقة مكتملة عند معالجة التحميل أو التخزين باستخدام تحرير الدالالت‪.‬‬

‫شكل ‪ :14‬اكتساب وإطالق الدالالت‬

‫‪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‬دور نظام التشغيل وتنظيمه في معمارية الحاسوب‬


‫يدعم نظام التشغيل العملية الكاملة للحواسيب الحديثة‪ ،‬فهو عنصر أساس‪tt‬ي في معماري‪tt‬ة الحواس‪tt‬يب‪ ،‬ل‪tt‬ذا‬

‫سنتعّرف في هذا المقال عىل دوره وكيفية تنظيمه‪.‬‬

‫‪ 4.1.1‬تجريد العتاد‬
‫تتمثل العملية األساسية لنظام التشغيل ‪- Operating System‬أو ‪ OS‬اختصاًرا‪ -‬في تجري‪tt‬د ‪Abstraction‬‬

‫العتاد للمبرمج والمستخِدم‪ ،‬إذ يوّف ر نظام التشغيل واجهات عامة للخدمات التي يق‪tt‬دمها العت‪tt‬اد األساس‪tt‬ي‪ ،‬كم‪tt‬ا‬

‫يجب عىل المبرمجين معرفة تفاصيل العتاد األساس‪tt‬ي األك‪tt‬ثر خصوص‪tt‬يًة لتش‪tt‬غيل أّي ش‪tt‬يء في ع‪tt‬الم خ‪tt‬ال من‬

‫أنظمة التشغيل ‪ ،‬إذ لن تعمل برامجهم عىل عتاد آخر حتى عند وجود اختالفات طفيفة في هذا العتاد‪.‬‬

‫‪ 4.1.2‬تعدد المهام ‪Multitasking‬‬


‫نتوقع من الحواسيب الحديثة تنفي‪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‬‬

‫الشهيرة بسبب التنافس عىل هذه الموارد‪.‬‬


‫علوم الحاسوب من األلف إىل الياء‬ ‫نظام التشغيل‬

‫‪ 4.1.3‬الواجهات الموحدة ‪Standardised Interfaces‬‬


‫يرغب المبرمجون في كتابة برامج تعمل عىل أكبر عدد ممكن من المنصات العتادية‪ ،‬ويمكن ذل‪tt‬ك من خالل‬

‫دعم نظام التشغيل للواجهات الموَّحدة المعياري‪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‬داراتها‬

‫‪ IEEE 1003.1-2001‬وهو مكافئ لإلصدار الثالث من مواصفات يونيكس الواحدة‪.‬‬

‫ُدِم ج هذان المعياران المنفصالن فيما ُيعرف باسم اإلصدار الثالث من مواصفات يونيكس الواحدة‪ ،‬ووّحدت‪tt‬ه‬

‫منظمة ‪ ISO‬باالسم ‪ ISO/IEC 9945:2002‬في بداية عام ‪ ،2002‬لذا عندما يتحدث الناس عن معي‪tt‬ار ‪ POSIX‬أو‬

‫‪ SUS3‬أو ‪ ،ISO/IEC 9945:2002‬فإنهم يعنون الشيء نفسه‪.‬‬

‫‪ 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‬ذا يجب‬

‫عىل مبرمجي األنظمة فهم كل هذه المقايضات عند بناء نظام التشغيل‪.‬‬

‫‪ 4.2‬تنظيم نظام التشغيل‬


‫ُيَع ّد نظام التشغيل منظًم ا تقريًبا كما في الصورة التالية‪:‬‬

‫شكل ‪ :15‬نظام التشغيل‬

‫تنظيم النواة ‪ُ :Kernel‬تشَّغ ل عمليات الن‪t‬واة مباش‪t‬رًة في مج‪t‬ال المس‪t‬تخِدم ‪ ،Userspace‬وتتواص‪t‬ل الن‪t‬واة‬

‫مباشرًة مع العتاد ‪ Hardware‬وعبر المشّغ الت ‪.Drivers‬‬

‫‪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‬تواها إىل‬

‫مستوى صالحيات النواة‪ ،‬وبالتالي يمكنها الوصول إىل أّي جزء من النظام‪.‬‬

‫‪ 4.2.2‬النواة األحادية ‪ Monolithic‬والنواة الدقيقة ‪Microkernel‬‬


‫أحد األمور الجدلية التي ُتطَر ح غالًبا حول أنظمة التشغيل هو ما إذا كانت النواة أحادي‪tt‬ة ‪ Monolithic‬أو ن‪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‬انخفاض األداء‪ ،‬إذ يمكن أن يؤدي التواصل بين العديد من المكونات المختلفة إىل تقليل األداء‪.‬‬

‫‪ُ .2‬يَع ّد تطبيقها أصعب قلياًل عىل المبرمجين‪.‬‬

‫‪100‬‬
‫علوم الحاسوب من األلف إىل الياء‬ ‫نظام التشغيل‬

‫تأتي هذه المشاكل بسبب تطبيق معظم األنوية الدقيقة باستخدام نظام قائم عىل تمرير الرسائل ‪Message‬‬

‫‪ Passing‬بهدف الحفاظ عىل الفصل بين المكونات‪ ،‬ويشار إىل هذا النظام ع‪tt‬ادًة باس‪tt‬م التواص‪tt‬ل بين العملي‪tt‬ات‬

‫‪ Inter-process Communication‬أو ‪ IPC‬اختصاًرا‪.‬‬

‫يحدث التواصل بين المكونات باستخدام رسائل منفصلة يجب تجميعها ض‪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‬ير‬

‫األكاديمية حول ذلك‪ ،‬لذا نأمل أن تكون قادًرا عىل اتخاذ قرار بنفسك عندما تتعلم المزيد عن أنظمة التشغيل‪.‬‬

‫ا‪ .‬الوحدات ‪Modules‬‬

‫تطّبق نواة لينكس نظ‪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‬‬
‫علوم الحاسوب من األلف إىل الياء‬ ‫نظام التشغيل‬

‫شكل ‪ :16‬بعض طرق تطبيق االفتراضية المختلفة‬

‫يمكن تنظيم االفتراضية بعدة ط‪tt‬رق مختلف‪tt‬ة‪ ،‬إذ يمكن تش‪tt‬غيل م‪tt‬راقب آل‪tt‬ة افتراض‪tt‬ية ‪Virtual Machine‬‬

‫‪ Monitor‬صغير مباشرًة عىل العتاد وتوفير واجهة ألنظمة تشغيل المضيف التي تعم‪tt‬ل في األعىل‪ ،‬وُيطَل ق عىل‬

‫مراقب اآللة االفتراضية ‪ VMM‬اسم المشرف ‪ Hypervisor‬من الكلمة ‪.Supervisor‬‬

‫يشترك المشرف في كثير األمور مع النواة الدقيقة‪ ،‬كما يسعيان ليكّو نا طبقات صغيرًة لتقديم العتاد بطريقة‬

‫آمنة عن الطبقات التي تعلوها‪ ،‬ويمكن أاّل يكون لدى نظام التشغيل الموجود في الطبقة العليا أّي فكرة عن وج‪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‬ابه‬

‫لكيفية عمل برنامج ‪.VMWare‬‬

‫تتطلب هذه الطريقة تكلفًة أكبر‪ ،‬إذ يتعين عىل عملية التطبيق محاكاة نظ‪tt‬ام بأكمل‪tt‬ه وتحوي‪tt‬ل ك‪tt‬ل ش‪tt‬يء إىل‬

‫طلبات من نظام التشغيل األساسي‪ ،‬ولكنها تتيح محاكاًة معماريًة مختلفًة تماًم ا‪ ،‬إذ يمكنك ترجمة التعليمات آلًي ا‬

‫من نوع معالج إىل آخر كما يفعل نظام روزيتا ‪ Rosetta‬مع برمجيات ‪ Apple‬التي انتقلت من مع‪tt‬الج ‪PowerPC‬‬

‫إىل المعالجات القائمة عىل إنتل ‪.Intel‬‬

‫ُيَع ّد األداء مصدر قلق كبير عند استخدام أّي من تقنيات الوهمية‪ ،‬إذ يجب أن تمر العمليات ‪-‬التي كانت ُتَع ّد‬

‫سابًق ا عمليات سريعًة ومباشرًة عىل العتاد‪ -‬عبر طبقات التجريد‪.‬‬

‫ناقشت شركة إنتل ‪ 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‬ة ‪،‬‬

‫ولكن يمكنه االنتظار حتى ُيعِلم المعالج بحدوث شيء غير آمن‪.‬‬

‫ا‪ .‬القنوات الرسية ‪Covert Channels‬‬

‫إذا لم يكن تقسيم النظام ساكًنا وإنما آلًي ا‪ ،‬فهناك مشكلة أمنية محتملة متضمنة في النظام وُيَع ّد ه‪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‬ط من‬

‫األشياء‪ ،‬ويجب أاّل تكون قادرًة عىل تعطيل البرامج األخرى حتى إذا تعطلت هي نفسها‪.‬‬

‫‪ 4.3‬استدعاءات النظام ‪System Calls‬‬


‫استدعاءات النظام ‪ system calls‬هي كيفية تفاعل برامج مجال المستخِدم ‪ Userspace‬م‪tt‬ع ن‪tt‬واة النظ‪tt‬ام‬

‫‪ ،Kernel‬إذ سنشرح فيما يلي المبدأ العام لكيفية عمل هذه االستدعاءات‪ ،‬وسنتعّرف عىل الصالحيات في نظ‪tt‬ام‬

‫التشغيل للوصول إىل الموارد‪.‬‬

‫‪ 4.3.1‬أرقام استدعاءات النظام‬


‫لكل استدعاء نظام رقم يعرفه مج‪tt‬ال المس‪t‬تخِدم والن‪t‬واة‪ ،‬إذ يع‪ِt‬رف كالهم‪t‬ا أّن رقم اس‪tt‬تدعاء النظ‪tt‬ام ‪ 10‬ه‪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‬‬

‫‪ 4.3.5‬تحليل استدعاء النظام‬


‫بما أّن مكتب‪t‬ات النظ‪t‬ام تجع‪t‬ل األنظم‪t‬ة تس‪t‬تدعي نياب‪t‬ة عن‪t‬ك‪ ،‬فيجب تط‪t‬بيق اخ‪t‬تراق منخفض المس‪t‬توى‬

‫لتوضيح كيفية عمل استدعاءات النظام‪ ،‬وسنوضح كيفية عمل أبسط استدعاء نظام )(‪ getpid‬الذي ال يأخذ أّي‬
‫وسيط ويعيد معّرف البرنامج أو العملية التي تكون قيد التشغيل حالًي ا‪.‬‬

‫>‪#include <stdio.h‬‬

‫‪ */‬خاصة باستدعاء النظام )(‪/* syscall‬‬


‫>‪#include <sys/syscall.h‬‬
‫>‪#include <unistd.h‬‬

‫‪105‬‬
‫علوم الحاسوب من األلف إىل الياء‬ ‫نظام التشغيل‬

‫‪ */‬أرقام استدعاءات النظام *‪/‬‬


‫>‪#include <asm/unistd.h‬‬

‫)‪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‬تجري‬

‫استدعاء النظام ‪ getpid‬ويخّزن القيمة في المعِّرف ‪.pid‬‬

‫سنلقي نظرًة عىل كيفي‪tt‬ة تط‪tt‬بيق العدي‪tt‬د من المعماري‪tt‬ات له‪tt‬ذه الش‪tt‬يفرة البرمجي‪tt‬ة وس‪tt‬نّط لع عىل الش‪tt‬يفرة‬

‫البرمجية الحقيقية التي يمكن أن تكون خطيرًة ولكن يجب االلتزام بها‪ ،‬فه‪tt‬ذه هي بالض‪tt‬بط الطريق‪tt‬ة ال‪tt‬تي يعم‪tt‬ل‬

‫بها نظامك‪.‬‬

‫ا‪ .‬معمارية ‪PowerPC‬‬

‫ُيَع ّد نظام ‪ PowerPC‬معماريَة ‪ RISC‬شائعة في حواسيب ‪ Apple‬القديمة‪ ،‬وهو جوهر أجه‪tt‬زة أح‪tt‬دث إص‪tt‬دار‬

‫من ‪ Xbox‬مثاًل ‪ ،‬وفيما يلي مثال عن استدعاء نظام ‪:PowerPC‬‬

‫يتِلف استدعاُء النظام المسّج الِت نفسها الستدعاء الدالة في نظام ‪/* ،powerpc‬‬
‫باستثناء المسّج ل ‪ LR‬الذي يحتاجه التسلسل "‪* "sc; bnslr‬‬
‫والمسّج ل ‪ CR‬حيث ُيتَلف المسجل ‪ CR0.SO‬فقط الذي يشير إلى *‬

‫‪106‬‬
‫علوم الحاسوب من األلف إىل الياء‬ ‫نظام التشغيل‬

* .‫حالة إعادة خطأ‬


*/

#define __syscall_nr(nr, type, name, args...)


unsigned long __sc_ret, __sc_err;
{
register unsigned long __sc_0 __asm__ ("r0");
register unsigned long __sc_3 __asm__ ("r3");
register unsigned long __sc_4 __asm__ ("r4");
register unsigned long __sc_5 __asm__ ("r5");
register unsigned long __sc_6 __asm__ ("r6");
register unsigned long __sc_7 __asm__ ("r7");
__sc_loadargs_##nr(name, args);
__asm__ __volatile__
("sc \n\t"
"mfcr %0 "
: "=&r" (__sc_0),
"=&r" (__sc_3), "=&r" (__sc_4),
"=&r" (__sc_5), "=&r" (__sc_6),
"=&r" (__sc_7)
: __sc_asm_input_##nr
: "cr0", "ctr", "memory",
"r8", "r9", "r10","r11", "r12");
__sc_ret = __sc_3;
__sc_err = __sc_0;
}
if (__sc_err & 0x10000000)
{
errno = __sc_ret;
__sc_ret = -1;
}
return (type) __sc_ret

#define __sc_loadargs_0(name, dummy...)


__sc_0 = __NR_##name
#define __sc_loadargs_1(name, arg1)

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 __sc_asm_input_0 "0" (__sc_0)


#define __sc_asm_input_1 __sc_asm_input_0, "1" (__sc_3)
#define __sc_asm_input_2 __sc_asm_input_1, "2" (__sc_4)
#define __sc_asm_input_3 __sc_asm_input_2, "3" (__sc_5)
#define __sc_asm_input_4 __sc_asm_input_3, "4" (__sc_6)
#define __sc_asm_input_5 __sc_asm_input_4, "5" (__sc_7)

#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‬ام‪ ،‬إذ‬

‫يكون مع االستدعاء ‪ getpid‬بالصورة )‪._syscall0(int,getpid‬‬

‫سنبدأ اآلن بتفكيك الماكرو ‪ __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‬وسنوّس عه‬

‫‪ ،__sc_loadargs_0(name,‬ويمكننا رؤية ‪ __sc_loadargs‬الذي يضبط ‪ __sc_0‬ليك‪tt‬ون‬ ‫إىل ;)‪args‬‬

‫رقم استدعاء النظام‪ ،‬والحظ معاِم ل اللصق مرًة أخرى مع البادئة _‪ __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‬ابيب‬

‫‪ Pipeline‬في مكان ما‪.‬‬

‫انتهينا تقريًب ا‪ ،‬ولكن الشيء الوحيد المتبقي هو إعادة القيمة من استدعاء النظام‪ ،‬إذ نرى ضبط القيمة ‪__sc‬‬

‫‪ _ret‬من المس‪tt‬جل ‪ r3‬وض‪tt‬بط القيم‪tt‬ة ‪ __sc_err‬من المس‪tt‬جل ‪ ،r0‬فالقيم‪tt‬ة األوىل هي القيم‪tt‬ة الُم ع‪tt‬ادة‪،‬‬

‫واألخرى هي قيمة الخطأ‪ ،‬إذ يمكن أن تفشل استدعاءات النظام مثل أّي دالة أخ‪tt‬رى‪ ،‬لكن تكمن المش‪tt‬كلة في أّن‬

‫استدعاء النظام يمكن أن يعيد أّي قيمة ممكنة‪ ،‬إذ ال يمكننا أن نقول أن القيمة الس‪tt‬البة تش‪tt‬ير إىل الفش‪tt‬ل‪ ،‬ألنه‪tt‬ا‬

‫‪110‬‬
‫علوم الحاسوب من األلف إىل الياء‬ ‫نظام التشغيل‬

‫يمكن أن تكون مقبولًة لبعض استدعاءات النظام‪ ،‬لذا تضمن دالة استدعاء النظ‪tt‬ام أّن نتيجته‪tt‬ا في المس‪tt‬جل ‪r3‬‬

‫وأّن أّي رمز خطأ موجود في المسجل ‪ r0‬قبل إعادة النتيجة‪.‬‬

‫يجب التحقق من رمز الخطأ للتأكد من ضبط الِبّت العلوي الذي من شأنه اإلشارة إىل عدد سالب‪ ،‬ف‪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‬ل‬

‫عىل نظام ‪.PowerPC‬‬

‫ب‪ .‬استدعاءات نظام ‪x86‬‬

‫إليك الواجهة المطَّبقة لمعالج ‪:x86‬‬

‫توجد أرقام األخطاء المرئية للمستخِدم ضمن المجال من ‪ -1‬إلى ‪/* 124-‬‬
‫*‬ ‫راجع >‪<asm-i386/errno.h‬‬
‫‪*/‬‬

‫)‪#define __syscall_return(type, res‬‬


‫{ ‪do‬‬
‫{ ))‪if ((unsigned long)(res) >= (unsigned long)(-125‬‬
‫;)‪errno = -(res‬‬
‫;‪res = -1‬‬
‫}‬
‫;)‪return (type) (res‬‬
‫)‪} while (0‬‬

‫‪ _foo */‬يجب أن تكون ‪ ،__foo‬بينما ‪ __NR_bar‬يمكن أن تكون ‪/* _NR_bar‬‬


‫)‪#define _syscall0(type,name‬‬
‫)‪type name(void‬‬
‫{‬
‫;‪long __res‬‬
‫"‪__asm__ volatile ("int $0x80‬‬
‫)‪: "=a" (__res‬‬
‫;))‪: "0" (__NR_##name‬‬
‫;)‪__syscall_return(type,__res‬‬
‫}‬

‫‪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
‫علوم الحاسوب من األلف إىل الياء‬ ‫نظام التشغيل‬

__asm__ volatile ("int $0x80"


: "=a" (__res)
: "0" (__NR_##name),"b" ((long)(arg1)),"c" ((long)(arg2)),
"d" ((long)(arg3)),"S" ((long)(arg4)));
__syscall_return(type,__res);
}

#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 ‫تختلف معمارية‬

.‫ ولديه مسجالت أقل بكثير‬،RISC ‫ الذي ُيَع دذ من النوع‬PowerPC ‫ عىل عكس‬CISC

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‬ائط‬

‫استدعاء __‪ __asm‬عندما نبدأ بتحميل المسجالت في وحدات الماكرو األخرى‪.‬‬

‫الحظ الماكرو ‪ __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‬‬
‫علوم الحاسوب من األلف إىل الياء‬ ‫نظام التشغيل‬

‫‪ 4.4‬الصالحيات في نظام التشغيل‬


‫ُيَع ّد تطبيق األمان أحد مهام نظام التشغيل الرئيسية بهدف عدم السماح لتطبيق أو مستخِدم بالتضارب م‪tt‬ع‬

‫أّي تطبيق آخر يعمل في النظام‪ ،‬وهذا يعني أن التطبيقات يجب أاّل تكون قادرًة عىل الكتابة في ذاك‪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‬مستويات الصالحيات‬
‫ُتَع ّد حماية العتاد مجموعًة من الحلقات متحدة المركز حول مجموعة أساسية من العمليات‪.‬‬

‫شكل ‪ :17‬مستويات الصالحيات في معمارية ‪x86‬‬

‫توجد التعليمات ذات الحماية األكبر في الحلقة الداخلية‪ ،‬وهي التعليم‪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‬‬

‫يحتوي نموذج الحماية ‪ 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‬بًتا متوفًرا في‬

‫المسجل لعنوان ما كما هو موضح في الشكل التالي‪:‬‬

‫شكل ‪ :18‬تقطيع العنونة ‪ Segmentation Addressing‬في معمارية ‪x86‬‬

‫يوض‪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‬ذه‬

‫المقاطع المختلفة وأحجامها‪ ،‬وهو ما يفعله باستخدام الواصفات ‪.Descriptors‬‬

‫تكون واصفات المقاطع المتاحة للجميع محفوظًة في جدول الواصفات العام ‪Global Descriptor Table‬‬

‫أو ‪ GDT‬اختصاًرا‪ ،‬كما تحتوي كل عملية عىل عدد من المسجالت التي توّش ر إىل مدخالت في جدول ‪ ،GDT‬وهذه‬

‫المدخالت هي المقاطع التي يمكن للعملية الوصول إليها‪ ،‬كما توجد جداول واصفات محلي‪tt‬ة‪ ،‬وتتفاع‪tt‬ل جميعه‪tt‬ا‬

‫مع مقاطع حالة المهمات‪ ،‬لكنها ليست مهمًة حالًي ا‪.‬‬

‫‪117‬‬
‫علوم الحاسوب من األلف إىل الياء‬ ‫نظام التشغيل‬

‫شكل ‪ :19‬مقاطع ‪x86‬‬

‫الحظ كيف يمر االستدعاء البعيد عبر بواب‪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‬ع‬

‫‪ Segmentation Fault‬يعرفه معظم المبرمجين‪.‬‬

‫إذا احتاج تشغيل الشيفرة البرمجية إىل إج‪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‬عنارص العملية‬

‫شكل ‪ :20‬عناصر العملية‬


‫علوم الحاسوب من األلف إىل الياء‬ ‫العمليات في نظام تشغيل الحاسوب‬

‫‪ 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‬ان في‬

‫العديد منها‪.‬‬

‫ب‪ .‬المكدس ‪Stack‬‬

‫ُيَع ّد المكدس جزًءا مهًم ا آخ‪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‬الي من‬

‫المكدس‪ ،‬وإليك مثال عن مؤشر المكدس‪:‬‬

‫‪$ cat sp.c‬‬


‫)‪void function(void‬‬
‫{‬
‫;‪int i = 100‬‬
‫;‪int j = 200‬‬
‫;‪int k = 300‬‬
‫}‬

‫‪$ gcc -fomit-frame-pointer -S sp.c‬‬

‫‪123‬‬
‫علوم الحاسوب من األلف إىل الياء‬ ‫العمليات في نظام تشغيل الحاسوب‬

‫‪$ cat sp.s‬‬


‫‪.file‬‬ ‫"‪"sp.c‬‬
‫‪.text‬‬
‫‪.globl function‬‬
‫‪.type‬‬ ‫‪function, @function‬‬
‫‪function:‬‬
‫‪subl‬‬ ‫‪$16, %esp‬‬
‫‪movl‬‬ ‫)‪$100, 4(%esp‬‬
‫‪movl‬‬ ‫)‪$200, 8(%esp‬‬
‫‪movl‬‬ ‫)‪$300, 12(%esp‬‬
‫‪addl‬‬ ‫‪$16, %esp‬‬
‫‪ret‬‬
‫‪.size‬‬ ‫‪function, .-function‬‬
‫‪.ident‬‬ ‫")‪"GCC: (GNU) 4.0.2 20050806 (prerelease) (Debian 4.0.1-4‬‬
‫‪.section‬‬ ‫‪.note.GNU-stack,"",@progbits‬‬

‫عرضنا في المثال السابق دالًة بسيطًة تخّص ص ثالثة متغيرات عىل المكدس‪ ،‬إذ توّض ح شيفرة فك التجمي‪tt‬ع‬

‫السابقة استخدام مؤشر المكدس في معمارية ‪.x86‬‬

‫أواًل ‪ ،‬نخّص ص مساحًة عىل المكدس لمتغيراتنا المحلية‪ ،‬كما تنمو المكدسات لألسفل‪ ،‬لذلك يجب أن نط‪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‬‬

‫الكومة ‪ Heap‬هي مساحة من الذاكرة تديرها العملية لتخصيص الذاكرة الس‪tt‬ريع‪ ،‬وُتس‪tt‬تخَدم م‪tt‬ع المتغ‪tt‬يرات‬

‫التي ال تكون متطلبات ذاكرتها معروفًة في وقت التصريف‪ ،‬وُيعَرف الجزء السفلي من الكوم‪t‬ة باالس‪tt‬م ‪ brk‬وه‪tt‬و‬

‫اس‪tt‬تدعاء النظ‪tt‬ام ال‪tt‬ذي يع‪ّtt‬دل الكوم‪tt‬ة‪ ،‬كم‪tt‬ا يمكن للعملي‪tt‬ة أن تطلب من الن‪tt‬واة تخص‪tt‬يص مزي‪tt‬د من ال‪tt‬ذاكرة‬

‫الستخدامها باستخدام االستدعاء ‪ brk‬لتوسيع المنطقة إىل األسفل‪.‬‬

‫يدير استدعاء مكتبة ‪ 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‬‬

‫مباشرة نظًرا لتعقيد إدارة الذاكرة بصورة صحيحة‪.‬‬

‫د‪ .‬تخطيط الذاكرة‬

‫شكل ‪ :22‬تخطيط ذاكرة العملية‬

‫تمتلك العملية مناطق أصغر من الذاكرة المخَّص صة لها ويكون لكل منها غرض محدد‪.‬‬

‫ذكرنا في الشكل السابق كيفية وضع العملية في الذاكرة باستخدام الن‪tt‬واة‪ ،‬إذ تحتف‪tt‬ظ الن‪tt‬واة لنفس‪tt‬ها ببعض‬

‫الذاكرة في الجزء العلوي من العملية‪ ،‬ويمكن مشاركة هذه الذاكرة فعلًي ا بين جمي‪t‬ع العملي‪t‬ات باس‪t‬تخدام ال‪t‬ذاكرة‬

‫الوهمية‪ ،‬كما يوجد أسفل ذلك مساحة للملفات والمكتبات المربوطة ‪ ،mmaped‬ثم يوجد المكدس وتحته الكومة‪،‬‬

‫‪125‬‬
‫علوم الحاسوب من األلف إىل الياء‬ ‫العمليات في نظام تشغيل الحاسوب‬

‫في حين توجد في الجزء الس‪tt‬فلي ص‪tt‬ورة البرن‪tt‬امج كم‪tt‬ا ج‪tt‬رى تحميله‪tt‬ا من المل‪tt‬ف القاب‪tt‬ل للتنفي‪tt‬ذ عىل الق‪tt‬رص‬

‫الصلب‪ ،‬وسنلقي نظرًة عىل عملية تحميل هذه البيانات في مقاالت الحقة‪.‬‬

‫‪ 5.2.3‬واصفات الملف ‪File Descriptors‬‬


‫تعّرفنا سابًق ا عىل الملفات االفتراضية المعط‪tt‬اة لك‪tt‬ل عملي‪tt‬ة وهي ‪ stdin‬و ‪ stdout‬و ‪ ،stderr‬إذ يك‪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‬‬
‫علوم الحاسوب من األلف إىل الياء‬ ‫العمليات في نظام تشغيل الحاسوب‬

‫ب‪ .‬األولوية ‪Priority‬‬

‫ُتَع ّد بعض العمليات أكثر أهميًة من غيرها وتحظى بأولوية أعىل‪.‬‬

‫ج‪ .‬اإلحصائيات‬

‫يمكن للنواة االحتفاظ بإحصائيات حول سلوك كل عملية والتي يمكن أن تس‪tt‬اعدها في اتخ‪tt‬اذ ق‪tt‬رارات ح‪tt‬ول‬

‫كيفية تصرف العملية مثل معرفة ما إذا كانت العملية تقرأ من الق‪tt‬رص الص‪tt‬لب في أغلب األحي‪tt‬ان أم أنه‪tt‬ا تنّف ذ‬

‫عمليات مكثفًة في وحدة المعالجة المركزية بمعّدل أعىل‪.‬‬

‫‪ 5.3‬تسلسل العمليات الهرمي‬


‫يمكن لنظام التشغيل تشغيل العديد من العمليات في ال‪tt‬وقت نفس‪tt‬ه‪ ،‬إاّل أن‪tt‬ه يب‪tt‬دأ بتش‪tt‬غيل عملي‪tt‬ة واح‪tt‬دة‬

‫مباشرًة ُتدَع ى بالعملية األولية ‪- init‬اختصاًرا للكلمة ‪ -Initial‬التي ال ُتَع ّد عمليًة خاصًة باستثناء أًن معِّرف العملي‪tt‬ة‬

‫‪ PID‬الخاص بها هو ‪ 0‬دائًم ا وستبقى ُم شَّغ لًة دائًم ا‪.‬‬

‫ُتَع ّد جميع العمليات األخرى أبناًء ‪ Children‬لهذه العملية األولية‪ ،‬فللعملي‪tt‬ات ش‪tt‬جرة عائل‪tt‬ة مث‪tt‬ل أّي ش‪tt‬جرة‬

‫أخرى‪ ،‬إذ يكون لكل عملية أًب ا ‪ Parent‬ويمكن أن يك‪tt‬ون له‪tt‬ا العدي‪tt‬د من األش‪tt‬قاء ‪ Siblings‬ال‪tt‬تي ُتَع ّد عملي‪tt‬ات‬

‫أنشأها األب نفسه‪.‬‬

‫ُيستخَدم المصطلح "توّلد ‪ "Spawn‬عند الحديث عن العمليات اآلباء التي تنشئ العمليات األبناء مثل القول‬

‫بأن "عملية وّلدت ابًنا"‪ ،‬كما يمكن أن تنشئ العمليات األبناء مزيًدا من األبناء وهكذا‪ ،‬وإليك مثال عن تنفيذ األم‪t‬ر‬

‫‪ pstree‬الذي يعرض العمليات الُم شَّغ لة مثل شجرة‪:‬‬

‫‪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‬استدعاءات النظام ‪ Fork‬و ‪Exec‬‬

‫‪ 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‬ة‬

‫‪ forked‬مع وجود عالقة أب‪-‬ابن‪.‬‬

‫‪ 5.4.2‬استدعاءات ‪Exec‬‬
‫يوّف ر التفريع ‪ Forking‬طريقًة للعملية الحالية بأن تبدأ عملية جديدة‪ ،‬فإذا لم تكن العملي‪tt‬ة الجدي‪tt‬دة ج‪tt‬زًءا من‬

‫برنامج العملية األب كما هو الحال في الصَدفة ‪ ،Shell‬إذ يجب أن يشِّغ ل المستخِدم أمًرا في عملية جدي‪tt‬دة ليس‬

‫لها عالقة بالص‪َt‬دفة‪ ،‬فيجب تش‪t‬غيل اس‪t‬تدعاء النظ‪t‬ام ‪ exec‬ال‪t‬ذي س‪t‬يبّدل بمحتوي‪t‬ات العملي‪t‬ة الُم ش‪َّt‬غ لة حالًي ا‬

‫معلومات من برنامج ثنائي‪.‬‬

‫العملية التي تتبعها الصَدفة عند إطالق برنامج جديد هي ‪ fork‬أواًل ‪ ،‬مما يؤدي إىل إنشاء عملي‪tt‬ة جدي‪tt‬دة‪ ،‬ثم‬

‫تنفيذ االستدعاء ‪ ،exec‬أي تحميل البرنامج الثنائي الذي ُيفتَرض تشغيله في الذاكرة وتنفيذه‪.‬‬

‫‪ 5.4.3‬كيفية تعامل لينكس مع ‪ fork‬و ‪exec‬‬


‫سنشرح كيفية تعامل نظام التشغيل لينكس مع عملية النسخ ‪ fork‬وعملية االستدعاء ‪.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‬‬
‫علوم الحاسوب من األلف إىل الياء‬ ‫العمليات في نظام تشغيل الحاسوب‬

‫شكل ‪ :23‬الخيوط ‪Threads‬‬

‫ُيسَّم ى االبن الهجين السابق بالخيط‪ ،‬كما تحتوي الخي‪t‬وط عىل ع‪t‬دد من المزاي‪t‬ا بالموازن‪t‬ة م‪t‬ع المك‪t‬ان ال‪t‬ذي‬

‫ُيستخَدم فيه االستدعاء ‪ fork‬ومنها ما يلي‪:‬‬

‫ال يمكن للعمليات المنفصلة أن ترى ذاكرة بعضها بعًض ا‪ ،‬وإنما يمكنها التواص‪tt‬ل م‪tt‬ع بعض‪tt‬ها بعًض ا ع‪tt‬بر‬ ‫‪.1‬‬

‫استدعاءات النظام األخرى فقط‪ ،‬لكن مع ذلك تشترك الخيوط في الذاكرة نفسها‪ ،‬لذا سيكون لديك ميزة‬

‫العمليات المتعددة مع االضطرار إىل استخدام استدعاءات النظام للتواصل فيم‪tt‬ا بينه‪tt‬ا‪ ،‬وتكمن مش‪tt‬كلة‬

‫ذلك في إمكانية تداخل الخيوط بسهولة مع بعضها بعًض ا‪ ،‬إذ يمكن أن يزيد أحد الخيوط متغيًرا‪ ،‬في حين‬

‫يمكن أن ينقصه خيط آخر بدون إعالم الخيط األول‪ ،‬وتسمى هذه األنواع من المشاكل بمش‪tt‬اكل ال‪tt‬تزامن‬

‫وهي كثيرة ومتنوعة‪ ،‬ولكن يمكن حل هذه المشكلة باستخدام مكتب‪t‬ات مج‪t‬ال المس‪t‬تخِدم ال‪t‬تي تس‪t‬اعد‬

‫المبرمجين عىل العمل مع الخيوط بصورة صحيحة‪ ،‬وتسمى أكثر الخيوط ش‪tt‬يوًع ا بخي‪tt‬وط ‪ POSIX‬أو كم‪tt‬ا‬

‫يشار إليها ‪ pthreads‬بصورة شائعة‪.‬‬

‫ُيَع ّد التبديل بين العمليات مكلًف ا جًدا‪ ،‬ومن أكثر األم‪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‬ذا‬

‫سيضّي ع نسخ الذاكرة الكثير من الوقت‪ ،‬وبالتالي ستوّف ر عملية النسخ عند الكتابة علينا النسخ فعلًي ا‪.‬‬

‫‪ 5.4.4‬العملية األولية ‪Init Process‬‬


‫ناقشنا سابًق ا الهدف العام للعملية األولية وسنفهم اآلن كيفية عملها‪ ،‬إذ تبدأ النواُة العمليَة األولية ‪ init‬عند‬

‫بدء التشغيل وتفّر ع وتنّف ذ هذه العملية بعد ذلك س‪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‬مى‬

‫العملية في هذه الحالة شبه ميتة أو زومبي ‪.Zombie‬‬

‫تبقى العملية في حالة زوم‪t‬بي ح‪t‬تى تجم‪t‬ع العملي‪t‬ة األب الش‪t‬يفرة الُم ع‪t‬ادة من االس‪t‬تدعاء ‪ ،wait‬ولكن إذا‬

‫انتهت العملية األب قبل جمع الشيفرة الُم عادة‪ ،‬فستبقى عملية الزومبي موجودًة وتنتظر بال هدف إلعطاء حالته‪tt‬ا‬

‫‪130‬‬
‫علوم الحاسوب من األلف إىل الياء‬ ‫العمليات في نظام تشغيل الحاسوب‬

‫لعمليٍة ما‪ ،‬ثم سُتنَس ب العملية االبن التي تكون في حالة زومبي إىل العملية األولية ال‪tt‬تي تمتل‪tt‬ك معالًج ا خاًص ا‬

‫يحصد القيمة الُم عادة‪ ،‬وستكون العملية حرًة في النهاية ويمكن إزالة واصفها من جدول عمليات النواة‪.‬‬

‫ا‪ .‬مثال عملية شبه ميتة ‪Zombie‬‬

‫إليك مثال عن عملية شبه ميتة أو عملية زومبي ‪ Zombie‬كما يقال‪:‬‬

‫‪$ cat zombie.c‬‬


‫>‪#include <stdio.h‬‬
‫>‪#include <stdlib.h‬‬

‫)‪int main(void‬‬
‫{‬
‫;‪pid_t pid‬‬
‫;))(‪printf("parent : %d\n", getpid‬‬
‫;)(‪pid = fork‬‬

‫{ )‪if (pid == 0‬‬


‫;))(‪printf("child : %d\n", getpid‬‬
‫;)‪sleep(2‬‬
‫;)"‪printf("child exit\n‬‬
‫;)‪exit(1‬‬
‫}‬

‫‪ */‬في األب *‪/‬‬


‫)‪while (1‬‬
‫{‬
‫;)‪sleep(1‬‬
‫}‬
‫}‬

‫‪$ ps ax | grep [z]ombie‬‬


‫‪pts/9‬‬ ‫‪S‬‬ ‫‪0:00 ./zombie‬‬
‫‪pts/9‬‬ ‫‪Z‬‬ ‫>‪0:00 [zombie] <defunct‬‬

‫أنشأنا في المثال السابق عملية زومبي‪ ،‬إذ ستكون العملية األب في حالة سكون ‪ Sleep‬إىل األب‪tt‬د‪ ،‬في حين‬

‫ستنتهي العملية االبن بعد بضع ثوان‪ ،‬ويمكنك رؤية نتائج تشغيل البرنامج بعد الشيفرة البرمجية‪.‬‬

‫‪131‬‬
‫علوم الحاسوب من األلف إىل الياء‬ ‫العمليات في نظام تشغيل الحاسوب‬

‫تكون العملية األب (‪ )16168‬في الحالة ‪ S‬لإلشارة إىل أنها في حالة سكون وتكون العملية االبن في الحالة ‪Z‬‬

‫لإلشارة إىل أنها في حالة زومبي‪ ،‬في حين يخبرن‪t‬ا خ‪t‬رج األم‪t‬ر ‪ ps‬أن العملي‪t‬ة أص‪t‬بحت زوم‪t‬بي أو ‪ defunct‬في‬

‫وصف العملية‪.‬‬

‫األقواس المربعة حول الحرف "‪ "z‬في الكلمة "‪ "zombie‬هي خدعة صغيرة لتزيل عمليات األمر ‪ grep‬نفسها من‬

‫خرج األمر ‪ ،ps‬إذ يفّس ر األمر ‪ grep‬كل شيء بين األقواس المربعة عىل أنه صنف محرفي ‪،Character Class‬‬

‫أي يبحث عن تطابق واحد فقط بين المحارف الموجودة بين القوسين والنص‪ ،‬ولكن بما أن اسم العملية سيكون‬

‫"‪ "grep [z]ombie‬مع األقواس‪ ،‬فلن يكون هناك تطابق‪.‬‬

‫‪ 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‬ا‪ ،‬ولكن‬

‫هناك عدد من المكونات المختلفة للجدولة‪.‬‬

‫‪ 5.5.1‬الجدولة ذات األولوية ‪ Preemptive‬والجدولة التعاونية ‪Co-operative‬‬


‫يمكن أن تنقسم استراتيجيات الجدولة إىل فئتين‪:‬‬

‫الجدولة التعاونية ‪ :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‬ة‬

‫التالية التي سُتشَّغ ل‪ ،‬وهذا هو نوع الجدولة الذي تستخدمه جميع أنظمة التشغيل الحديثة‪.‬‬

‫‪ 5.5.2‬الوقت الفعيل ‪Realtime‬‬


‫تحتاج بعض العمليات إىل معرفة المدة التي ستستغرقها شريحتها الزمنية والمدة التي الُم س‪tt‬تغَرقة قب‪tt‬ل أن‬

‫تحصل عىل شريحة زمنية أخرى لتعمل‪ ،‬ولنفترض أنه ل‪tt‬ديك نظاًم ا يش‪ّt‬غ ل جه‪tt‬از القلب والرئ‪tt‬تين‪ ،‬إذ ال تري‪tt‬د أن‬

‫تتأخر النبضة التالية ألّن شيًئا آخر قّرر العمل في النظام‪.‬‬

‫تقّدم أنظمة الوقت الفعلي الصارمة ‪ Hard Realtime‬ضمانات حول جدولة الق‪tt‬رارات مث‪tt‬ل الح‪tt‬د األقص‪tt‬ى‬

‫لمقدار الوقت الذي سُتقاَط ع فيه العملية قبل تشغيلها مرًة أخرى‪ ،‬إذ ُتستخَدم غالًبا في التطبيقات الحرج‪tt‬ة مث‪tt‬ل‬

‫التطبيقات الطبية والعسكرية وتطبيقات الطائرات‪ ،‬في حين ال تكون الض‪tt‬مانات في أنظم‪tt‬ة ال‪tt‬وقت الفعلي غ‪tt‬ير‬

‫الصارمة ‪ Soft Realtime‬صارمًة ولكن يمكن التنبؤ بسلوك النظام العام‪.‬‬

‫يمكن استخدام نظام لينكس عىل أساس نظام وقت فعلي غير صارم‪ ،‬إذ ُيستخَدم في األنظمة ال‪tt‬تي تتعام‪tt‬ل‬

‫مع الصوت والفيديو‪ ،‬وإذا أردَت تسجيل بث صوتي‪ ،‬فال بد أنك ال تريد مقاطعتك لفترات طويلة من الوقت ألنك‬

‫ستفقد البيانات الصوتية التي ال يمكن استرجاعها‪.‬‬

‫‪ 5.5.3‬القيمة اللطيفة ‪Nice Value‬‬


‫تسِند أنظمة يونيكس لكل عملية قيمًة لطيفة أو درجة لطف خاصة بها ‪ ،Nice Value‬إذ ينظر المج‪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‬د‬

‫العمليات التالية التي ستشَّغ ل‪.‬‬

‫شكل ‪ :24‬المجدول )‪O(1‬‬

‫يستخِدم المجدول )‪ 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‬كال‬

‫التواصل بين العمليات ‪.Interprocess Communication‬‬

‫ُيستدَع ى معالج اإلشارة بصورة غير متزامنة‪ ،‬إذ ُيقاَط ع البرن‪tt‬امج المش‪َّt‬غ ل حالًي ا عّم ا يفعل‪tt‬ه لمعالج‪tt‬ة ح‪tt‬دث‬

‫اإلشارة‪ ،‬كما ُتَع ّد المقاطعة أحد أنواع اإلشارات التي ُتحَّد د في ترويس‪tt‬ات النظ‪tt‬ام باالس‪tt‬م ‪ ،SIGINT‬إذ ُتس ‪َّt‬لم إىل‬

‫العملية عند الضغط عىل االختصار ‪.ctrl-c‬‬

‫تستخِدم العملية استدعاء نظام ‪ read‬لقراءة الدخل من لوحة المفاتيح‪ ،‬إذ ستراقب النواة مجرى الدخل بحًث ا‬

‫عن محارف خاصة‪ ،‬لكن ستنتقل إىل وضع معالجة اإلشارة في حالة ظهور االختص‪tt‬ار ‪ ،ctrl-c‬وس‪tt‬تبحث الن‪tt‬واة‬

‫لمعرفة ما إذا سّجلت العملية معالًجا لهذه المقاطعة‪ ،‬فإذا كان األمر كذلك‪ ،‬فسُيمَّرر التنفيذ إىل تلك الدال‪tt‬ة ال‪tt‬تي‬

‫ستعالج المقاطعة‪ ،‬وإذا لم تسّجل العملية معالًج ا له‪tt‬ذه اإلش‪tt‬ارة‪ ،‬فس‪tt‬تتخذ الن‪tt‬واة بعض اإلج‪tt‬راءات االفتراض‪tt‬ية‪،‬‬

‫ويكون اإلجراء االفتراضي هو إنهاء العملية باستخدام ‪.ctrl-c‬‬

‫يمكن أن تخت‪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‬ا من‬

‫حيث توقفت بمجرد انتهاء معالج اإلشارة من عمله‪ ،‬ويقّدم البرنامج البسيط التالي تشغيل بعض اإلشارات‪:‬‬

‫‪$ cat signal.c‬‬


‫>‪#include <stdio.h‬‬
‫>‪#include <unistd.h‬‬
‫>‪#include <signal.h‬‬

‫)‪void sigint_handler(int signum‬‬


‫{‬
‫;)"‪printf("got SIGINT\n‬‬
‫}‬

‫)‪int main(void‬‬
‫{‬
‫;)‪signal(SIGINT, sigint_handler‬‬
‫;))(‪printf("pid is %d\n", getpid‬‬
‫)‪while (1‬‬
‫;)‪sleep(1‬‬
‫}‬

‫‪136‬‬
‫علوم الحاسوب من األلف إىل الياء‬ ‫العمليات في نظام تشغيل الحاسوب‬

‫‪$ gcc -Wall -o signal signal.c‬‬


‫‪$ ./signal‬‬
‫‪pid is 2859‬‬
‫‪got SIGINT # press ctrl-c‬‬
‫‪# press ctrl-z‬‬
‫‪[1]+‬‬ ‫‪Stopped‬‬ ‫‪./signal‬‬

‫‪$ kill -SIGINT 2859‬‬


‫‪$ fg‬‬
‫‪./signal‬‬
‫‪got SIGINT‬‬
‫\‪Quit # press ctrl-‬‬

‫‪$‬‬

‫يعّرف البرنامج البسيط السابق معالًجا لإلشارة ‪ 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‬‬

‫عندما تموت العملية االبن بسبب اإلشارة ‪.SIGSEGV‬‬

‫يمكنك رؤية استخدام حوالي خمس إش‪tt‬ارات مختلف‪tt‬ة للتواص‪tt‬ل بين العملي‪tt‬ات والن‪tt‬واة والحف‪tt‬اظ عىل س‪tt‬ير‬

‫األمور حتى في برنامج بسيط‪ ،‬وهناك العديد من اإلشارات األخ‪t‬رى‪ ،‬لكنن‪t‬ا اس‪t‬تخدمنا في ه‪t‬ذا المث‪t‬ال اإلش‪t‬ارات‬

‫األك‪tt‬ثر ش‪tt‬يوًع ا‪ ،‬إذ تحت‪tt‬وي معظمه‪tt‬ا عىل دوال نظ‪tt‬ام تعّرفه‪tt‬ا الن‪tt‬واة‪ ،‬ولكن هن‪tt‬اك بعض اإلش‪tt‬ارات المحج‪tt‬وزة‬

‫للمستخِد مين الستخدامها ألغراضهم الخاصة في برامجهم (‪.)SIGUSR‬‬

‫‪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‬ي‬

‫للذاكرة الوهمية‪ ،‬بل لها تأثيٌر آخر مفيد للغاية سنراه الحًق ا‪.‬‬

‫‪ 6.1‬ما هي الذاكرة الوهمية ‪Virtual Memory‬؟‬


‫تدور الذاكرة الوهمية حول فكرة االستفادة من فضاء العناوين ‪ ،Address Space‬حيث يشير فضاء العناوين‬

‫الخاص بالمعالج إىل مجال العناوين الُم حتَم لة التي يمكن استخدامها عن‪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‬‬

‫‪ 6.1.1‬المعالجات ذات ‪ 64‬بت‬


‫جميع المعالجات الجديدة هي معالجات ‪ 64‬بت ال‪t‬تي ‪-‬كم‪t‬ا ي‪t‬وحي اس‪t‬مها‪ -‬تحت‪t‬وي عىل مس‪ّt‬جالت بع‪t‬رض‬

‫‪ 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‬ون‬

‫المصّرف ‪ Compiler‬واقًع ا تحت الضغط القادم من المسّجالت‪.‬‬

‫ا‪ .‬العناوين المعيارية ‪Canonical Addresses‬‬

‫تحتوي معالجات ‪ 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‬اوين‬

‫غير معيارية ‪.Non-canonical‬‬

‫شكل ‪ :25‬توضيح للعناوين المعيارية ‪canonical addresses‬‬

‫‪140‬‬
‫علوم الحاسوب من األلف إىل الياء‬ ‫الذاكرة الوهمية ‪Virtual Memory‬‬

‫يمكن العثور عىل قيمة البت األكثر أهمية للمعالج من خالل االستعالم عن المعالج نفسه باس‪tt‬تخدام تعليم‪tt‬ة‬

‫الحصول عىل المعلومات‪ .‬ستكون قيمة البت األكثر أهمية ‪ 48‬بالرغم من أن القيمة الدقيقة تعتم‪tt‬د عىل التق‪tt‬ديم‬

‫‪ ،Implementation‬مما يؤدي إىل توفير ‪ 248 = 256‬تيرابايت ‪ TiB‬من فضاء العناوين القابلة لالستخدام‪.‬‬

‫ُيَع د تقليل فضاء العناوين الُم حتَم ل أنه يمكن تحقيق توفيٍر كبير مع جميع أجزاء منط‪tt‬ق العنون‪tt‬ة في المع‪tt‬الج‬

‫والمكونات ذات الصلة‪ ،‬ألنها تعلم أنها لن تحتاج للتعامل مع عناوين ‪ 64‬بت كاملة‪ .‬يحّدد التق‪tt‬ديُم البت‪tt‬ات العلي‪tt‬ا‬

‫عىل أنه يجب تمديد إشارتها‪ ،‬مما يؤدي إىل منع أنظمة التشغيل القابلة للنقل التي تستخدم هذه البتات لتخ‪tt‬زين‬

‫أو تحديد المعلومات اإلضافية وضمان التوافق عند الرغبة في تقديم مزيٍد من فضاء العناوين مستقباًل ‪.‬‬

‫‪ 6.1.2‬استخدام فضاء العناوين‬


‫تعمل الذاكرة االفتراضية ‪-‬كما ه‪tt‬و الح‪tt‬ال م‪tt‬ع معظم مكون‪tt‬ات نظ‪tt‬ام التش‪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‬ة‬

‫والتنفيذ للصفحة الحالية‪ ،‬حيث يمكن لنظام التشغيل مثاًل تمييز صفحات الشيفرة البرمجية لعمليٍة ما باستخدام‬

‫راية قابلة للتنفيذ ويمكن للمعالج اختيار عدم تنفيذ أّي شيفرة برمجية من الصفحات بدون ضبط هذه البتات‪.‬‬

‫شكل ‪ :26‬صفحات الذاكرة الوهمية‬

‫‪141‬‬
‫علوم الحاسوب من األلف إىل الياء‬ ‫الذاكرة الوهمية ‪Virtual Memory‬‬

‫يمكن أن يفكر المبرمجون في هذه المرحلة في أنه يمكنهم بسهولة تخصيص كميات صغيرة من ال‪tt‬ذاكرة ‪-‬أي‬

‫أصغر بكثير من ‪ 4‬كيلوبايتات‪ -‬باستخدام االستدعاء ‪ malloc‬أو اس‪tt‬تدعاءات مماثل‪tt‬ة‪ .‬ت‪tt‬دعم عملي‪tt‬ات تخص‪tt‬يص‬

‫حجم الصفحة كومَة ‪ Heap‬الذاكرة‪ ،‬حيث يقسمها تقديم االستدعاء ‪ malloc‬ويديرها بطريقة فّع الة‪.‬‬

‫‪ 6.3‬الذاكرة الحقيقية ‪Physical Memory‬‬


‫يقسم نظام التشغيل فضاء العناوين الُم حتَم لة إىل صفحات ‪ ،Pages‬ويقسم ال‪tt‬ذاكرة الحقيقي‪tt‬ة المتاح‪tt‬ة إىل‬

‫إط‪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‬ك‬

‫إىل نظام التشغيل باستخدام نظام ‪ BIOS‬أثناء عملية التهيئة ‪.Initialisation‬‬

‫‪ 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‬‬

‫‪ 6.5‬العناوين الوهمية ‪Virtual Address‬‬


‫ال يعرف أو يهتم البرنامج عند وصوله إىل الذاكرة بمكان تخزين الذاكرة الحقيقي‪t‬ة ال‪t‬تي ت‪t‬دعم العن‪t‬وان‪ ،‬ولكن‪t‬ه‬

‫يعرف أن األمر متروك لنظام التشغيل والعتاد‪ ،‬بحيث يتعاون‪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‬بايت ضمن صفحة واحدة ويجري التعامل معها عىل أنها كتلة واحدة‪.‬‬

‫‪ 6.5.3‬ترجمة العنوان الوهمي ‪Virtual Address‬‬


‫تش‪tt‬ير ترجم‪tt‬ة أو نق‪tt‬ل العن‪tt‬اوين الوهمي‪tt‬ة ‪ Virtual address translation‬إىل عملي‪tt‬ة اكتش‪tt‬اف الص‪tt‬فحة‬

‫الحقيقية المربوطة مع الصفحة الوهمية‪ .‬سنتعامل فقط مع رقم الص‪tt‬فحة عن‪tt‬د ترجم‪tt‬ة عن‪tt‬وان وهمي إىل عن‪tt‬وان‬

‫حقيقي‪ ،‬حيث نأخذ رقم الصفحة من العنوان الُم عَط ى ونبحث عنه في جدول الص‪tt‬فحات للعث‪tt‬ور عىل مؤش‪tt‬ر إىل‬

‫عنوان حقيقي مع إضافة اإلزاحة من العنوان الوهمي إليه‪ ،‬مما يؤدي إىل إعطاء الموقع الفعلي في نظام الذاكرة‪.‬‬

‫تخض‪tt‬ع ج‪tt‬داول الص‪tt‬فحات لس‪tt‬يطرة نظ‪tt‬ام التش‪tt‬غيل‪ ،‬ف‪tt‬إن لم يكن العن‪tt‬وان ال‪tt‬وهمي موج‪tt‬وًدا في ج‪tt‬دول‬

‫الصفحات‪ ،‬فسيعرف نظام التشغيل أن العملية تحاول الوصول إىل الذاكرة التي ليست مخَّص صًة لها ولن ُيسَم ح‬

‫لها بالوصول‪.‬‬

‫‪143‬‬
‫علوم الحاسوب من األلف إىل الياء‬ ‫الذاكرة الوهمية ‪Virtual Memory‬‬

‫شكل ‪ :27‬ترجمة العنوان الوهمي‬

‫يوّض ح المثال السابق جدول صفحات خطي بسيط‪ ،‬حيث سيتطلب فضاء العناوين ذو ‪ 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‬من الذاكرة المتجاورة لجدول الصفحات فقط‪.‬‬

‫‪ 6.6‬مفاهيم متعلقة بالعناوين الوهمية والصفحات وجداول الصفحات‬


‫ُتَع د العناوين الوهمية والصفحات وجداول الصفحات أساس كل نظام تشغيل حديث‪ ،‬ألنه‪tt‬ا تش ‪ّt‬كل أس‪tt‬اس‬

‫معظم األشياء التي نستخدم أنظمتنا من أجلها‪.‬‬

‫‪144‬‬
‫علوم الحاسوب من األلف إىل الياء‬ ‫الذاكرة الوهمية ‪Virtual Memory‬‬

‫‪ 6.6.1‬فضاءات العناوين المفردة‬


‫يمكن لكل عملية التظاهر بأنها تستطيع الوصول إىل فض‪tt‬اء العن‪tt‬اوين الكام‪tt‬ل المت‪tt‬اح من المع‪tt‬الج من خالل‬

‫إعطاء كل عملية جدول صفحات خاص بها‪ ،‬إذ يمكن أن تستخدم عمليتان العنوان نفس‪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‬ا ذات‬

‫صلة‪ ،‬حيث إن لم يؤّش ر جدول الصفحات إىل الذاكرة الحقيقية أو لم يؤّش ر تبديل جدول الصفحات إىل ملف عىل‬

‫القرص الصلب‪ ،‬فسنقول أن الملف مربوط بالذاكرة ‪.mmap‬‬

‫تحتاج عادًة إىل فتح ‪ 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‬‬

‫توّش ر الصفحة في جدول الصفحات إىل هذه الصفحة الجديدة‪ .‬يمكن بعد ذلك تحديث سمات الصفحة للحصول‬

‫عىل أذونات الكتابة ويكون للعملية نسختها الفريدة من الصفحة‪.‬‬

‫‪ 6.6.5‬ذاكرة القرص الصلب المخبئة ‪Cache‬‬


‫توجد في األنظمة الحديثة ذاكرة متوفرة أكثر مم‪t‬ا يس‪t‬تخدمه النظ‪t‬ام حالًي ا ب‪t‬داًل من وج‪t‬ود ذاك‪t‬رة قليل‪t‬ة ج‪ًt‬دا‬

‫واالضطرار إىل تبديل الذاكرة‪ .‬يخبرنا تسلس‪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‬تي‬

‫سُتزال عند زيادة ضغط الذاكرة في النظام‪.‬‬

‫ا‪ .‬ذاكرة الصفحة المخبئة ‪Page Cache‬‬

‫المصطلح الذي يمكن أن تسمعه عند مناقشة النواة ‪ Kernel‬هو ذاكرة الصفحة المخبئية ‪ Page Cache‬التي‬

‫تشير إىل قائمة الصفحات التي تحتفظ بها النواة والتي تشير إىل الملفات الموجودة عىل القرص الص‪tt‬لب‪ ،‬حيث‬

‫تندرج صفحة التبديل والصفحات المربوطة بالذاكرة وصفحات ذاكرة القرص الصلب المخبئية ضمن ه‪tt‬ذه الفئ‪tt‬ة‪.‬‬

‫تحتف‪tt‬ظ الن‪tt‬واة به‪tt‬ذه القائم‪tt‬ة ألنه‪tt‬ا تحت‪tt‬اج إىل أن تك‪tt‬ون ق‪tt‬ادرة عىل البحث عنه‪tt‬ا بس‪tt‬رعة اس‪tt‬تجابًة لطلب‪tt‬ات‬

‫القراءة والكتابة‪.‬‬

‫‪ 6.7‬مواصفات الذاكرة الوهمية في لينكس‬


‫تبقى مفاهيم الذاكرة الوهمية األساس‪t‬ية ثابت‪t‬ة‪ ،‬إاّل أن تفاص‪t‬يل التق‪t‬ديمات تعتم‪t‬د بص‪t‬ورة كب‪t‬يرة عىل نظ‪t‬ام‬

‫التشغيل والعتاد‪.‬‬

‫‪ 6.7.1‬مخطط فضاء العناوين‬


‫يقسم لينكس فضاء العناوين المتاح إىل مكون ن‪t‬واة ‪ Kernel‬مش‪t‬ترك وفض‪t‬اء عن‪t‬اوين خ‪t‬اص بالمس‪t‬تخدم‪،‬‬

‫وهذا يعني أن العناوين الموجودة في منفذ النواة لفضاء العناوين ترتبط مع الذاكرة الحقيقية نفسها لك‪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‬جيجابايتات كاملة باستخدام توّس عات خاصة‪.‬‬

‫شكل ‪ :28‬مخطط فضاء العناوين في لينكس‬

‫‪ 6.7.2‬جدول الصفحات المكون من المستويات الثالثة‬


‫هناك العديد من الط‪tt‬رق المختلف‪tt‬ة لنظ‪tt‬ام التش‪tt‬غيل لتنظيم ج‪tt‬داول الص‪tt‬فحات‪ ،‬ولكن يخت‪tt‬ار نظ‪tt‬ام لينكس‬

‫استخدام النظام الهرمي‪ .‬تستخدم جداول الصفحات تسلساًل هرمًي ا بعمق ثالثة مستويات‪ ،‬لذلك ُيش‪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‬‬

‫شكل ‪ :29‬جدول صفحات لينكس المكون من ثالثة مستويات‬

‫يتضمن المثال السابق االنتقال إىل ج‪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‬‬

‫‪ 6.8‬دعم العتاد للذاكرة الوهمية في معمارية الحاسوب‬


‫ذكرنا من في السابق ما هي الذاكرة الوهمية والذاكرة الحقيقية في معمارية الحاسوب‪ ،‬ووض‪tt‬حنا ب ‪t‬أن العت‪tt‬اد‬

‫يعمل مع نظام التشغيل لتقديم ‪ Implementation‬الذاكرة الوهمية‪ ،‬وألقينا نظ‪tt‬رة عىل تفاص‪tt‬يل كيفي‪tt‬ة ح‪tt‬دوث‬

‫ذلك‪ ،‬حيث تعتمد الذاكرة الوهمية بصورة كبيرة عىل معمارية العتاد‪ ،‬حيث يكون لكل معمارية خواصها الدقيق‪tt‬ة‪،‬‬

‫ولكن هناك عدد من العناصر الخاصة بالذاكرة الوهمية في العتاد التي سنستعرضها في الفقرات التالية‪.‬‬

‫‪ 6.8.1‬الوضع الحقيقي والوضع الوهمي‬


‫تحتوي جميع المعالجات عىل مفهوٍم ما للعم‪t‬ل في الوض‪t‬ع الحقيقي ‪ Physical Mode‬أو الوض‪t‬ع ال‪t‬وهمي‬

‫‪ ،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‬كاًل‬

‫من أشكال تنظيم الذاكرة يسمى التقطيع ‪.Segmentation‬‬

‫ا‪ .‬مشاكل التقطيع‬

‫كان التقطيع أمًرا مهًم ا سابًق ا‪ ،‬ولكن قّللت الذاكرة الوهمية من أهميت‪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‬‬

‫شكل ‪ :30‬التقطيع ‪Segmentation‬‬

‫هناك ثالثة مسجالت مقاطع في الشكل السابق تؤّش ر جميعه‪tt‬ا إىل المق‪tt‬اطع‪ ،‬ويظه‪tt‬ر ح‪tt‬د اإلزاح‪tt‬ة األقص‪tt‬ى‬

‫الُم قَّيد بعدد البتات المتاحة في المنطقة الُم ظَّللة‪ .‬إذا أراد البرنامج عنواًنا خارج هذا النط‪tt‬اق‪ ،‬فيجب إع‪tt‬ادة ض‪tt‬بط‬

‫مسّج الت المقاطع الذي سرعان ما يصبح مصدر إزعاج كبير فيما بعد‪ .‬بينما تسمح ذاكرة البرنامج الوهمية بتحديد‬

‫العنوان الذي تريده ويطّبق نظام التشغيل والعتاد عملية الترجمة إىل عنوان حقيقي‪.‬‬

‫‪ 6.8.2‬مخزن الرتجمة المؤقت ‪TLB‬‬


‫ُيَع د مخزن الترجمة المؤقت ‪- Translation Lookaside Buffer‬أو ‪ TLB‬اختصاًرا‪ -‬مكون المعالج الرئيسي‬

‫المسؤول عن الذاكرة الوهمية‪ ،‬وه‪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‬ون‬

‫بتها المتسخ غير مضبوط‪ ،‬فسيؤدي ذلك إىل حدوث خطأ في نظام التشغيل لتمييز الصفحة بوصفها متسخة‪.‬‬

‫‪ 6.8.3‬إدارة مخزن ‪TLB‬‬


‫يمكننا القول أن مخزن ‪ TLB‬يستخدمه العتاد ولكن تديره البرمجيات‪ ،‬فاألمر متروك لنظام التشغيل لتحميل‬

‫المدخالت الصحيحة إىل مخزن ‪ TLB‬وإلزالة المدخالت القديمة منه‪.‬‬

‫ا‪ .‬تفريغ مخزن ‪TLB‬‬

‫ُتسَّم ى عملية إزالة المدخالت من مخزن ‪ 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‬‬

‫ليحتفظ ببتات معّرف ‪.ASID‬‬

‫‪153‬‬
‫علوم الحاسوب من األلف إىل الياء‬ ‫الذاكرة الوهمية ‪Virtual Memory‬‬

‫يمكن تنفيذ ذلك من خالل وجود مسجل إضافي بوصفه جزًءا من حالة العملية التي تتض‪tt‬من مع‪ّt‬رف ‪.ASID‬‬

‫ينظ‪tt‬ر مخ‪tt‬زن ‪ TLB‬إىل ه‪tt‬ذا المس‪ّt‬جل عن‪tt‬د ترجم‪tt‬ة الص‪tt‬فحة الوهمي‪tt‬ة إىل الص‪tt‬فحة الحقيقي‪tt‬ة‪ ،‬وس‪tt‬يطابق فق‪tt‬ط‬

‫المدخالت التي لها معّرف ‪ ASID‬الخاص بالعملية التي تكون قيد التشغيل حالًي ا‪ .‬يح‪ّtt‬دد حجم ه‪tt‬ذا المس‪tt‬جل رقم‬

‫معّرفات ‪ ASID‬المتاحة وبالتالي له تأثير عىل األداء‪.‬‬

‫ب‪ .‬مخزن ‪ TLB‬المحمل برمجيا وعتادًي ا‬

‫ُيَع د التحكم في مخزن ‪ 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‬ام‬

‫التشغيل مدخلة الترجمة غير الموجودة في مخزن ‪.TLB‬‬

‫يوّف ر التنفيذ الُم ق‪َّt‬د م للم‪t‬رور عىل ج‪t‬دول الص‪t‬فحات في العت‪t‬اد المتخص‪t‬ص مزاي‪t‬ا الس‪t‬رعة عن‪t‬د البحث عن‬

‫الترجمات‪ ،‬ولكنه يزيل المرونة عن مطّبقي أنظمة التشغيل ال‪tt‬ذين يرغب‪tt‬ون في تنفي‪tt‬ذ مخطط‪tt‬ات بديل‪tt‬ة لج‪tt‬داول‬

‫الصفحات‪.‬‬

‫يمكن تصنيف جميع المعماريات عىل نطاق واسع ضمن هاتين المنهجيتين السابقتين‪ ،‬وسنّط لع تالًي ا عىل‬

‫بعض المعماريات الشائعة ودعم الذاكرة الوهمية‪.‬‬

‫‪ 6.9‬دعم العتاد للذاكرة الوهمية‬


‫ي‪tt‬وّف ر عت‪tt‬اد المع‪tt‬الج ج‪tt‬دول بحث يرب‪tt‬ط العن‪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‬‬

‫ا‪ .‬فضاءات العناوين ‪Address spaces‬‬

‫شرحنا سابًق ا مفهوم مع‪ّ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‬ة بنهج‬

‫"الكل أو ال شيء"‪ ،‬إذ يجب أن تتخىل الخيوط عن الحماية من بعضها البعض لمشاركة بعض البايتات‪.‬‬

‫شكل ‪ : 31‬رسم توضيحي للمناطق ومفاتيح الحماية في معالج إيتانيوم‬

‫ت‪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‬‬

‫شكل ‪ :32‬رسم توضيحي لترجمة مخزن ‪ TLB‬في معالج إيتانيوم‬

‫ال ُتحتَس ب البتات الثالثة األوىل ( بتات المنطقة) في ترجمة العنوان الوهمي‪ ،‬لذلك إذا كانت هناك عمليتان‬

‫تشتركان في معّرف ‪ RID‬أي تحتفظان بالقيمة نفسها في أحد مسجالت المنطقة الخاصة بهما‪ ،‬فسيكون ل‪tt‬ديهما‬

‫اسم بديل لتلك المنطقة‪ .‬إذا احتوت العملية ‪ A‬عىل معّرف ‪ RID‬قيمته ‪ 0x100‬في مسجل المنطق‪tt‬ة ‪ 3‬واحت‪tt‬وت‬

‫العملية ‪ B‬معّرف ‪ RID‬نفسه الذي قيمته ‪ 0x100‬في مسّجل المنطق‪tt‬ة ‪ ،5‬فسُتس‪َّt‬م ى المنطق‪tt‬ة ‪ 3‬من العملي‪tt‬ة ‪A‬‬

‫باسم بديل هو ‪ .process-B, region 5‬تعني هذه المشاركة المحدودة أن كلتا العمليتين تتلقيان فوائد مدخالت‬

‫مخزن ‪ TLB‬المشتركة دون الحاجة إىل منح إذن الوصول إىل كامل فضاء العناوين‪.‬‬

‫مفاتيح الحماية ‪Protection Keys‬‬

‫ُتوَس م كل مدخلة من مخزن ‪ 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‬‬

‫ب‪ .‬أداة إيتانيوم العتادية للمرور عىل جدول الصفحات ‪Page-Table‬‬

‫يؤدي تبديل السياق إىل نظ‪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‬وهمي‬

‫ويعتمد اآلخر عىل جدول التعمية ‪.Hash Table‬‬

‫تجدر اإلشارة إىل أنه يمكن العمل بدون أداة عتادية للمرور عىل جدول الصفحات‪ ،‬حيث يحل نظام التش‪tt‬غيل‬

‫كل خطأ ‪ TLB‬ويصبح المعالج معمارية محَّم لة برمجًي ا‪ ،‬ولكن ُيَع د ت‪t‬أثير تعطي‪tt‬ل ‪ HPW‬عىل األداء كب‪t‬يًرا ج‪ًt‬دا دون‬

‫الحصول عىل أّي فائدة‪.‬‬

‫جدول الصفحات الخطي الوهمي ‪Virtual Linear Page-Table‬‬

‫يشار إىل تقديم جدول الصفحات الخطي الوهمي في التوثيقات عىل أنه الصيغة القصيرة لجدول الصفحات‬

‫الُم عَّم اة وهمًي ا ‪- 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‬‬

‫شكل ‪ :33‬رسم توضيحي لجدول صفحات هرمي‬

‫يأخذ الجدول الخطي الذي حجمه ‪ 512‬جيبي بايت ‪ GiB‬مع فضاء عناوين ‪ 64‬بت ما مقداره ‪ %0.003‬فق‪tt‬ط‬

‫من ‪ 16‬إكسابايت المتاحة‪ ،‬وبالتالي يمكن إنشاء جدول صفحات خطي وهمي ‪Virtual Linear Page-table‬‬

‫‪-‬أو ‪ VLPT‬اختصاًرا‪ -‬في منطقة متجاورة من فضاء العناوين الوهمية‪.‬‬

‫يستخدم العتاد عند حدوث خطأ في مخزن ‪ 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‬‬

‫شكل ‪ :34‬تقديم جدول ‪ VHPT‬بصيغة قصيرة في معالج إيتانيوم‬

‫شكل ‪ :35‬صيغ مدخلة ‪ PTE‬في معالج إيتانيوم‬

‫يمكن جعل هذه العملية مباشرًة إذا احتفظ نظام التشغيل بجدول ص‪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‬ات‬

‫مرور ‪ HPW‬الناجحة الالحقة‪.‬‬

‫العيب الرئيسي هو أن جدول ‪ 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‬من‬

‫المنطقة الواحدة‪.‬‬

‫جدول التعمية الوهمي ‪Virtual Hash Table‬‬

‫يمكن أن يكون استخدام مدخالت مخزن ‪ 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‬كل‬

‫"صيغ مدخلة ‪ PTE‬في معالج إيتانيوم"‪.‬‬

‫الميزة الرئيسية لهذه الطريقة هي أن جدول التعمية العام يمكن تثبيته باستخدام مدخلة ‪ 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‬ذي‬

‫يتطلب مدخلة واحدة فقط في مخزن ‪.TLB‬‬

‫‪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‬‬

‫‪ 7.1‬الربامج المرصفة ‪ Compiled‬والربامج المفرسة ‪Interpreted‬‬


‫يجب أن يكون البرنامج الذي يمكن تحميله مباشرًة في الذاكرة بصيغة ثنائية ‪ ،binary format‬حيث ُتسَّم ى‬

‫عملية تحويل الشيفرة البرمجية المكتوبة بلغٍة مثل لغة ‪ C‬إىل ملف ثنائي ج‪t‬اهز للتنفي‪tt‬ذ بعملي‪tt‬ة التص‪tt‬ريف ال‪t‬تي‬

‫ُتطَّبق باستخدام مصِّرف ‪ ،Compiler‬والمثال األكثر انتشاًرا هو المصِّرف ‪.gcc‬‬

‫للبرامج الُم صَّرفة بعض العيوب في تطوير البرمجي‪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‬من اللغات التي تستخدم آلًة افتراضية تفّس ر الشيفرة البرمجية‪.‬‬

‫‪ 7.1.1‬اآلالت االفرتاضية ‪Virtual Machines‬‬


‫يعتمد البرنامج الُم صَّرف كلًي ا عىل عتاد اآللة التي ُيصَّرف من أجلها‪ ،‬إذ يجب أن يكون ه‪tt‬ذا العت‪tt‬اد ق‪tt‬ادًرا عىل‬

‫نسخ البرنامج في الذاكرة وتنفيذه‪ ،‬حيث ُتَع د اآللة االفتراضية ‪ Virtual Machine‬تجريًدا برمجًي ا للعتاد‪.‬‬

‫تستخدم لغة جافا ‪ Java‬مثاًل نهًجا هجيًنا يجمع بين التصريف والتفس‪tt‬ير‪ ،‬فهي لغ‪tt‬ة ُم ص ‪َّt‬رفة جزئًي ا وُم فَّس رة‬

‫جزئًي ا‪ُ .‬تصَّرف شيفرة جافا في برنامج يعمل ضمن آلة جافا االفتراض‪tt‬ية ‪ Java Virtual Machine‬أو يش‪tt‬ار إليه‪tt‬ا‬

‫‪ JVM‬اختصاًرا‪ ،‬وبالتالي يمكن تشغيل البرنامج الُم صَّرف عىل أّي عتاد يحتوي عىل آلة ‪ JVM‬خاصة ب‪t‬ه‪ ،‬أي يمكن‪t‬ك‬

‫أن تكتب شيفرتك البرمجية مرة واحدة وتشّغ لها في أّي مكان‪.‬‬

‫‪ 7.2‬بناء ملف قابل للتنفيذ‬


‫هناك ثالث خطوات منفصلة تتضمنها عملية إنشاء ملف قابل للتنفيذ عندما نتحدث عن الُم صِّرفات وه‪tt‬ذه‬

‫الخطوات هي‪:‬‬

‫‪ .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‬تي‬

‫يكتبها اإلنسان إىل تمثيلها الداخلي بعملية التحليل ‪.Parsing‬‬

‫هناك خطوة قبل تحليل الشيفرة البرمجية في الشيفرة المكتوبة بلغة ‪ ،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‬اختصاًرا‪ -‬التي تسمح ببعض القواعد اإلضافية األكثر مالءمة للغات الحديثة‪.‬‬

‫‪ 7.3.2‬توليد شيفرة التجميع ‪Assembly Generation‬‬


‫وظيفة المصِّرف هي ترجمة لغ‪t‬ة عالي‪t‬ة المس‪t‬توى ‪ higher level language‬إىل ش‪t‬يفرة تجمي‪t‬ع مناس‪t‬بة‬

‫للهدف من التصريف‪ ،‬فلكل معمارية مجموعة تعليمات مختلفة وأعداد مختلفة من المسجالت وقواع‪tt‬د مختلف‪tt‬ة‬

‫للتشغيل الصحيح‪.‬‬

‫ا‪ .‬المحاذاة ‪Alignment‬‬

‫ُتَع د محاذاة المتغيرات في الذاكرة أمًرا مهًم ا للمص‪ِّt‬رفات‪ ،‬إذ يحت‪t‬اج م‪t‬برمجو األنظم‪t‬ة أن يكون‪t‬وا عىل دراي‪t‬ة‬

‫بقيود المحاذاة لمساعدة المصِّرف عىل إنشاء أكثر شيفرة برمجية فّع الة ممكنة‪.‬‬

‫‪164‬‬
‫علوم الحاسوب من األلف إىل الياء‬ ‫سلسلة األدوات ‪Toolchain‬‬

‫شكل ‪ :36‬المحاذاة في الذاكرة‬

‫ال تستطيع وحدات المعالجة المركزية ‪ 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‬غيل‬

‫الذي يتعين عليه بعد ذلك تحميل المسجل يدوًيا عىل أجزاء‪ ،‬مما يتسبب في مزيد من الِح مل‪.‬‬

‫حاشية البنية ‪Structure Padding‬‬

‫يجب أن يأخذ المبرمجون المحاذاة في الحسبان خاصًة عند إنشاء الب‪tt‬نى ‪ ،struct‬حيث يمكن للم‪tt‬برمجين‬

‫في بعض األحي‪tt‬ان أن يتس‪tt‬ببوا في س‪tt‬لوك دون المس‪tt‬توى األمث‪tt‬ل‪ ،‬بينم‪tt‬ا يع‪tt‬رف المص‪ِّtt‬رف قواع‪tt‬د المح‪tt‬اذاة‬

‫للمعماريات التي يبنيه‪tt‬ا‪ .‬ينص معي‪tt‬ار ‪ C99‬عىل أن الب‪tt‬نى س‪ُtt‬ترَّتب في ال‪tt‬ذاكرة ب‪tt‬الترتيب الُم ح‪َّt‬د د في التص‪tt‬ريح‬

‫نفسه‪ ،‬وستكون جميع العناصر بالحجم نفسه في مصفوفة من البنى‪.‬‬

‫إليك مثال عن حاشية بنية ‪:Struct Padding‬‬

‫‪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;

$ gcc -o struct struct.c

$ gcc -fpack-struct -o struct-packed struct.c

$ ./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‬بايتات من‬

‫النوع ‪ .int‬يضيف المصِّرف حاشية للبنية كما يلي‪:‬‬

‫شكل ‪ :37‬محاذاة المتغيرات‬

‫نوّجه في المثال السابق المصِّرف إىل ع‪tt‬دم حش‪t‬و الب‪t‬نى‪ ،‬وبالت‪tt‬الي يمكنن‪t‬ا أن ن‪t‬رى أن الع‪tt‬دد الص‪tt‬حيح يب‪t‬دأ‬

‫مباشرًة بعد قيمتين من النوع ‪.char‬‬

‫محاذاة خط الذاكرة المخبئية ‪Cache line alignment‬‬

‫تحدثنا سابًق ا عن استخدام األسماء البديلة في الذاكرة المخبئية‪ ،‬وكي‪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‬ير‬

‫قابلة للنقل‪.‬‬

‫إليك مثال عن محاذاة المكدس ‪:Stack Alignment‬‬

‫‪$ cat stack.c‬‬


‫>‪#include <stdio.h‬‬

‫{ ‪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‬د‬

‫من عدم وضع افتراضات حول حجم األنواع أو قواعد المحاذاة‪.‬‬

‫مفاهيم لغة ‪ C‬الخاصة بالمحاذاة‬

‫هناك عدد من تسلسالت الشيفرة البرمجية الش‪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‬‬

‫‪#define PAGE_SIZE‬‬ ‫)‪(__IA64_UL_CONST(1) << PAGE_SHIFT‬‬


‫‪#define PAGE_MASK‬‬ ‫))‪(~(PAGE_SIZE - 1‬‬
‫)‪#define PAGE_ALIGN(addr‬‬ ‫)‪(((addr) + PAGE_SIZE - 1) & PAGE_MASK‬‬

‫‪169‬‬
‫علوم الحاسوب من األلف إىل الياء‬ ‫سلسلة األدوات ‪Toolchain‬‬

‫يمكننا أن نرى في المثال السابق أن هناك عدًدا من الخيارات المختلفة ألحجام الصفحات داخل النواة ال‪tt‬تي‬

‫ت‪tt‬تراوح من ‪ 4‬كيلوبايت‪tt‬ات إىل ‪ 64‬كيلوب‪tt‬ايت‪َُ .‬يع‪tt‬د الم‪tt‬اكرو ‪ PAGE_SIZE‬واض ‪ًt‬حا إىل ح‪tt‬د م‪tt‬ا‪ ،‬فه‪tt‬و يعطي حجم‬

‫الصفحة الحالي المحّدد ضمن النظام عن طريق انزياح قيمته ‪ 1‬باستخدام رقم االنزياح الُم عَط ى‪ ،‬ويعادل ذل‪tt‬ك ‪2n‬‬

‫حيث ‪ n‬هو انزياح الصفحة ‪.PAGE_SHIFT‬‬

‫لدينا بعد ذلك تعريف قناع الصفحة ‪ PAGE_MASK‬الذي يسمح لن‪t‬ا ب‪t‬العثور عىل تل‪tt‬ك البت‪tt‬ات الموج‪t‬ودة في‬

‫الصفحة الحالية فقط‪ ،‬أي إزاحة ‪ offset‬العنوان في صفحته‪.‬‬

‫‪ 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‬ة‬

‫وينتقل إىل شيء أصغر يوصل للنتيجة نفسها‪.‬‬

‫ا‪ .‬فك الحلقات ‪Unrolling Loops‬‬

‫إذا احتوت الشيفرة البرمجية عىل حلقة مثل حلقة ‪ for‬أو ‪ while‬وكان لدى الُم صِّرف فكرة عن عدد المرات‬

‫التي ستنّف ذ فيها‪ ،‬فسيكون فك الحلقة أكثر فاعلية بحيث ُتنَّف ذ تسلسلًي ا‪ ،‬إذ ُتكَّرر شيفرة الحلقة الداخلية لتنفيذها‬

‫عدد المرات ذاك أخرى بداًل من تنفيذ الجزء الداخلي من الحلقة ثم العودة إىل البداية لتكرار العملية‪.‬‬

‫تزيد هذه العملية من حجم الشيفرة البرمجية‪ ،‬إذ يمكن أن تسمح للمعالج بتنفي‪tt‬ذ التعليم‪tt‬ات بفعالي‪tt‬ة‪ ،‬حيث‬

‫يمكن أن تتسبب الفروع في تقليل كفاءة خط أنابيب التعليمات الواردة إىل المعالج‪.‬‬

‫ب‪ .‬الدوال المضمنة ‪Inlining Functions‬‬

‫يمكن وضع دوال ُم ضَّم نة الستدعائها ضمن المستدعي ‪ ،callee‬ويمكن للمبرمج تحديد ذل‪tt‬ك للمص ‪ِّt‬رف من‬

‫خالل وضع الكلمة ‪ inline‬في تعريف الدالة‪ ،‬ويمكنك مقايضة حجم الش‪t‬يفرة البرمجي‪tt‬ة بتسلس‪t‬ل تنفي‪tt‬ذها من‬

‫خالل ذلك‪.‬‬

‫ج‪ .‬توقع الفرع ‪Branch Prediction‬‬

‫إذا صادف الحاسوب تعليمة ‪ ،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‬مي‬

‫هذه العملية بالربط ‪.Linking‬‬

‫الحظ أنه ال يزال يجب ربط برنامجك بمكتبات نظام معينة للعم‪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‬من‬

‫الذاكرة بمقدار ‪."2‬‬

‫ا‪ .‬إمكانية رؤية الرموز ‪Symbol Visibility‬‬

‫ال بد أنك شاهدت في البرامج المكتوبة بلغة ‪ C‬المصطلحين ‪ static‬و ‪ extern‬م‪tt‬ع المتغ‪tt‬يرات‪ ،‬إذ يمكن‬

‫أن تؤثر هذه المعِّدالت ‪ Modifiers‬عىل ما نسميه إمكانية رؤية الرموز‪.‬‬

‫ال‪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‬ات‬

‫أخرى دون وجود تعارضات بين الرموز‪ .‬يمكن القول بأننا نقّي د رؤية الرمز‪ ،‬ألننا ال نسمح للرابط برؤيته بعكس الرمز‬

‫الذي لم ُيصَّر ح عنه بأنه ‪ static‬ويمكن للرابط رؤيته‪.‬‬

‫‪ 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‬ف‬

‫قابل للتنفيذ بلغة ‪.C‬‬

‫‪ 7.6‬تطبيق عميل لبناء برنامج تنفيذي من شيفرة مصدرية بلغة ‪C‬‬


‫بع‪tt‬د أن تعّرفن‪tt‬ا عىل الخط‪tt‬وات الثالث لبن‪tt‬اء مل‪tt‬ف قاب‪tt‬ل للتنفي‪tt‬ذ هي‪ :‬التص‪tt‬ريف ‪ Compiling‬والتجمي‪tt‬ع‬

‫‪ Assembling‬والربط ‪ ،Linking‬وسنطّبق اآلن هذه الخطوات عملًي ا لبناء ملف قابل للتنفيذ‪.‬‬

‫تابع فيما الخطوات المتبعة لبناء تطبيق بسيط خطوة بخط‪tt‬وة‪ .‬الح‪t‬ظ أن األم‪t‬ر ‪ gcc‬يش‪ّt‬غ ل برن‪tt‬امج تش‪t‬غيٍل‬
‫‪ driver program‬يخفي معظم الخطوات عنك‪ ،‬وهذا هو ما تريده بالضبط في ظل الظروف العادية‪ ،‬ألن األوامر‬

‫والخيارات الدقيقة للحصول عىل ملف قابٍل للتنفيذ عىل نظام حقيقي يمكن أن تكون معقدة للغاية وخاصًة بك‪tt‬ل‬

‫معمارية عىل حدة‪.‬‬

‫سنشرح عملية التصريف في المث‪tt‬الين الت‪tt‬اليين‪ ،‬حيث سنس‪tt‬تخدم ملفين مص‪tt‬دريين مكت‪tt‬وبين بلغ‪tt‬ة ‪ ،C‬إذ‬

‫يعّرف أحدهما الدالة الرئيسية )(‪ main‬التي ُتَع د نقطة الدخول األولية‪ ،‬ويصّر ح الملف اآلخ‪tt‬ر عن دال‪tt‬ة مس‪tt‬اعدة‪،‬‬

‫وهناك متغير عام واحد‪.‬‬

‫إليك مثال مرحًبا بالعالم ‪:Hello World‬‬

‫>‪#include <stdio.h‬‬

‫‪ */‬نحتاج نموذًج ا أولًيا ليعرف المصّرف نوع الدالة )(‪/* function‬‬


‫;)‪int function(char *input‬‬

‫‪173‬‬
‫علوم الحاسوب من األلف إىل الياء‬ ‫سلسلة األدوات ‪Toolchain‬‬

‫‪ */‬بما أن هذا المتغير ساكن ‪ ،static‬فيمكننا تعريفه في كٍّل من الملفين ‪ hello.c‬و‪/* function.c‬‬

‫;‪static int i = 100‬‬

‫‪ */‬هذا متغير عام *‪/‬‬


‫;‪int global = 10‬‬

‫)‪int main(void‬‬
‫{‬
‫‪ */‬يجب أن تعيد الدالة )(‪ function‬قيمة المتغير العام ‪/* global‬‬
‫;)"!‪int ret = function("Hello, World‬‬
‫;)‪exit(ret‬‬
‫}‬

‫إليك مثال عىل دالة‪:‬‬

‫>‪#include <stdio.h‬‬

‫;‪static int i = 100‬‬

‫‪ُ */‬م صّرح عنه بأنه خارجي ‪ extern‬ألنه ُم عَّرف في الملف ‪/* hello.c‬‬
‫;‪extern int global‬‬

‫)‪int function(char *input‬‬


‫{‬
‫;)‪printf("%s\n", input‬‬
‫;‪return global‬‬
‫}‬

‫‪ 7.6.1‬الترص يف ‪Compiling‬‬
‫لكل المصِّرفات خياٌر لتنفيذ الخطوة األوىل من التصريف فق‪t‬ط مث‪t‬ل اس‪t‬تخدام الراي‪t‬ة ‪ -S‬لوض‪t‬ع الخ‪t‬رج في‬

‫ملف يحمل اسم ملف الدخل نفسه ولكن مع الالحقة ‪ ،.s‬وبالتالي يمكننا عرض الخط‪tt‬وة األوىل باس‪tt‬تخدام األم‪tt‬ر‬

‫‪ gcc -S‬كما هو موضح في المثال التالي‪:‬‬

‫‪$ gcc -S hello.c‬‬


‫‪$ gcc -S function.c‬‬
‫‪$ cat function.s‬‬

‫‪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‬‬

‫‪addl r15 = @ltoffx(global#), r1‬‬


‫;;‬
‫‪ld8.mov r14 = [r15], global#‬‬
‫;;‬
‫]‪ld4 r14 = [r14‬‬
‫;;‬
‫‪mov r8 = r14‬‬
‫‪mov ar.pfs = r34‬‬
‫‪mov b0 = r33‬‬
‫‪.restore sp‬‬
‫‪mov r12 = r35‬‬
‫‪br.ret.sptk.many b0‬‬
‫;;‬
‫‪.endp function#‬‬
‫‪.ident‬‬ ‫")‪"GCC: (GNU) 3.3.5 (Debian 1:3.3.5-11‬‬

‫ُتَع د عملية التجميع ‪ Assembly‬معقدة قلياًل ‪ ،‬ولكن يجب أن تكون قادًرا عىل معرفة مكان تعريف المتغ‪tt‬ير ‪i‬‬

‫بوص‪ttt‬فه ‪ data4‬أي ‪ 4‬بايت‪ttt‬ات أو ‪ 32‬بت بحجم الن‪ttt‬وع ‪ ،int‬ومك‪ttt‬ان تعري‪ttt‬ف الدال‪ttt‬ة ‪( function‬بالش‪ttt‬كل‬

‫‪ )function:‬واستدعاء الدالة )(‪.printf‬‬

‫أصبح لدينا اآلن ملفا تجميع جاهزين لتجميعهما في شيفرة اآللة البرمجية ‪.machine code‬‬

‫‪ 7.6.2‬التجميع ‪Assembly‬‬
‫التجميع هو عملية مباشرة إىل حد ما‪ ،‬وُيطَلق عىل المجّم ع ‪ as‬ويأخذ وسائًط ا بطريقة مماثلة لألمر ‪.gcc‬‬

‫إليك مثال عن التجميع‪:‬‬

‫‪$ as -o function.o function.s‬‬


‫‪$ as -o hello.o hello.s‬‬
‫‪$ ls‬‬
‫‪function.c‬‬ ‫‪function.o‬‬ ‫‪function.s‬‬ ‫‪hello.c‬‬ ‫‪hello.o‬‬ ‫‪hello.s‬‬

‫تنتج عن عملية التجميع التعليمات الُم صَّرفة ‪ ،Object Code‬حيث تكون هذه الشيفرة ج‪tt‬اهزًة لربطه‪tt‬ا م‪tt‬ع‬

‫بعضها البعض في الملف النهائي القابل للتنفيذ‪ .‬يمكنك تخطي االضطرار إىل استخدام الُم جِّم ع ي‪tt‬دوًيا من خالل‬

‫استدعاء المصِّرف مع الراية ‪ -c‬التي تحّو ل ملف الدخل مباشرًة إىل شيفرة ك‪tt‬ائن‪ ،‬وتض‪tt‬عها في مل‪tt‬ف ل‪tt‬ه البادئ‪tt‬ة‬

‫نفسها ولكن مع الالحقة ‪..o‬‬

‫‪176‬‬
‫علوم الحاسوب من األلف إىل الياء‬ Toolchain ‫سلسلة األدوات‬

‫تخدام بعض‬tt‫ا اس‬tt‫ ولكن يمكنن‬،‫ة‬tt‫يغة ثنائي‬tt‫ا في ص‬tt‫رًة ألنه‬tt‫ال يمكننا فحص شيفرة التعليمات الُم صَّرفة مباش‬

‫وز‬tt‫تعرض الرم‬tt‫تي س‬tt‫ ال‬readelf --symbols ‫ل األداة‬tt‫َّرفة مث‬tt‫ات الُم ص‬tt‫ات التعليم‬tt‫األدوات لفحص ملف‬

:‫الموجودة في ملف الكائن كما يلي‬

$ readelf --symbols ./hello.o

Symbol table '.symtab' contains 15 entries:


Num: Value Size Type Bind Vis Ndx Name
0000000000000000 0 NOTYPE LOCAL DEFAULT UND
0000000000000000 0 FILE LOCAL DEFAULT ABS hello.c
0000000000000000 0 SECTION LOCAL DEFAULT 1
0000000000000000 0 SECTION LOCAL DEFAULT 3
0000000000000000 0 SECTION LOCAL DEFAULT 4
0000000000000000 0 SECTION LOCAL DEFAULT 5
0000000000000000 4 OBJECT LOCAL DEFAULT 5 i
0000000000000000 0 SECTION LOCAL DEFAULT 6
0000000000000000 0 SECTION LOCAL DEFAULT 7
0000000000000000 0 SECTION LOCAL DEFAULT 8
0000000000000000 0 SECTION LOCAL DEFAULT 10
0000000000000004 4 OBJECT GLOBAL DEFAULT 5 global
0000000000000000 96 FUNC GLOBAL DEFAULT 1 main
0000000000000000 0 NOTYPE GLOBAL DEFAULT UND function
0000000000000000 0 NOTYPE GLOBAL DEFAULT UND exit

$ readelf --symbols ./function.o

Symbol table '.symtab' contains 14 entries:


Num: Value Size Type Bind Vis Ndx Name
0000000000000000 0 NOTYPE LOCAL DEFAULT UND
0000000000000000 0 FILE LOCAL DEFAULT ABS function.c
0000000000000000 0 SECTION LOCAL DEFAULT 1
0000000000000000 0 SECTION LOCAL DEFAULT 3
0000000000000000 0 SECTION LOCAL DEFAULT 4
0000000000000000 0 SECTION LOCAL DEFAULT 5
0000000000000000 4 OBJECT LOCAL DEFAULT 5 i
0000000000000000 0 SECTION LOCAL DEFAULT 6

177
‫علوم الحاسوب من األلف إىل الياء‬ ‫سلسلة األدوات ‪Toolchain‬‬

‫‪0000000000000000‬‬ ‫‪0 SECTION LOCAL‬‬ ‫‪DEFAULT‬‬ ‫‪7‬‬


‫‪0000000000000000‬‬ ‫‪0 SECTION LOCAL‬‬ ‫‪DEFAULT‬‬ ‫‪8‬‬
‫‪0000000000000000‬‬ ‫‪0 SECTION LOCAL‬‬ ‫‪DEFAULT‬‬ ‫‪10‬‬
‫‪0000000000000000‬‬ ‫‪128 FUNC‬‬ ‫‪GLOBAL DEFAULT‬‬ ‫‪1 function‬‬
‫‪0000000000000000‬‬ ‫‪0 NOTYPE‬‬ ‫‪GLOBAL DEFAULT‬‬ ‫‪UND printf‬‬
‫‪0000000000000000‬‬ ‫‪0 NOTYPE‬‬ ‫‪GLOBAL DEFAULT‬‬ ‫‪UND global‬‬

‫ُيَع د هذا الخرج معقًدا للغاية‪ ،‬ولكن يجب أن تكون قادًرا عىل فهم الكثير منه مثل‪:‬‬

‫الحظ الرمز الذي يحمل االسم ‪ i‬في الخرج ‪ ،hello.o‬حيث ُيس‪َt‬بق ه‪tt‬ذا الرم‪tt‬ز بالكلم‪t‬ة ‪ LOCAL‬أي أن‪t‬ه‬ ‫•‬

‫محلي‪ ،‬ألننا صّرحنا عنه بأنه ساكن ‪ ،static‬وبالتالي ُيمَّيز عىل أنه محلي لملف الكائن‪.‬‬

‫الحظ المتغير ‪ global‬في الخرج نفسه الُم عَّرف عىل أنه متغير عام ‪ ،GLOBAL‬مما يعني أنه مرئي خارج‬ ‫•‬

‫هذا الملف‪ ،‬وتكون الدالة الرئيسية )(‪ main‬مرئية من خارج الملف‪.‬‬

‫الح‪tt‬ظ أن الرم‪tt‬ز ‪ function‬ل‪tt‬ه الن‪tt‬وع ‪ UND‬أو غ‪tt‬ير ُم ع‪َّtt‬رف ‪ Undefined‬من أج‪tt‬ل اس‪tt‬تدعاء الدال‪tt‬ة‬ ‫•‬

‫)(‪ ،function‬أي أن األمر متروك للرابط ‪ Linker‬للعثور عىل عنوان الدالة‪.‬‬

‫الحظ الرموز الموجودة في الملف ‪ function.c‬وكيفية مالءمتها مع الخرج‪.‬‬ ‫•‬

‫‪ 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‬تدَع ى في‬ ‫•‬

‫البرنامج‪.‬‬

‫‪ :crti.o‬توّف ره مكتبات النظام‪.‬‬ ‫•‬

‫‪crtbegin.o‬‬ ‫•‬

‫‪crtsaveres.o‬‬ ‫•‬

‫‪crtend.o‬‬ ‫•‬

‫‪crtn.o‬‬ ‫•‬

‫يمكنك أن ترى بعد ذلك أننا نرب‪tt‬ط ملفي الكائن‪tt‬ات ‪ hello.o‬و ‪ ،function.o‬ثم نح‪ّtt‬دد بعض المكتب‪tt‬ات‬

‫اإلضافية باستخدام رايات ‪ ،-l‬حيث ُتَع د هذه المكتبات خاصًة بالنظام ومطلوبة لكل برنامج‪ .‬الراي‪tt‬ة الرئيس‪tt‬ية هي‬

‫الراية ‪ -lc‬التي تجلب مكتبة ‪ C‬التي تحتوي عىل جميع الدوال المشتركة مثل الدالة )(‪ .printf‬نربط بعد ذل‪tt‬ك‬

‫مرة أخرى بعض ملفات كائنات النظام التي تطّبق بعض عمليات التنظيف بعد انتهاء البرامج‪ُ .‬تَع د هذه التفاصيل‬

‫معقدة‪ ،‬إال أن مفهومها واضح ومباشر‪.‬‬

‫سنربط بعدها جميع ملفات التعليمات الُم صَّرفة مع بعضها بملف واحد قابل للتنفيذ وجاهز للتشغيل‪.‬‬

‫‪ 7.6.4‬الملف القابل للتنفيذ ‪Executable‬‬


‫سندخل في مزيد من التفاصيل حول الملف القابل للتنفيذ الحًق ا‪ ،‬ولكن يمكننا إجراء فحص بطريق‪t‬ة مماثل‪t‬ة‬

‫لملفات الكائنات لمعرفة ما يحدث‪.‬‬

‫‪179‬‬
‫علوم الحاسوب من األلف إىل الياء‬ Toolchain ‫سلسلة األدوات‬

:‫إليك مثال عن ملف قابل للتنفيذ‬

ianw@lime:~/programs/csbu/wk7/code$ gcc -o program hello.c function.c


ianw@lime:~/programs/csbu/wk7/code$ readelf --symbols ./program

Symbol table '.dynsym' contains 11 entries:


Num: Value Size Type Bind Vis Ndx Name
0000000000000000 0 NOTYPE LOCAL DEFAULT UND
6000000000000de0 0 OBJECT GLOBAL DEFAULT ABS _DYNAMIC
0000000000000000 176 FUNC GLOBAL DEFAULT UND printf@GLIBC_2.2 (2)
600000000000109c 0 NOTYPE GLOBAL DEFAULT ABS __bss_start
0000000000000000 704 FUNC GLOBAL DEFAULT UND exit@GLIBC_2.2 (2)
600000000000109c 0 NOTYPE GLOBAL DEFAULT ABS _edata
6000000000000fe8 0 OBJECT GLOBAL DEFAULT ABS _GLOBAL_OFFSET_TABLE_
7: 60000000000010b0 0 NOTYPE GLOBAL DEFAULT ABS _end
0000000000000000 0 NOTYPE WEAK DEFAULT UND _Jv_RegisterClasses
0000000000000000 544 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.2
(2)
0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__

Symbol table '.symtab' contains 127 entries:


Num: Value Size Type Bind Vis Ndx Name
0000000000000000 0 NOTYPE LOCAL DEFAULT UND
40000000000001c8 0 SECTION LOCAL DEFAULT 1
40000000000001e0 0 SECTION LOCAL DEFAULT 2
4000000000000200 0 SECTION LOCAL DEFAULT 3
4000000000000240 0 SECTION LOCAL DEFAULT 4
4000000000000348 0 SECTION LOCAL DEFAULT 5
40000000000003d8 0 SECTION LOCAL DEFAULT 6
40000000000003f0 0 SECTION LOCAL DEFAULT 7
4000000000000410 0 SECTION LOCAL DEFAULT 8
4000000000000440 0 SECTION LOCAL DEFAULT 9
40000000000004a0 0 SECTION LOCAL DEFAULT 10
40000000000004e0 0 SECTION LOCAL DEFAULT 11
40000000000005e0 0 SECTION LOCAL DEFAULT 12
4000000000000b00 0 SECTION LOCAL DEFAULT 13
4000000000000b40 0 SECTION LOCAL DEFAULT 14
4000000000000b60 0 SECTION LOCAL DEFAULT 15
4000000000000bd0 0 SECTION LOCAL DEFAULT 16
4000000000000ce0 0 SECTION LOCAL DEFAULT 17

180
‫علوم الحاسوب من األلف إىل الياء‬ Toolchain ‫سلسلة األدوات‬

6000000000000db8 0 SECTION LOCAL DEFAULT 18


6000000000000dd0 0 SECTION LOCAL DEFAULT 19
6000000000000dd8 0 SECTION LOCAL DEFAULT 20
6000000000000de0 0 SECTION LOCAL DEFAULT 21
6000000000000fc0 0 SECTION LOCAL DEFAULT 22
6000000000000fd0 0 SECTION LOCAL DEFAULT 23
6000000000000fe0 0 SECTION LOCAL DEFAULT 24
6000000000000fe8 0 SECTION LOCAL DEFAULT 25
6000000000001040 0 SECTION LOCAL DEFAULT 26
6000000000001080 0 SECTION LOCAL DEFAULT 27
60000000000010a0 0 SECTION LOCAL DEFAULT 28
60000000000010a8 0 SECTION LOCAL DEFAULT 29
0000000000000000 0 SECTION LOCAL DEFAULT 30
0000000000000000 0 SECTION LOCAL DEFAULT 31
0000000000000000 0 SECTION LOCAL DEFAULT 32
0000000000000000 0 SECTION LOCAL DEFAULT 33
0000000000000000 0 SECTION LOCAL DEFAULT 34
0000000000000000 0 SECTION LOCAL DEFAULT 35
0000000000000000 0 SECTION LOCAL DEFAULT 36
0000000000000000 0 SECTION LOCAL DEFAULT 37
0000000000000000 0 SECTION LOCAL DEFAULT 38
0000000000000000 0 SECTION LOCAL DEFAULT 39
0000000000000000 0 FILE LOCAL DEFAULT ABS /build/buildd/glibc-2.3.2
0000000000000000 0 FILE LOCAL DEFAULT ABS /build/buildd/glibc-2.3.2
0000000000000000 0 FILE LOCAL DEFAULT ABS /build/buildd/glibc-2.3.2
0000000000000000 0 FILE LOCAL DEFAULT ABS /build/buildd/glibc-2.3.2
0000000000000000 0 FILE LOCAL DEFAULT ABS /build/buildd/glibc-2.3.2
0000000000000000 0 FILE LOCAL DEFAULT ABS /build/buildd/glibc-2.3.2
0000000000000000 0 FILE LOCAL DEFAULT ABS <command line>
0000000000000000 0 FILE LOCAL DEFAULT ABS /build/buildd/glibc-2.3.2
0000000000000000 0 FILE LOCAL DEFAULT ABS <command line>
0000000000000000 0 FILE LOCAL DEFAULT ABS <built-in>
0000000000000000 0 FILE LOCAL DEFAULT ABS abi-note.S
0000000000000000 0 FILE LOCAL DEFAULT ABS /build/buildd/glibc-2.3.2
0000000000000000 0 FILE LOCAL DEFAULT ABS abi-note.S
0000000000000000 0 FILE LOCAL DEFAULT ABS /build/buildd/glibc-2.3.2
0000000000000000 0 FILE LOCAL DEFAULT ABS abi-note.S
0000000000000000 0 FILE LOCAL DEFAULT ABS <command line>
0000000000000000 0 FILE LOCAL DEFAULT ABS /build/buildd/glibc-2.3.2

181
‫علوم الحاسوب من األلف إىل الياء‬ Toolchain ‫سلسلة األدوات‬

0000000000000000 0 FILE LOCAL DEFAULT ABS <command line>


0000000000000000 0 FILE LOCAL DEFAULT ABS <built-in>
0000000000000000 0 FILE LOCAL DEFAULT ABS abi-note.S
0000000000000000 0 FILE LOCAL DEFAULT ABS init.c
0000000000000000 0 FILE LOCAL DEFAULT ABS /build/buildd/glibc-2.3.2
0000000000000000 0 FILE LOCAL DEFAULT ABS /build/buildd/glibc-2.3.2
0000000000000000 0 FILE LOCAL DEFAULT ABS initfini.c
0000000000000000 0 FILE LOCAL DEFAULT ABS /build/buildd/glibc-2.3.2
0000000000000000 0 FILE LOCAL DEFAULT ABS <command line>
0000000000000000 0 FILE LOCAL DEFAULT ABS /build/buildd/glibc-2.3.2
0000000000000000 0 FILE LOCAL DEFAULT ABS <command line>
0000000000000000 0 FILE LOCAL DEFAULT ABS <built-in>
0000000000000000 0 FILE LOCAL DEFAULT ABS /build/buildd/glibc-2.3.2
4000000000000670 128 FUNC LOCAL DEFAULT 12 gmon_initializer
0000000000000000 0 FILE LOCAL DEFAULT ABS /build/buildd/glibc-2.3.2
0000000000000000 0 FILE LOCAL DEFAULT ABS /build/buildd/glibc-2.3.2
0000000000000000 0 FILE LOCAL DEFAULT ABS initfini.c
0000000000000000 0 FILE LOCAL DEFAULT ABS /build/buildd/glibc-2.3.2
0000000000000000 0 FILE LOCAL DEFAULT ABS <command line>
0000000000000000 0 FILE LOCAL DEFAULT ABS /build/buildd/glibc-2.3.2
0000000000000000 0 FILE LOCAL DEFAULT ABS <command line>
0000000000000000 0 FILE LOCAL DEFAULT ABS <built-in>
0000000000000000 0 FILE LOCAL DEFAULT ABS /build/buildd/glibc-2.3.2
0000000000000000 0 FILE LOCAL DEFAULT ABS auto-host.h
0000000000000000 0 FILE LOCAL DEFAULT ABS <command line>
0000000000000000 0 FILE LOCAL DEFAULT ABS <built-in>
6000000000000fc0 0 NOTYPE LOCAL DEFAULT 22 __CTOR_LIST__
6000000000000fd0 0 NOTYPE LOCAL DEFAULT 23 __DTOR_LIST__
6000000000000fe0 0 NOTYPE LOCAL DEFAULT 24 __JCR_LIST__
6000000000001088 8 OBJECT LOCAL DEFAULT 27 dtor_ptr
40000000000006f0 128 FUNC LOCAL DEFAULT 12 __do_global_dtors_aux
4000000000000770 128 FUNC LOCAL DEFAULT 12 __do_jv_register_classes
0000000000000000 0 FILE LOCAL DEFAULT ABS hello.c
6000000000001090 4 OBJECT LOCAL DEFAULT 27 i
0000000000000000 0 FILE LOCAL DEFAULT ABS function.c
6000000000001098 4 OBJECT LOCAL DEFAULT 27 i
0000000000000000 0 FILE LOCAL DEFAULT ABS auto-host.h
0000000000000000 0 FILE LOCAL DEFAULT ABS <command line>
0000000000000000 0 FILE LOCAL DEFAULT ABS <built-in>

182
‫علوم الحاسوب من األلف إىل الياء‬ Toolchain ‫سلسلة األدوات‬

6000000000000fc8 0 NOTYPE LOCAL DEFAULT 22 __CTOR_END__


6000000000000fd8 0 NOTYPE LOCAL DEFAULT 23 __DTOR_END__
6000000000000fe0 0 NOTYPE LOCAL DEFAULT 24 __JCR_END__
6000000000000de0 0 OBJECT GLOBAL DEFAULT ABS _DYNAMIC
4000000000000a70 144 FUNC GLOBAL HIDDEN 12 __do_global_ctors_aux
6000000000000dd8 0 NOTYPE GLOBAL DEFAULT ABS __fini_array_end
60000000000010a8 8 OBJECT GLOBAL HIDDEN 29 __dso_handle
40000000000009a0 208 FUNC GLOBAL DEFAULT 12 __libc_csu_fini
0000000000000000 176 FUNC GLOBAL DEFAULT UND printf@@GLIBC_2.2
40000000000004a0 32 FUNC GLOBAL DEFAULT 10 _init
4000000000000850 128 FUNC GLOBAL DEFAULT 12 function
40000000000005e0 144 FUNC GLOBAL DEFAULT 12 _start
6000000000001094 4 OBJECT GLOBAL DEFAULT 27 global
6000000000000dd0 0 NOTYPE GLOBAL DEFAULT ABS __fini_array_start
40000000000008d0 208 FUNC GLOBAL DEFAULT 12 __libc_csu_init
600000000000109c 0 NOTYPE GLOBAL DEFAULT ABS __bss_start
40000000000007f0 96 FUNC GLOBAL DEFAULT 12 main
6000000000000dd0 0 NOTYPE GLOBAL DEFAULT ABS __init_array_end
6000000000000dd8 0 NOTYPE WEAK DEFAULT 20 data_start
4000000000000b00 32 FUNC GLOBAL DEFAULT 13 _fini
0000000000000000 704 FUNC GLOBAL DEFAULT UND exit@@GLIBC_2.2
600000000000109c 0 NOTYPE GLOBAL DEFAULT ABS _edata
6000000000000fe8 0 OBJECT GLOBAL DEFAULT ABS _GLOBAL_OFFSET_TABLE_
60000000000010b0 0 NOTYPE GLOBAL DEFAULT ABS _end
6000000000000db8 0 NOTYPE GLOBAL DEFAULT ABS __init_array_start
6000000000001080 4 OBJECT GLOBAL DEFAULT 27 _IO_stdin_used
60000000000010a0 8 OBJECT GLOBAL DEFAULT 28 __libc_ia64_register_back
6000000000000dd8 0 NOTYPE GLOBAL DEFAULT 20 __data_start
0000000000000000 0 NOTYPE WEAK DEFAULT UND _Jv_RegisterClasses
0000000000000000 544 FUNC GLOBAL DEFAULT UND __libc_start_main@@GLIBC_
0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__

:‫إليك بعض األشياء التي يجب مالحظتها‬

.‫الحظ طريقة بناء الملف القابل للتنفيذ السهلة‬ •

dynsym ‫وز‬tt‫ل رم‬tt‫ة عم‬tt‫رح كيفي‬tt‫ سنش‬.symtab‫ و‬dynsym :‫ا‬tt‫وز هم‬tt‫الحظ وجود نوعين من جداول الرم‬ •

.@ ‫ ولكن الحظ أن بعضها يحمل الرمز‬،‫الحًق ا‬

183
‫علوم الحاسوب من األلف إىل الياء‬ ‫سلسلة األدوات ‪Toolchain‬‬

‫الحظ الرموز العديدة الُم ضَّم نة من ملفات الكائنات اإلضافية‪ ،‬حيث يبدأ الكث‪tt‬ير منه‪tt‬ا ب‪tt‬الرمز __ لتجنب‬ ‫•‬

‫التعارض مع األسماء التي يختارها المبرمج‪ .‬اقرأ واختر الرموز التي ذكرناه‪tt‬ا س‪tt‬ابًق ا من ملف‪tt‬ات الكائن‪tt‬ات‬

‫واكتشف إن تغيرت بأّي شكل من األشكال‪.‬‬

‫‪184‬‬
‫‪ .8‬ما وراء العملية‬

‫‪ 8.1‬نظرة عىل الملفات القابلة للتنفيذ‬


‫يحتوي البرنامج الذي يعمل في الذاكرة عىل مكونين رئيسيين هما‪ :‬الشيفرة البرمجية ‪ Code‬المعروف‪tt‬ة أيًض ا‬

‫باسم النص ‪ Text‬والبيانات ‪ .Data‬ال يبقى الملف القابل للتنفيذ في الذاكرة‪ ،‬ولكنه يقض‪tt‬ي معظم وقت‪tt‬ه بوص‪tt‬فه‬

‫ملًف ا عىل القرص الصلب ينتظر تحميله عند التشغيل‪ُ .‬يَع د الملف مجرد مصفوفة متجاورة من البتات‪ ،‬لذا تبتك‪tt‬ر‬

‫جميع األنظمة طرًق ا لتنظيم الشيفرة البرمجية والبيانات ضمن الملفات للتنفيذ عند الطلب‪ ،‬حيث يشار إىل ه‪tt‬ذه‬

‫الصيغة من الملفات باسم ملف ثنائي ‪ Binary‬أو ملف قاب‪tt‬ل للتنفي‪tt‬ذ ‪ ،Executable‬وتك‪tt‬ون البت‪tt‬ات والبايت‪tt‬ات‬

‫الخاصة بالملف بصيغة جاهزة لوضعها في الذاكرة وتفسيرها مباشرًة بواسطة عتاد المعالج‪.‬‬

‫‪ 8.2‬تمثيل الملفات القابلة للتنفيذ‬


‫يجب أن تحّدد أّي صيغة لملٍف قابل للتنفيذ ‪ executable file‬مك‪tt‬اَن وج‪tt‬ود الش‪tt‬يفرة البرمجي‪tt‬ة والبيان‪tt‬ات‬

‫ضمن الملف الثنائي‪ ،‬حيث ُتَع د الشيفرة البرمجية والبيانات القس‪t‬مين األساس‪t‬يين لمل‪ٍt‬ف قاب‪t‬ل للتنفي‪t‬ذ‪ ،‬وأح‪t‬د‬

‫المكون‪ttt‬ات اإلض‪ttt‬افية ال‪ttt‬تي لم ن‪ttt‬ذكرها ح‪ttt‬تى اآلن ه‪ttt‬و مس‪ttt‬احة تخ‪ttt‬زين المتغ‪ttt‬يرات العام‪ttt‬ة غ‪ttt‬ير الُم هَّي أة‬

‫‪.uninitialised global variables‬‬

‫إذا صّرحنا عن متغير وأعطيناه قيمة أولية‪ ،‬فيجب تخزين هذه القيم‪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‬‬

‫القديم‪ ،‬ولكن ُيرَّجح أن االشتقاق الدقيق له ضاع مع الوقت‪.‬‬

‫‪ 8.2.1‬الصيغة الثنائية ‪Binary Format‬‬


‫ُينَش أ الملف القابل بالتنفي‪tt‬ذ باس‪tt‬تخدام سلس‪t‬لة أدوات من الش‪t‬يفرة المص‪tt‬درية‪ ،‬حيث يجب أن يك‪t‬ون ه‪tt‬ذا‬

‫الملف بصيغة مح‪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‬ا‬

‫بيانات البرنامج ويبلغ طولها ‪ 20‬كيلوبايت"‪.‬‬

‫هناك صيغة معينة أصبحت في اآلونة األخيرة معياًرا لتمثيل الملفات القابل‪tt‬ة للتنفي‪tt‬ذ في األنظم‪t‬ة الحديث‪tt‬ة‬

‫القائمة عىل نظام يونكس‪ ،‬ويطَلق عىل هذا التنسيق بصيغة الرابط والملفات القابلة للتنفي‪tt‬ذ ‪Executable and‬‬

‫‪- Linker Format‬أو ‪ ELF‬اختصاًرا‪ ،‬حيث سنشرحها بمزيد من التفصيل الحًق ا‪.‬‬

‫‪ 8.2.2‬تاريخ الصيغة الثنائية‬


‫سنوضح فيما يلي صيغتين للملفات الثنائية سبقت ظهور صيغة ملفات ‪ ELF‬هما ‪ a.out‬و ‪.COFF‬‬

‫ا‪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‬يغة‬

‫ملفات ‪ ELF‬في لينكس‪.‬‬

‫‪ 8.3‬صيغة ملفات ‪ELF‬‬


‫ُتَع د صيغة ملفات ‪ ELF‬صيغًة مرنة لتمثي‪tt‬ل الش‪tt‬يفرة الثنائي‪tt‬ة في النظ‪tt‬ام‪ ،‬حيث يمكن‪tt‬ك باتب‪tt‬اع معي‪tt‬ار ‪ELF‬‬

‫تمثيل النواة ‪ Kernel‬ثنائًي ا بسهولة مثل تمثيل ملف قابل للتنفيذ أو مكتبة نظام عادية‪ .‬يمكن اس‪tt‬تخدام األدوات‬

‫نفسها لفحص وتشغيل جميع ملفات ‪ ELF‬ويمكن للمطورين الذين يفهم‪tt‬ون ص‪tt‬يغة ملف‪tt‬ات ‪ ELF‬االس‪tt‬تفادة من‬

‫مهاراتهم في معظم األنظمة الحديثة المبنية عىل يونكس‪.‬‬

‫توّس ع الصيغة ‪ ELF‬صيغة الملفات ‪ COFF‬وتمنح الترويسة مرونة كافية لتحديد ع‪tt‬دد عش‪tt‬وائي من األقس‪tt‬ام‪،‬‬

‫بحيث يكون لكل منها خاصياته الخاصة‪ ،‬مما يسّه ل الربط الديناميكي وتنقيح األخطاء ‪.Debugging‬‬

‫شكل ‪ :38‬نظرة عامة عىل ‪ELF‬‬

‫‪187‬‬
‫علوم الحاسوب من األلف إىل الياء‬ ‫ما وراء العملية‬

ELF ‫ ترويسة ملفات‬8.3.1


‫م من‬tt‫ل قس‬tt‫رات لك‬tt‫وي عىل مؤش‬tt‫ ثم يحت‬،‫ف‬tt‫ تِص ف المل‬File Header ‫يحتوي الملف عىل ترويسة ملف‬

‫ة‬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;

:readelf ‫ كما هو موضح باستخدام األداة‬ELF ‫إليك مثال عن ترويسة‬

$ readelf --header /bin/ls

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
‫علوم الحاسوب من األلف إىل الياء‬ ‫ما وراء العملية‬

‫‪Entry point address:‬‬ ‫‪0x10002640‬‬


‫‪Start of program headers:‬‬ ‫)‪52 (bytes into file‬‬
‫‪Start of section headers:‬‬ ‫)‪87460 (bytes into file‬‬
‫‪Flags:‬‬ ‫‪0x0‬‬
‫‪Size of this header:‬‬ ‫)‪52 (bytes‬‬
‫‪Size of program headers:‬‬ ‫)‪32 (bytes‬‬
‫‪Number of program headers:‬‬ ‫‪8‬‬
‫‪Size of section headers:‬‬ ‫)‪40 (bytes‬‬
‫‪Number of section headers:‬‬ ‫‪29‬‬
‫‪Section header string table index: 28‬‬

‫]‪[...‬‬

‫يوّض ح المثال السابق نموذًجا سهل القراءة عىل اإلنسان كما مولًدا باس‪tt‬تخدام برن‪tt‬امج ‪ ،readelf‬وه‪tt‬و ج‪tt‬زء‬

‫من أدوات ‪ Binutils‬في ‪.GNU‬‬

‫ُتوَجد المصفوفة ‪ e_ident‬في بداية أّي ملف ‪ ،ELF‬وتبدأ دائًم ا بمجموعة بايتات سحرية‪ .‬الب‪tt‬ايت األول ه‪tt‬و‬

‫‪ 0x7F‬ثم الثالث‪tt‬ة بايت‪tt‬ات التالي‪tt‬ة هي "‪ ."ELF‬يمكن‪t‬ك فحص مل‪tt‬ف ‪ ELF‬الثن‪t‬ائي ل‪t‬ترى ذل‪t‬ك بنفس‪t‬ك باس‪tt‬تخدام‬

‫األمر ‪.hexdump‬‬

‫يفحص المثال التالي عدد ‪ ELF‬السحري‪:‬‬

‫‪ianw@mingus:~$ hexdump -C /bin/ls | more‬‬


‫‪7f 45 4c 46 01 02 01 00‬‬ ‫‪00 00 00 00 00 00 00 00‬‬ ‫|‪|.ELF............‬‬

‫… )يتبع ذلك بقية البرنامج( ‪...‬‬

‫الحظ وجود البت ‪ 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‬تخزين البتات األقل أهمية أواًل ) ‪ ،‬حيث يستخدم هذا الجهاز المكمل الثنائي لتمثيل األعداد‬

‫السالبة‪ .‬الحظ بعد ذلك أن الخاصية ‪ Machine‬تخبرنا أنه جهاز ‪ PowerPC‬الثنائي‪.‬‬

‫‪189‬‬
‫علوم الحاسوب من األلف إىل الياء‬ ‫ما وراء العملية‬

‫يبدو أن عنوان نقطة الدخول واضح وصريح بدرجة كافية‪ ،‬وهو العن‪t‬وان الموج‪t‬ود في ال‪t‬ذاكرة ال‪t‬ذي تب‪t‬دأ من‪t‬ه‬

‫شيفرة البرن‪t‬امج‪ُ .‬يق‪t‬ال لم‪t‬برمجي لغ‪t‬ة ‪ C‬المبت‪t‬دئين أن الدال‪t‬ة الرئيس‪t‬ية )(‪ main‬هي أول برن‪t‬امج ُيس‪t‬تدَع ى في‬

‫برامجهم‪ ،‬ولكن يمكننا التحقق من أنه ليس كذلك باستخدام عنوان نقطة الدخول كما يلي‪:‬‬

‫‪$ cat test.c‬‬


‫>‪#include <stdio.h‬‬

‫)‪int main(void‬‬
‫{‬
‫;)‪printf("main is : %p\n", &main‬‬
‫;‪return 0‬‬
‫}‬

‫‪$ gcc -Wall -o test test.c‬‬

‫‪$ ./test‬‬
‫‪main is : 0x10000430‬‬

‫'‪$ readelf --headers ./test | grep 'Entry point‬‬


‫‪Entry point address:‬‬ ‫‪0x100002b0‬‬

‫‪$ objdump --disassemble ./test | grep 100002b0‬‬


‫‪100002b0 <_start>:‬‬
‫‪100002b0:‬‬ ‫‪7c 29 0b 78‬‬ ‫‪mr‬‬ ‫‪r9,r1‬‬

‫الحظ في المثال السابق أنه يمكننا أن نرى أن نقطة الدخول هي دال‪tt‬ة تس‪tt‬مى ‪ ._start‬لم يع ‪ّt‬رف برنامجن‪tt‬ا‬

‫هذه الدالة عىل اإلطالق‪ ،‬ويشير الخط السفلي في بداية اسم الدالة إىل أنها موج‪tt‬ودة في فض‪tt‬اء أس‪tt‬ماء منفص‪tt‬ل‪.‬‬

‫سنشرح الحًق ا كيفية بدء البرنامج بالتفصيل‪.‬‬

‫تحتوي الترويسة بعد ذلك عىل مؤش‪t‬رات إىل المك‪t‬ان الموج‪t‬ود في المل‪t‬ف ال‪t‬ذي تب‪t‬دأ في‪t‬ه األج‪t‬زاء المهم‪t‬ة‬

‫األخرى من ملف ‪ ELF‬مثل جدول المحتويات‪.‬‬

‫‪190‬‬
‫علوم الحاسوب من األلف إىل الياء‬ ‫ما وراء العملية‬

‫‪ 8.3.2‬الرموز ‪ Symbols‬والمنقوالت ‪Relocation‬‬


‫توفر مواصفات ملف ‪ ELF‬جداول رموز ‪ Symbol Tables‬تربط بين السالسل النصية أو الرم‪t‬وز ومواق‪t‬ع في‬

‫الملف‪ُ .‬تَع د الرم‪t‬وز مطلوب‪t‬ة للرب‪t‬ط ‪ ،Linking‬فمثاًل يمكن أن يتطلب إس‪t‬ناُد قيم‪t‬ة للمتغ‪t‬ير ‪ foo‬الُم ص‪َّt‬ر ح عن‪t‬ه‬

‫بالشكل ‪ extern int foo‬رابًط ا للعثور عىل عنوان المتغير ‪ ،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‬ة‬

‫مثاًل ‪ ،‬ولكن يتطلب ذلك تحليل الرمز "‪."foo‬‬

‫‪ 8.3.3‬المقاطع ‪ Segments‬واألقسام ‪Sections‬‬


‫تحدد صيغة ‪ ELF‬عرضين لملف ‪ ،ELF‬حيث ُيستخَدم أحدهما للربط واآلخر للتنفيذ‪ ،‬مم‪tt‬ا ي‪tt‬وفر مرون‪tt‬ة كب‪tt‬يرة‬

‫لمصممي األنظمة‪ .‬سنتحدث عن األقسام الموجودة في شيفرة الكائن التي تنتظر أن ُترَبط بملف قاب‪tt‬ل للتنفي‪tt‬ذ‪،‬‬

‫وُيرَبط قسم واحد أو أكثر مع مقطٍع ما في الملف القابل للتنفيذ‪.‬‬

‫ا‪ .‬المقاطع ‪Segments‬‬

‫من األسهل في بعض األحيان النظر إىل المستوى األعىل من التجريد ‪ 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‬‬
‫علوم الحاسوب من األلف إىل الياء‬ ‫ما وراء العملية‬

‫ب‪ .‬األقسام ‪Sections‬‬

‫تشّكل األقسام مقاطًع ا‪ ،‬حيث ُتَع د األقس‪tt‬ام طريق‪tt‬ة لتنظيم المل‪tt‬ف الثن‪tt‬ائي في من‪tt‬اطق منطقي‪tt‬ة لتوص‪tt‬يل‬

‫المعلوم‪tt‬ات بين المص‪ِّt‬رف والراب‪tt‬ط‪ُ .‬تس‪tt‬تخَدم األقس‪tt‬ام في بعض الملف‪tt‬ات الثنائي‪tt‬ة الخاص‪tt‬ة مث‪tt‬ل ن‪tt‬واة لينكس‬

‫‪ Linux Kernel‬بطرق أكثر تحديًدا سنوضحها الحًق ا‪.‬‬

‫رأينا كيف تصل المقاطع في النهاية إىل كتلة بيانات في ملف عىل القرص الص‪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‬‬

‫;"!‪char *a_string = "Hello, World‬‬

‫‪193‬‬
‫علوم الحاسوب من األلف إىل الياء‬ ‫ما وراء العملية‬

int a_var_with_value = 0x100;

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 ‫يوضح المثال التالي خرج األداة‬

:‫تحليل كل جزء من برنامجنا البسيط السابق ومعرفة ما سيحدث به في خرج الملف الثنائي النهائي‬

$ readelf --all ./sections


ELF Header:
...
Size of section headers: 40 (bytes)
Number of section headers: 37
Section header string table index: 34

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
‫علوم الحاسوب من األلف إىل الياء‬ ‫ما وراء العملية‬

[10] .init PROGBITS 10000280 000280 000028 00 AX 0 0


4
[11] .text PROGBITS 100002b0 0002b0 000560 00 AX 0 0
16
[12] .fini PROGBITS 10000810 000810 000020 00 AX 0 0
4
[13] .rodata PROGBITS 10000830 000830 000024 00 A 0 0
4
[14] .sdata2 PROGBITS 10000854 000854 000000 00 A 0 0
4
[15] .eh_frame PROGBITS 10000854 000854 000004 00 A 0 0
4
[16] .ctors PROGBITS 10010858 000858 000008 00 WA 0 0
4
[17] .dtors PROGBITS 10010860 000860 000008 00 WA 0 0
4
[18] .jcr PROGBITS 10010868 000868 000004 00 WA 0 0
4
[19] .got2 PROGBITS 1001086c 00086c 000010 00 WA 0 0
1
[20] .dynamic DYNAMIC 1001087c 00087c 0000c8 08 WA 5 0
4
[21] .data PROGBITS 10010944 000944 000008 00 WA 0 0
4
[22] .got PROGBITS 1001094c 00094c 000014 04 WAX 0 0
4
[23] .sdata PROGBITS 10010960 000960 000008 00 WA 0 0
4
[24] .sbss NOBITS 10010968 000968 000000 00 WA 0 0
1
[25] .plt NOBITS 10010968 000968 000060 00 WAX 0 0
4
[26] .bss NOBITS 100109c8 000968 2800004 00 WA 0 0
4
[27] .comment PROGBITS 00000000 000968 00018f 00 0 0
1
[28] .debug_aranges PROGBITS 00000000 000af8 000078 00 0 0
8
[29] .debug_pubnames PROGBITS 00000000 000b70 000025 00 0 0
1
[30] .debug_info PROGBITS 00000000 000b95 0002e5 00 0 0
1
[31] .debug_abbrev PROGBITS 00000000 000e7a 000076 00 0 0
1
[32] .debug_line PROGBITS 00000000 000ef0 0001de 00 0 0
1

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‬‬

‫‪There are no section groups in this file.‬‬


‫‪...‬‬

‫‪Symbol table '.symtab' contains 201 entries:‬‬


‫‪Num:‬‬ ‫‪Value‬‬ ‫‪Size Type‬‬ ‫‪Bind‬‬ ‫‪Vis‬‬ ‫‪Ndx Name‬‬
‫‪...‬‬
‫‪100109cc 0x2800000 OBJECT‬‬ ‫‪GLOBAL DEFAULT‬‬ ‫‪26 big_big_array‬‬
‫‪...‬‬
‫‪10010960‬‬ ‫‪4 OBJECT‬‬ ‫‪GLOBAL DEFAULT‬‬ ‫‪23 a_string‬‬
‫‪...‬‬
‫‪10010964‬‬ ‫‪4 OBJECT‬‬ ‫‪GLOBAL DEFAULT‬‬ ‫‪23 a_var_with_value‬‬
‫‪...‬‬
‫‪10000430‬‬ ‫‪96 FUNC‬‬ ‫‪GLOBAL DEFAULT‬‬ ‫‪11 main‬‬

‫لنلِق أواًل نظرة عىل المتغير ‪ 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 ‫قيمتين مخزنتين في مسجلين‬

.‫ يوجد في المكان نفسه‬a_var_with_value ‫ يمكننا أيًض ا أن نرى أن المتغير‬.‫عن العنوان‬

‫تخَدمان‬tt‫" ُيس‬Code ‫" و"الشيفرة‬Text ‫ تذكر أن "النص‬..text ‫ في القسم‬main ‫بينما توجد الدالة الرئيسية‬

.‫لإلشارة إىل برنامج في الذاكرة‬

‫ األقسام والمقاطع مع بعضها بعًض ا‬.‫ج‬

:‫إليك مثال يحتوي عىل األقسام والمقاطع مع بعضها بعًض ا‬

$ readelf --segments /bin/ls

Elf file type is EXEC (Executable file)


Entry point 0x100026c0
There are 8 program headers, starting at offset 52

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

Section to Segment mapping:


Segment Sections...
00
.interp
.interp .note.ABI-tag .hash .dynsym .dynstr .gnu.version .gnu.version_ r
.rela.dyn .rela.plt .init .text .fini .rodata .eh_frame_hdr
.data .eh_frame .got2 .dynamic .ctors .dtors .jcr .got .sdata .sbss .p lt .bss

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‬اختصاًرا‪ -‬التي تتطلب أن‬

‫يكون قسم البيانات قاباًل للتنفيذ‪ .‬هذه هي حياة مبرمج األنظمة‪ ،‬إذ ُو ِض عت القواعد لكسرها‪.‬‬

‫تستدعي واجهة ‪ ABI‬في معمارية ‪ PowerPC‬شيفرات اختباري‪tt‬ة ‪ Stubs‬لل‪tt‬دوال في المكتب‪tt‬ات الديناميكي‪tt‬ة‬

‫مباشرًة في جدول اإلزاحة العام ‪- 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‬دًدا من‬

‫المفاهيم المهمة لفهم واجهات ‪.ABI‬‬

‫‪198‬‬
‫علوم الحاسوب من األلف إىل الياء‬ ‫ما وراء العملية‬

‫‪ 8.4.1‬ترتيب البايتات‬
‫ُترَّتب البايتات باستخدام ترتيب ‪ Endianess‬الذي يحتوي عىل نوعين هما‪ Big-endian :‬أي تخزين البت‪tt‬ات‬

‫األقل أهمية أواًل ‪ ،‬و ‪ Little-endian‬أي تخزين البتات األكثر أهمية أواًل ‪.‬‬

‫‪ 8.4.2‬العرف المتبع في االستدعاءات‬


‫يمكن تنفيذ االستدعاءات بطريقتين هما‪ :‬تمرير المع‪tt‬امالت باس‪tt‬تخدام المس‪tt‬جالت ‪ registers‬أو المك‪tt‬دس‬

‫‪ 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‬‬
‫علوم الحاسوب من األلف إىل الياء‬ ‫ما وراء العملية‬

‫‪ 8.5.1‬المكتبات الساكنة ‪Static Libraries‬‬


‫الطريقة األكثر مباشرة الستخدام دالة المكتبة هي ربط ملفات الكائنات من المكتبة مباشرة بملف‪tt‬ك النه‪tt‬ائي‬

‫القابل للتنفيذ كما هو الحال مع تلك الملفات التي صَّرفتها بنفسك‪ ،‬وعندها ُتسَّم ى المكتب‪tt‬ة مكتب‪tt‬ة س‪tt‬اكنة‪ ،‬ألن‬

‫المكتبة ستبقى دون تغيير ما لم ُيعاد تصريف البرنامج‪ُ .‬تَع د هذه الطريقة الستخدام مكتب‪tt‬ة الطريق‪tt‬ة األس‪tt‬هل ألن‬

‫النتيجة النهائية هي ملف قابل للتنفيذ بسيط بدون اعتماديات‪.‬‬

‫ُتَع د المكتبة الساكنة مجموعًة من ملفات الكائن‪tt‬ات‪ ،‬حيث ُيحتَف ظ بملف‪tt‬ات الكائن‪tt‬ات في س‪tt‬جل ‪،Archive‬‬

‫مما يؤدي إىل استخدام الحقتها المعتادة ‪ ..a‬يمكنك التفكير في ه‪tt‬ذه الس‪tt‬جالت بوص‪tt‬فها ملًف ا مض‪tt‬غوًط ا ولكن‬

‫بدون ضغط‪ .‬يوّض ح المثال التالي كيفية إنشاء مكتبة ساكنة بسيطة ويق‪t‬دم بعض األدوات الش‪t‬ائعة للتعام‪t‬ل م‪t‬ع‬

‫المكتبات‪:‬‬

‫‪$ cat library.c‬‬


‫‪ */‬دالة مكتبة *‪/‬‬
‫)‪int function(int input‬‬
‫{‬
‫;‪return input + 10‬‬
‫}‬

‫‪$ cat library.h‬‬


‫‪ */‬تعريف الدالة *‪/‬‬
‫;)‪int function(int‬‬

‫‪$ cat program.c‬‬


‫>‪#include <stdio.h‬‬
‫‪ */‬ترويسة ملف المكتبة *‪/‬‬
‫"‪#include "library.h‬‬

‫)‪int main(void‬‬
‫{‬
‫;)‪int d = function(100‬‬

‫;)‪printf("%d\n", d‬‬
‫}‬

‫‪$ gcc -c library.c‬‬

‫‪200‬‬
‫علوم الحاسوب من األلف إىل الياء‬ ‫ما وراء العملية‬

‫‪$ ar rc libtest.a library.o‬‬


‫‪$ ranlib ./libtest.a‬‬
‫‪$ nm --print-armap ./libtest.a‬‬

‫‪Archive index:‬‬
‫‪function in library.o‬‬

‫‪library.o:‬‬
‫‪T function‬‬

‫‪$ gcc -L . program.c -ltest -o program‬‬

‫‪$ ./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‬اص‬

‫بالدالة )(‪ function‬عند إزاحة بمقدار صفر كما هو متوقع‪.‬‬

‫‪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‬اركة‬

‫الشيفرة البرمجية بين التطبيقين‪ ،‬مما يوفر موارد كبيرة مع المكتبات شائعة االستخدام‪.‬‬

‫ُيَع د الربط الديناميكي الذي تحدثنا عنه سابًق ا أحد األجزاء األكثر تعقيًدا في نظام التشغيل الحديث‪.‬‬

‫‪ 8.6‬مفاهيم متقدمة متعلقة بصيغة ملفات ‪ELF‬‬


‫تعرفنا في الفقرات السابقة عىل الملفات القابلة للتنفيذ في نظام التشغيل وتمثيلها باستخدام الصيغة ‪ELF‬‬

‫وسنوضح في الفقرات التالية بعض المفاهيم المتعلقة بص‪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‫سمح هذا الخطأ للمهاجم بإدخال بيانات عش‬

.‫البرمجية في تلك الصفحات‬

$ readelf --segments /bin/ls

Elf file type is EXEC (Executable file)


Entry point 0x4046d4
There are 8 program headers, starting at offset 64

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

Section to Segment mapping:


Segment Sections...

.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
‫علوم الحاسوب من األلف إىل الياء‬ ‫ما وراء العملية‬

‫‪.ctors .dtors .jcr .dynamic .got .got.plt .data .bss‬‬


‫‪.dynamic‬‬
‫‪.note.ABI-tag .note.gnu.build-id‬‬
‫‪.eh_frame_hdr‬‬
‫‪07‬‬

‫يجب تحميل مقاطع البرنامج عىل هذه العن‪t‬اوين‪ ،‬حيث تتمث‪tt‬ل الخط‪tt‬وة األخ‪t‬يرة للراب‪t‬ط ‪ Linker‬في تحلي‪tt‬ل‬

‫معظم المنقوالت ‪ Relocations‬وتصحيحها باستخدام العناوين المطلق‪tt‬ة الُم فتَرض‪tt‬ة‪ ،‬ثم تجاه‪tt‬ل البيان‪tt‬ات ال‪tt‬تي‬

‫تصف االنتقال في الملف الثنائي النهائي دون وجود طريقة إليجاد هذه المعلومات بعد اآلن‪.‬‬

‫تحت‪t‬وي الملف‪t‬ات القابل‪t‬ة للتنفي‪t‬ذ عموًم ا عىل اعتمادي‪t‬ات ‪ Dependencies‬خارجي‪t‬ة للمكتب‪t‬ات المش‪t‬تركة‬

‫‪ Shared Libraries‬أو أجزاء من الشيفرة البرمجية المشتركة يمكن تجريدها ومشاركتها بين أجزاء النظام بأكمله‪،‬‬

‫حيث تتعلق جميع األجزاء الغريبة في المثال السابق باستخدام المكتبات المشتركة التي سنوّض حها الحًق ا‪.‬‬

‫‪ 8.6.1‬تنقيح األخطاء ‪Debugging‬‬


‫ُيشار تقليدًيا إىل الطريقة األساسية لتنقيح أخطاء ما بعد التعط‪tt‬ل باس‪tt‬م التفري‪tt‬غ األساس‪tt‬ي ‪،Core Dump‬‬

‫حيث يأتي مصطلح األساسي ‪ Core‬من الخص‪tt‬ائص الفيزيائي‪tt‬ة األص‪tt‬لية لل‪tt‬ذاكرة المغناطيس‪tt‬ية األساس‪tt‬ية ال‪tt‬تي‬

‫تستخدم اتجاه الحلقات المغناطيسية الصغيرة لتخزين الحالة‪ُ .‬يَع د التفريغ األساس‪tt‬ي لقط‪tt‬ة كامل‪tt‬ة للبرن‪tt‬امج عن‪tt‬د‬

‫عمله في وقت معين‪ ،‬ويمكن بعد ذلك استخدام منقح أخطاء ‪ Debugger‬لفحص هذا التفريغ وإعادة بن‪tt‬اء حال‪tt‬ة‬

‫البرنامج‪ .‬يوضح المثال التالي نموذًجا لبرنامج يكتب في موقع ذاكرة عش‪tt‬وائية لغ‪tt‬رض التعط‪tt‬ل‪ ،‬حيث س‪tt‬تتوقف‬

‫العمليات وُيسَّجل تفريغ للحالة الحالية‪:‬‬

‫‪$ cat coredump.c‬‬


‫{ )‪int main(void‬‬
‫;‪char *foo = (char*)0x12345‬‬
‫;'‪*foo = 'a‬‬

‫;‪return 0‬‬
‫}‬

‫‪$ gcc -Wall -g -o coredump coredump.c‬‬

‫‪$ ./coredump‬‬
‫)‪Segmentation fault (core dumped‬‬

‫‪$ file ./core‬‬

‫‪204‬‬
‫علوم الحاسوب من األلف إىل الياء‬ ‫ما وراء العملية‬

‫‪./core: ELF 32-bit LSB core file Intel 80386, version 1 (SYSV), SVR4-‬‬
‫'‪style, from './coredump‬‬

‫‪$ gdb ./coredump‬‬


‫‪...‬‬
‫‪(gdb) core core‬‬
‫]‪[New LWP 31614‬‬
‫‪Core was generated by `./coredump'.‬‬
‫‪Program terminated with signal 11, Segmentation fault.‬‬
‫‪#0‬‬ ‫‪0x080483c4 in main () at coredump.c:3‬‬
‫;'‪*foo = 'a‬‬
‫)‪(gdb‬‬

‫وبالتالي فإن ملف التفريغ األساسي هو مجرد مل‪tt‬ف ‪ ELF‬يحت‪tt‬وي عىل مجموع‪tt‬ة من األقس‪tt‬ام ال‪tt‬تي يفهمه‪tt‬ا‬

‫منقح األخطاء لتمثيل أجزاء من البرنامج الُم شَّغ ل‪.‬‬

‫‪ 8.6.2‬الرموز ومعلومات تنقيح األخطاء‬


‫يتطلب منقح األخطاء ‪ gdb‬الملف القابل للتنفيذ األصلي وملف التفريغ األساس‪t‬ي إلع‪t‬ادة بن‪t‬اء بيئ‪t‬ة جلس‪t‬ة‬

‫تنقيح األخطاء‪ .‬الحظ أن الملف القابل للتنفيذ األصلي ُأنشئ باستخدام الراية ‪ -g‬التي توجه المصِّرف ‪Compiler‬‬

‫لتضمين جميع معلومات األخطاء‪ ،‬حيث يجري االحتفاظ بالمعلومات المتعلقة بعملي‪tt‬ة تنقيح األخط‪tt‬اء اإلض‪tt‬افية‬

‫في أقسام خاصة من ملف ‪ ،ELF‬إذ تصف هذه المعلومات بالتفصيل أشياًء مثل قيم المسّجل التي تحتوي حالًي ا‬

‫عىل المتغيرات المستخدمة في الشيفرة البرمجية وحجم المتغيرات وطول المصفوفات وغ‪tt‬ير ذل‪tt‬ك‪ .‬تك‪tt‬ون ه‪tt‬ذه‬

‫المعلومات بصيغة ‪ DWARF‬المعيارية التي ُتَع د مرادًف ا لصيغة ‪ ELF‬تقريًبا‪.‬‬

‫يمكن أن يؤدي تضمين معلومات تنقيح األخطاء إىل جعل الملفات والمكتبات القابلة للتنفيذ كبيرة ج‪ً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‬‬
‫علوم الحاسوب من األلف إىل الياء‬ ‫ما وراء العملية‬

$ gcc -g -shared -o libtest.so libtest.c


$ objcopy --only-keep-debug libtest.so libtest.debug
$ objcopy --add-gnu-debuglink=libtest.debug libtest.so
$ objdump -s -j .gnu_debuglink libtest.so

libtest.so: file format elf32-i386

Contents of section .gnu_debuglink:


6c696274 6573742e 64656275 67000000 libtest.debug...
52a7fd0a R...

‫اء‬tt‫ة لبق‬tt‫اك حاج‬tt‫ون هن‬tt‫ إذ لن تك‬،‫ ولكنها ُتَع د هدًف ا لإلزالة من الخرج النهائي‬،‫تشغل الرموز مساحة أقل بكثير‬

‫ ُتَع د‬.‫ة‬tt‫ورة النهائي‬tt‫ذ بالص‬tt‫ لملف قابل للتنفي‬object files ‫معظم الرموز بمجرد ربط ملفات التعليمات الُم صَّرفة‬

‫غيل‬tt‫ ولكن لن تكون الرموز بعد ذلك ضرورية تماًم ا لتش‬،Relocation ‫الرموز مطلوبة إلصالح مدخالت المنقوالت‬

‫ه‬tt‫ظ أن‬tt‫ الح‬.‫وز‬tt‫ة الرم‬tt‫اراٍت إلزال‬tt‫ام لينكس خي‬tt‫امج في نظ‬tt‫ لتجريد البرن‬GNU ‫ توفر سلسلة أدوات‬.‫البرنامج النهائي‬

‫ ولكنها ُتوَض ع في جداول‬،)Dynamic Linking ‫يجب تحليل بعض الرموز في وقت التشغيل (للربط الديناميكي‬

.‫رموز ديناميكية منفصلة حتى ال ُتزال وتجعل الخرج النهائي عديم الفائدة‬

Coredump ‫ التفريغ األسايس‬8.6.3


:‫ بوصفها صيغة ثنائية‬ELF ‫ يوضح المثال التالي مرونة صيغة‬.ELF ‫ُيَع د التفريغ األساسي مجرد ملف‬

$ readelf --all ./core


ELF Header:
Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
Class: ELF32
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: CORE (Core file)
Machine: Intel 80386
Version: 0x1
Entry point address: 0x0
Start of program headers: 52 (bytes into file)
Start of section headers: 0 (bytes into file)
Flags: 0x0
Size of this header: 52 (bytes)

206
‫علوم الحاسوب من األلف إىل الياء‬ ‫ما وراء العملية‬

Size of program headers: 32 (bytes)


Number of program headers: 15
Size of section headers: 0 (bytes)
Number of section headers: 0
Section header string table index: 0

There are no sections in this file.

There are no sections to group in this file.

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

There is no dynamic section in this file.

There are no relocations in this file.

There are no unwind sections in this file.

No version information found in this file.

Notes at offset 0x00000214 with length 0x0000022c:


Owner Data size Description
CORE 0x00000090 NT_PRSTATUS (prstatus structure)

207
‫علوم الحاسوب من األلف إىل الياء‬ ‫ما وراء العملية‬

CORE 0x0000007c NT_PRPSINFO (prpsinfo structure)


CORE 0x000000a0 NT_AUXV (auxiliary vector)
LINUX 0x00000030 Unknown note type: (0x00000200)

$ eu-readelf -n ./core

Note segment of 556 bytes at offset 0x214:


Owner Data size Type
CORE 144 PRSTATUS
info.si_signo: 11, info.si_code: 0, info.si_errno: 0, cursig: 11
sigpend: <>
sighold: <>
pid: 31614, ppid: 31544, pgrp: 31614, sid: 31544
utime: 0.000000, stime: 0.000000, cutime: 0.000000, cstime: 0.000000
orig_eax: -1, fpvalid: 0
ebx: 1219973108 ecx: 1243440144 edx: 1
esi: 0 edi: 0 ebp: 0xbfecb828
eax: 74565 eip: 0x080483c4 eflags: 0x00010286
esp: 0xbfecb818
ds: 0x007b es: 0x007b fs: 0x0000 gs: 0x0033 cs: 0x0073 ss: 0x007b
CORE 124 PRPSINFO
state: 0, sname: R, zomb: 0, nice: 0, flag: 0x00400400
uid: 1000, gid: 1000, pid: 31614, ppid: 31544, pgrp: 31614, sid: 31544
fname: coredump, psargs: ./coredump
CORE 160 AUXV
SYSINFO: 0xb774f414
SYSINFO_EHDR: 0xb774f000
HWCAP: 0xafe8fbff <fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge
mca cmov clflush dts acpi mmx fxsr sse sse2 ss tm pbe>
PAGESZ: 4096
CLKTCK: 100
PHDR: 0x8048034
PHENT: 32
PHNUM: 8
BASE: 0
FLAGS: 0
ENTRY: 0x8048300
UID: 1000
EUID: 1000

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‬ر‬

‫‪ thread‬إلظهار الخيوط والتبديل بينها‪.‬‬

‫‪209‬‬
‫علوم الحاسوب من األلف إىل الياء‬ ‫ما وراء العملية‬

‫تنشئ النواة ملف التفريغ األساسي ضمن حدود إعدادات ‪ ulimit‬الحالية‪ ،‬إذ يمكن أن يؤدي البرنامج الذي‬

‫يستخدم قدًرا كبيًرا من الذاكرة إىل وجود ملف تفريغ كبير جًدا‪ ،‬وُيحتَم ل أن يمأل القرص الص‪tt‬لب ويزي‪tt‬د المش‪tt‬اكل‬

‫سوًءا‪ ،‬وُيضَبط ‪ ulimit‬عىل مستوى منخفض أو حتى عىل القيم‪tt‬ة الص‪tt‬فر ألن معظم األش‪tt‬خاص ال‪tt‬ذين ليس‪tt‬وا‬

‫مطورين لديهم استخدام ضئيل لملف التفريغ األساسي‪ .‬لكن يظل التفري‪tt‬غ األساس‪tt‬ي ه‪tt‬و الطريق‪tt‬ة األك‪tt‬ثر فائ‪tt‬دة‬

‫لتنقيح أخطاء حالة غير متوقعة بعد التعطل‪.‬‬

‫‪ 8.6.4‬إنشاء أقسام مخصصة‬


‫ُيَع د تنظيم الشيفرة والبيانات والرموز شيًئا يمكن للمبرمج ترك إعدادات سلسلة أدواته االفتراضية كم‪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‬ير‬

‫تقديمات نظام الملفات للنواة‪:‬‬

‫)‪$ cd /lib/modules/$(uname -r‬‬

‫‪$ sudo modinfo ./kernel/fs/fuse/fuse.ko‬‬


‫‪filename:‬‬ ‫‪/lib/modules/3.2.0-4-amd64/./kernel/fs/fuse/fuse.ko‬‬
‫‪alias:‬‬ ‫‪devname:fuse‬‬
‫‪alias:‬‬ ‫‪char-major-10-229‬‬
‫‪license:‬‬ ‫‪GPL‬‬
‫‪description:‬‬ ‫‪Filesystem in Userspace‬‬
‫‪author:‬‬ ‫>‪Miklos Szeredi <miklos@szeredi.hu‬‬
‫‪depends:‬‬
‫‪intree:‬‬ ‫‪Y‬‬
‫‪vermagic:‬‬ ‫‪3.2.0-4-amd64 SMP mod_unload modversions‬‬
‫‪parm:‬‬ ‫‪max_user_bgreq:Global limit for the maximum number of‬‬
‫)‪backgrounded requests an unprivileged user can set (uint‬‬
‫‪parm:‬‬ ‫‪max_user_congthresh:Global limit for the maximum congestion‬‬
‫)‪threshold an unprivileged user can set (uint‬‬

‫‪$ objdump -s -j .modinfo ./kernel/fs/fuse/fuse.ko‬‬

‫‪210‬‬
‫علوم الحاسوب من األلف إىل الياء‬ ‫ما وراء العملية‬

./kernel/fs/fuse/fuse.ko: file format elf64-x86-64

Contents of section .modinfo:


616c6961 733d6465 766e616d 653a6675 alias=devname:fu
73650061 6c696173 3d636861 722d6d61 se.alias=char-ma
6a6f722d 31302d32 32390070 61726d3d jor-10-229.parm=
6d61785f 75736572 5f636f6e 67746872 max_user_congthr
6573683a 476c6f62 616c206c 696d6974 esh:Global limit
20666f72 20746865 206d6178 696d756d for the maximum
20636f6e 67657374 696f6e20 74687265 congestion thre
73686f6c 6420616e 20756e70 72697669 shold an unprivi
6c656765 64207573 65722063 616e2073 leged user can s
65740070 61726d74 7970653d 6d61785f et.parmtype=max_
00a0 75736572 5f636f6e 67746872 6573683a user_congthresh:
00b0 75696e74 00706172 6d3d6d61 785f7573 uint.parm=max_us
00c0 65725f62 67726571 3a476c6f 62616c20 er_bgreq:Global
00d0 6c696d69 7420666f 72207468 65206d61 limit for the ma
00e0 78696d75 6d206e75 6d626572 206f6620 ximum number of
00f0 6261636b 67726f75 6e646564 20726571 backgrounded req
75657374 7320616e 20756e70 72697669 uests an unprivi
6c656765 64207573 65722063 616e2073 leged user can s
65740070 61726d74 7970653d 6d61785f et.parmtype=max_
75736572 5f626772 65713a75 696e7400 user_bgreq:uint.
6c696365 6e73653d 47504c00 64657363 license=GPL.desc
72697074 696f6e3d 46696c65 73797374 ription=Filesyst
656d2069 6e205573 65727370 61636500 em in Userspace.
61757468 6f723d4d 696b6c6f 7320537a author=Miklos Sz
65726564 69203c6d 696b6c6f 7340737a eredi <miklos@sz
65726564 692e6875 3e000000 00000000 eredi.hu>.......
01a0 64657065 6e64733d 00696e74 7265653d depends=.intree=
01b0 59007665 726d6167 69633d33 2e322e30 Y.vermagic=3.2.0
01c0 2d342d61 6d643634 20534d50 206d6f64 -4-amd64 SMP mod
01d0 5f756e6c 6f616420 6d6f6476 65727369 _unload modversi

01e0 6f6e7320 00 ons .

‫ يوضح المثال التالي‬.‫ الُم ضَّم ن في ملف الوحدة لتقديم تفاصيلها‬.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)

#define __UNIQUE_ID(prefix) __PASTE(__PASTE(__UNIQUE_ID_, prefix),


__COUNTER__)

/* ‫ يسمح إنشاء مستويين للمعامل بأن يكون‬.‫تحويل الماكرو غير المباشر إلى سلسلة نصية‬
‫" عند التصريف‬bar" ‫ مثاًل إلى السلسلة‬stringify(FOO)__ ‫ حيث يتحّول‬،‫ماكرو بحد ذاته‬
-DFOO=bar ‫* باستخدام‬/

#define __stringify_1(x...) #x
#define __stringify(x...) __stringify_1(x)

#define __MODULE_INFO(tag, name, info)


static const char __UNIQUE_ID(name)[]
__used __attribute__((section(".modinfo"), unused, aligned(1)))
= __stringify(tag) "=" info

/* tag = "info" ‫* معلومات عامة للصيغة‬/


#define MODULE_INFO(tag, info) __MODULE_INFO(tag, tag, info)

/*
* ‫" فقط‬Name" ‫" أو‬Name <email>" ‫استخدم للمؤلفين المتعددين الذين يستخدمون‬
* ‫ متعددة‬MODULE_AUTHOR() ‫تعليمات أو سطور‬
*/
#define MODULE_AUTHOR(_author) MODULE_INFO(author, _author)

/* ---- */

MODULE_AUTHOR("Your Name <your@name.com>");

،__MODULE_INFO ‫اكرو األعم‬tt‫ تغّل ف الم‬MODULE_AUTHOR ‫لنبدأ من الجزء السفلي حيث نرى أن الوحدة‬

‫ية‬tt‫لة النص‬tt‫وي عىل السلس‬tt‫ ليحت‬static const char [] ‫وع‬tt‫يًرا من الن‬tt‫ني متغ‬tt‫ا نب‬tt‫رى أنن‬tt‫ا أن ن‬tt‫ويمكنن‬

212
‫علوم الحاسوب من األلف إىل الياء‬ ‫ما وراء العملية‬

‫">‪ ."author=Your Name <your@name.com‬الحظ أن المتغير لديه معامل إضافي (__‪__attribute‬‬

‫)))"‪ (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‬متعددة في ملف واحد دون استخدام اسم المتغير نفسه‪.‬‬

‫يمكننا فحص الرموز الموضوعة في الوحدة النهائية لمعرفة النتيجة النهائية كما يلي‪:‬‬

‫‪$ objdump --syms ./fuse.ko | grep modinfo‬‬

‫‪l‬‬ ‫‪d‬‬ ‫‪.modinfo‬‬ ‫‪0000000000000000 .modinfo‬‬


‫‪l‬‬ ‫‪O .modinfo‬‬ ‫‪0000000000000013 __UNIQUE_ID_alias1‬‬
‫‪l‬‬ ‫‪O .modinfo‬‬ ‫‪0000000000000018 __UNIQUE_ID_alias0‬‬
‫‪000000000000002b l‬‬ ‫‪O .modinfo‬‬ ‫‪0000000000000011 __UNIQUE_ID_alias8‬‬
‫‪000000000000003c l‬‬ ‫‪O .modinfo‬‬ ‫‪000000000000000e __UNIQUE_ID_alias7‬‬
‫‪000000000000004a l‬‬ ‫‪O .modinfo‬‬ ‫‪0000000000000068‬‬
‫‪__UNIQUE_ID_max_user_congthresh6‬‬
‫‪00000000000000b2 l‬‬ ‫‪O .modinfo 0000000000000022‬‬
‫‪__UNIQUE_ID_max_user_congthreshtype5‬‬
‫‪00000000000000d4 l‬‬ ‫‪O .modinfo‬‬ ‫‪000000000000006e‬‬
‫‪__UNIQUE_ID_max_user_bgreq4‬‬
‫‪l‬‬ ‫‪O .modinfo‬‬ ‫‪000000000000001d __UNIQUE_ID_max_user_bgreqtype3‬‬
‫‪000000000000015f l‬‬ ‫‪O .modinfo‬‬ ‫‪000000000000000c __UNIQUE_ID_license2‬‬
‫‪000000000000016b l‬‬ ‫‪O .modinfo‬‬ ‫‪0000000000000024 __UNIQUE_ID_description1‬‬
‫‪000000000000018f l‬‬ ‫‪O .modinfo‬‬ ‫‪000000000000002a __UNIQUE_ID_author0‬‬
‫‪00000000000001b9 l‬‬ ‫‪O .modinfo‬‬ ‫‪0000000000000011 __UNIQUE_ID_alias0‬‬
‫‪00000000000001d0 l‬‬ ‫‪O .modinfo‬‬ ‫‪0000000000000009 __module_depends‬‬
‫‪00000000000001d9 l‬‬ ‫‪O .modinfo‬‬ ‫‪0000000000000009 __UNIQUE_ID_intree1‬‬
‫‪00000000000001e2 l‬‬ ‫‪O .modinfo‬‬ ‫‪000000000000002f __UNIQUE_ID_vermagic0‬‬

‫‪ 8.6.5‬سكربتات الرابط ‪Linker Scripts‬‬


‫تتمثل وظيفة الرابط في بناء األقسام ‪ Sections‬لتشكيل المقاطع ‪ Segments‬من خالل استخدام س‪tt‬كربت‬

‫الرابط الذي يِص ف مكان بدء المقاطع واألقسام الموجودة فيها ويحدد المعامالت األخرى‪.‬‬

‫‪213‬‬
‫علوم الحاسوب من األلف إىل الياء‬ ‫ما وراء العملية‬

‫يوضح المثال اآلتي مقتطًف ا من سكربت الرابط االفتراضي الذي سيعرضه الرابط عند إعطاء الراية التفصيلية‬

‫ السكربت االفتراضي ُم ضَّم ٌن في الرابط ويعتمد عىل‬.gcc ‫ مع‬--verbose ‫ و‬-Wl ‫ باستخدام الرايتين‬Verbose

.‫ المعيارية إلنشاء برامج عاملة لمساحة مستخدٍم خاصة بمنصة البناء‬API ‫تعريفات واجهة‬

$ gcc -Wl,--verbose -o test test.c


GNU ld (GNU Binutils for Debian) 2.26
...
using internal linker script:
==================================================
OUTPUT_FORMAT("elf64-x86-64", "elf64-x86-64",
"elf64-x86-64")
OUTPUT_ARCH(i386:x86-64)
ENTRY(_start)
SEARCH_DIR("=/usr/local/lib/x86_64-linux-gnu"); ...
SECTIONS
{
/* :text segment ‫* أقسام للقراءة فقط ُم دَمجة في مقطع النص‬/
PROVIDE (__executable_start = SEGMENT_START("text-segment", 0x400000)); . =
SEGMENT_START("text-segment", 0x400000) + SIZEOF_HEADERS;
.interp : { *(.interp) }
.note.gnu.build-id : { *(.note.gnu.build-id) }
.hash : { *(.hash) }
.gnu.hash : { *(.gnu.hash) }
.dynsym : { *(.dynsym) }
.dynstr : { *(.dynstr) }
.gnu.version : { *(.gnu.version) }
.gnu.version_d : { *(.gnu.version_d) }
.gnu.version_r : { *(.gnu.version_r) }
.rela.dyn :
{
...
}
PROVIDE (etext = .);
.rodata : { *(.rodata .rodata.* .gnu.linkonce.r.*) }
.rodata1 : { *(.rodata1) }
...

‫يمكنك أن ترى في المثال السابق كيف يحدد سكربت الرابط أموًرا متعددة مثل مواقع البدء واألقسام المراد‬

‫ير‬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‬امج‬

‫الثنائي عند عنوان نقطة الدخول أي الدالة ‪._init‬‬

‫‪ 8.7.1‬اتصال النواة بالربامج‬


‫تحتاج النواة ‪ Kernel‬إىل توصيل بعض األشياء للبرامج عند بدء تشغيلها مثل وسائط البرنامج ‪arguments‬‬

‫ومتغيرات البيئة الحالية ‪ 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‬‬
‫علوم الحاسوب من األلف إىل الياء‬ ‫ما وراء العملية‬

‫ا‪ .‬مكتبة النواة ‪Kernel Library‬‬

‫ذكرنا سابًق ا أن استدعاءات النظام بطيئة وأن األنظمة الحديثة لديها آليات لتجنب الِح مل الناتج عن استدعاء‬

‫مص‪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‬الثنائي‪.‬‬

‫يوضح المثال التالي نتيجة تفكيك ‪ Disassembley‬بدء تشغيل البرنامج‪:‬‬

‫‪$ cat test.c‬‬

‫)‪int main(void‬‬
‫{‬
‫;‪return 0‬‬
‫}‬

‫‪$ gcc -o test test.c‬‬

‫‪$ readelf --headers ./test | grep Entry‬‬

‫‪216‬‬
‫علوم الحاسوب من األلف إىل الياء‬ ‫ما وراء العملية‬

Entry point address: 0x80482b0

$ objdump --disassemble ./test

[...]

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
‫علوم الحاسوب من األلف إىل الياء‬ ‫ما وراء العملية‬

‫‪804838b:‬‬ ‫‪90‬‬ ‫‪nop‬‬


‫‪804838c:‬‬ ‫‪90‬‬ ‫‪nop‬‬
‫‪804838d:‬‬ ‫‪90‬‬ ‫‪nop‬‬
‫‪804838e:‬‬ ‫‪90‬‬ ‫‪nop‬‬
‫‪804838f:‬‬ ‫‪90‬‬ ‫‪nop‬‬

‫‪<__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‬ة األوىل‬

‫‪ 0x8048400‬الدالة ‪ ،__libc_csu_fini‬وتمثل القيمة ‪ 0x8048390‬الدال‪tt‬ة ‪ ،__libc_csu_init‬وتمث‪tt‬ل‬

‫القيمة ‪ 0x8048368‬الدالة الرئيسية )(‪ ،main‬ثم ُتستدَع ى قيمة الدالة ‪.__libc_start_main‬‬

‫إن الدال‪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‬فقط‪.‬‬

‫يمكننا أن نرى اآلن أن الدال‪tt‬ة ‪ __libc_start_main‬س‪tt‬تتلقى ع‪tt‬دًدا من مع‪tt‬امالت ال‪tt‬دخل في المك‪tt‬دس‬

‫‪ ،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>

void __attribute__((constructor)) program_init(void) {


printf("init\n");
}

void __attribute__((destructor)) program_fini(void) {


printf("fini\n");
}

int main(void)
{
return 0;
}

$ gcc -Wall -o test test.c

$ ./test
init
fini

$ objdump --disassemble ./test | grep program_init


<program_init>:

$ objdump --disassemble ./test | grep program_fini


080483b0 <program_fini>:

$ objdump --disassemble ./test

[...]
<_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

$ readelf --sections ./test


There are 34 section headers, starting at offset 0xfb0:

Section Headers:

220
‫علوم الحاسوب من األلف إىل الياء‬ ‫ما وراء العملية‬

[Nr] Name Type Addr Off Size ES Flg Lk Inf


Al
[ 0] NULL 00000000 000000 000000 00 0 0
0
[ 1] .interp PROGBITS 08048114 000114 000013 00 A 0 0
1
[ 2] .note.ABI-tag NOTE 08048128 000128 000020 00 A 0 0
4
[ 3] .hash HASH 08048148 000148 00002c 04 A 4 0
4
[ 4] .dynsym DYNSYM 08048174 000174 000060 10 A 5 1
4
[ 5] .dynstr STRTAB 080481d4 0001d4 00005e 00 A 0 0
1
[ 6] .gnu.version VERSYM 08048232 000232 00000c 02 A 4 0
2
[ 7] .gnu.version_r VERNEED 08048240 000240 000020 00 A 5 1
4
[ 8] .rel.dyn REL 08048260 000260 000008 08 A 4 0
4
[ 9] .rel.plt REL 08048268 000268 000018 08 A 4 11
4
[10] .init PROGBITS 08048280 000280 000017 00 AX 0 0
4
[11] .plt PROGBITS 08048298 000298 000040 04 AX 0 0
4
[12] .text PROGBITS 080482e0 0002e0 000214 00 AX 0 0
16
[13] .fini PROGBITS 080484f4 0004f4 00001a 00 AX 0 0
4
[14] .rodata PROGBITS 08048510 000510 000012 00 A 0 0
4
[15] .eh_frame PROGBITS 08048524 000524 000004 00 A 0 0
4
[16] .ctors PROGBITS 08049528 000528 00000c 00 WA 0 0
4
[17] .dtors PROGBITS 08049534 000534 00000c 00 WA 0 0
4
[18] .jcr PROGBITS 08049540 000540 000004 00 WA 0 0
4
[19] .dynamic DYNAMIC 08049544 000544 0000c8 08 WA 5 0
4
[20] .got PROGBITS 0804960c 00060c 000004 04 WA 0 0
4
[21] .got.plt PROGBITS 08049610 000610 000018 04 WA 0 0
4

221
‫علوم الحاسوب من األلف إىل الياء‬ ‫ما وراء العملية‬

[22] .data PROGBITS 08049628 000628 00000c 00 WA 0 0


4
[23] .bss NOBITS 08049634 000634 000004 00 WA 0 0
4
[24] .comment PROGBITS 00000000 000634 00018f 00 0 0
1
[25] .debug_aranges PROGBITS 00000000 0007c8 000078 00 0 0
8
[26] .debug_pubnames PROGBITS 00000000 000840 000025 00 0 0
1
[27] .debug_info PROGBITS 00000000 000865 0002e1 00 0 0
1
[28] .debug_abbrev PROGBITS 00000000 000b46 000076 00 0 0
1
[29] .debug_line PROGBITS 00000000 000bbc 0001da 00 0 0
1
[30] .debug_str PROGBITS 00000000 000d96 0000f3 01 MS 0 0
1
[31] .shstrtab STRTAB 00000000 000e89 000127 00 0 0
1
[32] .symtab SYMTAB 00000000 001500 000490 10 33 53
4
[33] .strtab STRTAB 00000000 001990 000218 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)

$ objdump --disassemble-all --section .ctors ./test

./test: file format elf32-i386

Contents of section .ctors:


ffffffff 98830408 00000000 ............

‫ة‬tt‫ل الدال‬tt‫دس من أج‬tt‫ة إىل المك‬tt‫يرة المدفوع‬tt‫ة األخ‬tt‫__ هي القيم‬libc_csu_init ‫ة‬tt‫ة التهيئ‬tt‫انت دال‬tt‫ك‬

‫ فيمكننا أن نرى أنها‬،__libc_csu_init ‫ إذا اتبعنا سلسلة االستدعاءات ابتداًء من‬.__libc_start_main

‫ة‬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‬أي تخزين البتات األقل أهمية أواًل والقيمة صفر‪.‬‬

‫العنوان بصيغة ‪ 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‬تدعيها‬

‫الدالة ‪ __libc_start_main‬عند اكتمال الدالة الرئيسية )(‪.main‬‬

‫الحظ تطبيق الكثير من العمل قبل أن يبدأ البرنامج وحتى بعد أن تعتقد أنه انتهى بقليل‪.‬‬

‫‪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‬‬

‫ألنها تطّبق جزًءا من عملية الربط مباشرًة عند تنفيذ البرامج في النظام‪.‬‬

‫‪ 9.1.1‬تفاصيل المكتبة الديناميكية‬


‫تشبه المكتبات إىل حٍد كبير برنامًجا ال ُيشَّغ ل أبًدا‪ ،‬إذ لديها قسم الشيفرة البرمجية وقس‪t‬م البيان‪t‬ات (ال‪t‬دوال‬

‫والمتغيرات) تماًم ا مثل الملف‪tt‬ات القاب‪t‬ل للتنفي‪tt‬ذ‪ ،‬ولكن ال يمكن تش‪t‬غيلها‪ ،‬فهي ت‪t‬وفر فق‪tt‬ط مكتب‪t‬ة من ال‪t‬دوال‬
‫علوم الحاسوب من األلف إىل الياء‬ ‫مفهوم الربط الديناميكي‬

‫للمطورين الستدعائها‪ .‬لذا يمكن ملف ‪ ELF‬أن يمثل مكتبة ديناميكية تماًم ا كما يمثل ملًف ا قاباًل للتنفيذ مع وجود‬

‫بعض االختالفات األساسية مثل عدم وجود مؤش‪tt‬ر للمك‪tt‬ان ال‪tt‬ذي يجب أن يب‪tt‬دأ في‪tt‬ه التنفي‪tt‬ذ‪ ،‬ولكن ُتَع د جمي‪tt‬ع‬

‫المكتبات المشتركة مجرد كائنات بصيغة ‪ ELF‬مثل أي ملف آخر قابل للتنفيذ‪.‬‬

‫تحتوي ترويسة ملف ‪ ELF‬عىل رايتين حصريتين هما ‪ ET_EXEC‬و ‪ ET_DYN‬لتمييز ملف ‪ ELF‬بوص‪tt‬فه ملًف ا‬

‫قاباًل للتنفيذ أو ملَف كائن مشترك‪.‬‬

‫‪ 9.1.2‬تضمني المكتبات في ملف قابل للتنفيذ‬


‫يمكن تض‪tt‬مين المكتب‪tt‬ات في مل‪tt‬ف قاب‪tt‬ل للتنفي‪tt‬ذ ولكن يجب أن ن‪tt‬راعي مس‪tt‬ألتين مهم‪tt‬تين متعلق‪tt‬تين‬

‫بالمصِّرف والرابط الديناميكي كما سنوضح اآلن‪.‬‬

‫ا‪ .‬الترصيف ‪Compilation‬‬

‫تمتلك ملفات الكائنات مراجًع ا إىل دوال المكتبة تماًم ا كما هو الحال مع أّي مرجع خارجي آخر عندما تصِّرف‬

‫برنامجك الذي يستخدم مكتبة ديناميكية‪ .‬يجب تضمين ترويسة المكتبة ليعرف المصِّرف األنواع المحددة للدوال‬

‫التي تستدعيها‪ ،‬إذ يحتاج المصِّرف فقط معرفَة األنواع المرتبطة بالدالة (مث‪tt‬ل أن تأخ‪t‬ذ الدال‪t‬ة الن‪t‬وع ‪ int‬وتعي‪tt‬د‬

‫النوع *‪ )char‬بحيث يمكنه تخصيص مساحة الستدعاء الدالة بصورة صحيحة‪.‬‬

‫لم يكن هذا هو الحال دائًم ا مع معايير لغة ‪ ،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‬بحيث ُيطَلب‬

‫منك تحديد أنواع الدوال الُم ضَّم نة‪.‬‬

‫ب‪ .‬الربط ‪Linking‬‬

‫يطّبق الرابط الديناميكي الكثير من العمل للمكتب‪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‬اري‬

‫‪ /bin/ls‬يمثل تحديد المكتبات الديناميكية‪:‬‬

‫‪$ readelf --dynamic /bin/ls‬‬

‫‪225‬‬
‫علوم الحاسوب من األلف إىل الياء‬ ‫مفهوم الربط الديناميكي‬

‫‪Dynamic segment at offset 0x22f78 contains 27 entries:‬‬


‫‪Tag‬‬ ‫‪Type‬‬ ‫‪Name/Value‬‬
‫)‪0x0000000000000001 (NEEDED‬‬ ‫]‪Shared library: [librt.so.1‬‬
‫)‪0x0000000000000001 (NEEDED‬‬ ‫]‪Shared library: [libacl.so.1‬‬
‫)‪0x0000000000000001 (NEEDED‬‬ ‫]‪Shared library: [libc.so.6.1‬‬
‫)‪0x000000000000000c (INIT‬‬ ‫‪0x4000000000001e30‬‬
‫‪... snip ...‬‬

‫يمكنك أن ترى أن المثال السابق يحدد ثالث مكتبات‪ .‬المكتبة األكثر شيوًع ا التي تتشارك بها أغلبية ال‪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‬ة‬

‫معتمدة عىل مكتبة أخرى كما يلي‪:‬‬

‫‪$ ldd /bin/ls‬‬


‫)‪librt.so.1 => /lib/tls/librt.so.1 (0x2000000000058000‬‬
‫)‪libacl.so.1 => /lib/libacl.so.1 (0x2000000000078000‬‬
‫)‪libc.so.6.1 => /lib/tls/libc.so.6.1 (0x2000000000098000‬‬
‫)‪libpthread.so.0 => /lib/tls/libpthread.so.0 (0x20000000002e0000‬‬
‫‪/lib/ld-linux-ia64.so.2 => /lib/ld-linux-ia64.so.2‬‬
‫)‪(0x2000000000000000‬‬
‫)‪libattr.so.1 => /lib/libattr.so.1 (0x2000000000310000‬‬
‫‪$ readelf --dynamic /lib/librt.so.1‬‬

‫‪Dynamic segment at offset 0xd600 contains 30 entries:‬‬


‫‪Tag‬‬ ‫‪Type‬‬ ‫‪Name/Value‬‬
‫)‪0x0000000000000001 (NEEDED‬‬ ‫]‪Shared library: [libc.so.6.1‬‬
‫)‪0x0000000000000001 (NEEDED‬‬ ‫]‪Shared library: [libpthread.so.0‬‬
‫‪... snip ...‬‬

‫يمكننا أن نرى في المثال السابق أن مكتب‪tt‬ة ‪ libpthread‬مطلوب‪tt‬ة من مك‪tt‬ان م‪tt‬ا‪ ،‬حيث إذا تعّم قن‪tt‬ا قلياًل ‪،‬‬

‫فيمكننا أن نرى أنها مطلوبة من المكتبة ‪.librt‬‬

‫‪ 9.2‬الرابط الديناميكي ‪Dynamic Linker‬‬


‫الرابط الديناميكي هو البرنامج الذي يدير المكتبات الديناميكية المش‪tt‬تركة ب‪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‬مفّس ر‬

‫الملفات القابلة للتنفيذ الذي يعتمد عىل المكتبات الديناميكية ليكون الرابط الديناميكي‪.‬‬

‫يوَض ح المثال التالي كيفية التحقق من مفّس ر البرنامج‪:‬‬

‫‪ianw@lime:~/programs/csbu$ readelf --headers /bin/ls‬‬

‫‪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‬‬

‫الدالة ‪X‬‬ ‫‪0x564773‬‬

‫جدول ‪ :24‬مثال عىل الترحيل‬

‫هناك العديد من أنواع االنتقاالت لكل معماري‪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‬دى‬

‫المكتبتين من المكتبة األخرى‪:‬‬

‫‪$ cat addendtest.c‬‬


‫;]‪extern int i[4‬‬
‫;‪int *j = i + 2‬‬

‫‪$ cat addendtest2.c‬‬


‫;]‪int i[4‬‬

‫‪$ gcc -nostdlib -shared -fpic -s -o addendtest2.so addendtest2.c‬‬


‫‪$ gcc -nostdlib -shared -fpic -o addendtest.so addendtest.c ./addendtest2.so‬‬

‫‪$ readelf -r ./addendtest.so‬‬

‫‪Relocation section '.rela.dyn' at offset 0x3b8 contains 1 entries:‬‬


‫‪Offset‬‬ ‫‪Info‬‬ ‫‪Type‬‬ ‫‪Sym. Value‬‬ ‫‪Sym. Name +‬‬
‫‪Addend‬‬
‫‪0000000104f8‬‬ ‫‪000f00000027 R_IA64_DIR64LSB‬‬ ‫‪0000000000000000 i + 8‬‬

‫لدينا عملية انتقال واحدة في ‪ addendtest.so‬من النوع ‪ R_IA64_DIR64LSB‬الذي إن بحثت عن‪tt‬ه في‬

‫واجهة ‪ ،IA64 ABI‬فيمكن تقسيمه إىل ما يلي‪:‬‬

‫‪ :R_IA64 .1‬تبدأ جميع االنتقاالت بهذه البادئة‪.‬‬

‫‪ :DIR64 .2‬انتقال من النوع ‪ 64‬بت المباشر‪.‬‬

‫‪ :LSB .3‬بم‪tt‬ا أن ‪ IA64‬يمكنه‪tt‬ا أن تعم‪tt‬ل في أنم‪tt‬اط ‪( Big Endian‬تخ‪tt‬زين البت‪tt‬ات األق‪tt‬ل أهمي‪tt‬ة أواًل )‬

‫و ‪( Little Endian‬تخزين البتات األكثر أهمي‪t‬ة أواًل )‪ ،‬فس‪t‬يكون ه‪t‬ذا االنتق‪t‬ال ‪( Little Endian‬الب‪t‬ايت‬

‫األقل أهمية ‪.)Least Significant Byte‬‬

‫‪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‬ظ أن‬

‫قسم البيانات ال يزال يمثل إزاحة ثابتة عن قسم الشيفرة البرمجية‪ ،‬ولكن يجب إضافة اإلزاحة إىل عنوان التحميل‬

‫للعثور عىل عنوان البيانات‪.‬‬

‫‪ 9.3‬جدول اإلزاحة العام ‪Global Offset Table‬‬


‫ال بد أنك الحظت مشكلة خطيرة في االنتقاالت ‪ Relocations‬عند التفكير في أه‪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‬ذاكرة الوهمي‪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‬‬

‫شكل ‪ :39‬الوصول إىل الذاكرة عبر ‪GOT‬‬

‫ُيَع د جدول ‪ GOT‬خاًص ا بكل عملية‪ ،‬ويجب أن يكون للعملية أذونات للكتابة خاصة بها‪ .‬بينم‪tt‬ا يمكن مش‪tt‬اركة‬

‫شيفرة المكتبة ويجب أن يكون للعملية فقط أذونات قراءة وتنفيذ الشيفرة البرمجي‪tt‬ة‪ ،‬وإال فس‪tt‬يكون هن‪tt‬اك خ‪tt‬رق‬

‫أمني خطير إذا تمكنت العملية من تعديل الشيفرة البرمجية‪.‬‬

‫‪ 9.3.1‬كيفية عمل جدول ‪GOT‬‬


‫يوضح المثال التالي كيفية استخدام جدول ‪:GOT‬‬

‫‪$ cat got.c‬‬


‫;‪extern int i‬‬

‫)‪void test(void‬‬
‫{‬
‫;‪i = 100‬‬
‫}‬

‫‪$ gcc -nostdlib‬‬ ‫‪-shared -o got.so ./got.c‬‬

‫‪$ objdump --disassemble ./got.so‬‬

‫‪231‬‬
‫علوم الحاسوب من األلف إىل الياء‬ ‫مفهوم الربط الديناميكي‬

./got.so: file format elf64-ia64-little

Disassembly of section .text:

<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;;

$ readelf --sections ./got.so


There are 17 section headers, starting at offset 0x640:

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
‫علوم الحاسوب من األلف إىل الياء‬ ‫مفهوم الربط الديناميكي‬

‫‪WA‬‬ ‫‪0‬‬ ‫‪0‬‬ ‫‪1‬‬


‫‪[ 9] .dynamic‬‬ ‫‪DYNAMIC‬‬ ‫‪0000000000010470‬‬ ‫‪00000470‬‬
‫‪WA‬‬ ‫‪3‬‬ ‫‪0‬‬ ‫‪8‬‬
‫‪[10] .got‬‬ ‫‪PROGBITS‬‬ ‫‪0000000000010570‬‬ ‫‪00000570‬‬
‫‪WAp‬‬ ‫‪0‬‬ ‫‪0‬‬ ‫‪8‬‬
‫‪[11] .sbss‬‬ ‫‪NOBITS‬‬ ‫‪0000000000010590‬‬ ‫‪00000590‬‬
‫‪W‬‬ ‫‪0‬‬ ‫‪0‬‬ ‫‪1‬‬
‫‪[12] .bss‬‬ ‫‪NOBITS‬‬ ‫‪0000000000010590‬‬ ‫‪00000590‬‬
‫‪WA‬‬ ‫‪0‬‬ ‫‪0‬‬ ‫‪1‬‬
‫‪[13] .comment‬‬ ‫‪PROGBITS‬‬ ‫‪0000000000000000‬‬ ‫‪00000590‬‬
‫‪0‬‬ ‫‪0‬‬ ‫‪1‬‬
‫‪[14] .shstrtab‬‬ ‫‪STRTAB‬‬ ‫‪0000000000000000‬‬ ‫‪000005b6‬‬
‫‪000000000000008a‬‬ ‫‪0000000000000000‬‬ ‫‪0‬‬ ‫‪0‬‬ ‫‪1‬‬
‫‪[15] .symtab‬‬ ‫‪SYMTAB‬‬ ‫‪0000000000000000‬‬ ‫‪00000a80‬‬
‫‪16‬‬ ‫‪12‬‬ ‫‪8‬‬
‫‪[16] .strtab‬‬ ‫‪STRTAB‬‬ ‫‪0000000000000000‬‬ ‫‪00000cd8‬‬
‫‪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‬‬

‫يوّض ح المثال السابق كيفية إنشاء مكتبة مشتركة بسيطة تشير إىل رمز خارجي‪ .‬ال نع‪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‬وان‬

‫‪ ،0x6000000000000000‬فسيكون القسم ‪ .got‬موجوًدا عن‪t‬د العن‪t‬وان ‪ ،0x6000000000010570‬وسيؤّش ر‬

‫المسّجل ‪ r1‬دائًم ا إىل هذا العنوان‪.‬‬

‫كما يمكننا أن نرى أننا نخزن القيمة ‪ 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‬‬
‫علوم الحاسوب من األلف إىل الياء‬ ‫مفهوم الربط الديناميكي‬

‫‪$ readelf --relocs ./got.so‬‬

‫‪Relocation section '.rela.dyn' at offset 0x3f8 contains 1 entries:‬‬


‫‪Offset‬‬ ‫‪Info‬‬ ‫‪Type‬‬ ‫‪Sym. Value‬‬ ‫‪Sym. Name +‬‬
‫‪Addend‬‬
‫‪000f00000027 R_IA64_DIR64LSB‬‬ ‫‪0000000000000000 i + 0‬‬

‫كما يمكننا التحقق من انتقال مدخلة جدول ‪ ،GOT‬حيث يمثل االنتقاُل استبداَل القيمة عند اإلزاحة ‪10588‬‬

‫بموقع الذاكرة الذي ُخِّزن فيه الرمز ‪.i‬‬

‫يبدأ القسم ‪ .got‬عند اإلزاحة ‪ 0x10570‬عن الخرج السابق‪ ،‬حيث رأينا كيف تحّم ل الشيفرة البرمجية عنواًنا‬

‫يبعد عن القسم ‪ .got‬بمقدار ‪( 0x18‬أو ‪ 24‬في النظام العشري)‪ ،‬مما يمنحنا عنواًنا مقداره ‪0x10570 + 0x18‬‬

‫‪ = 0x10588‬يمّث ل العنوان الذي ُط ِّبق االنتقال ألجله‪ .‬لذا يجب أن يصلح الرابط الديناميكي االنتقال قبل أن يبدأ‬

‫البرنامج للتأكد من أن قيمة الذاكرة عند اإلزاحة ‪ 0x10588‬هي عنوان المتغير العام ‪.i‬‬

‫‪ 9.4‬المزيد حول المكتبات‬


‫يمكن أن تحتوي المكتبات عىل العدي‪tt‬د من ال‪tt‬دوال‪ ،‬ويمكن أن يحت‪tt‬وي البرن‪tt‬امج عىل العدي‪tt‬د من المكتب‪tt‬ات‬

‫إلنجاز عمله‪ .‬يستخدم البرنامج دالة أو دالتين فق‪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‬رحه‬

‫في الفقرات التالية‪.‬‬

‫‪ 9.4.1‬جدول البحث عن اإلجراءات ‪Procedure Lookup Table‬‬


‫يسّه ل جدول البحث عن اإلجراءات ‪- Procedure Lookup Table‬أو ‪ PLT‬اختصاًرا‪ -‬م‪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‬تخدام‬

‫الطريقة نفسها لإلشارة إىل المتغيرات في جدول ‪ GOT‬نفسها‪.‬‬

‫تحّم ل هذه الدالة الوهمية بعض المعامالت ال‪tt‬تي تري‪tt‬د تمريره‪tt‬ا إىل الراب‪tt‬ط ال‪tt‬ديناميكي لتتمكن من تحلي‪tt‬ل‬

‫الدالة ثم استدعاء دالة بحث خاصة بالرابط الديناميكي‪ .‬يجد الرابط الديناميكي عنوان الدال‪tt‬ة الفعلي‪ ،‬ويكتب ه‪tt‬ذا‬

‫الموقع في استدعاء الملف الثنائي في أعىل استدعاء الدالة الوهمية‪ ،‬وبالتالي يمكن تحميل العنوان دون الحاج‪tt‬ة‬

‫إىل العودة إىل المحمل الديناميكي مرة أخرى في المرة التالية الستدعاء الدالة‪ .‬إن لم ُتس‪t‬تدَع ى دال‪ٌt‬ة مطلًق ا‪ ،‬فلن‬

‫ُتعَّد ل مدخلة جدول ‪ PLT‬أبًدا ولكن لن يكون هناك وقت تشغيل إضافي‪.‬‬

‫ا‪ .‬كيفية عمل جدول ‪PLT‬‬

‫يجب أن تبدأ اآلن في إدراك أن هناك ق‪tt‬دًرا ال ب‪tt‬أس ب‪tt‬ه من العم‪tt‬ل في تحلي‪tt‬ل رم‪tt‬ز دين‪tt‬اميكي‪ .‬لنطل‪tt‬ع عىل‬

‫تطبيق "‪ "hello World‬البسيط الذي يجري استدعاء مكتب‪t‬ة واح‪t‬د فق‪t‬ط ه‪t‬و اس‪t‬تدعاء الدال‪t‬ة ‪ printf‬لع‪t‬رض‬

‫السلسلة النصية للمستخدم كما يلي‪:‬‬

‫‪$ cat hello.c‬‬


‫>‪#include <stdio.h‬‬

‫)‪int main(void‬‬
‫{‬
‫;)"‪printf("Hello, World!\n‬‬
‫;‪return 0‬‬
‫}‬

‫‪$ gcc -o hello hello.c‬‬

‫‪$ readelf --relocs ./hello‬‬

‫‪Relocation section '.rela.dyn' at offset 0x3f0 contains 2 entries:‬‬


‫‪Offset‬‬ ‫‪Info‬‬ ‫‪Type‬‬ ‫‪Sym. Value‬‬ ‫‪Sym. Name +‬‬
‫‪Addend‬‬
‫‪6000000000000ed8 000700000047 R_IA64_FPTR64LSB‬‬ ‫‪0000000000000000‬‬
‫‪_Jv_RegisterClasses + 0‬‬
‫‪6000000000000ee0 000900000047 R_IA64_FPTR64LSB‬‬ ‫‪0000000000000000‬‬
‫‪__gmon_start__ + 0‬‬

‫‪Relocation section '.rela.IA_64.pltoff' at offset 0x420 contains 3 entries:‬‬

‫‪235‬‬
‫علوم الحاسوب من األلف إىل الياء‬ ‫مفهوم الربط الديناميكي‬

Offset Info Type Sym. Value Sym. Name +


Addend
6000000000000f10 000200000081 R_IA64_IPLTLSB 0000000000000000 printf + 0
6000000000000f20 000800000081 R_IA64_IPLTLSB 0000000000000000
__libc_start_main + 0
6000000000000f30 000900000081 R_IA64_IPLTLSB 0000000000000000
__gmon_start__ + 0

‫ع‬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 ‫معرفة مكان هذا العنوان من خالل االطالع األقسام‬

$ readelf --sections ./hello


There are 40 section headers, starting at offset 0x25c0:

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
‫علوم الحاسوب من األلف إىل الياء‬ ‫مفهوم الربط الديناميكي‬

[27] .sdata PROGBITS 6000000000000f40 00000f40


0000000000000000 WAp 0 0 8
[28] .sbss NOBITS 6000000000000f50 00000f50
0000000000000000 WA 0 0 8
[29] .bss NOBITS 6000000000000f58 00000f50
0000000000000000 WA 0 0 8
[30] .comment PROGBITS 0000000000000000 00000f50
00000000000000b9 0000000000000000 0 0 1
[31] .debug_aranges PROGBITS 0000000000000000 00001010
0000000000000000 0 0 16
[32] .debug_pubnames PROGBITS 0000000000000000 000010a0
0000000000000000 0 0 1
[33] .debug_info PROGBITS 0000000000000000 000010c5
00000000000009c4 0000000000000000 0 0 1
[34] .debug_abbrev PROGBITS 0000000000000000 00001a89
0000000000000000 0 0 1
[35] .debug_line PROGBITS 0000000000000000 00001bad
00000000000001fe 0000000000000000 0 0 1
[36] .debug_str PROGBITS 0000000000000000 00001dab
00000000000006a1 0000000000000001 MS 0 0 1
[37] .shstrtab STRTAB 0000000000000000 0000244c
000000000000016f 0000000000000000 0 0 1
[38] .symtab SYMTAB 0000000000000000 00002fc0
0000000000000b58 0000000000000018 39 60 8
[39] .strtab STRTAB 0000000000000000 00003b18
0000000000000000 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)

‫ل‬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
‫علوم الحاسوب من األلف إىل الياء‬ ‫مفهوم الربط الديناميكي‬

40000000000004d6: 10 41 38 30 28 00 ld8 r17=[r14],8


40000000000004dc: 00 00 04 00 nop.i 0x0;;
40000000000004e0: 11 08 00 1c 18 10 [MIB] ld8 r1=[r14]
40000000000004e6: 60 88 04 80 03 00 mov b6=r17
40000000000004ec: 60 00 80 00 br.few b6;;
40000000000004f0: 11 78 00 00 00 24 [MIB] mov r15=0
40000000000004f6: 00 00 00 02 00 00 nop.i 0x0
40000000000004fc: d0 ff ff 48 br.few
40000000000004c0 <_init+0x50>;;
11 78 04 00 00 24 [MIB] mov r15=1
00 00 00 02 00 00 nop.i 0x0
400000000000050c: c0 ff ff 48 br.few
40000000000004c0 <_init+0x50>;;
11 78 08 00 00 24 [MIB] mov r15=2
00 00 00 02 00 00 nop.i 0x0
400000000000051c: b0 ff ff 48 br.few
40000000000004c0 <_init+0x50>;;
0b 78 40 03 00 24 [MMI] addl r15=80,r1;;
00 41 3c 70 29 c0 ld8.acq r16=[r15],8
400000000000052c: 01 08 00 84 mov r14=r1;;
11 08 00 1e 18 10 [MIB] ld8 r1=[r15]
60 80 04 80 03 00 mov b6=r16
400000000000053c: 60 00 80 00 br.few b6;;
0b 78 80 03 00 24 [MMI] addl r15=96,r1;;
00 41 3c 70 29 c0 ld8.acq r16=[r15],8
400000000000054c: 01 08 00 84 mov r14=r1;;
11 08 00 1e 18 10 [MIB] ld8 r1=[r15]
60 80 04 80 03 00 mov b6=r16
400000000000055c: 60 00 80 00 br.few b6;;
0b 78 c0 03 00 24 [MMI] addl r15=112,r1;;
00 41 3c 70 29 c0 ld8.acq r16=[r15],8
400000000000056c: 01 08 00 84 mov r14=r1;;
11 08 00 1e 18 10 [MIB] ld8 r1=[r15]
60 80 04 80 03 00 mov b6=r16
400000000000057c: 60 00 80 00 br.few b6;;

‫ا في‬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‬جل‬

‫‪ ،r15‬ثم نتفّر ع إىل المسجل ‪.r16‬‬

‫ناقشنا سابًق ا كيفية استدعاء الدوال باس‪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‬ة‬

‫‪ Op Register‬قبل استدعاء الدالة‪.‬‬

‫نعلم أن المس‪tt‬جل ‪ r1‬سيؤّش ر إىل ج‪tt‬دول ‪ ،GOT‬ثم س‪tt‬نذهب بمق‪tt‬دار ‪ 80‬ب‪tt‬ايت بع‪tt‬د ج‪tt‬دول ‪ GOT‬أي‬

‫بمقدار (‪.)0x50‬‬

‫‪$ objdump --disassemble-all ./hello‬‬


‫‪Disassembly of section .got:‬‬

‫‪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‬‬
‫علوم الحاسوب من األلف إىل الياء‬ ‫مفهوم الربط الديناميكي‬

‫إذا أض‪tttttt‬فنا القيم‪tttttt‬ة ‪ 0x50‬إىل العن‪tttttt‬وان ‪ ،0x6000000000000ec0‬فسنص‪tttttt‬ل إىل العن‪tttttt‬وان‬

‫‪ 0x6000000000000f10‬أو القسم ‪..IA_64.pltoff‬‬

‫يمكننا فك شيفرة خرج البرنامج ‪ objdump‬لنتمّكن من رؤية ما جرى تحميله بالضبط‪ .‬ي‪tt‬ؤدي تب‪tt‬ديل ت‪tt‬رتيب‬

‫البايت ألول ‪ 8‬بايتات ‪ f0 04 00 00 00 00 00 40‬إىل الحصول عىل العنوان ‪،0x4000000000004f0‬‬

‫إذ يب‪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‬ود‬

‫هذه المنطقة المحجوزة في المقطع الديناميكي للملف الثنائي الموّض ح في المثال التالي‪:‬‬

‫‪Dynamic segment at offset 0xcb8 contains 25 entries:‬‬


‫‪Tag‬‬ ‫‪Type‬‬ ‫‪Name/Value‬‬
‫)‪0x0000000000000001 (NEEDED‬‬ ‫]‪Shared library: [libc.so.6.1‬‬
‫)‪0x000000000000000c (INIT‬‬ ‫‪0x4000000000000470‬‬
‫)‪0x000000000000000d (FINI‬‬ ‫‪0x4000000000000a20‬‬
‫)‪0x0000000000000019 (INIT_ARRAY‬‬ ‫‪0x6000000000000c90‬‬
‫)‪0x000000000000001b (INIT_ARRAYSZ‬‬ ‫)‪24 (bytes‬‬
‫)‪0x000000000000001a (FINI_ARRAY‬‬ ‫‪0x6000000000000ca8‬‬
‫)‪0x000000000000001c (FINI_ARRAYSZ‬‬ ‫)‪8 (bytes‬‬
‫)‪0x0000000000000004 (HASH‬‬ ‫‪0x4000000000000200‬‬
‫)‪0x0000000000000005 (STRTAB‬‬ ‫‪0x4000000000000330‬‬
‫)‪0x0000000000000006 (SYMTAB‬‬ ‫‪0x4000000000000240‬‬
‫)‪0x000000000000000a (STRSZ‬‬ ‫)‪138 (bytes‬‬
‫)‪0x000000000000000b (SYMENT‬‬ ‫)‪24 (bytes‬‬
‫)‪0x0000000000000015 (DEBUG‬‬ ‫‪0x0‬‬
‫)‪0x0000000070000000 (IA_64_PLT_RESERVE‬‬ ‫‪0x6000000000000ec0 --‬‬
‫‪0x6000000000000ed8‬‬
‫)‪0x0000000000000003 (PLTGOT‬‬ ‫‪0x6000000000000ec0‬‬
‫)‪0x0000000000000002 (PLTRELSZ‬‬ ‫)‪72 (bytes‬‬

‫‪241‬‬
‫علوم الحاسوب من األلف إىل الياء‬ ‫مفهوم الربط الديناميكي‬

‫)‪0x0000000000000014 (PLTREL‬‬ ‫‪RELA‬‬


‫)‪0x0000000000000017 (JMPREL‬‬ ‫‪0x4000000000000420‬‬
‫)‪0x0000000000000007 (RELA‬‬ ‫‪0x40000000000003f0‬‬
‫)‪0x0000000000000008 (RELASZ‬‬ ‫)‪48 (bytes‬‬
‫)‪0x0000000000000009 (RELAENT‬‬ ‫)‪24 (bytes‬‬
‫)‪0x000000006ffffffe (VERNEED‬‬ ‫‪0x40000000000003d0‬‬
‫)‪0x000000006fffffff (VERNEEDNUM‬‬ ‫‪1‬‬
‫)‪0x000000006ffffff0 (VERSYM‬‬ ‫‪0x40000000000003ba‬‬
‫)‪0x0000000000000000 (NULL‬‬ ‫‪0x0‬‬

‫الحظ أننا حصلنا عىل قيمة جدول ‪ 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 = ((Elf64_Addr‬‬


‫‪(l->l_info[DT_IA_64 (PLT_RESERVE)]->d_un.d_ptr + l-‬‬
‫;))‪>l_addr‬‬
‫تعريف هذا الكائن المشترك *‪/‬‬ ‫‪*/‬‬
‫;‪reserve[0] = (Elf64_Addr) l‬‬

‫‪ */‬سُت ستدَع ى هذه الدالة لتطبيق االنتقال ‪/* Relocation‬‬


‫)‪if (!profile‬‬
‫‪doit = (Elf64_Addr) ((struct fdesc *) &_dl_runtime_resolve)-‬‬
‫;‪>ip‬‬
‫‪else‬‬
‫{‬
‫‪if (GLRO(dl_profile) != NULL‬‬
‫))‪&& _dl_name_match_p (GLRO(dl_profile), l‬‬
‫{‬
‫هذا هو الكائن الذي نبحث عنه‪ .‬لنفترض أننا نريد استخدام التشخيص *‪/‬‬
‫‪ */‬مع بدء المؤقتات ‪Profiling‬‬
‫;‪GL(dl_profile_map) = l‬‬
‫}‬
‫‪doit = (Elf64_Addr) ((struct fdesc *) &_dl_runtime_profile)-‬‬
‫;‪>ip‬‬
‫}‬

‫;‪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‬ال‬

‫الدالة ‪ printf‬العدد صفر‪.‬‬

‫نحّم ل المسجل ‪ 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‬عمل الرابط الديناميكي مع المكتبات‬


‫يوفر وجود الربط الديناميكي ‪ Dynamic Linking‬بعض المزايا التي يمكننا االستفادة منها وبعض المشاكل‬

‫اإلضافية التي يجب حلها للحصول عىل نظام فّع ال‪.‬‬

‫‪ 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‬‬

‫ُيستخَدم نظام ‪ 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‬ون ‪ soname‬بالش‪tt‬كل ‪ ،libNAME.so.MAJOR‬ويجب ض‪tt‬بطه في الحق‪tt‬ل ‪ DT_SONAME‬من القس‪tt‬م‬

‫الديناميكي لمكتبة مشتركة‪ ،‬حيث يمكن لمؤلف المكتبة تحديد هذا اإلصدار عند إنشاء المكتبة‪.‬‬

‫يمكن أن يحّدد كل ملف مكتبة لإلصدار الثانوي عىل القرص الصلب رقَم اإلصدار الرئيسي نفسه في الحق‪tt‬ل‬

‫‪ ،DT_SONAME‬مما يسمح للرابط الديناميكي بمعرفة أن ملف المكتبة يطّبق تعدياًل رئيس‪ًt‬ي ا معيًن ا لواجه‪tt‬تي ‪API‬‬

‫و ‪ ABI‬الخاصتين بالمكتبة‪.‬‬

‫لذا ُيشَّغ ل تطبيق اسمه ‪ ldconfig‬إلنشاء روابط رمزية لإلصدار الرئيسي إىل أحدث إصدار ثانوي عىل النظام‪.‬‬

‫يعم‪t‬ل تط‪t‬بيق ‪ ldconfig‬من خالل تش‪t‬غيل جمي‪t‬ع المكتب‪t‬ات ال‪t‬تي تطّب ق رقم إص‪t‬دار رئيس‪t‬ي معين‪ ،‬ثم يخت‪t‬ار‬

‫المكتبة التي تحتوي عىل أعىل رقم تعديل ث‪tt‬انوي‪ ،‬ثم ينِش ئ رابًط ا رمزًي ا من ‪ libNAME.so.MAJOR‬إىل مل‪tt‬ف‬

‫المكتبة الفعلي الموجود عىل القرص الصلب مثل ‪.libNAME.so.MAJOR.MINOR‬‬

‫الجزء األخير من التسلسل الهرمي هو اسم تصريف ‪ Compile Name‬المكتبة‪ .‬إن أردت تصريف برنامجك‬

‫لربطه بمكتبة‪ ،‬فيمكنك استخدام الراية ‪ -lNAME‬التي تبحث عن الملف ‪ libNAME.so‬في مسار بحث المكتبة‪.‬‬

‫الحظ أننا لم نحدد أي رقم إصدار‪ ،‬ألننا نريد فقط الربط بأحدث مكتبة عىل النظام‪ .‬يعود األم‪tt‬ر إىل إج‪tt‬راء التث‪tt‬بيت‬

‫الخاص بالمكتبة إلنشاء رابط رمزي بين اسم التصريف ‪ libNAME.so‬وأحدث شيفرة مكتبة عىل النظام‪ ،‬ويمكن‬

‫التعامل مع ذلك باستخدام نظام الحزم ‪ dpkg‬أو ‪ .rpm‬ال ُيَع د ذلك عملية آلية‪ ،‬إذ ُيحتَم ل أاّل تكون أح‪tt‬دث مكتب‪tt‬ة‬

‫عىل النظام هي المكتبة التي ترغب في تصريفها دائًم ا‪ ،‬فمثاًل يمكن أن تكون أحدث مكتبة ُم ثَّبتة إصداًرا تطويرًي ا‬

‫غير مناسب لالستخدام العام‪ ،‬ويوضح الشكل التالي العملية العامة لنظام ‪:sonames‬‬

‫شكل ‪ :40‬نظام ‪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‬امج‬

‫‪ ldconfig‬عند تثبيت مكتبات جديدة‪.‬‬

‫‪ 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‬ز من‬

‫مواصفات ملف ‪ ELF‬كما يلي‪:‬‬

‫{ ‪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_name‬‬

‫القيمة الموجودة في كائن مشترك قابل للنقل‪ ،‬حيث تحتفظ هذه القيمة باإلزاحة عن قسم‬
‫‪st_value‬‬
‫الفهرس المعطى في الحقل‬

‫أي حجم مرتبط بالرمز‬ ‫‪st_size‬‬

‫معلومات عن ارتباط ‪ Binding‬الرمز الذي سنشرحه الحًق ا ويكون نوع هذا الرمز دالة أو كائن‬
‫‪st_info‬‬
‫أو غير ذلك‬

‫غير ُم ستخَدم حالًي ا‬ ‫‪st_other‬‬

‫فهرس القسم الذي يوجد فيه الرمز (اّط لع عىل الحقل ‪)st_value‬‬ ‫‪st_shndx‬‬

‫جدول ‪ :25‬حقول رمز ‪ELF‬‬

‫تكون السلسلة النصية الفعلية السم الرمز ضمن قسم منفصل هو القسم ‪ .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‬ط‬

‫الديناميكي بالعثور عىل مدخلة الرمز بصورة أسر ع باستخدام عملية بحث واحدة أو اثنتين فقط‪.‬‬

‫ب‪ .‬ارتباط الرموز ‪Symbol Binding‬‬

‫تشير عملية العثور عىل عنوان رمز إىل عملية ارتباط هذا الرمز‪ ،‬ولكن ارتب‪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‬نرى‬

‫الحًق ا‪.‬‬

‫يوضح المثال التالي برنامًجا بلغة سي ‪ C‬نحّلله لفحص ارتباطات الرموز‪:‬‬

‫‪$ cat test.c‬‬


‫;‪static int static_variable‬‬

‫;‪extern int extern_variable‬‬

‫;)‪int external_function(void‬‬

‫)‪int function(void‬‬
‫{‬
‫;)(‪return external_function‬‬
‫}‬

‫)‪static int static_function(void‬‬


‫{‬
‫;‪return 10‬‬
‫}‬

‫‪#pragma weak weak_function‬‬


‫)‪int weak_function(void‬‬
‫{‬
‫;‪return 10‬‬
‫}‬

‫‪249‬‬
‫علوم الحاسوب من األلف إىل الياء‬ ‫مفهوم الربط الديناميكي‬

‫‪$ gcc -c test.c‬‬


‫‪$ objdump --syms test.o‬‬

‫‪test.o:‬‬ ‫‪file format elf32-powerpc‬‬

‫‪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‬اط في‬

‫العمود الثاني في كلتا الحالتين‪ ،‬ويجب أن تكون الشيفرات البرمجية واضحة تماًم ا‪.‬‬

‫تجاوز الرموز ‪Overriding Symbols‬‬

‫يجب أن يكون المبرمج قادًرا عىل تجاوز رمز في مكتبة‪ ،‬مما يعني تخ‪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());
}

$ gcc -shared -fPIC -o liboverride.so override.c -ldl


$ gcc -o test test.c
$ LD_PRELOAD=./liboverride.so ./test
Calling GETPID
15187

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‬عيفة في لينكس‬

‫باستخدام مكتبة ‪ glibc‬سابًق ا‪.‬‬

‫لكن كان ذلك غير صحيح بالنسبة لنص معيار يونكس في ذلك الوقت (‪ )SysVr4‬الذي يفرض أن‪tt‬ه يجب أن‬

‫يتعامل الرابط الساكن مع الرموز الضعيفة التي يجب أن تظل بعيدة عن الرابط الديناميكي‪ .‬تطابق تقديم لينكس‬

‫الخاص بجعل الرابط الديناميكي يتجاوز الرموز الضعيفة مع منصة ‪ IRIX‬الخاصة بشركة ‪ SGI‬واختلف عن األنظمة‬

‫األخرى مثل ‪ Solaris‬و ‪ AIX‬في ذلك الوقت‪ .‬لذا لغى المطورون هذا السلوك عن‪t‬دما أدرك‪t‬وا أن‪t‬ه ينته‪t‬ك المعي‪t‬ار‪،‬‬

‫وتغير السلوك القديم ليتطّلب ضبط راية بيئة خاصة (‪.)LD_DYNAMIC_WEAK‬‬

‫تحديد ترتيب االرتباط‬

‫رأينا كيف يمكننا تجاوز دالة في مكتبة من خالل التحميل المسبق لمكتبة مشتركة أخرى له‪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‬‬
‫علوم الحاسوب من األلف إىل الياء‬ ‫مفهوم الربط الديناميكي‬

Symbol Versioning ‫تحديد إصدار الرموز‬

‫افية‬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

test : test.c libtest.so liboverride.so


$(CC) -L. -ltest -o test test.c

testsym : test.c libtestsym.so liboverride.so


$(CC) -L. -ltestsym -o testsym test.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: *; };

$ gcc -shared -fPIC -Wl,-version-script=Versions -o libtestver.so


libtest.c

$ gcc -L. -ltestver -o testver test.c

$ LD_LIBRARY_PATH=. LD_PRELOAD=./liboverride.so ./testver


libtest foo called

l F .text 00000054 foo


000005c8 g F .text 00000038 test_foo

‫ تكون الدالة‬.‫يمكننا ذكر ما إذا كان الرمز عاًم ا أم محلًي ا في أبسط الحاالت عىل النحو الوارد في المثال السابق‬

‫ ولكن إن‬،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‬ل‬

‫‪ @VER‬الذي يصف اإلصدار المعطى للرمز‪.‬‬

‫إن قّدم المطور دالة لها االسم نفس‪tt‬ه تق‪tt‬ديًم ا ثنائًي ا أو برمجًي ا مختلًف ا‪ ،‬فيمكن‪tt‬ه زي‪tt‬ادة رقم اإلص‪tt‬دار‪ .‬تلتق‪tt‬ط‬

‫التطبيقات الجديدة أحدث إصدار من الرمز عند بنائها بمقابل المكتبة المشتركة‪ .‬لكن ستطلب التطبيقات المبنية‬

‫بمقابل اإلصدارات السابقة من المكتبة نفسها إصدارات أقدم‪ ،‬فمثاًل سيكون له‪tt‬ا سالس‪tt‬ل ‪ @VER‬أق‪tt‬دم في اس‪tt‬م‬

‫الرمز الذي تطلبه‪ ،‬وبالتالي ستحصل عىل التقديم األصلي‪.‬‬

‫‪255‬‬
‫‪ .10‬قائمة المصطلحات‬

‫واجهة برمجة التطبيقات ‪ Application Programming Interface‬أو ‪ API‬اختصاًر ا‪ :‬مجموعة‬ ‫•‬

‫المتغيرات والدوال الُم ستخَدمة للتواصل بين أجزاء البرامج المختلفة‪.‬‬

‫واجهة التطبيق الثنائية ‪ 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‬من ألي‬ ‫•‬

‫شخص حقوق االستخدام والتعديل وإعادة توزيع الشيفرة البرمجية بحرية‪.‬‬

‫الصدَف ة ‪ :Shell‬الواجهة المستخدمة للتفاعل مع نظام التشغيل‪.‬‬ ‫•‬


‫أحدث إصدارات أكاديمية حسوب‬

You might also like