بسم الله الرحمن الرحيم - المقدمة : هل تساءلت يوما عن طريقة عمل البرامج , هل تريد أن تعرف طريقة ترابط البرامج مع بعضها وعلاقتها بنظام التشغيل , هل تثيرك خاصية تعدد المهام وتريد أن تعرف بعض خصائصه . اذا كان جوابك نعم لأحد هذه الأسئلة فالمقالة ستناسبك بإذن الله (طبعا المقالة تحوي أكثر مما ذكر أعلاه) , في هذه المقالة سأتكلم قليلا عن الـ(process ). طبعا هي ليست مقالة علمية موثقة انما مافهمته من دراستي الجامعية لنظم التشغيل وقرأتي لكتاب( operating system concepts) الطبعة الثامنة النسخة الخاصة بالطلاب لذا ارجوا من أهل العلم والخبرة أن يقوّمو مقالي ويصححوا أخطائه ويزيلوا شوائبه . المواضيع التي سنناقشها في هذه المقالة هي كالاتي : - ماهو process - محتويات الـ(process). - تعدد المهام وعلاقته بالـ(process). - أوضاع ال process . -( Process Control Block (PCB . -وعلاقة (قرابة ال process ) ببعضه . - طريقة انشاء الـ(process) - ماهو ال process : تبدأ القصة عندما تضغط على ايقونة البرنامج الموجود في جهازك أو عندما تكتب اسم البرنامج في الطرفية عندها يتم تحميل البرنامج الى الذاكرة العشوائية (RAM) ليتم تهيئته لدخول على المعالج , بعد هذه العملية يطلق على البرنامج اسم الـ(process )اي أننا ننسى تماما اسم برنامج ونستبدله ب process . نسنتج من هذا أنّ الـ(process )ماهو الا برنامجا قيد التنفيذ . وهذا يعني بطبيعة الحال أن البرنامج قبل التنفيذ يكون عبارة عن ملف تنفيذي لا محل له من الإعراب وله محل في القرص الصلب . - هيكلة (محتويات) process : طبعا هناك هيكلة موّحدة للـ(process )قلنا سابقا أنّ الـ(process )عبارة عن برنامج قيد التنفيذ , ومعضم البرامج بطبيعة الحال تحوي على دوال ومؤشرات (متغيرات ديناميكية pointer ) وكذالك متغيرات محليّة (local) وأخرى عامة (global) لذا احتجنا الى تصميم هيكلة يضم أجزأ البرنامج وينظمه داخل الذاكرة , هذه الهيكلة تكون بالشكل التالي : نلاحظ من الصورةأن الهيكلة تتكون من : stack , heep ,data ,text الـ(stack )يحوي بطبيعة الحال على المتغيرات المؤقتة من : متغيرات محليّة , استدعاءات الدوال ...) . أما ال data فيحوي على : متغيرات عامة (global variables ) ال heep يحوي على المتغيرات الديناميكية . أما text فيحوي على كود البرنامج . - تعدد المهام وعلاقته بالـ(process) : هل تعلم أنه لايوجد شئ اسمه تعدد المهام في علوم الحاسب وأقصد تعدد المهام الفعلي !!!!. نعم لقد سمعتني بوضوح قد يقول قائل : ولكني أقرأ المقال الآن واسمع الصوتيات وأحادث اصدقائي في نفس الوقت ؟؟ أقول هذا ما تظنه ولكن المعالج لايعمل بهذا الشكل ولايمكن أن يعالج برنامجين في نفس الوقت انها كذبة كبيرة وخدعة عظيمة . كل ما هنالك أنّ المعالج يعمل بسرعات خارقة (أجزاء الثانية ) ويقوم بمعالجة البيانات بطريقة مجزأة حيث يعطي لكل برنامج وقت محدد لكي يعالجه أي أنّ الـ(process ) (قلنا سابقا أنه برنامج قيد التنفيذ ) سوف يدخل ويخرج عدة مرات على المعالج بطريقة مرتبة ومنظمة ومدروسة . وهذا المقال لن يتناول طريقة تعدد المهام ولا خوارزمياته , لأن الموضوع كبير ومعقد نوع ما . المهم أنّ تعلم أنّ الـ(process) سوف يدخل على المعالج ثم يعالج بياناته في وقت محدد إن انتهى الـ(process) قبل الوقت المحدد له فإنه ينتهي أو يختفي , أما إن انتهى الوقت المحدد قبل أن ينهي الـ(process) عمله فسوف يخرج الـ(process)الى قائمة تسمى ready اي جاهز للمعالجة وينتظر حتى يحلّ دوره في هذه القائمة . ذالك يعني أنّ الـ(process) سوف يدخل ويخرج عدة مرات من المعالج , هناك مشكلة ما , أين تكمن المشكلة ؟؟!!. تكمن المشكلة عزيزي أن المعالج يحوي على مسجلات وعدّاد لعدّ خطوات البرنامج (حيث أنّ المعالج يعالج البرنامج بعدّة خطوات ) تخيل معي أن لدينا process ولنسميه (p1) يحتاج الى عشر خطوات لمعالجته , دخل هذا process الى المعالج وبدأ عداد المعالج pc بعدّ الخطوات وبدأت المسجلات (ax,bx,dx...) بتخزين القيم والعمليات الرياضية , العداد وصل الى الخطوة رقم ثلاثة ثم انتهى وقت الـ(process) وخرج ودخل process أخر سيتم تصفير المسجلات والبدء من جديد , p1 سوف يأتي دوره وسيدخل مرة أخرى طبعا العداد سيبدأ العدّ من جديد والمسجلات ستبدأ بتخزين البيانات وعندما يصل العداد للخطوة رقم ثلاثة سينتهي وقته وسيخرج من المعالج , اذا لن ينتهي الـ(process) على هذه الحالة , ما الحل ؟؟. سوف نرى الحل بإذن الله في فقرة PCB . أوضاع الـ(process): في الفقرة أعلاه قلنا أن الـ(process) سوف يدخل على المعالج عدة مرات ويخرج هذا وضع واحد من اوضاع ال process . انضر الى هذا الشكل : Resized to 81% (was 800 x 322) - Click image to enlarge كما نلاحظ هناك عدة أوضاع أو حالات وهي : new , ready ,running waiting , terminated new : نعني به مرحلة انشاء الـ(process) وتحميله من القرص الصلب وتجهيزه لدخول على قائمة ready . ready : اي جاهز للمعالجة في هذه المرحلة الـ(process) ينتضر دوره لدخول على المعالج . running : الـ(process) في هذه المرحلة يدخل على المعالج . waiting : بعض الـ(process) التي تتطلب الحصول على مدخلات أو تخرج مخرجات تكون في قائمة waiting حيث ينتظر الحصول على مدخل ما أو أن يخرج مخرج ما . terminated : بعد أن ينهي الـ(process) عمله يتم القضاء علىه في هذه المرحلة . -( Process Control Block (PCB : PCB نستطيع أن نترجمه ب (وحدة تحكم ال process ) حيث يحتفظ بكل مايتعلق بـ (process). نظام التشغيل يتحكم بـ(process) عبر ال PCB . تكلمنا سابقا عن مشكلة تعدد المهام , حيث يدخل الـ(process) الى المعالج ويبدأ ال PC بالعدّ (الـ PC يقوم بحفظ عنوان الخطوة القادمة ) وتبدأ المسجلات بحفظ القيم ثم يخرج الـ(process) من المعالج ومن ثمّ تصفّر المسجلات والعدّاد . لحل هذه المشكلة علينا قبل أن يخرج الـ(process) علينا بأخذ نسخة من قيم المسجلات , حيث اذا اتي دور الـ(process) من جديد نقوم بإسناد قيم النسخ الموجودة لدينا الى مسجلات المعالج , ولكن أين نتحتفظ بهذه النسخ ؟. نعم ان كان جوابك هو PCB فقد أصبت كبد الحقيقة , ولكن هذه واحده من مهام ال PCB . انضر لهذا الشكل نجد أن PCB يتكون من : process state : يحتفظ بوضع الـ (process) الحالي (راجع أوضاع ال process) . Program counter : او (PC) وهو العدّاد الذي تكلمنا عنه سابقا حيث يحتفظ بنسخة من عدّاد المعالج . CPU registers : يحتفظ بنسخ من مسجلات المعالج . الباقي لن أعلق عليه ذالك ﻷنه يتعلق بأمور أخرى أحتاج أن اكتب مقالات عنها . وما ذكرته يعتبر من الأمور الرئيسية في الـ(process) . - علاقة (قرابة ال process's ) ببعضه: هل تظن حقا أن الـ(process) يتم انشائه عشوائيا , ويتم القضاء عليه (قتله ) كذالك عشوائيا في احدى زوايا الـ(RAM)!! عزيزي لاتظن ذالك ﻷن الـ(process) له قرابة وجذور , قد تكون علاقة (process) بأخر علاقة الوالد والإبن أو علاقة الجد والحفيد , اذ لم يكن لا أب ولا جد فالعلاقة حتما ستكون علاقة ابناء عمومة . نظام التشغيل نفسه عبارة عن مجموعة من الـ(process's) في البداية نظام التشغيل يكون عبارة عن process واحد ومن ثمّ هذا الـ(process) يقوم بإنشاء عدة أولاد له ( process's أخرين ) ﻷدأ مهام معينة , وتختلف طريقة الإنشاء بإختلاف نظام التشغيل ولكنها قريبة من بعض ولكل (process) عنوان خاص به لايمكن أن يتشارك معه أحد في هذا العنوان نطلق على هذا العنوان اصطلاحا (pid) وهو اختصار لـ(process identifier) . سنأخذ مثالا على طريقة انشاء الـ (process) في نظام سولاريس (solaris) , انظر لشكل الأتي : نجد أنّ اول (process) تم انشائه هو (sched) . (sched) هذا سيقوم بإنشاء ثلاثة ابناء له وهم : init , pageout ,fsflush . وكل واحد من هؤلأ ال (process's) مناط له مهمة معينة . Fsflush و pageout : مسوؤلان عن تنظيم الذاكرة والملفات . Init : مسوؤل عنّ الـ(process's) الذي ينتجه المستخدم . طيب السؤال الذي يطرح نفسه ماالذي يحصل عندما ينتهي البرنامج ؟؟ طبعا ينتهي الـ(process) ويقتل , المشكلة أنّ قتل الـ(process) يعني قتل جميع أبنائه (مع أنّ هناك طرق لربط الإبن مع الجد ) , اذا نستنتج أنّ قتل ال(sched) يعني قتل النظام كاملا في سولاريس , أي أنك قمت بغلق الجهاز . ملاحظة : أصحاب لنكس تستطيعون أنّ تتأكدون من هذه المعلومة عبر فتح الطرفية ثمّ فتح اي برنامج من الطرفية ولاتضع علامة (&) في أمر فتح البرنامج , قم بإغلاق الطرفية ستجد أنّ البرنامج اغلق بإغلاق الطرفية , وذالك ﻷنك قتلت (process) الطرفية - أصلحك الله - و (process) الطرفية هو الذي أنشئ (process) البرنامج الذي قمت بفتحه وأنت قمت بقتل الأب فقتل الإبن . - طريقة انشاء ال (process) : قلنا سابقا أنّ اي برنامج قيد التنفيذ عبارة عن (process) , وقلنا كذالك أنّ ال (process) يستطيع أنّ ينشئ ابنا له (process أخر) . تستطيع أنّ تنشئ (process) عبر برنامجك وذلك بإستدعاء دالة النظام (fork()) . ولكن السؤال الذي يظهر , ماهو مواصفات هذا الـ(process) , أي ما الذي سيفعله هذا الـ(process) وما محتويات هيكلته ؟ هذا الـ(process) سيكون نسخة من ابيه , سيفعل ما يفعله ابوه , فلو أنّ اباه سيطبع جملة (Hello world) فسيقوم الابن بطباعة هذه الجملة , لماذا ورث الابن عمل أبيه ؟؟ ﻷنّ الـ (process) عبارة عن برنامج قيد التنفيذ فما الفائدة من وجوده في ال RAM وهو لا يعمل شئ . طبعا تستطيع أن تغير وظيفة الـ (process) الجديد عبر دالة النظام (exec) , لا أحب أنّ اتكلم كلاما نظريا هكذا مارأيك أن نستعرض كود محترم بـ(C++) ﻹنشاء (process) : plain text include <iostream> using namespace std; int main() { /* fork a child process */ fork(); cout<<"hello world"<<endl; } ماالذي سينتج الأن ستطبع كلمة Hello world مرتين مرة طبعها الـ(process) الأصلي والثانية طبعها الـ(process) الجديد . الأن اريد أن أغير مهمة الـ(process) الأخر عبر دالة (exec) ولكن كيف أفرق بين الأب والإبن ؟؟ نستطيع أن نفرق بينهم عبر دالة الـ(fork) نفسها حيث أنّ هذه الدالة ترجع للـ(process) الإبن صفرا وترجع أقل من صفر اذ لم تنجح في الإنشاء , وترجع للـ(process) الأب عنوان الإبن . انضر للكود الأتي : expand |plain text #include <cstdlib> #include <iostream> using namespace std; int main() { pid_t pid; /* fork a child process */ pid =fork(); cout<<"hello world"<<endl; if (pid < 0) { /* error occurred */ cout<<"Fork Failed\n"<<endl; exit(-1); } else if (pid == 0) { /* child process */ cout<<"I am the child "<<pid<<endl; } else { /* parent process */ cout<<"I am the parent "<<pid<<endl; } } الأن اصبحنا نعرف ونفرق الإبن من الأب ولكن هل حللنا كل مشاكلنا ؟ بقي مشكلة أنّ الإبن يقوم بعمل أباه وكذالك تصور معي لو مات الأب قبل الإبن فسوف يموت الإبن ولن يكمل عمله لذالك علينا أن نستعمل دالة النظام (exec) لتغير عمّل الـ(process) ودالة النظام (wait) لنجعل الأب ينتضر ابنه انضر للكود الأتي مكتوب بلغة الـ© وقد أخذته من كتاب ( operating system concepts) expand |plain text int main() { pid_t pid; /* fork a child process */ pid = fork(); if (pid < 0) { /* error occurred */ fprintf(stderr, "Fork Failed\n"); exit(-1); } else if (pid == 0) { /* child process */ execlp("/bin/ls","ls",NULL); } else { /* parent process */ /* parent will wait for the child to complete */ printf("I am the parent %d\n",pid); wait(NULL); printf("Child Complete\n"); exit(0); } } نجد أنّنا قمنا بتغير عمل (process) وحمّلنا برنامج (ls) على (process) الإبن . ملاحظة : ls هو برنامج خاص في لنكس يعرض محتويات المجلد . بالنسبة لوندوز لا أعلم ان كان دالة (fork) تعمل على وندوز ولكنك تستطيع أن تستعمل (win32Api) . مشابه لـ(fork) الإختلاف يكمن في أن دالة createProcess تطلب منك أن تغير مهمة (process) الإبن أثناء الإنشاء وبعض الفروقات البسيطة انضر للكود المأخوذ من الكتاب السابق ذكره وبلغة الـ© كذالك : expand |plain text #include <windows.h> #include <stdio.h> int main( VOID ) { STARTUPINFO si; PROCESS_INFORMATION pi; ZeroMemory( &si, sizeof(si) ); si.cb = sizeof(si); ZeroMemory( &pi, sizeof(pi) ); // Start the child process. if( !CreateProcess( NULL, // No module name (use command line). "C:\\WINDOWS\\system32\\mspaint.exe", // Command line. NULL, // Process handle not inheritable. NULL, // Thread handle not inheritable. FALSE, // Set handle inheritance to FALSE. 0, // No creation flags. NULL, // Use parent's environment block. NULL, // Use parent's starting directory. &si, // Pointer to STARTUPINFO structure. &pi ) // Pointer to PROCESS_INFORMATION structure. ) { printf( "CreateProcess failed (%d).\n", GetLastError() ); return -1; } // Wait until child process exits. WaitForSingleObject( pi.hProcess, INFINITE ); // Close process and thread handles. CloseHandle( pi.hProcess ); CloseHandle( pi.hThread ); } للأسف لقد وصلنا الى نهاية المقال , هذه مجرد لمحة بسيطة عن الـ(process) , أسأل الله سبحانه أن ينفع بها .
include <iostream> using namespace std; int main() { /* fork a child process */ fork(); cout<<"hello world"<<endl; }
#include <cstdlib> #include <iostream> using namespace std; int main() { pid_t pid; /* fork a child process */ pid =fork(); cout<<"hello world"<<endl; if (pid < 0) { /* error occurred */ cout<<"Fork Failed\n"<<endl; exit(-1); } else if (pid == 0) { /* child process */ cout<<"I am the child "<<pid<<endl; } else { /* parent process */ cout<<"I am the parent "<<pid<<endl; } }
int main() { pid_t pid; /* fork a child process */ pid = fork(); if (pid < 0) { /* error occurred */ fprintf(stderr, "Fork Failed\n"); exit(-1); } else if (pid == 0) { /* child process */ execlp("/bin/ls","ls",NULL); } else { /* parent process */ /* parent will wait for the child to complete */ printf("I am the parent %d\n",pid); wait(NULL); printf("Child Complete\n"); exit(0); } }
#include <windows.h> #include <stdio.h> int main( VOID ) { STARTUPINFO si; PROCESS_INFORMATION pi; ZeroMemory( &si, sizeof(si) ); si.cb = sizeof(si); ZeroMemory( &pi, sizeof(pi) ); // Start the child process. if( !CreateProcess( NULL, // No module name (use command line). "C:\\WINDOWS\\system32\\mspaint.exe", // Command line. NULL, // Process handle not inheritable. NULL, // Thread handle not inheritable. FALSE, // Set handle inheritance to FALSE. 0, // No creation flags. NULL, // Use parent's environment block. NULL, // Use parent's starting directory. &si, // Pointer to STARTUPINFO structure. &pi ) // Pointer to PROCESS_INFORMATION structure. ) { printf( "CreateProcess failed (%d).\n", GetLastError() ); return -1; } // Wait until child process exits. WaitForSingleObject( pi.hProcess, INFINITE ); // Close process and thread handles. CloseHandle( pi.hProcess ); CloseHandle( pi.hThread ); }