<?xml version="1.0"?>
<rss version="2.0"><channel><title>&#x627;&#x644;&#x628;&#x631;&#x645;&#x62C;&#x629;: &#x644;&#x63A;&#x629; C</title><link>https://academy.hsoub.com/programming/c/page/3/?d=2</link><description>&#x627;&#x644;&#x628;&#x631;&#x645;&#x62C;&#x629;: &#x644;&#x63A;&#x629; C</description><language>ar</language><item><title>&#x627;&#x644;&#x641;&#x635;&#x644; &#x627;&#x644;&#x62E;&#x627;&#x645;&#x633;: &#x62A;&#x645;&#x62B;&#x64A;&#x644; &#x627;&#x644;&#x623;&#x639;&#x62F;&#x627;&#x62F; &#x648;&#x627;&#x644;&#x646;&#x635;&#x648;&#x635; &#x628;&#x627;&#x644;&#x628;&#x62A;&#x627;&#x62A; &#x648;&#x625;&#x62C;&#x631;&#x627;&#x621; &#x627;&#x644;&#x639;&#x645;&#x644;&#x64A;&#x627;&#x62A; &#x639;&#x644;&#x649; &#x645;&#x633;&#x62A;&#x648;&#x649; &#x627;&#x644;&#x628;&#x62A;</title><link>https://academy.hsoub.com/programming/c/%D8%A7%D9%84%D9%81%D8%B5%D9%84-%D8%A7%D9%84%D8%AE%D8%A7%D9%85%D8%B3-%D8%AA%D9%85%D8%AB%D9%8A%D9%84-%D8%A7%D9%84%D8%A3%D8%B9%D8%AF%D8%A7%D8%AF-%D9%88%D8%A7%D9%84%D9%86%D8%B5%D9%88%D8%B5-%D8%A8%D8%A7%D9%84%D8%A8%D8%AA%D8%A7%D8%AA-%D9%88%D8%A5%D8%AC%D8%B1%D8%A7%D8%A1-%D8%A7%D9%84%D8%B9%D9%85%D9%84%D9%8A%D8%A7%D8%AA-%D8%B9%D9%84%D9%89-%D9%85%D8%B3%D8%AA%D9%88%D9%89-%D8%A7%D9%84%D8%A8%D8%AA-r993/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2020_09/5.png.bbbd759d620b424633b4241950fb9eba.png" /></p>

<h2>
	تمثيل الأعداد الصحيحة (Representing integers)
</h2>

<p>
	لا بدّ أنك تعلم أن الحواسيب تمثّل الأعداد بنظام العد ذو الأساس 2 (base 2) والمعروف أيضًا بالنظام الثنائي (binary). التمثيل الثنائي للأعداد الموجبة واضحٌ فعلى سبيل المثال التمثيل الثنائي للعدد 5<sub>10</sub> (أي للعدد 5 في نظام العد العشري) هو b101 (أي 101 بنظام العد الثنائي (binary)). بينما يستخدم التمثيلُ الأوضح للأعداد السالبة بتًا للإشارة (sign bit) لتحديد فيما إذا كان العدد موجبًا أو سالبًا، ولكن يوجد تمثيلٌ آخر يدعى بالمتمّم الثنائي (two's complement) وهو التمثيل الأكثر شيوعًا لأن العمل معه أسهل ضمن العتاد. لإيجاد المتمم الثنائي للعدد السالب ‎-x تجد التمثيل الثنائي للعدد x أولًا، ثم تقلب (flip) كل البتات أي تقلب الأصفار واحداتٍ والواحدات أصفارًا، ثم تجمع 1 لناتج القلب، فلتمثيل العدد ‎-5<sub>10</sub> بالنظام الثنائي على سبيل المثال، تبدأ بتمثيل العدد 5<sub>10</sub> بالنظام الثنائي بكتابته بنسخة 8 بت (8bit version) وهو b00000101، ثم تقلب (flip) كل البتات وتضيف له 1 فينتج b11111011. يتصرف البت الموجود أقصى اليسار في المتمم الثنائي كبت إشارة، فهو 0 في الأعداد الموجبة و1 في الأعداد السالبة. يجب إضافة أصفار للعدد الموجب وواحدات للعدد السالب عند تحويل عدد من النوع 8 بت إلى 16 بتًا، أي يجب نسخ قيمة بت الإشارة إلى البتات الجديدة وتدعى هذه العملية بامتداد الإشارة (sign extension). كل أنواع الأعداد الصحيحة في لغة البرمجة C لها إشارة أي أنها قادرة على تمثيل الأعداد السالبة والموجبة إلّا إذا صرّحت عنهم كأعداد صحيحة بِلا إشارة (unsigned)، والاختلاف الذي يجعل هذا التصريح (declaration) مهمًا هو أن عمليات الأعداد الصحيحة التي لا تملك إشارة (unsigned integers) لا تستخدم امتداد الإشارة (sign extension).
</p>

<h2>
	العاملات الثنائية (Bitwise operators)
</h2>

<p>
	يُصاب متعلمو لغة البرمجة C بالارتباك أحيانًا بالنسبة للعاملين الثنائيين (&amp; و |)، حيث يعامِل هذان العامِلان الأعدادَ الصحيحة (integers) كمتجهات من البتات (bit vectors) وتُحسَب العمليات المنطقية (logical operations) بتطبيقها على البتات المتناظرة (corresponding bits).، حيث تَحسب &amp; عملية AND بحيث ينتج 1 إذا كانت قيمة كلا المُعامَلين (operands) هي 1 وينتج 0 بخلاف ذلك. المثال التالي هو تطبيق العامِل &amp; على عددين مكونين من 4 بتات:
</p>

<pre class="ipsCode" id="ips_uid_5585_6">
    1100
  &amp; 1010
    ----
    1000
</pre>

<p>
	وهذا يعني في لغة البرمجة C أن قيمة التعبير 12 &amp; 10 هي 8.
</p>

<p>
	ويحسب العامِل | عملية OR بحيث ينتج 1 إذا كانت قيمة أحد المعامَلين 1 وينتج 0 بخلاف ذلك كما في المثال التالي:
</p>

<pre class="ipsCode">
  1100   
| 1010
   ----
  1110
</pre>

<p>
	أي قيمة التعبير 12 | 10 هي 14.
</p>

<p>
	ويحسب العامل ^ عملية XOR بحيث ينتج 1 إذا كانت قيمة أحد المعامَلين 1 وليس كلاهما كما في المثال التالي:
</p>

<pre class="ipsCode" id="ips_uid_5585_8">
   1100
 ^ 1010
   ----
   0110
</pre>

<p>
	أي قيمة التعبير 12 ^ 10 هي 6.
</p>

<p>
	يُستخدَم العامل &amp; لتصفير (clear) مجموعة بتات من متجهة بتات، بينما يُستخدم العامل | لضبط (set) البتات، ويُستخدم العامل ^ لقلب (flip) أو تبديل (toggle) البتات كما يلي:
</p>

<ul>
<li>
		<strong>تصفير البتات (Clearing bits):</strong> قيمة x&amp;0 هي 0 و x&amp;1 هي x مهما كانت قيمة x، لذلك عند تطبيق العملية AND على متجهة مع العدد 3 فسيُختار البتان الموجودان أقصى اليمين فقط وتُضبَط بقية البتات بالقيمة 0 كما يلي:
	</li>
</ul>
<pre class="ipsCode" id="ips_uid_5585_10">
    xxxx
  &amp; 0011
    ----
    00xx
</pre>

<p>
	حيث تدعى القيمة 3 العشرية أو 0011 الثنائية بقناع (mask) لأنها تختار بعض البتات وتقنّع البتات الباقية.
</p>

<ul>
<li>
		<strong>ضبط البتات (Setting bits):</strong> قيمة x|0 هي x و x|1 هي 1 مهما كانت قيمة x، لذلك عند تطبيق OR على متجهة مع العدد 3 فستُضبط البتات الموجودة أقصى اليمين بينما ستُترَك بقية البتات كما هي:
	</li>
</ul>
<pre class="ipsCode">
  xxxx
| 0011
  ----
  xx11
</pre>

<ul>
<li>
		<strong>تبديل أو عكس البتات (Toggling bits):</strong> إذا طبّقت XOR مع العدد 3 فستُقلب البتات الموجودة أقصى اليمين وتُترك بقية البتات كما هي. جرّب حساب المتمم الثنائي للعدد 12 باستخدام ^ كتمرينٍ لك، <strong>تلميح:</strong> ما هو تمثيل المتمم الثنائي للعدد ‎-1؟
	</li>
</ul>
<p>
	توفر لغة البرمجة C عاملات إزاحة أيضًا مثل &lt;&lt; و &gt;&gt; التي تزيح البتات يمينًا ويسارًا، حيث تضاعف الإزاحةُ لليسار العددَ، فناتج 5 &lt;&lt; 1 هو 10 أما ناتج 5 &lt;&lt; 2 هو 20، وتقسم الإزاحة لليمين العدد على 2 (ويكون الناتج مُقرّبًا (rounding down)) حيث ناتج 5 &gt;&gt; 1 هو 2 وناتج 2 &gt;&gt; 1 هو 1.
</p>

<h2>
	تمثيل الأعداد العشرية (Representing floating-point numbers)
</h2>

<p>
	تُمثَّل الأعداد العشرية باستخدام النسخة الثنائية (binary version) للصيغة العلمية (scientific notation)، وتُكتَب الأعداد الكبيرة في الصيغة العشرية (decimal notation) كحاصل ضرب معامِلٍ (coefficient) مع 10 مرفوعة لأسّ، فسرعة الضوء المقدّرة بالمتر/ ثانية تساوي تقريبًا ‎2.998 . 10<sup>8</sup> على سبيل المثال.
</p>

<p>
	تستخدم معظم الحواسيب معيار (IEEE standard) لحساب الأعداد العشرية (<a href="https://en.wikipedia.org/wiki/IEEE_floating_point" rel="external nofollow">معيار IEEE للأعداد العشرية</a>)، حيث يقابل النوع float في C المعيار IEEE المكون من 32 بتًا أما النوع double يقابل المعيار 64 بتًا. البت الموجود أقصى اليسار (leftmost bit) هو بت الإشارة (sign bit) ويرمز له s في معيار 32 بت، و تمثل ال8 بتات التالية الأسَّ (exponent) و يرمز له q وآخر 23 بت هي المعامِل (coefficient) ويرمز له c، وبالتالي قيمة العدد العشري هي ‎(-1)<sup>s</sup>c.2<sup>q</sup>. هذه القيمة صحيحة تقريبًا ولكن هناك شيء بسيط أيضًا، فالأعداد العشرية موحّدة بحيث يوجد رقمٌ واحد قبل الفاصلة، لذلك يُفضَّل في النظام العشري على سبيل المثال الصيغة ‎2.998 . 10<sup>8</sup> على الصيغة ‎2998 . 10<sup>5</sup> أو على أي تعبيرٍ آخر مكافئ. أما بالنظام الثنائي فالأعداد موحّدة بحيث يوجد الرقم 1 قبل الفاصلة الثنائية دومًا، وبما أن الرقم في هذا الموقع هو 1 دومًا لذلك يمكن توفير مساحة وذلك بعدم إدخال هذا الرقم ضمن التمثيل. فتمثيل العدد الصحيح 13<sub>10</sub> هو b1101 على سبيل المثال، أما التمثيل العشري هو ‎1.101 . 2<sup>3</sup> حيث 3 هو الأس (exponent) وجزء المعامِل الذي يمكن أن يُخزَّن هو 101 (متبوعًا ب 20 صفرًا). هذا صحيحٌ تقريبًا ولكن يوجد شيءٌ آخر أيضًا، وهو أن الأس يُخزّن مع معدل انحياز (bias)، وقيمة معدل الانحياز في معيار 32 بت هي 127، وبالتالي يمكن تخزين الأس 3 كَ 130. تُستخدَم عمليات الاتحاد (union operations) والعمليات الثنائية (bitwise operations) لضغط (pack) وفك ضغط (unpack) الأعداد العشرية في C كما في المثال التالي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_3130_7" style="">
<span class="kwd">union</span><span class="pln">
</span><span class="pun">{</span><span class="pln">
    </span><span class="typ">float</span><span class="pln"> f</span><span class="pun">;</span><span class="pln">
    </span><span class="kwd">unsigned</span><span class="pln"> </span><span class="typ">int</span><span class="pln"> u</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span><span class="pln"> p</span><span class="pun">;</span><span class="pln">

p</span><span class="pun">.</span><span class="pln">f </span><span class="pun">=</span><span class="pln"> </span><span class="pun">-</span><span class="lit">13.0</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">unsigned</span><span class="pln"> </span><span class="typ">int</span><span class="pln"> sign </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="pln">p</span><span class="pun">.</span><span class="pln">u </span><span class="pun">&gt;&gt;</span><span class="pln"> </span><span class="lit">31</span><span class="pun">)</span><span class="pln"> </span><span class="pun">&amp;</span><span class="pln"> </span><span class="lit">1</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">unsigned</span><span class="pln"> </span><span class="typ">int</span><span class="pln"> exp </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="pln">p</span><span class="pun">.</span><span class="pln">u </span><span class="pun">&gt;&gt;</span><span class="pln"> </span><span class="lit">23</span><span class="pun">)</span><span class="pln"> </span><span class="pun">&amp;</span><span class="pln"> </span><span class="lit">0xff</span><span class="pun">;</span><span class="pln">

</span><span class="kwd">unsigned</span><span class="pln"> </span><span class="typ">int</span><span class="pln"> coef_mask </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="lit">1</span><span class="pln"> </span><span class="pun">&lt;&lt;</span><span class="pln"> </span><span class="lit">23</span><span class="pun">)</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="lit">1</span><span class="pun">;</span><span class="pln">
</span><span class="kwd">unsigned</span><span class="pln"> </span><span class="typ">int</span><span class="pln"> coef </span><span class="pun">=</span><span class="pln"> p</span><span class="pun">.</span><span class="pln">u </span><span class="pun">&amp;</span><span class="pln"> coef_mask</span><span class="pun">;</span><span class="pln">

printf</span><span class="pun">(</span><span class="str">"%d\n"</span><span class="pun">,</span><span class="pln"> sign</span><span class="pun">);</span><span class="pln">
printf</span><span class="pun">(</span><span class="str">"%d\n"</span><span class="pun">,</span><span class="pln"> exp</span><span class="pun">);</span><span class="pln">
printf</span><span class="pun">(</span><span class="str">"0x%x\n"</span><span class="pun">,</span><span class="pln"> coef</span><span class="pun">);</span></pre>

<p>
	يسمح النوع union بتخزين القيمة العشرية باستخدام المتغير p.f ثم قراءته كعدد صحيح بلا إشارة (unsigned integer) باستخدام المتغير p.u. وللحصول على بت الإشارة تُزاح البتات يمينًا بمقدار 31 ثم يُستخدم قناع 1 بت (أي تطبيق &amp; على ناتج الإزاحة مع العدد 1) وذلك لاختيار البت الموجود أقصى اليمين فقط. وللحصول على الأس تُزاح البتات بمقدار 23 ثم تُختار ال8 بتات الموجودة أقصى اليمين، حيث تملك القيمة الست عشرية 0xff ثمانية واحدات. وللحصول على المعامِل (coefficient) تحتاج لإزالة 23 بتًا الموجودين أقصى اليمين وتجاهل بقية البتات، ويمكن تحقيق ذلك من خلال إنشاء قناع مكون من واحدات في البتات ال23 الموجودة أقصى اليمين وأصفارًا في البتات الموجودة على اليسار، والطريقة الأسهل لإنشاء هذا القناع هي إزاحة 1 يسارًا بمقدار 23 ثم يُطرَح من ناتج الإزاحة 1. خرج البرنامج السابق هو كما يلي:
</p>

<pre class="ipsCode">
1
130
0x500000
</pre>

<p>
	بت الإشارة للعدد السالب هو 1 كما هو متوقع، والأس هو 130 متضمنًا معدل الانحياز، والمعامل هو 101 متبوعًا ب20 صفرًا ولكنه طُبع بالنظام الست عشري (0x500000).
</p>

<h2>
	أخطاء الاتحادات وأخطاء الذاكرة (Unions and memory errors)
</h2>

<p>
	يوجد استخدامان شائعان لاتحادات لغة البرمجة C أحدهما هو الوصول إلى التمثيل الثنائي للبيانات كما ذُكر سابقًا. والاستخدام الآخر هو تخزين البيانات غير المتجانسة (heterogeneous data)، فيمكنك استخدام اتحاد (union) لتمثيل عددٍ والذي من الممكن أن يكون عددًا صحيحًا (integer) أو عشريًا (float) أو مركبًا (complex) أو كسريًا (rational). الاتحادات معرضةٌ للخطأ على كل حال، ويعود الأمر للمبرمج لتتبع نوع البيانات الموجودة في الاتحاد، فإذا كتبت قيمة عشرية ثم فُسِّرت كعدد صحيح فستكون النتيجة لامعنىً لها، وهو أمرٌ مماثل لقراءة موقع من الذاكرة بصورة خاطئة مثل قراءة قيمة موقع من مصفوفة في حين تكون هذه المصفوفة انتهت أي قراءة قيمة من خارج حدود المصفوفة. تخصص الدالة التالية مكانًا للمصفوفة في المكدس وتملؤه بأعداد من 0 إلى 99:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_9752_8" style="">
<span class="kwd">void</span><span class="pln"> f1</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
</span><span class="typ">int</span><span class="pln"> i</span><span class="pun">;</span><span class="pln">
</span><span class="typ">int</span><span class="pln"> array</span><span class="pun">[</span><span class="lit">100</span><span class="pun">];</span><span class="pln">

</span><span class="kwd">for</span><span class="pln"> </span><span class="pun">(</span><span class="pln">i</span><span class="pun">=</span><span class="lit">0</span><span class="pun">;</span><span class="pln"> i</span><span class="pun">&lt;</span><span class="lit">100</span><span class="pun">;</span><span class="pln"> i</span><span class="pun">++)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
array</span><span class="pun">[</span><span class="pln">i</span><span class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> i</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	ثم تعرّف الدالة التالية مصفوفةً أصغر وتدخل عناصرًا قبل بداية المصفوفة وبعد نهايتها عمدًا:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_9752_10" style="">
<span class="kwd">void</span><span class="pln"> f2</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
</span><span class="typ">int</span><span class="pln"> x </span><span class="pun">=</span><span class="pln"> </span><span class="lit">17</span><span class="pun">;</span><span class="pln">
</span><span class="typ">int</span><span class="pln"> array</span><span class="pun">[</span><span class="lit">10</span><span class="pun">];</span><span class="pln">
</span><span class="typ">int</span><span class="pln"> y </span><span class="pun">=</span><span class="pln"> </span><span class="lit">123</span><span class="pun">;</span><span class="pln">

printf</span><span class="pun">(</span><span class="str">"%d\n"</span><span class="pun">,</span><span class="pln"> array</span><span class="pun">[-</span><span class="lit">2</span><span class="pun">]);</span><span class="pln">
printf</span><span class="pun">(</span><span class="str">"%d\n"</span><span class="pun">,</span><span class="pln"> array</span><span class="pun">[-</span><span class="lit">1</span><span class="pun">]);</span><span class="pln">
printf</span><span class="pun">(</span><span class="str">"%d\n"</span><span class="pun">,</span><span class="pln"> array</span><span class="pun">[</span><span class="lit">10</span><span class="pun">]);</span><span class="pln">
printf</span><span class="pun">(</span><span class="str">"%d\n"</span><span class="pun">,</span><span class="pln"> array</span><span class="pun">[</span><span class="lit">11</span><span class="pun">]);</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	تكون نتيجة استدعاء الدالة f1 ثم استدعاء الدالة f2 ما يلي:
</p>

<pre class="ipsCode">
17
123
98
99
</pre>

<p>
	تعتمد التفاصيل على المصرّف (compiler) الذي يرتّب المتغيرات في المكدّس، ويمكنك من خلال النتائج السابقة استنتاج أن المصرّف يضع المتغيرين x و y قرب بعضهما البعض أسفل المصفوفة (أي في عناوين أسفل عنوان المصفوفة)، وعندما تقرأ قيمةً خارج المصفوفة فكأنك تريد الحصول على قيمٍ متروكة من استدعاء دالة سابقة في المكدس. كل المتغيرات في المثال السابق أعدادٌ صحيحة (integers) لذلك سيكون سهلًا معرفة ما يحدث إلى حدٍ ما، ولكن يمكن أن يكون للقيم التي تقرؤها من خارج حدود المصفوفة أيّ نوع. إذا غيّرت في الدالة f1 بحيث تستخدم مصفوفة أعداد عشرية (array of floats) فالنتيجة هي:
</p>

<pre class="ipsCode">
17
123
1120141312
1120272384
</pre>

<p>
	آخر قيمتين من الخرج السابق هما ما تحصل عليه عندما تفسّر قيمة عشرية كعدد صحيح، وإذا صادفت هذا الخرج خلال عملية تنقيح الأخطاء (debugging) سيكون تفسير ما يحصل صعبًا جدًا.
</p>

<h2>
	تمثيل السلاسل (Representing strings)
</h2>

<p>
	سلاسل لغة البرمجة C هي سلاسلٌ منتهية بالقيمة الخالية (null-terminated)، لذلك لا تنسَ البايت الإضافي في نهاية السلسلة وذلك عند تخصيص مكان لهذه السلسلة. تُرمّز الحروف والأرقام في سلاسل C بواسطة ترميز ASCII، فترميز ASCII للأرقام من 0 إلى 9 هو من 48 إلى 57 وليس ترميزها من 0 إلى 9، فالرمز الآسكي 0 يمثل الحرف الخالي (NUL) الذي يحدد نهاية السلسلة، أما الرموز الآسكية من 1 إلى 9 فهي محارف خاصة تُستخدم في بعض بروتوكولات الاتصالات، والرمز الآسكي 7 هو جرس (bell) فينتج عن طباعته إصدارُ صوت في بعض الطرفيات. 65 هو الرمز الآسكي للحرف A وللحرف a هو 97 والتي تُكتب ثنائيًا كما يلي :
</p>

<pre class="ipsCode">
65 = b0100 0001
97 = b0110 0001
</pre>

<p>
	حيث ستلاحظ أنهما مختلفان فقط ببتٍ واحد إذا تمعّنت النظر قليلًا، ويُستخدم هذا النمط أيضًا لبقية الحروف، فيتصرّف البت السادس (إذا ابتدأت العد من اليمين) كبت حالة الحرف (case bit) فهو 0 للحروف الكبيرة و 1 للحروف الصغيرة. جرّب كتابة دالة تحوّل الحرف الصغير إلى حرفٍ كبير وذلك من خلال قلب (flipping) البت السادس فقط، ويمكنك أيضًا صنع نسخة أسرع من الدالة وذلك من خلال قراءة سلسلة مكونة من 32 بتًا أو 64 بتًا وهذا أفضل من قراءة حرفٍ واحد فقط في كل مرة، حيث ينشَأ هذا التحسين بسهولة أكثر إذا كان طول السلسلة من مضاعفات 4 أو 8 بايتات. إذا قرأت قيمةً خارج حدود المصفوفة ستظهر لك محارف غريبة، ولكن إذا كتبت سلسلةً ثم قرأتها كعدد صحيح (int) أو عشري (float) فسيكون تفسير (interpret) النتيجة صعبًا. وإذا شغلّت البرنامج التالي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_9752_12" style="">
<span class="kwd">char</span><span class="pln"> array</span><span class="pun">[]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> </span><span class="str">"allen"</span><span class="pun">;</span><span class="pln">
</span><span class="typ">float</span><span class="pln"> </span><span class="pun">*</span><span class="pln">p </span><span class="pun">=</span><span class="pln"> array</span><span class="pun">;</span><span class="pln">
printf</span><span class="pun">(</span><span class="str">"%f\n"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">*</span><span class="pln">p</span><span class="pun">);</span></pre>

<p>
	ستجد أن التمثيل الآسكي لأول 8 حروف من اسمي، يقول الكاتب، التي فُسّرت كعدد عشري مضبوط بالنوع double (double-precision floating point number) هو 69779713878800585457664.
</p>

<p>
	ترجمة -وبتصرّف- للفصل More bits and bytes من كتاب <a href="http://greenteapress.com/thinkos/" rel="external nofollow">Think OS A Brief Introduction to Operating Systems</a>
</p>
]]></description><guid isPermaLink="false">993</guid><pubDate>Mon, 14 Sep 2020 18:00:00 +0000</pubDate></item><item><title>&#x627;&#x644;&#x641;&#x635;&#x644; &#x627;&#x644;&#x631;&#x627;&#x628;&#x639;: &#x641;&#x647;&#x645; &#x627;&#x644;&#x645;&#x644;&#x641;&#x627;&#x62A; Files &#x648;&#x623;&#x646;&#x638;&#x645;&#x629; &#x627;&#x644;&#x645;&#x644;&#x641;&#x627;&#x62A; file systems</title><link>https://academy.hsoub.com/programming/c/%D8%A7%D9%84%D9%81%D8%B5%D9%84-%D8%A7%D9%84%D8%B1%D8%A7%D8%A8%D8%B9-%D9%81%D9%87%D9%85-%D8%A7%D9%84%D9%85%D9%84%D9%81%D8%A7%D8%AA-files-%D9%88%D8%A3%D9%86%D8%B8%D9%85%D8%A9-%D8%A7%D9%84%D9%85%D9%84%D9%81%D8%A7%D8%AA-file-systems-r979/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2020_09/4.png.97fd5513d20520269ee8c7d188fe82ea.png" /></p>

<p>
	تُفقََد البيانات المخزَّنةٌ في الذاكرة الرئيسية لعمليةٍ ما عندما تكمل هذه العملية عملها أو تتعطل لسببٍ ما، ويُطلَق على البيانات المخزّنة في القرص الصلب (hard disk drive واختصاره HDD) والبيانات المخزّنة على أقراص التخزين ذات الحالة الثابتة (solid state drive وتختصر إلى SSD) ببياناتٍ دائمة (persistent)، أي أنها لا تُفقََد بعد اكتمال العملية حتى لو أغلِق الحاسوب. القرص الصلب (Hard disk drive) معقد، حيث تخزَّن البيانات ضمن كتل (blocks) التي تتواجد ضمن قطاعات (sectors)، وتشكّل القطاعاتُ مساراتٍ (tracks) ثم تنظَّم المسارات في دوائر متحدة المركز على أطباق (platters) القرص الصلب. أما أقراص التخزين ذات الحالة الثابتة (Solid state drives) فهي أبسط إلى حدٍ ما لأنّ الكتل مرقّمة تسلسليًا، ولكنها تثير تعقيدًا مختلفًا فيمكن أن تُكتَب كل كتلة عددًا محدودًا من المرات قبل أن تصبح غير موثوقة للاستخدام مرةً أخرى. والمبرمج غير مجبرٍ للتعامل مع تلك التعقيدات ولكن ما يحتاجه حقًا هو تجريدٌ مناسب لعتاد التخزين الدائم (persistent storage hardware)، وتجريد التخزين الدائم الأكثر شيوعًا هو نظام الملفات (file system)، فيمكن القول بتجريد أن:
</p>

<ul>
<li>
		نظام الملفات ما هو إلا ربط (mapping) بين اسم الملف ومحتوياته، فإذا عُدَّت أسماء الملفات مفاتيحًا (keys) ومحتويات الملف قيمًا (values) فإن نظام الملفات مشابه <a href="https://en.wikipedia.org/wiki/Key%E2%80%93value_database" rel="external nofollow">لقاعدة البيانات ذات النوع مفتاح-قيمة</a> (key-value database)
	</li>
	<li>
		الملف هو سلسلة من البايتات
	</li>
</ul>
<p>
	تكون أسماء الملفات عادةً من النوع سلسلة (string) وتكون بنظام هرمي (hierarchical) حيث يكون اسم الملف عبارة عن مسار يبدأ من المجلد الأعلى مستوى (top-level directory or folder) مرورًا بمجلدات فرعية حتى الوصول إلى الملف المطلوب. الاختلاف الرئيسي بين الآلية الأساسية (underlying mechanism) والتي هي التخزين الدائم وتجريدها والذي هو نظام الملفات هو أن الملفات تعمل على أساس البايت (byte-based) أما التخزين الدائم يعمل على أساس الكتلة (block-based). يترجم نظام التشغيل عمليات الملف ذات الأساس البايتي في مكتبة C إلى عمليات ذات أساس كتلي على أجهزة التخزين الدائم، ويتراوح حجم الكتلة بين 1 و 8 كيبي بايت (KiB التي تساوي 2<sup>10</sup> أو 1024 بايت). تفتح الشيفرة التالية ملفًا وتقرأ أول بايت:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_6733_7" style="">
<span class="typ">FILE</span><span class="pln"> </span><span class="pun">*</span><span class="pln">fp </span><span class="pun">=</span><span class="pln"> fopen</span><span class="pun">(</span><span class="str">"/home/downey/file.txt"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"r"</span><span class="pun">);</span><span class="pln">
</span><span class="kwd">char</span><span class="pln"> c </span><span class="pun">=</span><span class="pln"> fgetc</span><span class="pun">(</span><span class="pln">fp</span><span class="pun">);</span><span class="pln">
fclose</span><span class="pun">(</span><span class="pln">fp</span><span class="pun">);</span></pre>

<p>
	يحدث ما يلي عند تشغيل الشيفرة السابقة:
</p>

<ol>
<li>
		تستخدم الدالة <code>fopen</code> اسم الملف لإيجاد المجلد الأعلى مستوىً (top-level directory) وهو <code>/</code> ثم المجلد الفرعي <code>home</code> ثم المجلد الفرعي المتواجد ضمن <code>home</code> وهو <code>downey</code>.
	</li>
	<li>
		لتجد بعد ذلك ملفًا اسمه <code>file.txt</code> وتفتحه للقراءة، وهذا يعني أن <code>fopen</code> تنشئ بنية بيانات (data structure) تمثل الملف المقروء، حيث تتتبّع بينةُ البيانات الكميةَ المقروءة من الملف، وتدعى هذه الكمية المقروءة بموضع الملف (file position). وتدعى بنية البيانات تلك بكتلة تحكم الملف (File Control Block) في DOS، ولكنني أريد، يقول الكاتب، تجنّب ذلك المصطلح لأن له معنىً آخر في UNIX، وبالتالي لا يوجد اسمٌ جيد لبنية البيانات تلك في UNIX، وبما أنها مدخلة في جدول الملف المفتوح لذلك سأسميها، يقول الكاتب، بمدخلة جدول الملف المفتوح (OpenFileTableEntry).
	</li>
	<li>
		يتحقق نظام التشغيل من وجود الحرف التالي من الملف مسبقًا في الذاكرة عند استدعاء الدالة <code>fgetc</code>، إذا كان موجود فإن الدالة <code>fgetc</code> تقرأ الحرف التالي وتقدّم موضع الملف إلى الحرف الذي بعده ثم تعيد النتيجة.
	</li>
	<li>
		أما إذا لم يوجد الحرف التالي في الذاكرة فيصدر نظام التشغيل طلب إدخال/إخراج (I/O request) للحصول على الكتلة التالية. القرص الصلب بطيء لذلك تُقاطَع العملية التي تنتظر وصول بياناتٍ من القرص الصلب عادةً وتشغَّل عملية أخرى ريثما تصل تلك البيانات.
	</li>
	<li>
		تُخزَّن الكتلة الجديدة من البيانات في الذاكرة عند اكتمال عملية الإدخال/الإخراج (I/O operation) ثم تستأنف العملية عملها، حيث يُقرأ أول حرف ويُخزَّن كمتغير محلي.
	</li>
	<li>
		يكمل نظام التشغيل أية عملية معلّقة (pending operations) أو يلغيها ثم يزيل البيانات المخزنة في الذاكرة ويحرر مدخلة جدول الملف المفتوح (OpenFileTableEntry) عندما تغلق العمليةُ الملف.
	</li>
</ol>
<p>
	عملية الكتابة في ملف مشابهة لعملية القراءة من ملف ولكن مع وجود خطوات إضافية، حيث يفتح المثال التالي ملفًا للكتابة ويغير أول حرف من الملف:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_6733_10" style="">
<span class="typ">FILE</span><span class="pln"> </span><span class="pun">*</span><span class="pln">fp </span><span class="pun">=</span><span class="pln"> fopen</span><span class="pun">(</span><span class="str">"/home/downey/file.txt"</span><span class="pun">,</span><span class="pln"> </span><span class="str">"w"</span><span class="pun">);</span><span class="pln">
fputc</span><span class="pun">(</span><span class="str">'b'</span><span class="pun">,</span><span class="pln"> fp</span><span class="pun">);</span><span class="pln">
fclose</span><span class="pun">(</span><span class="pln">fp</span><span class="pun">);</span></pre>

<p>
	يحدث ما يلي عند تشغيل الشيفرة السابقة:
</p>

<ol>
<li>
		تستخدم الدالة <code>fopen</code> اسم الملف لإيجاده، فإذا كان الملف غير موجود مسبقًا فتنشئ الدالة <code>fopen</code> ملفًا جديدًا وتضيف مدخلةً في المجلد الأب <code>home/downey/</code>.
	</li>
	<li>
		ينشئ نظام التشغيل مدخلة جدول الملف المفتوح (OpenFileTableEntry) التي تحدد أن الملف مفتوح للكتابة وتهيئ موضع الملف بالقيمة 0.
	</li>
	<li>
		تحاول الدالة <code>fputc</code> كتابة أو إعادة كتابة البايت الأول من الملف، حيث إذا كان الملف موجودًا مسبقًا فيجب على نظام التشغيل تحميل الكتلة الأولى من الملف إلى الذاكرة، وإذا لم يوجد الملف فسيخصّص نظام التشغيل مكانًا للكتلة الجديدة في الذاكرة ويطلب كتلة جديدة من القرص الصلب.
	</li>
	<li>
		يمكن ألّا تُنسَخ الكتلة المعدّلة في الذاكرة إلى القرص الصلب بعد تعديلها مباشرةً، حيث تُخزَّن البيانات المكتوبة في الملف تخزينًا مؤقتًا (buffered) أي أنها تُخزَّن في الذاكرة، ولكنها لا تُكتَب في القرص الصلب إلّا عند وجود كتلة واحدة على الأقل لكتابتها.
	</li>
	<li>
		تُكتَب البيانات المخزنة تخزينًا مؤقتًا في القرص الصلب وتُحرَّر مدخلة جدول الملف المفتوح عند إغلاق الملف.
	</li>
</ol>
<p>
	باختصار توفر مكتبة C تجريدًا هو نظام الملفات الذي يربط أسماء الملفات بمجرىً من البايتات، ويُبنى هذا التجريد على أجهزة التخزين الدائم التي تنظَّم ضمن كتل.
</p>

<h2>
	أداء القرص الصلب (Disk performance)
</h2>

<p>
	الأقراص الصلبة بطيئة حيث إن الوقت الوسطي لقراءة كتلة من القرص الصلب إلى الذاكرة يتراوح بين 5 و 25 ميلي ثانية على الأقراص الصلبة الحالية HDDs (تعرّف على <a href="https://en.wikipedia.org/wiki/Hard_disk_drive_performance_characteristics" rel="external nofollow">خصائص أداء القرص الصلب</a>). أما SSDs فهي أسرع من HDDs، حيث تستغرق قراءة كتلة حجمها 4 كيبي بايت 25 ميكرو ثانية وتستغرق كتابتها 250 ميكرو ثانية (تعرّف على <a href="https://en.wikipedia.org/wiki/Solid-state_drive#Controller" rel="external nofollow">متحكم قرص التخزين ذات الحالة الثابتة</a>). وإذا وازنت الأرقام السابقة مع دورة ساعة المعالج (clock cycle of the CPU)، حيث إن المعالج الذي يملك معدل ساعة (clock rate) مقداره 2 جيجا هرتز يكمل دورةَ ساعةٍ كل 0.5 نانو ثانية، والوقت اللازم لجلب بايت من الذاكرة إلى المعالج هو حوالي 100 نانو ثانية، وبالتالي إذا أكمل المعالج تعليمةً واحدةً في كل دورة ساعة (التي مقدارها 0.5 نانو ثانية) فإنه سيكمل 200 تعليمة خلال انتظاره وصول بايت من الذاكرة إليه (100/0.5=200). وسيكمل المعالج 2000 تعليمة بدورة ساعة 1 ميكرو ثانية، وبالتالي يكمل المعالج 50,000 تعليمة خلال وقت انتظار جلب بايت من SSD والذي يقدر ب 25 ميكرو ثانية. ويستطيع المعالج إكمال 2,000,000 تعليمة خلال ميلي ثانية وبذلك يستطيع إكمال 40 مليون تعليمة خلال وقت انتظار جلب بايت من القرص الصلب HDD والمقدّر ب 20 ميلي ثانية. إذا لم يكن لدى المعالج أي عملٍ للقيام به خلال عملية انتظار جلب بيانات من القرص الصلب فإنه يبقى خاملًا بلا عمل، لذلك ينتقل المعالج لتنفيذ عملية أخرى ريثما تصل البيانات من القرص الصلب.
</p>

<p>
	أحد أهم التحديات التي تواجه عملية تصميم نظام التشغيل هو الفجوة في الأداء بين الذاكرة الرئيسية والتخزين الدائم، لذلك توفر أنظمة التشغيل والعتاد مجموعة خاصيات الهدف منها سد هذه الفجوة وهذه الخاصيات هي:
</p>

<ul>
<li>
		تحويلات الكتلة (Block transfers): يتراوح الوقت اللازم لتحميل بايت واحد من القرص الصلب بين 5 و 25 ميلي ثانية، بالمقابل فإن الوقت الإضافي لتحميل كتلة حجمها 8 كيبي بايت (KiB) هو وقت مهمل، لذلك تحاول أنظمة التشغيل قراءة كتل كبيرة الحجم في كل عملية وصول إلى القرص الصلب.
	</li>
	<li>
		الجلب المسبق (Prefetching): يستطيع نظام التشغيل في بعض الأحيان توقّعَ أن العملية ستقرأ كتلةً ما ثم يبدأ بتحميل تلك الكتلة من القرص الصلب قبل أن تُطلَب. فإذا فتحتَ ملفًا وقرأت أول كتلة على سبيل المثال فهذا يؤدي إلى وجود احتمال كبير أنك ستقرأ الكتلة الثانية، لذلك سيحمّل نظام التشغيل كتل إضافية قبل أن تُطلَب.
	</li>
	<li>
		التخزين المؤقت (Buffering): يخزّن نظام التشغيل بيانات الكتابة في ملفٍ ما في الذاكرة ولا يكتبها في القرص الصلب مباشرةً، لذلك إذا عدّلت الكتلة مراتٍ متعددة عند وجودها في الذاكرة فلن يكتبها نظام التشغيل في القرص الصلب إلّا مرةً واحدة.
	</li>
	<li>
		التخبئة (Caching): إذا استخدمت عمليةٌ كتلةً ما مؤخرًا فإنها ستستخدمها مرة أخرى قريبًا، وإذا احتفظ نظام التشغيل بنسخة من هذه الكتلة في الذاكرة فإنه سيتعامل مع الطلبات المستقبلية لهذه الكتلة بسرعة الذاكرة.
	</li>
</ul>
<p>
	تُطبَّق بعض الخاصيات السابقة عن طريق العتاد أيضًا، حيث توفر بعض الأقراص الصلبة ذاكرةً مخبئيةً على سبيل المثال وتُخزَّن فيها الكتل المستخدمة مؤخرًا، وتقرأ العديد من الأقراص الصلبة عدة كتل في نفس الوقت على الرغم من وجود كتلة واحدة مطلوبة فقط.
</p>

<p>
	تحسّن الآليات السابقة من أداء البرامج ولكنها لا تغير شيئًا من سلوكها، ولا يتوجب على المبرمجين معرفة الكثير عن تلك الآليات باستثناء حالتين هما:
</p>

<ol>
<li>
		إذا أصبح أداء البرنامج سيئًا بشكل غير متوقع فيجب عليك معرفة هذه الآليات لتشخيص المشكلة.
	</li>
	<li>
		يصبح تنقيح أخطاء (debug) البرنامج أصعب عندما تُخزَّن البيانات تخزينًا مؤقتًا (buffered)، فإذا طبع البرنامج قيمة ثم تعطّل البرنامج على سبيل المثال، فإن تلك القيمة لن تظهر لأنها يمكن أن تكون في مخزَن مؤقت (buffer). وإذا كتب برنامجٌ ما بياناتٍ في القرص الصلب ثم أُغلق الحاسوب فجأةً قبل كتابة البيانات في القرص الصلب فيمكن أن تُفقَد تلك البيانات إذا كانت في الذاكرة المخبئية (cache) ولم تنقَل بعد إلى القرص الصلب.
	</li>
</ol>
<h2>
	بيانات القرص الصلب الوصفية (Disk metadata)
</h2>

<p>
	تكون الكتل التي تشكّل الملف منظمةً في القرص الصلب بحيث قد تكون هذه الكتل مجاورةً لبعضها البعض وبذلك يكون أداء نظام الملفات أفضل، ولكن قد لا تحوي معظم أنظمة التشغيل تخصيصًا متجاورًا (contiguous allocation) للكتل، حيث يكون لأنظمة التشغيل كامل الحرية بوضع كتلةٍ ما في أي مكان تريده على القرص الصلب وتستخدم بنى بيانات مختلفة لتتبع تلك الكتل. تُدعى بنية البيانات في العديد من أنظمة ملفات UNIX ب inode والتي ترمز إلى عقدة دليل (index node). تدعى معلومات الملفات مثل موقع كتل هذه الملفات بالبيانات الوصفية (metadata)، فمحتوى الملف هو بيانات (data) ومعلومات الملف بيانات أيضًا ولكنها بيانات توصف بيانات أخرى لذلك تدعى وصفية (meta). بما أن inodes تتوضع على القرص الصلب مع بقية البيانات لذلك فهي مصممة لتتوافق مع كتل القرص الصلب بدقة. تتضمن inode لنظام UNIX معلومات عن الملف وهذه المعلومات هي معرف (ID) المستخدم مالك الملف، ورايات الأذونات (permission flags) والتي تحدد مَن المسموح له قراءة أو كتابة أو تنفيذ الملف، والعلامات الزمنية (timestamps) التي تحدد آخر تعديل وآخر دخول إلى الملف، وتتضمن أيضًا أرقام أول 12 كتلة من الكتل المشكّلة للملف. فإذا كان حجم الكتلة الواحدة 8 كيبي بايت (KiB) فإن حجم أول 12 كتلة من الملف هو 96 كيبي بايت وهذا الرقم كبير كفاية لأغلبية حجوم الملفات ولكنه ليس بكافٍ لجميع الملفات بالتأكيد، لذلك تحتوي inode مؤشرًا إلى كتلة غير موجهة (indirection block) التي تتضمن مؤشّرات إلى كتلٍ أخرى فقط. يعتمد عدد المؤشرات في كتلةٍ غير موجهةٍ على أحجام الكتل وعددها وهو 1024 كتلةً عادةً، حيث تسطيع كتلةٌ غير موجهة عنونة 8 ميبي بايت (MiB التي تساوي 2<sup>20</sup> بايتًا) باستخدام 1024 كتلة وبحجم 8 كيبي بايت لكل كتلة، وهذا رقمٌ كافٍ لجميع الملفات باستثناء الملفات الكبيرة أي أنه لا يزال غير كافٍ لجميع الملفات، لذلك تتضمن inode أيضًا مؤشرًا إلى كتلة غير موجهة مضاعفة (double indirection block) التي تتضمن بدورها مؤشرات إلى كتل غير موجهة، وبالتالي يمكننا عنونة 8 جيبي بايت (GiB التي تساوي 2<sup>30</sup> بايتًا) باستخدام 1024 كتلة غير موجهة. وإذا لم يكن ذلك كافيًا أيضًا فتوجد كتلة غير موجهة ثلاثية (triple indirection block) أخيرًا، والتي تتضمن مؤشرات إلى كتل غير موجهة مضاعفة وبذلك تكون كافية لملف حجمه 8 تيبي بايت (TiB التي تساوي 2<sup>40</sup> بايتًا) كحدٍ أعلى. بدا ذلك كافيًا لمدة طويلة عندما صُمِّمت inodes لنظام UNIX، ولكنها أضحت قديمةً الآن. تستخدم بعض أنظمة الملفات مثل FAT <a href="https://en.wikipedia.org/wiki/File_Allocation_Table" rel="external nofollow">جدول تخصيص الملف</a> (File Allocation Table) كبديل عن الكتل غير الموجهة، حيث يتضمن جدول التخصيص مدخلةً لكل كتلة وتدعى الكتلة هنا بعنقود (cluster)، ويتضمن المجلد الجذر (root directory) مؤشرًا لأول عنقود في كل ملف، وتشير مدخلة جدول FAT والتي تمثل عنقودًا إلى العنقود التالي في الملف بشكل مشابه للائحة المترابطة (linked list).
</p>

<h2>
	تخصيص الكتلة (Block allocation)
</h2>

<p>
	ينبغي على أنظمة الملفات تتبّع الكتل التابعة لكل ملف وتتبّع الكتل المتاحة للاستخدام أيضًا، حيث يجد نظام الملفات كتلةً متوفرة لملفٍ ما ثم يخصصها له عندما يُنشَأ هذا الملف، ويجعل نظام الملفات كتل ملفٍ ما متاحةً لإعادة التخصيص عندما يُحذَف ذلك الملف. حيث أهداف تخصيص الكتلة هي ما يلي:
</p>

<ul>
<li>
		السرعة (Speed): يجب أن يكون تخصيص الكتل وتحريرها سريعًا.
	</li>
	<li>
		الحد الأدنى من استهلاك المساحة (Minimal space overhead): يجب أن تكون بنى البيانات التي يستخدمها المخصّص (allocator) صغيرة الحجم بحيث تترك أكبر قدر ممكن من المساحة للبيانات.
	</li>
	<li>
		الحد الأدنى من التجزئة (Minimal fragmentation): إذا وُجدت كتل غير مستخدمة نهائيًا أو مستخدمة جزئيًا فإن هذه المساحة غير المستخدمة تدعى تجزئة (fragmentation).
	</li>
	<li>
		الحد الأعلى من التجاور (Maximum contiguity): يجب أن تكون البيانات التي تُستخدم في الوقت ذاته مجاورة لبعضها البعض فيزيائيًا إذا كان ممكنًا وذلك لتحسين الأداء.
	</li>
</ul>
<p>
	تصميم نظام ملفات يحقق هذه الأهداف أمرٌ صعب خاصةً أن أداء نظام الملفات معتمدٌ على خواص الحِمل (workload characteristics) مثل حجم الملفات وأنماط الوصول (access patterns) وغير ذلك، فنظام الملفات المتماشي جيدًا مع نوع حِمل قد لا يكون أداؤه جيدًا مع نوع حِملٍ آخر، ولهذا السبب تدعم معظم أنظمة التشغيل أنواعًا متعددة من أنظمة الملفات. يُعَد تصميم نظام الملفات مجالًا نشطًا للبحث والتطوير، حيث انتقلت أنظمة تشغيل Linux خلال العشر سنوات الماضية من ext2 والذي كان نظام ملفات UNIX التقليدي إلى ext3 والذي هو نظام ملفات مزودٌ بسجل (journaling) ومُعَد لتحسين السرعة (speed) والتجاور (contiguity)، ثم انتقلت بعد ذلك إلى ext4 الذي يستطيع التعامل مع ملفات وأنظمة ملفات أكبر، وقد يكون هناك انتقال (migration) آخر إلى نظام ملفات B-tree واختصاره Btrfs خلال السنوات القليلة القادمة.
</p>

<h2>
	هل كل شيء هو ملف؟
</h2>

<p>
	إن تجريد الملف حقيقةً هو تجريدٌ لمجرىً من البايتات (stream of bytes) والذي اتضح أنه مفيدٌ لكثيرٍ من الأشياء وليس لأنظمة الملفات فقط، أنبوب (pipe) نظام UNIX هو مثالٌ على ذلك والذي هو نموذجٌ بسيط للاتصالات بين العمليات (inter-process communication)، فتكون العمليات مُعدّةً بحيث يكون خرج عمليةٍ ما دخلًا لعمليةٍ أخرى. يتصرف الأنبوب على أساس أن أول عملية هي ملفٌ مفتوحٌ للكتابة بالتالي يمكنه أن يستخدم دوال مكتبة C مثل <code>fputs</code> و <code>fprintf</code> وأن العملية الأخرى ملفٌ مفتوحٌ للقراءة أي يمكنه استخدام الدوال <code>fgets</code> و <code>fscanf</code>. وتستخدم شبكة الاتصالات تجريد مجرى البايتات أيضًا، فمِقبس (socket) نظام UNIX هو بنية بيانات تمثل قناة اتصال بين العمليات الموجودة على حواسيب مختلفة عادةً، حيث تستطيع العمليات قراءة بيانات وكتابتها في مِقبس باستخدام دوال تتعامل مع الملفات. تجعل إعادة استخدام تجريد الملف الأمور أسهل بالنسبة للمبرمجين، حيث إنهم غير ملزمين إلا بتعلم واجهة برمجة تطبيقات واحدة (application program interface واختصارها <abbr title="Application Programming Interface | واجهة برمجية">API</abbr>). كما أنها تجعل البرامج متعددة الاستعمال بما أن البرنامج المجهّز ليعمل مع الملفات قادرٌ على العمل مع بيانات قادمة من الأنابيب (pipes) ومصادر أخرى أيضًا.
</p>

<p>
	ترجمة -وبتصرّف- للفصل Files and file systems من كتاب <a href="http://greenteapress.com/thinkos/" rel="external nofollow">Think OS A Brief Introduction to Operating Systems</a>
</p>
]]></description><guid isPermaLink="false">979</guid><pubDate>Tue, 01 Sep 2020 13:44:18 +0000</pubDate></item><item><title>&#x627;&#x644;&#x641;&#x635;&#x644; &#x627;&#x644;&#x62B;&#x627;&#x644;&#x62B;: &#x627;&#x644;&#x630;&#x627;&#x643;&#x631;&#x629; &#x627;&#x644;&#x648;&#x647;&#x645;&#x64A;&#x629; Virtual memory &#x641;&#x64A; &#x646;&#x638;&#x627;&#x645; &#x627;&#x644;&#x62A;&#x634;&#x63A;&#x64A;&#x644;</title><link>https://academy.hsoub.com/programming/c/%D8%A7%D9%84%D9%81%D8%B5%D9%84-%D8%A7%D9%84%D8%AB%D8%A7%D9%84%D8%AB-%D8%A7%D9%84%D8%B0%D8%A7%D9%83%D8%B1%D8%A9-%D8%A7%D9%84%D9%88%D9%87%D9%85%D9%8A%D8%A9-virtual-memory-%D9%81%D9%8A-%D9%86%D8%B8%D8%A7%D9%85-%D8%A7%D9%84%D8%AA%D8%B4%D8%BA%D9%8A%D9%84-r978/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2020_09/3.png.0b371a9918979f8ee8cb27ac5a806ac4.png" /></p>
<h2>
	نظرية بت المعلومات (A bit of information theory)
</h2>

<p>
	البت هو رقم ثنائي ووحدة معلومات أيضًا، فبت واحد يعني احتمالًا من اثنين إما 0 أو 1، أما وجود بتين يعني وجود 4 تشكيلات محتملة: 00 و 01 و 10 و 11. وإذا كان لديك b بت فهذا يعني وجود 2<sup>b</sup> قيمة محتملة، حيث يتكون البايت مثلًا من 8 بتات أي 256 (2<sup>8</sup>=256) قيمة محتملة. في الاتجاه المقابل، أي إذا علمت عدد القيم المحتملة ولكنك لا تعلم عدد البتات المناسبة، افترض أنك تريد تخزين حرف واحد من حروف الأبجدية التي تتكون من 26 حرفًا فكم بتًا تحتاج؟ لديك 16 قيمة محتملة ب 4 بتات (2<sup>4</sup> = 16) وبالتالي هذا غير كافٍ لتخزين 26 حرفًا. وتحصل على 32 قيمة محتملة ب 5 بتات وهو كافٍ لتخزين كل الحروف مع قيم فائضة أيضًا. لذلك إذا أردت الحصول على قيمة واحدة من أصل N قيمة محتملة يجب عليك اختيار أصغر قيمة ل b التي تحقق (2<sup>b</sup> ≥ N)، وبأخد اللوغاريتم الثنائي للطرفين ينتج (b ≥ log<sub>2</sub>(N)).
</p>

<p>
	تعطيك نتيجة رمي قطعة نقود بتًا واحدًا من المعلومات (لأن قطعة النقود تملك وجهين وبالتالي احتمالين فقط). أما نتيجة رمي حجر نرد فتعطيك log<sub>2</sub>(6)‎ بتًا من المعلومات (لأن حجر النرد له ستة أوجه). حيث إذا كان احتمال النتيجة هو 1 من N فذلك يعني أن النتيجة تحوي log<sub>2</sub>(N)‎ بتًا من المعلومات عمومًا، وإذا كان احتمال النتيجة هو p مثلًا فبذلك تحوي النتيجة log2(p)‎ من المعلومات. تدعى هذه الكمية من المعلومات بالمعلومات الذاتية (self-information) للنتيجة، وهي تقيس مقدار التفاجؤ الذي تسببه تلك النتيجة، ويدعى هذا المقدار أيضًا <strong>surprisal</strong>. فإذا كان حصانك مشاركًا في سباق خيل على سبيل المثال ويملك فرصةً واحدة للفوز من أصل 16 فرصة ثم يفوز بالفعل، وبالتالي تعطيك تلك النتيجة 4 بتات من المعلومات (log<sub>2</sub>(16)=4)، أما إذا فاز حصان ما بنسبة 75% من المرات، فيتضمن ذلك الفوز الأخير 0.42 بتًا من المعلومات فقط. حيث تحمل النتائج غير المتوقعة معلومات أكثر، أما عند تأكّدك من حدوث شيء ما فلن يعطيك حدوثه بالفعل إلّا كمية قليلة من المعلومات.
</p>

<p>
	ينبغي عليك أن تكون على معرفة بالتحويل بين عدد البتات الذي نرمز له ب b وعدد القيم N التي تشفّرها (encode) تلك البتات بحيث N=2<sup>b</sup>.
</p>

<h2>
	الذاكرة والتخزين (Memory and storage)
</h2>

<p>
	تُحفَظ معظم بيانات عملية ما في الذاكرة الرئيسية (main memory) ريثما تنفّذ تلك العملية، حيث أن الذاكرة الرئيسية هي نوع من الذواكر العشوائية (random access memory وتختصر إلى RAM). الذاكرة الرئيسية هي <strong>ذاكرة متطايرة (volatile)</strong> على معظم الحواسيب، والتي تعني أن محتوياتها تُفقد عند إغلاق الحاسوب. يملك الحاسوب المكتبي النموذجي ذاكرة تتراوح بين 4 و 8 جيبي بايت وربما أكثر بكثير من ذلك، حيث GiB تشير إلى جيبي بايت وهي 2<sup>30</sup> بايتًا. إذا قرأت وكتبت عملية ما ملفات فإن هذه الملفات تُخزّن على القرص الصلب (hard disk drive ويختصر إلى HDD) أو على (solid state drive ويختصر إلى SSD). وسائط التخزين هذه <strong>غير متطايرة (non-volatile)</strong>، لذلك تُستخدم للتخزين طويل الأمد. يحتوي الحاسوب المكتبي حاليًا HDD بسعة تتراوح بين 500 جيجا بايت و 2 تيرا بايت، حيث GB هي جيجا بايت وتقابل 10<sup>9</sup> بايتًا بينما تشير TB إلى تيرا بايت وتساوي 10<sup>12</sup> بايتًا. لابد أنك لاحظت استخدام وحدة النظام الثنائي الجيبي بايت، أي التي تعد الكيلوبايت مثلًا مساويًا 1024 بايتًا أو 2<sup>10</sup> حيث أساسها العدد 2، لقياس حجم الذاكرة الرئيسية واستخدام وحدتي النظام العشري الجيجا بايت و التيرا بايت، أي التي تعد الكيلو بايت مثلًا مساويًا 1000 بايتًا حيث أساسها العدد 10، لقياس حجم HDD. يقاس حجم الذاكرة بالوحدات الثنائية وحجم القرص الصلب بالوحدات العشرية وذلك لأسباب تاريخية وتقنية، ولكن تُستخدم الجيجا بايت واختصارها GB استخدامًا مبهمًا لذلك يجب أن تنتبه لذلك. يُستخدم مصطلح ذاكرة (memory) أحيانًا للدلالة على HDDs و SSDs و RAM، ولكن خصائص هذه الأجهزة الثلاث مختلفة جدًا. يشار إلى HDDs و SSDs بتخزين دائم (storage).
</p>

<h2>
	أحياز العنونة (Address spaces)
</h2>

<p>
	يُحدد كل بايت في الذاكرة الرئيسية بعدد صحيح يدعى عنوانًا حقيقيًا (physical address)، حيث تدعى مجموعة العناوين الحقيقية الصالحة <strong>بحيز العنونة الحقيقية (physical address space)</strong> وتتراوح تلك العناوين بين 0 و N - 1 حيث N هو حجم الذاكرة الرئيسية. أعلى قيمة عنوان صالحة في نظام ب 1 جيبي بايت ذاكرة حقيقية هو 2<sup>30</sup>- 1 ، أي 1,073,741,823 في نظام العد العشري و 0x3f fffff في نظام العد الست عشري حيث تحدد السابقة 0x أنه عدد ست عشري.
</p>

<p>
	توفر معظم أنظمة التشغيل <strong>ذاكرةً وهميةً (virtual memory)</strong> أيضًا، والتي تعني أن البرامج لا تتعامل أبدًا مع عناوين حقيقية (physical addresses) وليست ملزمة بمعرفة كمية الذاكرة الحقيقية المتوفرة. وبدلًا من ذلك تتعامل البرامج مع الذاكرة الوهمية والتي تتراوح قيمها بين 0 و M - 1، حيث M هو عدد العناوين الوهمية الصالحة. ويحدد نظام التشغيل والعتاد الذي يعمل عليه حجم حيّز العنونة الوهمية.
</p>

<p>
	لا بد أنك سمعت الناس يتحدثون عن نظامي التشغيل 32 بت و 64 بت، حيث يحدد هذان المصطلحان حجم المسجلات والذي هو حجم العنوان الوهمي أيضًا. فيكون العنوان الوهمي 32 بتًا على نظام 32 بت والذي يعني أن حيز العنونة الوهمية يتراوح بين 0 و 0xffff ffff، أي حجم العنونة الوهمية هو 2<sup>32</sup> بايتًا أو 4 جيبي بايت. أما على نظام 64 بت فحجم حيز العنونة الوهمية هو 2<sup>64</sup> بايتًا أو 2<sup>4</sup>.1024<sup>6</sup> بايتًا، أي 16 اكسبي بايت (exbibytes) والذي هو أكبر من حجم الذاكرة الحقيقية الحالية بمليار مرة تقريبًا.
</p>

<p>
	يولّد البرنامج عناوينًا وهمية بكل عملية قراءة أو كتابة في الذاكرة، ويترجمها العتاد إلى عناوين حقيقية بمساعدة نظام التشغيل قبل الوصول إلى الذاكرة الرئيسية، وتقوم هذه الترجمة على أساس per-process أي تعامَل كل عملية باستقلالية عن العمليات الأخرى، حيث حتى لو ولّدت عمليتان نفس العنوان الوهمي فسترتبطان بمواقع مختلفة من الذاكرة الحقيقية. إذًا الذاكرة الوهمية هي إحدى طرق نظام التشغيل لعزل العمليات عن بعضها البعض، حيث لا تستطيع عملية ما الوصول إلى بيانات عملية أخرى، فلا وجود لعنوان وهمي يستطيع توليد ارتباطات (maps) بذاكرة حقيقية مخصصة لعملية أخرى.
</p>

<h2>
	أجزاء الذاكرة (Memory segments)
</h2>

<p>
	تُنظّم بيانات العملية المُشغّلة ضمن خمسة أجزاء:
</p>

<ul>
	<li>
		جزء الشيفرة (code segment): ويتضمن نص البرنامج (program text) وهو تعليمات لغة الآلة التي تبني البرنامج.
	</li>
	<li>
		الجزء الساكن (static segment): يتضمن القيم غير القابلة للتغيير، قيم السلاسل النصية (string literals) مثلًا، حيث إذا احتوى برنامجك على سلسلة مثلًا "Hello, World" فستُخزَّن هذه الحروف في الجزء الساكن من الذاكرة.
	</li>
	<li>
		الجزء العام (global segment): يتضمن المتغيرات العامة (global variables) والمتغيرات المحلية (local variables) التي يُصرَّح عنها كساكنة <code>static</code>.
	</li>
	<li>
		جزء الكومة (heap segment): يتضمن قطع الذاكرة المخصصة في زمن التشغيل وذلك باستدعاء دالة مكتبة في لغة C هي <code>malloc</code> في أغلب الأحيان.
	</li>
	<li>
		جزء المكدس (stack segment): يتضمن استدعاء المكدس وهو سلسلة من إطارات المكدس (stack frames). يُخصَّص إطار المكدس ليتضمن المعاملات والمتغيرات المحلية الخاصة بالدالة في كل مرة تّستدعى فيها الدالة، ويزال إطار المكدس ذاك التابع لتلك الدالة من المكدس عندما تنتهي الدالة من عملها.
	</li>
</ul>

<p>
	يتشارك المصرِّف مع نظام التشغيل في تحديد ترتيب الأجزاء السابقة، حيث تختلف تفاصيل ذلك الترتيب من نظام تشغيل لآخر ولكن الترتيب الشائع هو:
</p>

<ul>
	<li>
		يوجد جزء نص البرنامج أو جزء الشيفرة قرب قاع (bottom) الذاكرة أي عند العناوين القريبة من القيمة 0.
	</li>
	<li>
		يتواجد الجزء الساكن غالبًا فوق جزء الشيفرة عند عناوين أعلى من عناوين جزء الشيفرة.
	</li>
	<li>
		ويتواجد الجزء العام فوق الجزء الساكن غالبًا.
	</li>
	<li>
		ويتواجد جزء الكومة فوق الجزء العام وإذا احتاج للتوسع أكثر فسيتوسع إلى عناوين أكبر.
	</li>
	<li>
		ويكون جزء المكدس قرب قمة الذاكرة (top of memory) أي قرب العناوين الأعلى في حيز العنونة الوهمية، وإذا احتاج المكدس للتوسع فسيتوسع للأسفل باتجاه عناوين أصغر.
	</li>
</ul>

<p>
	لتعرف ترتيب هذه الأجزاء على نظامك، نفّد البرنامج التالي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_6617_7" style=""><span class="com">#include</span><span class="pln"> </span><span class="str">&lt;stdio.h&gt;</span><span class="pln">
</span><span class="com">#include</span><span class="pln"> </span><span class="str">&lt;stdlib.h&gt;</span><span class="pln">
</span><span class="typ">int</span><span class="pln"> global</span><span class="pun">;</span><span class="pln">
</span><span class="typ">int</span><span class="pln"> main</span><span class="pun">()</span><span class="pln">
</span><span class="pun">{</span><span class="pln">
    </span><span class="typ">int</span><span class="pln"> local </span><span class="pun">=</span><span class="pln"> </span><span class="lit">5</span><span class="pun">;</span><span class="pln">
    </span><span class="kwd">void</span><span class="pln"> </span><span class="pun">*</span><span class="pln">p </span><span class="pun">=</span><span class="pln"> malloc</span><span class="pun">(</span><span class="lit">128</span><span class="pun">);</span><span class="pln">
    </span><span class="kwd">char</span><span class="pln"> </span><span class="pun">*</span><span class="pln">s </span><span class="pun">=</span><span class="pln"> </span><span class="str">"Hello, World"</span><span class="pun">;</span><span class="pln">
    printf</span><span class="pun">(</span><span class="str">"Address of main is %p\n"</span><span class="pun">,</span><span class="pln"> main</span><span class="pun">);</span><span class="pln">
    printf</span><span class="pun">(</span><span class="str">"Address of global is %p\n"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">&amp;</span><span class="pln">global</span><span class="pun">);</span><span class="pln">
    printf</span><span class="pun">(</span><span class="str">"Address of local is %p\n"</span><span class="pun">,</span><span class="pln"> </span><span class="pun">&amp;</span><span class="pln">local</span><span class="pun">);</span><span class="pln">
    printf</span><span class="pun">(</span><span class="str">"p points to %p\n"</span><span class="pun">,</span><span class="pln"> p</span><span class="pun">);</span><span class="pln">
    printf</span><span class="pun">(</span><span class="str">"s points to %p\n"</span><span class="pun">,</span><span class="pln"> s</span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	<code>main</code> هو اسم دالة ولكن عند استخدامها كمتغير فهي تشير إلى عنوان أول تعليمة لغة آلة في الدالة <code>main</code> والتي من المتوقع أن تكون في جزء الشيفرة (text segment). أما <code>global</code> فهو متغير عام (global variable) وبالتالي يُتوقع تواجده في الجزء العام (global segment)، و <code>local</code> هو متغير محلي أي يتواجد في جزء المكدس. ترمز <code>s</code> إلى سلسلة نصية (string literal) وهي السلسلة التي تكون جزءًا من البرنامج، على عكس السلسلة التي تُقرَأ من ملف أو التي يدخلها المستخدم. ومن المتوقع أن يكون موقع هذه السلسلة هو الجزء الساكن (static segment)، في حين يكون المؤشر <code>s</code> الذي يشير إلى تلك السلسلة متغيرًا محليًا. أما <code>p</code> فيتضمن العنوان الذي يعيده تنفيذ الدالة <code>malloc</code> حيث أنها تخصص حيزًا في الكومة، وترمز <code>malloc</code> إلى memory allocate أي تخصيص حيز في الذاكرة. يخبر التنسيق التسلسلي format sequence <code>%p</code>الدالة <code>printf</code> بأن تنسق كل عنوان كمؤشر (pointer) فتكون النتيجة عبارة عن عدد ست عشري (hexadecimal).
</p>

<p>
	عند تنفيذ البرنامج السابق سيكون الخرج كما يلي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5247_9" style=""><span class="typ">Address</span><span class="pln"> of main is </span><span class="lit">0x</span><span class="pln"> </span><span class="lit">40057d</span><span class="pln">
</span><span class="typ">Address</span><span class="pln"> of global is </span><span class="lit">0x</span><span class="pln"> </span><span class="lit">60104c</span><span class="pln">
</span><span class="typ">Address</span><span class="pln"> of local is </span><span class="lit">0x7ffe6085443c</span><span class="pln">
p points to </span><span class="lit">0x</span><span class="pln"> </span><span class="lit">16c3010</span><span class="pln">
s points to </span><span class="lit">0x</span><span class="pln"> </span><span class="lit">4006a4</span></pre>

<p>
	عنوان <code>main</code> هو الأقل كما هو متوقع، ثم يتبعه موقع سلسلة نصية (string literal)، ثم موقع <code>global</code>، وبعده العنوان الذي يشير إليه المؤشر <code>p</code>، ثم عنوان <code>local</code> أخيرًا وهو الأكبر. يتكون عنوان <code>local</code> وهو العنوان الأكبر من 12 رقمًا ست عشريًا حيث يقابل كل رقم ست عشري 4 بتات وبالتالي يتكون هذا العنوان من 48 بتًا، ويدل ذلك أن القسم المُستخدم من حيز العنونة الوهمية هو 248 بايتًا لأن حجم حيز العنونة الوهمية يكون مساويًا 2<sup>x</sup> بايتًا (تمثل x حجم العنوان الوهمي)، وأكبر عنوان مستخدم في هذا المثال هو 48 بتًا.
</p>

<p>
	جرب تنفيذ البرنامج السابق على حاسوبك وشاهد النتائج، أضف استدعاءً آخر للدالة <code>malloc</code> وتحقق فيما إذا أصبح عنوان كومة نظامك أكبر، وأضف أيضًا دالة تطبع عنوان متغير محلي وتحقق إذا أصبح عنوان المكدس يتوسع للأسفل.
</p>

<p>
	يوضح المخطط التالي عملية ترجمة العناوين:
</p>

<p style="text-align: center;">
	<img alt="DiagramOfTheAdressTranslationProcess.PNG" class="ipsImage ipsImage_thumbnailed" data-fileid="50643" data-unique="f0olwt4g9" src="https://academy.hsoub.com/uploads/monthly_2020_09/DiagramOfTheAdressTranslationProcess.PNG.219ca609dd1016c5cecbd6d645d2478e.PNG">
</p>

<h2>
	المتغيرات المحلية الساكنة (Static local variables)
</h2>

<p>
	تدعى المتغيرات المحلية في المكدس بمتغيرات تلقائية (automatic) لأنها تُخصص تلقائيًا عند استدعاء الدالة وتحرر مواقعها تلقائيًا أيضًا عندما ينتهي تنفيذ الدالة. أما في لغة البرمجة C فيوجد نوع آخر من المتغيرات المحلية، تدعى ساكنة (static)، تخصص مواقعها في الجزء العام (global segment) وتُهيّأ عند بدء تنفيذ البرنامج وتحافظ على قيمتها من استدعاءٍ لآخر للدالة.
</p>

<p>
	تتتبّع الدالة التالية عدد مرات استدعائها على سبيل المثال:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_5247_13" style=""><span class="typ">int</span><span class="pln"> times_called</span><span class="pun">()</span><span class="pln">
</span><span class="pun">{</span><span class="pln">
	</span><span class="kwd">static</span><span class="pln"> </span><span class="typ">int</span><span class="pln"> counter </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0</span><span class="pun">;</span><span class="pln">
	counter</span><span class="pun">++;</span><span class="pln">
	</span><span class="kwd">return</span><span class="pln"> counter</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	تحدد الكلمة المفتاحية <code>static</code> أن المتغير <code>counter</code> هو متغير محلي ساكن، حيث أن تهيئة المتغير المحلي الساكن تحدث مرة واحدة فقط عند بدء تنفيذ البرنامج، ويخصص موقع المتغير <code>counter</code> في الجزء العام مع المتغيرات العامة وليس في جزء المكدس.
</p>

<h2>
	ترجمة العناوين (Address translation)
</h2>

<p>
	كيف يُترجم العنوان الوهمي (VA) إلى عنوان حقيقي (PA)؟
</p>

<p>
	العملية الأساسية لتحقيق ذلك بسيطة، ولكن يمكن أن يكون التنفيذ البسيط أيضًا بطيئًا ويأخذ مساحة أكثر، لذلك يكون التنفيذ الحقيقي أعقد. توفر معظم العمليات وحدة إدارة الذاكرة (memory management unit وتختصر إلى MMU) التي تتموضع بين المعالج CPU والذاكرة الرئيسية وتطبّق ترجمة سريعة بين العناوين الوهمية والعناوين الحقيقية كما يلي:
</p>

<ol>
	<li>
		يولّد المعالج CPU عنوانًا وهميًا (VA) عندما يقرأ أو يكتب البرنامج متغيرًا .
	</li>
	<li>
		تقسّم MMU العنوان الوهمي إلى قسمين هما رقم الصفحة والإزاحة (page number و offset). الصفحة (page) تعني قطعة ذاكرة ويعتمد حجم هذه الصفحة على نظام التشغيل والعتاد ولكن حجوم الصفحات الشائعة هي بين 1 و 4 كيبي بايت.
	</li>
	<li>
		تبحث MMU عن رقم الصفحة في مخزَن الترجمة الجانبي المؤقت (translation lookaside buffer)، يُختصر إلى TLB، لتحصل MMU على رقم الصفحة الحقيقي المقابل ثم تدمج رقم الصفحة الحقيقي مع الإزاحة لينتج العنوان الحقيقي PA.
	</li>
	<li>
		يمرَّر العنوان الحقيقي إلى الذاكرة الرئيسية التي تقرأ أو تكتب الموقع المطلوب.
	</li>
</ol>

<p>
	يتضمن TLB نسخ بيانات مخبئية من جدول الصفحات (page table) والتي تُخزن في ذاكرة النواة، ويحتوي جدول الصفحات ربطًا بين أرقام الصفحات الوهمية و أرقام الصفحات الحقيقية. وبما أن لكل عملية جدول صفحاتها الخاص لذلك يجب على TLB أن يتأكد من أنه يستخدم مدخلات جدول صفحات العملية التي تنفّذ فقط. لفهم كيف تتم عملية الترجمة افترض أن العنوان الوهمي VA هو 32 بتًا والذاكرة الحقيقية 1 جيبي بايت مقسّمة إلى صفحات وكل صفحة ب 1 كيبي بايت:
</p>

<ul>
	<li>
		بما أن 1 جيبي بايت هي 2<sup>30</sup> بايتًا و 1 كيبي بايت هي 2<sup>10</sup> بايت لذلك يوجد 2<sup>20</sup> صفحةً حقيقية تدعى أحيانًا إطارات (frames).
	</li>
	<li>
		حجم حيز العنونة الوهمية هو 2<sup>32</sup> بايتًا وحجم الصفحة هو 2<sup>10</sup> بايتًا لذلك يوجد 2<sup>22</sup> صفحة وهمية.
	</li>
	<li>
		يحدد حجم الصفحة حجمَ الإزاحة وفي هذا المثال حجم الصفحة هو 2<sup>10</sup> بايتًا لذلك يتطلب 10 بتات لتحديد بايت من الصفحة.
	</li>
	<li>
		وإذا كان العنوان الوهمي 32 بتًا والإزاحة 10 بت فتشكّل ال22 بتًا المتبقية رقم الصفحة الوهمية.
	</li>
	<li>
		وبما أنه يوجد 2<sup>20</sup> صفحة حقيقية فكل رقم صفحة حقيقية هو 20 بتًا وأضف عليها إزاحة ب 10 بت فتكون العناوين الحقيقية الناتجة ب 30 بتًا.
	</li>
</ul>

<p>
	يبدو كل شيء معقولًا حتى الآن، ولكن التنفيذ الأبسط لجدول الصفحات هو مصفوفة بمدخلة واحدة لكل صفحة وهمية، وتتضمن كل مدخلة رقم الصفحة الفعلية وهي 20 بتًا في هذا المثال، بالإضافة إلى بعض المعلومات الإضافية لكل إطار أو صفحة حقيقية، وبالتالي من 3 إلى 4 بايتات لكل مدخلة من الجدول ولكن مع 2<sup>22</sup> صفحة وهمية يكون حجم جدول الصفحات 2<sup>24</sup> بايتًا أو 16 ميبي بايت. تحتاج كل عمليةٍ إلى جدول صفحات خاص بها لذلك إذا تضمن النظام 256 عملية مشغّلة فهو يحتاج 2<sup>32</sup> بايتًا أو 4 جيبي بايت لتخزين جداول الصفحات فقط! وذلك مع عناوين وهمية ب 32 بتًا وبالتالي مع عناوين وهمية ب 48 و 64 بتًا سيكون حجم تخزين جداول الصفحات كبيرًا جدًا. لا نحتاج لتلك المساحة كلها لتخزين جدول الصفحات لحسن الحظ، لأن معظم العمليات لا تستخدم إلا جزءًا صغيرًا من حيز العنونة الوهمية الخاص بها، وإذا لم تستخدم العملية صفحةً وهمية فلا داعي لإنشاء مدخلة لها في جدول الصفحات.
</p>

<p>
	يمكن القول بأن جداول الصفحات قليلة الكثافة (أو مخوَّخَة sparse) والتي تطبيقها باستخدام التنفيذات البسيطة، مثل مصفوفة مدخلات جدول الصفحات، هو أمر سيء. ولكن يمكن تنفيذ المصفوفات قليلة الكثافة أو المخوَّخَة (sparse arrays) بطرق أخرى أفضل لحسن الحظ. فأحد الخيارات هو جدول صفحات متعدد المستويات (multilevel page table) الذي تستخدمه العديد من أنظمة التشغيل، Linux مثلًا. الخيار الآخر هو الجدول الترابطي (associative table) الذي تتضمن كل مدخلة من مدخلاته رقم الصفحة الحقيقية والوهمية. يمكن أن يكون البحث في الجدول الترابطي بطيئًا برمجيًا، أما عتاديًا يكون البحث في كامل الجدول على التوازي (parallel)، لذلك تستخدم المصفوفات المترابطة (associative arrays) غالبًا في تمثيل مدخلات جدول الصفحات في TLB. يمكنك قراءة المزيد عن ذلك من خلال: <a href="https://en.wikipedia.org/wiki/Page_table" rel="external nofollow">https://en.wikipedia.org/wiki/Page_table</a>
</p>

<p>
	كما ذُكر سابقًا أن نظام التشغيل قادر على مقاطعة أية عملية مُشغّلة ويحفظ حالتها ثم يشغّل عملية أخرى. تدعى هذه الآلية بتحويل السياق (context switch). وبما أن كل عملية تملك جدول الصفحات الخاص بها فيجب على نظام التشغيل بالتعاون مع MMU أن يتأكد من أن كل عملية تحدث على جدول الصفحات الصحيح. وفي الآلات القديمة كان ينبغي أن تُستبدل معلومات جدول الصفحات الموجودة في MMU خلال كل عملية تحويل سياق، وبذلك كانت التكلفة باهظة. أما في الأنظمة الجديدة فإن كل مدخلة في جدول الصفحات ضمن MMU تتضمن معرّف عملية (process ID) لذلك يمكن أن تكون جداول صفحات عمليات متعددة موجودة في نفس الوقت في MMU.
</p>

<p>
	ترجمة -وبتصرّف- للفصل Virtual memory من كتاب <a href="http://greenteapress.com/thinkos/" rel="external nofollow">Think OS A Brief Introduction to Operating Systems</a>
</p>
]]></description><guid isPermaLink="false">978</guid><pubDate>Tue, 08 Sep 2020 18:06:00 +0000</pubDate></item><item><title>&#x627;&#x644;&#x641;&#x635;&#x644; &#x627;&#x644;&#x62B;&#x627;&#x646;&#x64A;: &#x627;&#x644;&#x639;&#x645;&#x644;&#x64A;&#x627;&#x62A; (Processes) &#x641;&#x64A; &#x623;&#x646;&#x638;&#x645;&#x629; &#x627;&#x644;&#x62A;&#x634;&#x63A;&#x64A;&#x644;</title><link>https://academy.hsoub.com/programming/c/%D8%A7%D9%84%D9%81%D8%B5%D9%84-%D8%A7%D9%84%D8%AB%D8%A7%D9%86%D9%8A-%D8%A7%D9%84%D8%B9%D9%85%D9%84%D9%8A%D8%A7%D8%AA-processes-%D9%81%D9%8A-%D8%A3%D9%86%D8%B8%D9%85%D8%A9-%D8%A7%D9%84%D8%AA%D8%B4%D8%BA%D9%8A%D9%84-r977/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2020_09/2.png.257cc945522a7c51945e8f20c1693007.png" /></p>

<h2>
	التجريد (Abstraction) والوهمية (Virtualization)
</h2>

<p>
	ينبغي معرفة مصطلحين مهمين قبل الخوض في الحديث عن العمليات (processes) هما:
</p>

<p>
	<strong>التجريد (abstraction)</strong>: هو تمثيلٌ مُبسّط لشيء معقد. عند قيادة سيارة ما على سبيل المثال يُفَهم أن توجيه عجلة القيادة يسارًا يوجه السيارة يسارًا والعكس صحيح. ترتبط عجلة القيادة بسلسلة من الأنظمة الميكانيكية والهيدروليكية، حيث توجّه هذه الأنظمة العجلات لتتحرك على الطريق. يمكن أن تكون هذه الأنظمة معقدة، ولكنّ السائق غير ملزَمٍ بالاكتراث بأي من تلك التفاصيل التي تجري داخل أنظمة السيارة، فالسائق يقود السيارة بالفعل وذلك بسبب امتلاكه نموذجًا ذهنيًا (mental model) بسيطًا عن عملية قيادة السيارة، وهذا النموذج البسيط هو التجريد (abstraction) بعينه. استخدام متصفح الويب (web browser) هو مثال آخر عن التجريد، فعند النقر على ارتباط (link) يعرض المتصفح الصفحة المرتبطة بهذا الارتباط. لا شكّ أن البرمجيات (software) وشبكات الاتصال (network communication) التي تجعل ذلك ممكنًا معقدة، ولكن لا يتوجب على المستخدم معرفة تفاصيل تلك الأمور المعقدة. جزء كبير من هندسة البرمجيات هو تصميم التجريدات التي تسمح للمستخدمين والمبرمجين استخدام أنظمة معقدة بطريقة سهلة دون الحاجة إلى معرفة تفاصيل تنفيذ هذه الأنظمة.
</p>

<p>
	<strong>الوهمية (Virtualization)</strong>: وهي نوع من التجريد (abstraction) الذي يخلق وهمًا (illusion) بوجود شيء فعليًا في حين أنه موجود وهميًا فقط. فمثلًا تشارك العديد من المكتبات العامة في تعاون بينها يسمح باستعارة الكتب من بعضها البعض. عندما تطلب كتابًا يكون الكتاب على رف من رفوف مكتبتك أحيانًا، ولكن يجب نقله من مكان آخر عند عدم توافره لديك، ثم سيصلك إشعار عندما يُتاح الكتاب للاستلام في كلتا الحالتين. ليس هناك حاجة أن تعرف مصدر الكتاب ولا أن تعرف الكتب الموجودة في مكتبتك. إذًا يخلق النظام وهمًا بأن مكتبتك تحتوي على كتب العالم جميعها. قد تكون مجموعة الكتب الموجودة في مكتبتك فعليًا صغيرة، لكن مجموعة الكتب المتاحة لك وهميًا هي كل كتاب موجود في تلك المكتبات المتشاركة. الإنترنت (Internet) هو مثال آخر عن الوهمية وهو مجموعة من الشبكات والبروتوكولات التي تعيد توجيه (forward) الحزم (packets) من شبكةٍ لأخرى. يتصرف نظام كل حاسوب كأنه متصل بكل حاسوب آخر على الإنترنت من وجهة نظر المستخدم أو المبرمج، حيث يكون الاتصال الفعلي أو الفيزيائي بين كل حاسوب وآخر قليلًا، أما الاتصال الوهمي كبير جدًا. يُستخدم المصطلح وهمي (virtual) ضمن عبارة آلة وهمية (virtual machine) أكثر الأحيان. والآلة الوهمية تعني البرمجية التي تمنح المستخدم القدرة على إنشاء نظام تشغيل على حاسوب يشغّل نظام تشغيل مختلف، وبذلك تخلق هذه الآلة وهمًا بأن هذا النظام المنشَأ يعمل على حاسوب مستقل بذاته، ولكن في الحقيقة يمكن تشغيل عدة أنظمة تشغيل وهمية على حاسوب واحد بنفس الوقت وكأنّ كل نظام تشغيل يعمل على حاسوب مختلف. وبالتالي يُدعى ما يحدث فعليًا physical وما يحدث وهميًا logical أو abstract.
</p>

<h2>
	العزل (Isolation)
</h2>

<p>
	العزل هو أحد أهم مبادئ الهندسة، فعزل المكونات عن بعضها البعض هو أمر جيد عند تصميم نظام متعدد المكونات من أجل ألّا يؤثر مكون على المكونات الأخرى تأثيرًا غير مرغوب به. عزل كل برنامج قيد التشغيل عن البرامج الأخرى من أهم أهداف نظام التشغيل، فبذلك لا يضطر المبرمجون إلى الاهتمام باحتمالية حدوث تفاعلات بين البرامج المشغّلة، وبالتالي يحتاج نظام التشغيل إلى كائن برمجي يحقق هذا العزل ألا وهو <strong>العملية (process)</strong>. ولكن ما هي العملية؟
</p>

<p>
	العملية (process) هي كائنٌ برمجي يمثل برنامجًا مشغلًا، وبالتالي يُمثَّل كل برنامج بعملية معينة. حيث يُقصَد بعبارة «كائن برمجي» بكائنٍ له روح البرمجة كائنية التوجه (object-oriented programming)، حيث يتضمن كل كائن بيانات (data) وتوابع (methods) تعمل على هذه البيانات. فالعملية هي كائن ولكن يتضمن البيانات التالية:
</p>

<ul>
<li>
		نص البرنامج (text of the program): وهو سلسلة من تعليمات لغة الآلة عادةً.
	</li>
	<li>
		بيانات مرتبطة بالبرنامج (Data associated with the program): والتي تنقسم إلى نوعين: بيانات ساكنة (static data) (تُخصَّص مواقعها في الذاكرة في وقت التصريف) و بيانات ديناميكية (dynamic data) (تُخصَّص مواقعها في الذاكرة في وقت التشغيل).
	</li>
	<li>
		حالة عمليات الإدخال/الإخراج المعلَّقة (The state of any pending input/output operations): مثل انتظار العملية قراءة بيانات من القرص، أو انتظار وصول حزمة عن طريق الشبكة، فحالات عمليات الانتظار هذه هي جزء من العملية نفسها (process).
	</li>
	<li>
		حالة عتاد البرنامج (The hardware state of the program): التي تتضمن البيانات المخزنة في المسجلات (registers)، ومعلومات الحالة (status information)، وعداد البرنامج الذي يحدد أية تعليمة ستنفّذ حاليًا. تشغّل كل عملية برنامجًا واحدًا في أغلب الأحيان، ولكن يمكن لعملية ما أن تحمّل وتشغّل برنامجًا آخر أيضًا. ويمكن أن تشغّل عدة عمليات نفس البرنامج، ففي هذه الحالة تتشارك العمليات بنص البرنامج ولكن ببيانات وحالات عتاد مختلفة.
	</li>
</ul>
<p>
	توفر معظم أنظمة التشغيل مجموعة أساسية من القدرات لعزل العمليات عن بعضها البعض هي:
</p>

<ul>
<li>
		تعدد المهام (Multitasking): تتمتع معظم أنظمة التشغيل بالقدرة على مقاطعة تنفيذ أية عملية في أي وقت تقريبًا مع حفظ حالة عتاد العملية المُقاطعة، ثم استئناف تشغيل العملية لاحقًا. على كل حال لا يضطر المبرمجون إلى التفكير كثيرًا في هذه المقاطعات (interruptions)، حيث يتصرف البرنامج كما لو أنه يعمل باستمرار على معالج مخصص له فقط دون غيره، ولكن ينبغي التفكير بالوقت الفاصل بين كل تعليمة وأخرى من البرنامج فهو وقت لا يمكن التنبؤ به.
	</li>
	<li>
		الذاكرة الوهمية (Virtual memory): تخلق معظم أنظمة التشغيل وهمًا بأن كل عملية لها قطعتها (chunk) من الذاكرة وهذه القطعة معزولة عن قطع العمليات الأخرى. يمكن القول مرة أخرى بأن المبرمجين غير مضطرين لمعرفة كيفية عمل الذاكرة الوهمية، فيمكنهم المتابعة في كتابة برامجهم معتبرين كل برنامجٍ له جزءٌ مخصص من الذاكرة.
	</li>
	<li>
		تجريد الجهاز (Device abstraction): تتشارك العمليات العاملة على نفس الحاسوب بمحرك الأقراص أو قرص التخزين (disk drive) وبطاقة الشبكة (network interface) وبطاقة الرسوميات (graphics card) ومكونات عتادية أخرى أيضًا. إذا تعاملت العمليات مع تلك المكونات العتادية للحاسوب مباشرةً ودون تنسيق من نظام التشغيل، سيؤدي ذلك إلى فوضى عارمة. فيمكن لعملية ما أن تقرأ بيانات شبكة عمليةٍ أخرى على سبيل المثال، أو يمكن أن تحاول عمليات متعددة تخزين بيانات في الموقع نفسه على محرك القرص الصلب (hard drive). الأمر متروك لنظام التشغيل في النهاية ليحافظ على النظام من خلال توفير تجريدات مناسبة.
	</li>
</ul>
<p>
	لا يحتاج المبرمج لمعرفة الكثير عن كيفية عمل قدرات نظام التشغيل لعزل العمليات عن بعضها البعض، ولكن إذا دفعك الفضول لمعرفة المزيد عن ذلك فهو أمر جيد، فالإبحار في معرفة مزيد من التفاصيل يصنع منك مبرمجًا أفضل.
</p>

<h2>
	عمليات UNIX
</h2>

<p>
	تخيل السيناريو التالي:
</p>

<p>
	العملية التي تعيها عند استخدامك الحاسوب للكتابة هي محرر النصوص، وإذا حرّكت الفأرة على الطرفية (terminal) يتنبه مدير النوافذ (window manager) وينبّه terminal التي بدورها تنبّه الصدفة (shell)، فإذا كتبت الأمر <code>make</code> فإن shell تنشئ عملية جديدة لتنفيذ الأمر Make التي بدورها تنشئ عملية أخرى لتنفيذ LaTeX وهكذا يستمر إنشاء العمليات حتى عرض نتائج تنفيذ الأمر make. يمكن أن تبدل إلى سطح المكتب (desktop) إذا أردت البحث عن شيء ما، فيؤدي ذلك إلى تنبيه مدير النوافذ أيضًا. ويتسبب نقرك على أيقونة متصفح الويب في إنشاء عملية تشغّل المتصفح. تنشئ بعض المتصفحات، Chrome مثلًا، عمليات لكل نافذة وتبويب جديدين. ولكن توجد عمليات أخرى تعمل في <strong>الخلفية (background)</strong> في ذات الوقت، تتعلق تلك العمليات في معظمها بنظام التشغيل.
</p>

<p>
	يمكن استخدام الأمر <code>ps</code> في UNIX لمعرفة معلومات عن العمليات التي تعمل حاليًا، فيظهر الخرج التالي عند تنفيذ الأمر:
</p>

<pre class="ipsCode">
<abbr title="Process IDentifier | معرّف العملية أو البرنامج">PID</abbr>    TTY     TIME    CMD
2687   pts/1   00:00:00  bash
2801   pts/1   00:01:24  emacs
24762  pts/1   00:00:00  ps
</pre>

<p>
	يمثل العمود الأول معرِّف العملية الفريد ID، بينما يمثل العمود الثاني الطرفية (terminal) التي أنشأت العملية حيث يشير (TTY) إلى عبارة teletypewriter (وهي جهاز قديم اُستخدِم لإرسال و استقبال الرسائل المكتوبة من خلال قنوات اتصال مختلفة)، ويمثل العمود الثالث الزمن الإجمالي المستغرَق خلال استخدام العملية للمعالج ويكون بالشكل التالي: ساعات، دقائق، ثواني، ويمثّل العمود الرابع والأخير اسم البرنامج المشغَّل، حيث <code>bash</code> هو اسم الصدفة (Shell) التي قاطعت الأوامر المكتوبة في الطرفية (terminal)، و emacs هو محرر النصوص المستخدَم، و ps هو البرنامج الذي ولّد الخرج السابق. فخرج الأمر <code>ps</code> هو قائمة تحوي العمليات المتعلقة بالطرفية (terminal) الحالية فقط، ولكن باستخدام الراية <code>-e</code> مع <code>ps</code> (أو باستخدام الراية <code>aux</code> التي هي خيارٌ آخر وشائع) فستظهر كل العمليات بما في ذلك عمليات المستخدمين الآخرين والذي برأيي، يقول الكاتب، هو ثغرة أمنية . يوجد على نظامي التشغيلي مثلًا، يقول الكاتب، 233 عملية حاليًا فيما يلي بعض منها:
</p>

<pre class="ipsCode">
<abbr title="Process IDentifier | معرّف العملية أو البرنامج">PID</abbr> TTY TIME  CMD
1   ?   00:00:17  init
2   ?   00:00:00  kthreadd
3   ?   00:00:02  ksoftirqd/0
4   ?   00:00:00  kworker/0:0
8   ?   00:00:00  migration/0
9   ?   00:00:00  rcu_bh
10  ?   00:00:16  rcu_sched
47  ?   00:00:00  cpuset
48  ?   00:00:00  khelper
49  ?   00:00:00  kdevtmpfs
50  ?   00:00:00  netns
51  ?   00:00:00  bdi-default
52  ?   00:00:00  kintegrityd
53  ?   00:00:00  kblockd
54  ?   00:00:00  ata_sff
55  ?   00:00:00  khubd
56  ?   00:00:00  md
57  ?   00:00:00  devfreq_wq
</pre>

<p>
	أول عملية تنشأ عند بدء نظام التشغيل هي <code>init</code> التي تنشئ العديد من العمليات ثم تبقى خاملة بلا عمل حتى تنتهي تلك العمليات التي أنشأتها. أما <code>kthreadd</code> فهي العملية التي يستخدمها نظام التشغيل لإنشاء <strong>خيوط (threads)</strong> جديدة (سنتكلم عن الخيوط لاحقًا ولكن يمكن القول أن الخيط هو نوع معين من العمليات)، ويشير k في بداية <code>kthreadd</code> إلى <strong>نواة (kernel)</strong>، وهي جزء نظام التشغيل المسؤول عن قدرات نظام التشغيل الأساسية مثل إنشاء الخيوط threads، ويشير حرف d الإضافي إلى <strong>عفريت (daemon)</strong>، وهو اسم آخر للعملية التي تعمل في الخلفية وتوفر خدمات نظام التشغيل. وبالنسبة للعملية <code>ksoftirqd</code> فهي عفريت للنواة (kernel daemon) أيضًا وعملها معالجة طلبات المقاطعة البرمجية software interrupt requests أو soft IRQ. أما <code>kworker</code> فهي عملية تنشئها النواة للعمل على عمليات معالجة خاصة بها.
</p>

<p>
	توجد عمليات متعددة تشغّل خدمات النواة، ففي حالتي، يقول الكاتب، توجد 8 عمليات <code>ksoftirqd</code> و35 عملية <code>kworker</code>. لن نخوض في تفاصيل العمليات المتبقية، ولكن إذا كنت مهتمًا يمكنك البحث عن معلومات عنها وتطبيق الأمر <code>ps</code> لترى العمليات المشغلة على نظامك.
</p>

<p>
	ترجمة -وبتصرّف- للفصل Processes من كتاب <a href="http://greenteapress.com/thinkos/" rel="external nofollow">Think OS A Brief Introduction to Operating Systems</a>
</p>
]]></description><guid isPermaLink="false">977</guid><pubDate>Fri, 04 Sep 2020 18:09:02 +0000</pubDate></item><item><title>&#x627;&#x644;&#x641;&#x635;&#x644; &#x627;&#x644;&#x623;&#x648;&#x644;: &#x645;&#x641;&#x647;&#x648;&#x645; &#x627;&#x644;&#x62A;&#x635;&#x631;&#x64A;&#x641; Compilation &#x641;&#x64A; &#x644;&#x63A;&#x627;&#x62A; &#x627;&#x644;&#x628;&#x631;&#x645;&#x62C;&#x629;</title><link>https://academy.hsoub.com/programming/c/%D8%A7%D9%84%D9%81%D8%B5%D9%84-%D8%A7%D9%84%D8%A3%D9%88%D9%84-%D9%85%D9%81%D9%87%D9%88%D9%85-%D8%A7%D9%84%D8%AA%D8%B5%D8%B1%D9%8A%D9%81-compilation-%D9%81%D9%8A-%D9%84%D8%BA%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-r976/</link><description><![CDATA[
<p><img src="https://academy.hsoub.com/uploads/monthly_2020_09/1.png.a3005856ec4c4af6c20f709f5593e666.png" /></p>

<h2>
	اللغات المصرفة (Compiled) واللغات المفسرة (Interpreted)
</h2>

<p>
	تندرج لغات البرمجة تحت صنفين اثنين: إما مُصرَّفة (compiled) أو مُفسَّرة (interpreted)، فيعني المصطلح لغة مُصرَّفة (compiled) ترجمة البرامج إلى لغة الآلة (machine language) لينفذها العتاد (hardware)، أما مصطلح لغة مُفسَّرة (interpreted) فيعني وجود برنامج يدعى «المفسِّر» (interpreter) يقرأ البرامج وينفذها مباشرةً وآنيًا . تُعَد لغة البرمجة C على سبيل المثال لغة مُصرَّفة (compiled) عادًة، بينما تُعَد لغة Python لغة مُفسَّرة (interpreted)، لكنّ التمييز بين المصطلحين غير واضح دائمًا حيث:
</p>

<p>
	<strong>أولًا</strong> يمكن للغات البرمجة المُفسَّرة أن تكون مُصرَّفة والعكس صحيح، فلغة C مثلًا هي لغة مصرَّفة ولكن يوجد مفسِرات لها تجعلها لغة مفسَّرةً أيضًا والأمر مماثل للغة Python المفسَّرة التي يمكن أن تكون مصرَّفة أيضًا.
</p>

<p>
	<strong>ثانيًا</strong> توجد لغات برمجة، جافا (Java) مثلًا، تستخدم نهجًا هجينًا (hybrid approach) يجمع بين التصريف والتفسير، حيث يبدأ هذا النهج بترجمة البرنامج إلى لغة وسيطة (intermediate language) عبر مصرِّف ثم تنفيذ البرنامج عبر مُفسِّر. تَستخدم لغة Java لغةً وسيطةً (intermediate language) تُدعى جافا بايتكود Java bytecode شبيهة بلغة الآلة، لكنها تُنفَّذ باستخدام مُفسِّر برمجيات يدعى بآلة جافا الافتراضية (Java virtual machine وتختصر إلى JVM).
</p>

<p>
	وسم لغة البرمجة بكونها لغة مفسَّرة أو مصرَّفة لا يكسبها خاصية جوهرية، على كل حال توجد اختلافات عامة بين اللغتين المُصرَّفة والمُفسَّرة.
</p>

<h2>
	الأنواع الساكنة (Static Types)
</h2>

<p>
	تدعم العديد من اللغات المُفسَّرة الأنواع الديناميكية (Dynamic Types)، وتقتصر اللغات المُصرَّفة على الأنواع الساكنة (Static Types). فيمكن في اللغات ساكنة النوع معرفة أنواع المتغيرات بمجرّد قراءة شيفرة البرنامج أي تكون أنواع المتغيرات محدَّدة قبل تنفيذ البرنامج، بينما تكون أنواع المتغيرات في اللغات التي توصف بأنها ديناميكية النوع غير معروفة قبل التنفيذ وتحدد وقت تنفيذ البرنامج. ويشير مصطلح ساكن (Static) إلى الأشياء التي تحدث في وقت التصريف (Compile time) (أي عند تصريف شيفرة البرنامج إلى شيفرة التنفيذ)، بينما يشير مصطلح Dynamic إلى الأشياء التي تحدث في وقت التشغيل (run time) (أي عندما يُشغَّل البرنامج).
</p>

<p>
	يمكن كتابة الدالة التالية في لغة Python على سبيل المثال:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_7004_6" style="">
<span class="pln">def add</span><span class="pun">(</span><span class="pln">x</span><span class="pun">,</span><span class="pln"> y</span><span class="pun">):</span><span class="pln">
	</span><span class="kwd">return</span><span class="pln"> x </span><span class="pun">+</span><span class="pln"> y</span></pre>

<p>
	لا يمكن معرفة نوع المتغيرين y وx بمجرد قراءة الشيفرة السابقة حيث لا يحدَّد نوعهما حتى وقت تنفيذ البرنامج، لذلك يمكن استدعاء هذه الدالة عدة مرات بتمرير قيمة بنوع مختلف إليها في كل مرة، وستعمل عملًا صحيحًا ما دام نوع القيمة المُمرَّرة إليها مناسبًا لتطبيق عملية الجمع عليها، وإلا سترمي الدالة اعتراضًا (exception) أو خطأً وقت التشغيل.
</p>

<p>
	يمكن كتابة نفس الدالة السابقة في لغة البرمجة C كما يلي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_7004_8" style="">
<span class="typ">int</span><span class="pln"> add</span><span class="pun">(</span><span class="typ">int</span><span class="pln"> x</span><span class="pun">,</span><span class="pln"> </span><span class="typ">int</span><span class="pln"> y</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln">
	</span><span class="kwd">return</span><span class="pln"> x </span><span class="pun">+</span><span class="pln"> y</span><span class="pun">;</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	يتضمّن السطر الأول من الدالة تصريحًا واضحًا وصريحًا بنوعي القيمتين التي يجب تمريرهما إلى الدالة ونوع القيمة التي تعيدها الدالة أيضًا، حيث يُصرَّح عن y وx كأعداد صحيحة (integers)، وهذا يعني أنه يمكن التحقق في وقت التصريف (compiled time) فيما إذا كان مسموحًا استخدام عامل الجمع مع النوع integer أم لا (إنه مسموح حقًا)، ويُصرَّح عن القيمة المُعادة كعدد صحيح (integer) أيضًا. وعندما تُستدعى الدالة السابقة في مكان آخر من البرنامج يستطيع المصرِّف (compiler) باستخدام التصريحات أن يتحقق من صحة نوع الوسطاء (arguments) الممررة للدالة، ومن صحة نوع القيمة التي تعيدها الدالة أيضًا.
</p>

<p>
	يحدث التحقق في اللغات المصرَّفة قبل بدء تنفيذ البرنامج لذلك يمكن إيجاد الأخطاء باكرًا، ويمكن إيجاد الأخطاء أيضًا في أجزاء البرنامج التي لم تُشغَّل على الإطلاق وهو الشيء الأهم. علاوًة على ذلك لا يتوجب على هذا التحقق أن يحدث في وقت التشغيل (runtime)، وهذا هو أحد الأسباب التي تجعل تنفيذ اللغات المُصرَّفة أسرع من اللغات المُفسَّرة عمومًا. يحافظ التصريح عن الأنواع في وقت التصريف (compile time) على مساحة الذاكرة في اللغات ساكنة النوع أيضًا، بينما تُخزَّن أسماء المتغيرات في الذاكرة عند تنفيذ البرنامج في اللغات ديناميكية النوع التي لا تحوي تصريحات واضحة لأنواعها وتكون أسماء هذه المتغيرات قابلة للوصول من قبل البرنامج. توجد دالة مبنيّة مسبقًا في لغة Python هي <code>locals</code>، تعيد هذه الدالة قاموسًا (dictionary) يتضمن أسماء المتغيرات وقيمها.
</p>

<p>
	ستجد تاليًا مثالًا عن مفسِّر Python:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_9484_10" style="">
<span class="pun">&gt;&gt;&gt;</span><span class="pln"> x </span><span class="pun">=</span><span class="pln"> </span><span class="lit">5</span><span class="pln">
</span><span class="pun">&gt;&gt;&gt;</span><span class="pln"> print locals</span><span class="pun">()</span><span class="pln">
</span><span class="pun">{</span><span class="str">'x'</span><span class="pun">:</span><span class="pln"> </span><span class="lit">5</span><span class="pun">,</span><span class="pln"> </span><span class="str">'__builtins__'</span><span class="pun">:</span><span class="pln"> </span><span class="pun">&lt;</span><span class="pln">module </span><span class="str">'__builtin__'</span><span class="pln"> </span><span class="pun">(</span><span class="pln">built</span><span class="pun">-</span><span class="pln">in</span><span class="pun">)&gt;,</span><span class="pln">
</span><span class="str">'__name__'</span><span class="pun">:</span><span class="pln"> </span><span class="str">'__main__'</span><span class="pun">,</span><span class="pln"> </span><span class="str">'__doc__'</span><span class="pun">:</span><span class="pln"> </span><span class="typ">None</span><span class="pun">,</span><span class="pln"> </span><span class="str">'__package__'</span><span class="pun">:</span><span class="pln"> </span><span class="typ">None</span><span class="pun">}</span></pre>

<p>
	يبيّن المثال السابق أنه يُخزَّن اسم المتغير في الذاكرة عند تنفيذ البرنامج (مع بعض القيم الأخرى التي تُعَد جزءًا من بيئة وقت التشغيل الافتراضية). بينما تتواجد أسماء المتغيرات في اللغات المُصرَّفة في الذاكرة في وقت التصريف (compile time) ولا تتواجد في وقت التشغيل (runtime). حيث يختار المصرّف موقعًا في الذاكرة لكل متغير ويسجل هذه المواقع كجزء من البرنامج المُصرَّف (سنتطرّق إلى مزيد من التفاصيل عن ذلك لاحقًا). يدعى موقع المتغير في الذاكرة عنوانًا (<strong>address</strong>) حيث تُخزَّن قيمة كل متغير في عنوانه، ولا تُخزَّن أسماء المتغيرات في الذاكرة على الإطلاق في وقت التشغيل (ولكن هذا شيء اختياري للمصرف فيمكن أن يضيف المصرِّف (compiler) أسماء المتغيرات إلى الذاكرة في وقت التشغيل بهدف تنقيح الأخطاء (debugging)، أي لمعرفة أماكن تواجد الأخطاء في البرنامج).
</p>

<h2>
	عملية التصريف (The compilation process)
</h2>

<p>
	يجب أن يفهم المبرمج فهمًا تامًا ما يحدث خلال عملية التصريف (compilation)، فإذا فُهِمت هذه العملية جيدًا سيساعد ذلك في تفسير رسائل الخطأ وتنقيح الأخطاء في الشيفرة وأيضًا في تجنُّب الزلات الشائعة. للتصريف خطوات هي:
</p>

<ol>
<li>
		<p>
			المعالجة المسبقة (Preprocessing): تتضمن لغة البرمجة C موجِّهات معالجة مسبقة (<strong>preprocessing directives</strong>) والتي تدخل حيز التنفيذ قبل تصريف البرنامج، فمثلًا يسبّب الموجِّه <code>‎#include</code> إدراج شيفرة مصدرية (source code) خارجية موضع استعماله.
		</p>
	</li>
	<li>
		<p>
			التحليل (Parsing): يقرأ المُصرِّف (compiler) أثناء هذه الخطوة الشيفرة المصدرية (source code) ويبني تمثيلًا داخليًّا internal) (representation للبرنامج يُدعى بشجرة الصيغة المجردة (<strong>abstract syntax tree</strong>). تُسمى عادًة الأخطاء المكتشفة خلال هذه الخطوة بأخطاء صياغية (syntax errors).
		</p>
	</li>
	<li>
		<p>
			التحقق الساكن (Static checking): يتحقق المصرِّف من صحة نوع المتغيرات والقيم وفيما إذا اُستدعيت الدوال بعدد ونوع وسطاء صحيحين وغير ذلك من التحققات. يُدعى اكتشاف الأخطاء في هذه الخطوة أحيانًا بالأخطاء الدلالية الساكنة (<strong>static semantic errors</strong>).
		</p>
	</li>
	<li>
		<p>
			توليد الشيفرة (Code generation): يقرأ المصرِّف التمثيل الداخلي (internal representation) للبرنامج ويولّد شيفرة الآلة (machine code) أو الشيفرة التنفيذية (byte code) للبرنامج.
		</p>
	</li>
	<li>
		<p>
			الربط (Linking): إذا استخدم البرنامج قيمًا ودوالًا مُعرَّفة في مكتبة، فيجب أن يجد المُصرِّف المكتبة المناسبة وأن يُضمِّن (include) الشيفرة المطلوبة المتعلقة بتلك المكتبة.
		</p>
	</li>
	<li>
		<p>
			التحسين (Optimization): يحسّن المصرف دومًا خلال عملية التصريف من الشيفرة ليصبح تنفيذها أسرع أو لجعلها تستهلك مساحةً أقل من الذاكرة. معظم هذه التحسينات هي تغييرات بسيطة توفر من الوقت والمساحة، ولكن تطبِّق بعض المُصرِّفات (compilers) تحسيناتٍ أعقد.
		</p>
	</li>
</ol>
<p>
	ينفذ المصرف كل خطوات التصريف ويولّد ملفًا تنفيذيًا (executable file) عند تشغيل الأداة <code>gcc</code>. المثال التالي هو شيفرة بلغة C:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_7004_11" style="">
<span class="com">#include</span><span class="pln"> </span><span class="str">&lt;stdio.h&gt;</span><span class="pln">
</span><span class="typ">int</span><span class="pln"> main</span><span class="pun">()</span><span class="pln">
</span><span class="pun">{</span><span class="pln">
	printf</span><span class="pun">(</span><span class="str">"Hello World\n"</span><span class="pun">);</span><span class="pln">
</span><span class="pun">}</span></pre>

<p>
	إذا حُفِظت الشيفرة السابقة في ملف اسمه <code>hello.c</code> فيمكن تصريفها ثم تشغيلها كما يلي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_9484_14" style="">
<span class="pln">$ gcc hello</span><span class="pun">.</span><span class="pln">c
$ </span><span class="pun">./</span><span class="pln">a</span><span class="pun">.</span><span class="pln">out</span></pre>

<p>
	تخزّن الأداة <code>gcc</code> الشيفرة القابلة للتنفيذ (executable code) في ملف يدعى افتراضيًا <code>a.out</code> (والذي يعني في الأصل خرج مُجمَّع (assembler output)). ينفذ السطر الثاني الملف التنفيذي، حيث تخبر البادئة <code>‎./‎</code> الصدفة (shell) لتبحث عن الملف التنفيذي في المجلّد (directory) الحالي. من الأفضل استخدام الراية <code>‎-o</code> لتوفير اسم أفضل للملف التنفيذي، حيث يُعطى الملف التنفيذي الناتج بعد عملية التصريف اسمًا افتراضيًا (a.out) بدون استخدام الراية <code>‎-o</code>، ولكن يُعطى اسمًا محددًا باستخدام الراية <code>‎-o</code> كما يلي:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_9484_18" style="">
<span class="pln">$ gcc hello</span><span class="pun">.</span><span class="pln">c </span><span class="pun">-</span><span class="pln">o hello
$ </span><span class="pun">./</span><span class="pln">hello</span></pre>

<h2>
	التعليمات المُصرَّفة (Object code)
</h2>

<p>
	تخبر الراية <code>‎-c</code> الأداة <code>gcc</code> بأن تصرِّف البرنامج وتولّد شيفرة الآلة (machine code) فقط، بدون أن تربط (link) البرنامج أو تولّد الملف التنفيذي.
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_9484_20" style="">
<span class="pln">$ gcc hello</span><span class="pun">.</span><span class="pln">c </span><span class="pun">-</span><span class="pln">c</span></pre>

<p>
	النتيجة هي توليد ملف يُدعى <code>hello.o</code>، حيث يرمز حرف o إلى <strong>object code</strong> وهو البرنامج المُصرّف. والتعليمات المُصرَّفة (object code) غير قابلة للتنفيذ لكن يمكن ربطها بملف تنفيذي. يقرأ الأمر <code>nm</code> في UNIX ملف التعليمات المُصرَّفة (object file) ويولّد معلومات عن الأسماء التي يُعرِّفها ويستخدمها الملف، فمثلًا:
</p>

<pre class="ipsCode prettyprint lang-c prettyprinted" id="ips_uid_7004_14" style="">
<span class="pln">$ nm hello</span><span class="pun">.</span><span class="pln">o
</span><span class="lit">0000000000000000</span><span class="pln"> T main
                 U puts</span></pre>

<p>
	يشير الخرج السابق إلى أن <code>hello.o</code> يحدد اسم التابع الرئيسي <code>main</code> ويستخدم دالة تدعى <code>puts</code>، والتي تشير إلى (put string). وتطّبق <code>gcc</code> تحسينًا (optimization) عن طريق استبدال <code>printf</code> (وهي دالة كبيرة ومعقدة) بالدالة <code>puts</code> البسيطة نسبيًا. يمكن التحكم بمقدار التحسين الذي تقوم به <code>gcc</code> مع الراية <code>‎-O</code>، حيث تقوم <code>gcc</code> بإجراء تحسينات قليلة جدًا افتراضيًا مما يجعل تنقيح الأخطاء (debugging) أسهل. بينما يفعّل الخيار <code>‎-O1</code> التحسينات الأكثر شيوعًا وأمانًا، وإذا استخدمنا مستويات أعلى (أي O2 وما بعده) فستفعِّل تحسينات إضافية، ولكنها تستغرق وقت تصريف أكبر.
</p>

<p>
	لا ينبغي أن يغير التحسين من سلوك البرنامج من الناحية النظرية بخلاف تسريعه، ولكن إذا كان البرنامج يحتوي خللًا دقيقًا (subtle bug) فيمكن أن تحمي عملية التحسين أثره أو تزيل عملية التحسين هذا الخلل. إيقاف التحسين فكرة جيدة أثناء مرحلة التطوير عادةً، وبمجرد أن يعمل البرنامج ويجتاز الاختبارات المناسبة يمكن تفعيل التحسين والتأكد من أن الاختبارات ما زالت ناجحة.
</p>

<h2>
	الشيفرة التجميعية (Assembly code)
</h2>

<p>
	تتشابه الرايتان <code>‎-S</code> و<code>‎-c</code>، حيث أن الراية <code>‎-S</code> تخبر الأداة <code>gcc</code> بأن تصرف البرنامج وتولد الشيفرة التجميعية (assembly code), والتي هي بالأساس نموذج قابل للقراءة تستطيع شيفرة الآلة (machine code) قراءته.
</p>

<pre class="ipsCode">
$ gcc hello.c -S
</pre>

<p>
	ينتج ملف يدعى <code>hello.s</code> والذي يبدو كالتالي
</p>

<pre class="ipsCode">
        .file "hello.c"
        .section .rodata
.LC0:
        .string "Hello World"
        .text
        .globl main
        .type main, @function
main:
.LFB0:
        .cfi_startproc
        pushq %rbp
        .cfi_def_cfa_offset 16
        .cfi_offset 6, -16
        movq %rsp, %rbp
        .cfi_def_cfa_register 6
        movl $.LC0, %edi
        call puts
        movl $0, %eax
        popq %rbp
        .cfi_def_cfa 7, 8
        ret
        .cfi_endproc
.LFE0:
        .size main, .-main
        .ident "GCC: (Ubuntu/Linaro 4.7.3-1ubuntu1) 4.7.3"
        .section .note.GNU-stack,"",@progbits
</pre>

<p>
	تُضبَط <code>gcc</code> عادةً لتولد الشيفرة للآلة التي تعمل عليها، ففي حالتي، يقول المؤلف، وُلِّدت شيفرة لغة آلة لمعمارية x86 للمعالجات والتي يمكن تنفيذها على شريحة واسعة من معالجات Intel ومعالجات AMD وغيرهما وفي حال استهداف معمارية مختلفة، فستولد شيفرة أخرى مختلفة عن تلك التي تراها الآن.
</p>

<h2>
	المعالجة المسبقة (Preprocessing)
</h2>

<p>
	يمكن استخدام الراية <code>‎-E</code> لتشغيل المعالج المُسبق (preprocessor) فقط بدون الخطوات الأخرى من عملية التصريف:
</p>

<pre class="ipsCode">
$ gcc hello.c -E
</pre>

<p>
	سينتج خرج من المعالج المسبق فقط. يحتوي المثال السابق تلقائيًا على الشيفرة المُضمَّنة (included code) المبنية مسبقًا والمتعلقة بالمكتبة <code>stdio.h</code> المذكورة في بداية البرنامج، وبالتالي يتضمن كل الملفات المُضمَّنة المتعلقة بتلك المكتبة، وكل الملفات الفرعية التابعة للملفات السابقة والملفات الموجودة في الملفات الفرعية أيضًا وهكذا. فعلى حاسوبي، يقول المؤلف، وصل العدد الإجمالي للشيفرة الإجمالية المضمنة إلى 800 سطر، ونظرًا أن كل برنامج C يتضمّن ملف الترويسات <code>stdio.h</code> تقريبًا، لذلك تُضمَّن تلك الأسطر في كل برنامج مكتوب بلغة C. وتتضمّن العديد من برامج C المكتبة <code>stdlib.h</code> أيضًا، وبالتالي ينتج أكثر من 1800 سطر إضافي من الشيفرة يجب تصريفها جميعًا.
</p>

<h2>
	فهم الأخطاء (Understanding errors)
</h2>

<p>
	أصبح فهم رسائل الخطأ أسهل بعد معرفة خطوات عملية التصريف، فمثلًا عند وجود خطأ في الموجّه <code>‎#include</code> ستصل رسالة من المعالج المسبق هي:
</p>

<pre class="ipsCode">
hello.c:1:20: fatal error: stdioo.h: No such file or directory
compilation terminated.
</pre>

<p>
	أما عند وجود خطأ صياغي (syntax error) متعلق بلغة البرمجة، ستصل رسالة من المُصرِّف (compiler) هي:
</p>

<pre class="ipsCode">
hello.c: In function 'main':
hello.c:6:1: error: expected ';' before '}' token
</pre>

<p>
	عند استخدام دالة غير معرَّفة في المكتبات القياسية ستصل رسالة من الرابط (linker) هي:
</p>

<pre class="ipsCode">
/tmp/cc7iAUbN.o: In function `main':
hello.c:(.text+0xf): undefined reference to `printff'
collect2: error: ld returned 1 exit status
</pre>

<p>
	<code>ld</code> هو اسم رابط UNIX ويشير إلى تحميل (loading)، حيث أن التحميل هو خطوة أخرى من عملية التصريف ترتبط ارتباطًا وثيقًا بخطوة الربط (linking).
</p>

<p>
	تجري لغة C تحققًا سريعًا جدًا ضمن وقت التشغيل بمجرد بدء البرنامج، لذلك من المحتمل أن ترى بعضًا من أخطاء وقت التشغيل (runtime errors) فقط وليس جميعها، مثل خطأ القسمة على صفر (divide by zero)، أو تطبيق عملية عدد عشري غير مسموحة وبالتالي الحصول على اعتراض عدد عشري (Floating point exception)، أو الحصول على خطأ تجزئة (Segmentation fault) عند محاولة قراءة أو كتابة موقع غير صحيح في الذاكرة.
</p>

<p>
	ترجمة -وبتصرّف- للفصل Compilation من كتاب <a href="http://greenteapress.com/thinkos/" rel="external nofollow">Think OS A Brief Introduction to Operating Systems</a>
</p>
]]></description><guid isPermaLink="false">976</guid><pubDate>Tue, 01 Sep 2020 18:04:00 +0000</pubDate></item></channel></rss>
