
يحتوي أي نظام تشغيل على آلية بدء تشغيل محددة. مبدأ تشغيل هذه الآلية لكل نظام مختلف. عادةً ما يقولون إن النظام يقوم بالتمهيد (Eng. Boot) ، وهذا هو اختصار لـ "bootstrap" ، والذي يشير إلى تعبير "شد نفسك فوق سياج بواسطة bootstraps" كيف ينتقل النظام بشكل مستقل من الحالة التي تكون فيها الذاكرة مليئة بالفراغ (
ملاحظة المترجم: إذا كانت دقيقة للغاية ، ثم القمامة ) إلى تنفيذ البرنامج المستقر. تقليديًا ، يتم تحميل جزء صغير من البرنامج في الذاكرة ، ويمكن تخزينه في ذاكرة القراءة فقط. في الماضي ، يمكن إدخاله باستخدام المفاتيح الموجودة في مقدمة الكمبيوتر. يقرأ محمل الإقلاع هذا برنامج تشغيل أكثر تعقيدًا قام بالفعل بتحميل نظام التشغيل. اليوم ، يقوم كمبيوتر سطح المكتب بالتمهيد كما يلي: يبحث رمز BIOS عن الأجهزة (محركات الأقراص الثابتة ، والأقراص المدمجة ، وعصائد USB) التي يتم التمهيد منها ، ثم يقوم نظام التشغيل بالتمهيد.
يمكن أيضًا تهيئة نظام التشغيل للأنظمة المدمجة بطريقة مماثلة. وفي الواقع ، يتم تحميل أنظمة التشغيل المدمجة التي تم تطويرها على أساس أنظمة تشغيل سطح المكتب. ولكن في معظم RTOS "الكلاسيكية" ، يتم استخدام طريقة أكثر بساطة (وبالتالي أسرع).
نظام التشغيل هو جزء من البرنامج. إذا كان هذا البرنامج بالفعل في الذاكرة (على سبيل المثال ، في شكل أو آخر من ROM) ، فأنت بحاجة فقط للتأكد من أن تسلسل أوامر وحدة المعالجة المركزية بعد إعادة تعيين ينتهي بتنفيذ رمز تهيئة نظام التشغيل. هكذا يعمل معظم RTOS ، بما في ذلك Nucleus SE (
ملاحظة المترجم: هذا ينطبق أيضًا على RTOS MAX ).
تشتمل معظم أدوات تطوير البرامج المدمجة على رمز بدء التشغيل اللازم للتعامل مع إعادة تعيين وحدة التحكم ونقلها إلى وظيفة نقطة الدخول في الوظيفة
الرئيسية () . لا تتعامل شفرة Nucleus SE القابلة لإعادة التوزيع مع هذه العملية ، حيث يجب أن تكون محمولة قدر الإمكان. بدلاً من ذلك ، يحتوي على الوظيفة
الرئيسية () ، والتي تتحكم في وحدة المعالجة المركزية وتهيئة نظام التشغيل وتبدأ تشغيله. ستتم مناقشة هذه الميزة بالتفصيل أدناه.
المقالات السابقة في السلسلة: تهيئة الذاكرة
تبدأ إعلامات جميع المتغيرات الثابتة في كود Nucleus SE ببادئة
ROM أو
RAM للإشارة إلى المكان الذي يجب أن توجد فيه. يتم
تعريف هذين التوجيهين
#define في ملف
nuse_types.h ويجب تهيئته مع الأخذ في الاعتبار تفاصيل مجموعة أدوات التطوير المستخدمة (المترجم والرابط). عادةً ، يجب أن يكون
ROM من النوع
const (
ملاحظة المترجم: من تجربتي ، const ليست دائماً كافية ، ساكنة أفضل ) ، و
RAM هي قيمة فارغة.
تتم تهيئة كافة متغيرات
ROM بشكل ثابت ، وهو أمر منطقي. لم يتم تهيئة متغيرات
RAM بشكل ثابت (حيث يعمل هذا فقط مع بعض أدوات الأدوات التي تم تكوينها لنسخها تلقائيًا من ROM إلى RAM) ؛ يتم تضمين رمز التهيئة الصريح في التطبيق وسيتم وصفه بالتفصيل أدناه.
Nucleus SE لا تخزن البيانات "الثابتة" في ذاكرة الوصول العشوائي ، والتي عادة ما تكون قليلة في النظم الصغيرة. بدلاً من استخدام هياكل البيانات المعقدة لوصف كائنات kernel ، يتم استخدام مجموعات من الجداول (المصفوفات) ، والتي يتم وضعها بسهولة في ذاكرة الوصول العشوائي أو RAM ، حسب الحاجة.
وظيفة () الرئيسية
التالي هو الكود الكامل لوظيفة
main () من Nucleus SE:
void main(void) { NUSE_Init(); /* initialize kernel data */ /* user initialization code here */ NUSE_Scheduler(); /* start tasks */ }
تسلسل العمليات بسيط للغاية:
- أولاً ، يتم استدعاء الدالة NUSE_Init () . يقوم بتهيئة جميع هياكل البيانات Nucleus SE وسيتم وصفه بالتفصيل أدناه.
- ثم يمكن للمستخدم إدراج أي رمز تهيئة التطبيق الذي سيتم تنفيذه قبل بدء جدولة المهام. لمزيد من المعلومات حول ما يمكن تحقيقه باستخدام هذا الرمز ، انظر لاحقًا في هذه المقالة.
- أخيرًا ، يبدأ جدولة Nucleus SE ( NUSE_Scheduler () ). سيتم أيضًا مناقشة هذا بالتفصيل لاحقًا في هذه المقالة.
دالة NUSE_Init ()
هذه الوظيفة تهيئة جميع متغيرات نواة نواة SE وبنى البيانات.
أدناه هو رمز الوظيفة الكاملة: void NUSE_Init(void) { U8 index; /* global data */ NUSE_Task_Active = 0; NUSE_Task_State = NUSE_STARTUP_CONTEXT; #if NUSE_SYSTEM_TIME_SUPPORT NUSE_Tick_Clock = 0; #endif #if NUSE_SCHEDULER_TYPE == NUSE_TIME_SLICE_SCHEDULER NUSE_Time_Slice_Ticks = NUSE_TIME_SLICE_TICKS; #endif /* tasks */ #if ((NUSE_SCHEDULER_TYPE != NUSE_RUN_TO_COMPLETION_SCHEDULER) || NUSE_SIGNAL_SUPPORT || NUSE_TASK_SLEEP || NUSE_SUSPEND_ENABLE || NUSE_SCHEDULE_COUNT_SUPPORT) for (index=0; index<NUSE_TASK_NUMBER; index++) { NUSE_Init_Task(index); } #endif /* partition pools */ #if NUSE_PARTITION_POOL_NUMBER != 0 for (index=0; index<NUSE_PARTITION_POOL_NUMBER; index++) { NUSE_Init_Partition_Pool(index); } #endif /* mailboxes */ #if NUSE_MAILBOX_NUMBER != 0 for (index=0; index<NUSE_MAILBOX_NUMBER; index++) { NUSE_Init_Mailbox(index); } #endif /* queues */ #if NUSE_QUEUE_NUMBER != 0 for (index=0; index<NUSE_QUEUE_NUMBER; index++) { NUSE_Init_Queue(index); } #endif /* pipes */ #if NUSE_PIPE_NUMBER != 0 for (index=0; index<NUSE_PIPE_NUMBER; index++) { NUSE_Init_Pipe(index); } #endif /* semaphores */ #if NUSE_SEMAPHORE_NUMBER != 0 for (index=0; index<NUSE_SEMAPHORE_NUMBER; index++) { NUSE_Init_Semaphore(index); } #endif /* event groups */ #if NUSE_EVENT_GROUP_NUMBER != 0 for (index=0; index<NUSE_EVENT_GROUP_NUMBER; index++) { NUSE_Init_Event_Group(index); } #endif /* timers */ #if NUSE_TIMER_NUMBER != 0 for (index=0; index<NUSE_TIMER_NUMBER; index++) { NUSE_Init_Timer(index); } #endif }
أولاً ، تهيئة المتغيرات العامة:
- NUSE_Task_Active - فهرس المهمة النشطة ، تمت تهيئته إلى صفر ؛ في وقت لاحق هذا قد يغير جدولة.
- NUSE_Task_State - تمت تهيئته بالقيمة NUSE_STARTUP_CONTEXT ، مما يحد من وظيفة واجهة برمجة التطبيقات لأي كود تهيئة لتطبيق لاحق.
- في حالة تنشيط دعم وقت النظام ، يتم تعيين NUSE_Tick_Clock على الصفر.
- إذا تم تنشيط جدولة Time Slice ، فسيتم تعيين NUSE_Time_Slice_Ticks للقيمة التي تم تكوينها NUSE_TIME_SLICE_TICKS .
ثم يتم استدعاء الوظائف لتهيئة كائنات kernel:
- يتم استدعاء NUSE_Init_Task () لتهيئة بنيات البيانات لكل مهمة. يتم تخطي هذه المكالمة فقط إذا تم استخدام جدولة "التشغيل إلى الإكمال" ، ولم يتم تكوين الإشارات وإيقاف المهمة وعداد الجدولة (نظرًا لأن هذا المزيج من الوظائف سينتج عنه غياب هياكل المهام هذه في ذاكرة الوصول العشوائي ، وبالتالي ، لن يتم إجراء التهيئة).
- يتم استدعاء NUSE_Init_Partition_Pool () لتهيئة كل كائن تجمع التقسيم. يتم تخطي هذه المكالمات إذا لم يتم تكوين تجمعات القسم.
- يتم استدعاء NUSE_Init_Mailbox () لتهيئة كل كائن صندوق البريد. يتم تخطي هذه المكالمات إذا لم يكن هناك صناديق بريد مهيأة.
- يتم استدعاء NUSE_Init_Queue () لتهيئة كل كائن قائمة انتظار. يتم تخطي هذه المكالمات إذا لم يتم تكوين قوائم انتظار.
- يتم استدعاء NUSE_Init_Pipe () لتهيئة كل كائن قناة. يتم تخطي هذه المكالمات إذا لم يتم تكوين قنوات.
- يتم استدعاء NUSE_Init_Semaphore () لتهيئة كل كائن إشارة. يتم تخطي هذه المكالمات إذا لم يتم تكوين أعمدة إشارة.
- يتم استدعاء NUSE_Init_Event_Group () لتهيئة كل كائن مجموعة حدث. يتم تخطي هذه المكالمات إذا لم تكن هناك مجموعات أحداث مكوّنة.
- يتم استدعاء NUSE_Init_Timer () لتهيئة كل كائن مؤقت. يتم تخطي هذه المكالمات إذا لم يتم تكوين أجهزة ضبط الوقت.
مهمة التهيئة
التالي هو الرمز الكامل للدالة NUSE_Init_Task (): void NUSE_Init_Task(NUSE_TASK task) { #if NUSE_SCHEDULER_TYPE != NUSE_RUN_TO_COMPLETION_SCHEDULER NUSE_Task_Context[task][15] = /* SR */ NUSE_STATUS_REGISTER; NUSE_Task_Context[task][16] = /* PC */ NUSE_Task_Start_Address[task]; NUSE_Task_Context[task][17] = /* SP */ (U32 *)NUSE_Task_Stack_Base[task] + NUSE_Task_Stack_Size[task]; #endif #if NUSE_SIGNAL_SUPPORT || NUSE_INCLUDE_EVERYTHING NUSE_Task_Signal_Flags[task] = 0; #endif #if NUSE_TASK_SLEEP || NUSE_INCLUDE_EVERYTHING NUSE_Task_Timeout_Counter[task] = 0; #endif #if NUSE_SUSPEND_ENABLE || NUSE_INCLUDE_EVERYTHING #if NUSE_INITIAL_TASK_STATE_SUPPORT || NUSE_INCLUDE_EVERYTHING NUSE_Task_Status[task] = NUSE_Task_Initial_State[task]; #else NUSE_Task_Status[task] = NUSE_READY; #endif #endif #if NUSE_SCHEDULE_COUNT_SUPPORT || NUSE_INCLUDE_EVERYTHING NUSE_Task_Schedule_Count[task] = 0; #endif }
إذا لم يتم تكوين جدولة "التشغيل إلى الإكمال" ، تتم تهيئة كتلة السياق
لمهمة NUSE_Task_Context [مهمة] [] . معظم العناصر لا يتم تعيين قيم لها ، لأنها تمثل سجلات الجهاز الشائعة ، والتي يجب أن يكون لها قيمة وسيطة عند بدء مهمة. في المثال (Freescale ColdFire) لتطبيق Nucleus SE (ولكن بالنسبة للمعالجات الأخرى ستكون الآلية مماثلة) يتم تعيين الإدخالات الثلاثة الأخيرة بشكل صريح:
- يحتوي NUSE_Task_Context [task] [15] على سجل الحالة ( SR ، سجل الحالة) ولديه قيمة التوجيه #define NUSE_STATUS_REGISTER .
- يحتوي NUSE_Task_Context [task] [16] على عداد البرنامج ( PC ، عداد البرنامج) ويحتوي على قيمة عنوان نقطة إدخال رمز المهمة: NUSE_Task_Start_Address [task] .
- NUSE_Task_Context [مهمة] [17] يحتوي على مؤشر المكدس ( SP ، مؤشر مكدس) ويتم تهيئته بالقيمة المحسوبة كمجموع عنوان مكدس المهام ( NUSE_Task_Stack_Base [مهمة] ) وحجم مكدس المهام ( NUSE_Task_Stack_Size [المهمة] ).
في حالة تنشيط دعم الإشارة ، يتم تعيين علامات إشارة المهمة (
NUSE_Task_Signal_Flags [مهمة] ) على صفر.
إذا تم تنشيط تعليق المهمة (أي ، استدعاء خدمة API
NUSE_Task_Sleep () ) ،
فسيتم تعيين عداد مهلة المهمة (
NUSE_Task_Timeout_Counter [مهمة] ) إلى صفر.
في حالة تنشيط حالة
التوقف المرحلي للمهام ، تتم تهيئة حالة المهمة (
NUSE_Task_Status [مهمة] ). يتم تعيين هذه القيمة الأولية بواسطة المستخدم (في
NUSE_Task_Initial_State [مهمة] ) إذا تم تنشيط دعم الحالة الأولية للمهمة. خلاف ذلك ، يتم تعيين الحالة إلى
NUSE_READY .
إذا تم تنشيط عداد التخطيط ، يتم تعيين عداد المهام (
NUSE_Task_Schedule_Count [مهمة] ) إلى صفر.
تهيئة تقسيم الأقسام
التالي هو الرمز الكامل
للدالة NUSE_Init_Partition_Pool () :
void NUSE_Init_Partition_Pool(NUSE_PARTITION_POOL pool) { NUSE_Partition_Pool_Partition_Used[pool] = 0; #if NUSE_BLOCKING_ENABLE NUSE_Partition_Pool_Blocking_Count[pool] = 0; #endif }
يتم تعيين عداد تجمع قسم "المستخدمة" (
NUSE_Partition_Pool__Partition_Used [pool] ) إلى صفر.
إذا تم تنشيط تأمين المهام ،
فسيتم تعيين عداد المهام المحظورة
لتجمعات الأقسام (
NUSE_Partition_Pool_Blocking_Count [pool]) على صفر.
تهيئة صناديق البريد
يوجد أدناه رمز
NUSE_Init_Mailbox () الكامل:
void NUSE_Init_Mailbox(NUSE_MAILBOX mailbox) { NUSE_Mailbox_Data[mailbox] = 0; NUSE_Mailbox_Status[mailbox] = 0; #if NUSE_BLOCKING_ENABLE NUSE_Mailbox_Blocking_Count[mailbox] = 0; #endif }
يتم تعيين مخزن بيانات صندوق البريد (
NUSE_Mailbox_Data [صندوق البريد] ) إلى صفر ،
وتصبح الحالة (
NUSE_Mailbox_Status [صندوق البريد] ) "غير مستخدمة" (أي ، صفر).
في حالة تنشيط تأمين المهام ، يتم تعيين عداد مهام صندوق البريد المحظورة (
NUSE_Mailbox_Blocking_Count [صندوق البريد] ) على صفر.
قائمة انتظار التهيئة
التالي هو الرمز الكامل
للدالة NUSE_Init_Queue () :
void NUSE_Init_Queue(NUSE_QUEUE queue) { NUSE_Queue_Head[queue] = 0; NUSE_Queue_Tail[queue] = 0; NUSE_Queue_Items[queue] = 0; #if NUSE_BLOCKING_ENABLE NUSE_Queue_Blocking_Count[queue] = 0; #endif }
المؤشرات إلى بداية ونهاية قائمة الانتظار (في الواقع ، هذه هي الفهارس
NUSE_Queue_Head [قائمة الانتظار ] و
NUSE_Queue_Tail [قائمة الانتظار] ) القيم المعينة التي تشير إلى بداية منطقة البيانات من قوائم الانتظار (أي أنها تأخذ قيمة الصفر). يتم تعيين العداد في قائمة الانتظار (
NUSE_Queue_Items [قائمة انتظار] ) أيضًا إلى صفر.
في حالة تنشيط تأمين المهام ، يتم تعيين عداد مهام قائمة الانتظار المحظورة (
NUSE_Queue_Blocking_Count [قائمة انتظار] ) على صفر.
تهيئة القناة
التالي هو الرمز الكامل
للدالة NUSE_Init_Pipe () :
void NUSE_Init_Pipe(NUSE_PIPE pipe) { NUSE_Pipe_Head[pipe] = 0; NUSE_Pipe_Tail[pipe] = 0; NUSE_Pipe_Items[pipe] = 0; #if NUSE_BLOCKING_ENABLE NUSE_Pipe_Blocking_Count[pipe] = 0; #endif }
المؤشرات إلى بداية ونهاية القناة (في الواقع ، هذه هي الفهارس - يتم
تعيين NUSE_Pipe_Head [توجيه الإخراج] و
NUSE_Pipe_Tail [توجيه الإخراج] ) قيمة تشير إلى بداية منطقة بيانات القناة (أي أنها تأخذ قيمة فارغة). يتم أيضًا تعيين
عداد القناة (
NUSE_Pipe_Items [توجيه الإخراج] ) إلى صفر.
في حالة تنشيط تأمين المهام ، يتم ضبط عداد المهام المحظورة في القناة (
NUSE_Pipe_Blocking_Count [توجيه الإخراج] ) على صفر.
التهيئة إشارة
التالي هو الرمز الكامل
للدالة NUSE_Init_Semaphore () :
void NUSE_Init_Semaphore(NUSE_SEMAPHORE semaphore) { NUSE_Semaphore_Counter[semaphore] = NUSE_Semaphore_Initial_Value[semaphore]; #if NUSE_BLOCKING_ENABLE NUSE_Semaphore_Blocking_Count[semaphore] = 0; #endif }
تتم تهيئة عداد الإشارة (
NUSE_Semaphore_Counter [إشارة] ) بالقيمة التي حددها المستخدم (
NUSE_Semaphore_Initial_Value [semaphore] ).
في حالة تنشيط تأمين المهام ، يتم تعيين عداد
مهام الإشارة المقفلة (
NUSE_Semaphore_Blocking_Count [semaphore] ) على صفر.
تهيئة مجموعات الأحداث
التالي هو الرمز الكامل
للدالة NUSE_Init_Event_Group () :
void NUSE_Init_Event_Group(NUSE_EVENT_GROUP group) { NUSE_Event_Group_Data[group] = 0; #if NUSE_BLOCKING_ENABLE NUSE_Event_Group_Blocking_Count[group] = 0; #endif }
تتم إعادة تعيين أعلام مجموعة الأحداث ، أي
NUSE_Event_Group_Data [مجموعة] يتم تعيين قيمة فارغة.
إذا تم تنشيط قفل المهام ، يتم تعيين عداد المهام المحظورة لمجموعة علامة الحدث (
NUSE_Event_Group_Blocking_Count [group] ) على صفر.
تهيئة الموقت
يوجد أدناه الرمز الكامل لـ
NUSE_Init_Timer () ؛
void NUSE_Init_Timer(NUSE_TIMER timer) { NUSE_Timer_Status[timer] = FALSE; NUSE_Timer_Value[timer] = NUSE_Timer_Initial_Time[timer]; NUSE_Timer_Expirations_Counter[timer] = 0; }
تم تعيين حالة المؤقت (
NUSE_Timer_Status [timer] ) على "غير مستخدم" ، أي
FALSE.تتم تهيئة قيمة العد التنازلي (
NUSE_Timer_Value [timer ]) من خلال القيمة التي حددها المستخدم (
NUSE_Timer_Initial_Time [timer] ).
يتم تعيين عداد الإكمال (
NUSE_Timer_Expirations_Counter [timer] ) على صفر.
تهيئة رمز التطبيق
بعد تهيئة بنيات بيانات Nucleus SE ، يصبح من الممكن تنفيذ التعليمات البرمجية المسؤولة عن تهيئة التطبيق قبل بدء المهمة. قد تكون هذه الميزة مفيدة للمهام التالية:
- تهيئة بنيات بيانات التطبيق. من السهل فهم وتصحيح ملء هياكل البيانات مقارنة بالتهيئة التلقائية للمتغيرات الثابتة.
- تعيين كائنات kernel. نظرًا لأن جميع كائنات kernel يتم إنشاؤها بشكل ثابت في مرحلة البناء ويتم تحديدها باستخدام قيم الفهرس ، فقد يكون من المفيد تعيين "مالك" أو تحديد استخدام هذه الكائنات. يمكن القيام بذلك باستخدام الأمر التوجيهي #define ، ومع ذلك ، إذا كان هناك العديد من حالات المهام ، فمن الأفضل تعيين فهارس الكائنات من خلال صفائف عمومية (مفهرسة بواسطة معرف المهمة).
- تهيئة الجهاز. يمكن أن يكون هذا مفيدًا للتثبيت الأولي للأجهزة الطرفية.
من الواضح أنه يمكن تحقيق العديد من هذه الأهداف قبل تهيئة Nucleus SE ، ولكن الميزة في موقع رمز التطبيق هنا هي أنه يمكنك الآن استخدام خدمات kernel (مكالمات API). على سبيل المثال ، قد يتم تعبئة قائمة انتظار أو صندوق بريد مسبقًا بالبيانات التي ستحتاج إلى معالجتها عند بدء المهمة.
مكالمات واجهة برمجة التطبيقات (API) لها قيود: جميع الإجراءات التي تؤدي عادةً إلى تنشيط برنامج الجدولة محظورة (على سبيل المثال ، مهام الإيقاف المؤقت / الحظر). تم تعيين المتغير العام
NUSE_Task_State على
NUSE_STARTUP_CONTEXT للإشارة إلى هذا القيد.
تشغيل المجدول
بعد اكتمال التهيئة ، يبقى فقط تشغيل المجدول لبدء تنفيذ رمز التطبيق - المهام. تم وصف تكوين المجدول وعمل أنواع مختلفة من المجدولين بالتفصيل في إحدى المقالات السابقة (
# 9 ) ، لذلك فقط ملخص موجز مطلوب هنا.
تسلسل الخطوات الأساسية كما يلي:
- تعيين المتغير العمومي NUSE_Task_State إلى NUSE_TASK_CONTEXT .
- حدد فهرس المهمة الأولى للتشغيل. في حالة تنشيط دعم المهمة الأولية ، يتم إجراء البحث عن المهمة الأولى ، وإلا ، يتم استخدام قيمة صفرية.
- يسمى المجدول - NUSE_Scheduler () .
ما يحدث بالضبط في الخطوة الأخيرة يعتمد على تحديد المجدول. عند استخدام جدولة التشغيل إلى الإكمال ، تبدأ دورة التخطيط ويتم استدعاء المهام بالتسلسل. عند استخدام برامج جدولة أخرى ، يتم تحميل سياق المهمة الأولى ويتم نقل التحكم إلى المهمة.
ستناقش المقالة التالية التشخيص وفحص الأخطاء.
نبذة عن الكاتب: يعمل Colin Walls في صناعة الإلكترونيات لأكثر من ثلاثين عامًا ، حيث خصص معظم وقته للبرامج الثابتة. وهو الآن مهندس برامج ثابتة في Mentor Embedded (قسم من Mentor Graphics). يتحدث Colin Walls غالبًا في المؤتمرات والندوات ، ومؤلف العديد من المقالات الفنية وكتابين عن البرامج الثابتة. يعيش في المملكة المتحدة.
مدونة كولن المهنية ، البريد الإلكتروني: colin_walls@mentor.com.