آموزش RTOS با STM32, RTOS, توصیه شده

کامپایلر GCC چیست؟ + بررسی نحوه عملکرد | آموزش RTOS با STM32 قسمت 1

کامپایلر GCC

این اولین قسمت از فصل اول آموزش FreeRTOS در STM32 نوشتن یک سیستم‌عامل بلادرنگ برای میکروکنترلرهاست. در این قسمت به بررسی کامپایلر GCC می پردازیم. همان‌طور که قبلاً گفتم فصل اول درباره پیش‌نیازهای نوشتن یک سیستم‌عامل بلادرنگ است. تمامی کسانی می‌خواهند به‌عنوان توسعه‌دهنده در حوزه میکروکنترلرها فعالیت کنند، باید با یک سری نرم‌افزارها و ابزارها و یک سری مفاهیم مرتبط با میکروکنترلرها آشنایی داشته باشند.

پس داشتن شناخت عمیق در هر حوزه‌ای برای یک توسعه‌دهنده آن حوزه از نون شب واجب تره. در این قسمت میخوام دررابطه‌با ابزاری صحبت کنم که اگه ساخته نمیشد ما باید با صفر و یک برنامه می‌نوشتیم. درست حدس زدید کامپایلرها . کامپایلر مفهومی است که امروزه به لطف توسعه‌دهندگان سیار گسترده و بزرگ شده؛ اما در این قسمت ما با کامپایلرها و تفاوتشان با IDEها آشنا میشیم و به سؤالاتی از قبیل اینکه اصلاً IDE چی هست؟ کامپایلر چیست؟ از کدوم کامپایلر استفاده کنیم بهتره؟ چطور یک کامپایلر نصب کنیم؟ حالا که نصب کردیم چطور ازش استفاده کنیم؟ و سؤالاتی ازاین‌قبیل پاسخ میدیم. این سؤالات اولین و ابتدایی‌ترین سؤالاتی هستند که با ورود به دنیای برنامه‌نویسی برامون ایجاد میشه که قراره باحوصله به اونها پاسخ بدیم. پس با سیسوگ همراه باشید.

کامپایلر چیست؟؟؟

کامپایلر در واقع یک ابزار نرم‌افزاری است که برنامه ما رو به فایلی تبدیل می‌کند که توسط کامپیوتر،Socها یا MCUها قابل‌اجرا باشد و از اونجا که ما با میکروکنترلر کار می‌کنیم، اغلب منظورمون از برنامه، کدی است که به زبان C یا ++C نوشته شده باشد.پس کامپایلر اون نرم‌افزاری نیس که بتونی کدت رو داخلش ادیت کنی.

خوب کامپایلرهای متفاوتی برای تبدیل کد C یا ++C برای سیستم‌عامل وجود دارد.مانند:

  • کامپایلر GCC که مخفف GNU Compiler Collection هست و توسط ریچارد استالمن توسعه داده شده و یک کامپایلر تمام‌عیار با همه ابزارهایی است که یک برنامه‌نویس به آن نیاز دارد.
  • کامپایلر Clang که بر مبنای پروژه LLVM توسعه داده شده و بسیار شبیه به کامپایلر GCC است.
  • کامپایلر MSVS که مخفف MicroSoft Visual Studio هست و توسط مایکروسافت توسعه داده شده است.

کامپایلر های C و C++

ما در این آموزش از کامپایلر GCC استفاده می‌کنیم.حالا چرا؟

زیرا کامپایلر GCC یک کامپایلر Open Platform هست. یعنی این کامپایلر قابلیتش رو داره که برنامه شما رو برای هر CPU کامپایل کنه.برای مثال اگه شما برنامه‌ای نوشته باشید و بخواهید توسط کامپایلر GCC(GCC compiler) کامپایل برنامتون رو انجام بدید، میتونید اون برنامه رو برای کامپیوتری که مثلاً سی پی یوش اینتل هست کامپایل کنید، یا برای رزبری پای که سی پی یوش آرم هست یا حتی میکروکنترلری که سی پی یوش ARM Cortex M3 هست.

شاید برای شما مفید باشد: آموزش رزبری پای از 0 تا 100

همچین دلایل مهمتری از این دلیلی که من گفتم هم وجود دارد که توصیه میکنم قبل از هر اقدامی این مطالب “کامپایلر Codevisionavr در مقابل کامپایلر GCC و مقایسه تخصصی آنها” و “مقایسه تخصصی کامپایلر کیل و GCC“مطالعه کنید.

خب حالا که یکم با GCC آشنا شدید همین اول بگم که نگید نگفتم :(کلاً کامپایلر GCC در سیستم‌عامل‌هایی که کرنل اونها بر مبنای UNIX نوشته شده باشد عملکرد بهتری دارد مثل لینوکس یا مک) اما کلاً عملکردش در ویندوز کندتره.

IDE چیست؟؟؟

بعضی نرم‌افزارها هستند که هم میشه داخلشون برنامه مون رو ویرایش کنیم و هم میشه با زدن یک کلید برنامه رو کامپایل کرد و هم بعد از اون میشه برنامه رو دیباگ هم کرد، مثل Arduino IDE یا STM32CubeIDE یا Keil . به این نرم‌افزارها IDE گفته می‌شود.IDE ها یک محیط یکپارچه از ابزارهای ادیتور کد، کامپایلر و دیباگر رو برای ما فراهم می‌کنند تا بتونیم راحت‌تر و بهتر برنامه‌نویسی کنیم.

خوب حالا قراره بریم سراغ عملیاتی‌کردن حرفامون و کامپایلر gcc رو نصب کنیم و با اون کار کنیم تا پیچ‌وخم کار تا حدودی دستمون بیاد اما قبلش باید بگم که کلاً از این به بعد ماجرا خبری از یک محیط گرافیکی قشنگ که حضرات مرحمت بفرمایند و کلید کامپایل رو بزنند و کامپایل انجام بشه نیس و ما قراره همه کارهامون رو با ترمینال لینوکس انجام بدیم. چون ترمینال دسترسی‌های بیش‌تری برای کامپایل به ما میده و بهتر میتونیم با قابلیت‌های کامپایلر آشنا بشیم. بزن بریم…

چطور کامپایلر GCC رو نصب کنیم؟

کامپایلر gcc در بعضی توزیع‌های لینوکس مثل Mint به‌صورت خودکار نصب هست؛ اما برای بقیه توزیع‌ها مثل Ubuntu باید از یه یار قدیمی و همیشگی به نام دستور apt استفاده کنیم و کامپایلر gcc رو از ریپوزیتوری دریافت و نصب کنیم. به همین سادگی.

حالا میخوایم یه کد خیلی ساده به زبان C رو توسط این کامپایلر ، کامپایل کنیم که کد به‌صورت زیر هست:

خوب حالا میخوایم کدمون رو کامپایل کنیم یا به عبارت دیگه به یک فایل اجرایی تبدیلش کنیم، پس از دستور gcc استفاده می‌کنیم که ساختارش به‌صورت زیر هست:

اگه به‌صورت بالا از دستور gcc استفاده کنیم نام فایل خروجی توسط کامپایلر به‌صورت پیش‌فرض تعیین می‌شود. حال برای افزودن نام فایل خروجی به‌صورت زیر عمل می‌کنیم:

دقت کنید نامی که پس از سوئیچ o- می‌آید،نام فایل خروجی است. خوب حالا که فایل خروجی ساخته شد، برای اجرای آن کافی است که نام فایل خروجی را در ترمینال فراخوانی کنیم:

نکتهیک فایل اجرایی در لینوکس مانند یک فایل اجرایی در ویندوز نیس که حتماً نیاز به پسوند داشته باشد. ممکنه یک فایل اجرایی در لینوکس هیچ پسوندی نداشته باشد و تنها این مهمه که اون فایل دسترسی اجراشدن داشته باشد؛ مانند فایل زیر:

حالا می‌خواهیم یکم کار روسخت تر کنیم و یک هدر فایل به برنامه اضافه کنیم که در این صورت برناممون به‌صورت زیر می‌شود:

همونطور که می‌بینید ما فایل کتابخانه metech.h رو که در داخل دایرکتوری برناممون بود، به برنامه اضافه کردیم و از تابع print آن استفاده کردیم.حالا برنامه رو مثل قبل کامپایل می‌کنیم و می‌بینیم که اجرا می‌شود .

حالا سؤال اینجاست که چرا کتابخانه metech.h رو داخل کوتیشن قرار دادیم و کتابخانه stdio.h رو داخل براکت زاویه‌دار؟ تفاوتشون چیه؟

در برنامه نویسی برای الحاق فایل های کتابخانه به برنامه با استفاده از دستور include# دو حالت وجود دارد که این دو حالت بر عملکرد کامپایلر در پیداکردن فایل کتابخانه تاثیرگذار است:

در برنامه‌نویسی برای الحاق فایل‌های کتابخانه به برنامه با استفاده از دستور include# دو حالت وجود دارد که این دو حالت بر عملکرد کامپایلر در پیداکردن فایل کتابخانه تأثیرگذار است:

1.Quotation Mode : در این حالت برای الحاق کتابخانه به برنامه از ” ” استفاده می‌کنیم و برای کامپایل اگر:

  • فایل کتابخانه در دایرکتوری سورس کد قرار داشته باشد، از دستور gcc به‌صورت زیر استفاده می‌کنیم:
  • فایل کتابخانه در دایرکتوری دیگری قرار داشته باشد، از دستور gcc به‌صورت زیر استفاده می‌کنیم:

در این حالت در واقع کامپایلر برای یافتن فایل کتابخانه به روش زیر عمل می‌کند:

روش یافتن کتابخانه

2.Angle Bracket Mode : در این حالت برای الحاق کتابخانه به برنامه از <> استفاده می‌کنیم و از این روش بیشتر برای الحاق کتابخانه‌های استاندارد سیستم مانند stdio.h استفاده می‌شود.حالا اگه بخواهیم کتابخانه‌ای که خودمون نوشتیم رو به این روش به سورس کد اضافه کنیم، از دستور gcc به‌صورت زیر استفاده می‌کنیم:

در این حالت در واقع کامپایلر برای یافتن فایل کتابخانه به روش زیر عمل می‌کند:

روش کامپایلر برای یافتن فایل کتابخانه

در واقع در این حالت تنها در بین کتابخانه‌های استاندارد سیستم جستجو انجام می‌شود و به همین دلیل است که بیشتر کتابخانه‌های از پیش تعریف شده را به این روش به برنامه الحاق می‌کنند.حالا محل این کتابخانه‌های استاندارد کجاست و شامل چه کتابخانه‌هایی می‌شود؟

محل کتابخانه ها

کتابخانه‌های استاندارد در واقع تعدادی از کتابخانه‌های زبان C هستند که توسط برنامه‌نویسان توسعه داده شده‌اند و به آنها کتابخانه‌های از پیش تعریف شده نیز گفته می‌شود.

حالا که یک مثال ساده زدیم و کمی با کامپایلر GCC در محیط ترمینال آشنا شدیم، میخوایم جزئی‌تر فرایند کامپایل برنامه را دنبال کنیم. پس با ما همراه باشید.

 

مراحل کامپایل یک برنامه توسط کامپایلر GCC

1.پیش‌پردازش (PreProcessing) 

این مرحله اولین مرحله از فرایند کامپایل یک برنامه است. در این مرحله دستورات پیش‌پردازنده با مقادیر واقعی‌شان جایگزین می‌شوند.حالا این دستورات پیش‌پردازنده چی هست اصلاً؟ دستورات پیش‌پردازنده به کلیه دستوراتی در زبان C گفته می‌شود که با علامت # شروع می‌شوند و در واقع کارشان جایگزینی یک مقدار یا فایل با یک نام است که در مرحله پیش‌پردازش این اسامی با مقادیر واقعی‌شان جایگزین می‌شوند. برای مثال دستورات پیش‌پردازنده مثال قبل:

حالا می‌خواهیم یک مثال ساده بزنیم و فایل خروجی پیش‌پردازنده رو از کامپایلر استخراج کنیم تا منظور حرفم رو بهتر متوجه بشید.

خب پس از کامپایل خواهیم دید که برنامه به‌درستی اجرا می‌شود و اما برای استخراج فایل پیش‌پردازش شده باید از دستور gcc به‌صورت زیر استفاده کنیم:

مانند:

معمولاً فایل‌های پیش‌پردازش شده دارای پسوند “i.” هستند.

حال اگر پس از اجرای دستور بالا فایل خروجی پیش‌پردازش شده را به‌صورت فایل متنی باز کنیم خواهیم دید که :

توجه:قبل از توضیح کد باید بگم در فایل‌های خروجی پیش‌پردازنده برای کامنت‌گذاری از علامت # همراه با یک عدد استفاده می‌شود. مانند:

توجه:قبل از توضیح کد باید بگم در فایل های خروجی پیش پردازنده برای کامنت گذاری از علامت # همراه با یک عدد استفاده میشود.مانند:

خب حالا بریم سراغ توضیح فایل:

در این مثال همونطور که میبینید از خط 512 سورس کد شروع میشود.همانطور که قبلا گفتم به جای دستورات پیش پردازنده محتوای آنها جایگذاری شده است.مثلا به جای:

محتوای آن یعنی:

جای‌گذاری شده است. حالا شاید بپرسید پس کتابخانه stdio.h چی شد؟ باید بگم که تا قبل از خط 512 در برنامه تمامی کدهایی که می‌بینید محتوای کتابخانه stdio.h هستند.

یا مثلاً به‌جای ماکروی METECH که در توابع کتابخانه استفاده شده بود، مقدار اختصاص‌داده‌شده به آن یعنی عدد 10 قرار گرفته است. یعنی این قسمت از کد:

به این قسمت:

تبدیل شده است.

نتیجه‌گیری

  1. دستورات پیش‌پردازنده با محتوایشان جایگزین شده‌اند و خود این دستورات یا کامنت شده یا حذف شده‌اند.
  2. در این مرحله، عملکرد کامپایلر به‌صورت بازگشتی است. یعنی ابتدا کتابخانه‌هایی که سورس کد به آنها نیاز دارد را می‌یابد، سپس کتابخانه‌هایی که کتابخانه‌های سورس کد به آنها نیاز دارد را می‌یابد و این کار را تا جایی ادامه می‌دهد که کتابخانه‌ای موردنیاز کتابخانه دیگر نباشد. حالا از همان نقطه شروع به جایگزینی کتابخانه‌ها با محتوای آنها در کد می‌کند و می‌رسیم به کدی که در حال حاضر می‌بینید.

حالا که با مرحله پیش‌پردازش و فایل خروجی آن آشنا شدید، جا داره بگم که شما برای مقداردهی به ثابت‌های کدتان میتونید به‌جای اینکه در برنامه مقداردهی کنید هنگام کامپایل این کار رو انجام بدید تا در موقع پیش‌پردازش مقدار موردنظر شما در برنامه جای‌گذاری شود. مثلاً فرض کنید کد زیر رو دارید:

خوب همونطور که در این کد می‌بینید ثابت A رو تعریف نکردیم و قطعاً اگه الان کد رو کامپایل کنیم با ارور مواجه میشیم، مگر اینکه از دستور gcc به‌صورت زیر استفاده کنیم:

مانند:

خوب حالا اگه کد رو کامپایل کنید مشکلی پیش نمی‌اید. چرا؟ چون در مرحله پیش‌پردازش مقدار 10 به‌جای ثابت A قرار داده می‌شود.

 

2. کامپایل (Compilling) 

در این مرحله کد پیش‌پردازش شده کامپایل می‌شود. یعنی کد زبان C به یک کد اسمبلی تبدیل می‌شود. زبان اسمبلی یکی از زبان‌های سطح پایین محسوب می‌شود که در آن ما معمولاً مستقیماً با رجیسترهای پردازنده درگیر هستیم و تمامی عملیات های ریاضی و منطقی خود را از طریق کار بر روی رجیسترها انجام می‌دهیم. حال برای دیدن کد اسمبلی خروجی می‌توان از دستور gcc به‌صورت زیر استفاده کرد:

مانند:

معمولاً فایل‌های تبدیل شده به فایل اسمبلی دارای پسوند “s.” هستند.

3. تبدیل کد اسمبلی به زبان ماشین(Creating Object File) 

در این مرحله کد اسمبلی به کد زبان ماشین(Object file) تبدیل می‌شود.Object file ها در واقع همان کدهای صفر و یکی هستند که توسط پردازنده قابل‌فهم و اجراست. پس در واقع Object file ها دستورالعمل اجرایی پردازنده هستند. حال برای دریافت کد زبان ماشین از دستور gcc به‌صورت زیر استفاده می‌کنیم:

مانند:

معمولاً Object File ها دارای پسوند “o.” هستند.

نکته: Object file ها را نمی‌توان به‌صورت یک فایل متنی باز کرد و محتوای آنها را دید چرا که یک فایل اجرایی هستند و یک فایل متنی نیستند؛ بنابراین اگر قصد دارید محتوای Object file را مشاهده کنید می‌توانید از این ابزار :

به این صورت استفاده کنید:

4. لینک کردن(Linking)

همان‌طور که گفتیم Object file بسیار شبیه به فایل اجرایی می‌باشد و تنها نکته‌ای که در آن وجود دارد این است که توابعی مثل printf که ما در برنامه استفاده می‌کنیم متأسفانه توسط Object File سورس کد قابل‌اجرا نیس، زیرا محتوای آن توابع نه در فایل کتابخانه‌ای قرار دارد و نه در خود سورس کد وجود دارد. پس باید این Object file به Object file هایی که بدنه این توابع در آنها قرار دارد متصل شود تا فایل نهایی قابلیت اجرا داشته باشد. به این کار Linking گفته می‌شود.

لینک کردن

جمع بندی

خلاصه مراحل کامپایل یک سورس کد:

خلاصه مراحل کامپایل یک سورس

خلاصه سوئیچ‌های دستور gcc برای گرفتن فایل‌های خروجی مختلف(فایل پیش‌پردازش شده، فایل اسمبلی،Object file)

سوئیچ‌های دستور gcc

چگونه کد خود را Close Source کنیم؟

Close Source کردن یک کد

شاید براتون سؤال پیش اومده باشه که در بلوک دیاگرام مراحل کامپایل این کتابخانه‌های استاتیک چی هستند؟

گاهی اوقات پیش میاد که ما قصد داریم کدمون رو به برنامه‌نویس‌های دیگه بدیم تا اونها هم به تونن از کتابخانه‌های ما استفاده کنن اما نمی‌خوابم اونها کتابخانه‌های ما رو تغییر بدن؛ بنابراین لازمه که اون کتابخانه‌ها را Close source کنیم.یکی از راه‌های Close source کردن کد،تبدیل آن به یک Object file است که عملاً قابلیت تغییر ندارد. حالا میخوایم یک مثال بزنیم تا مسئله واضح‌تر بشه: فرض کنید ما کد زیر را داریم:

همونطور که می‌بینید ما یک سورس کد main داریم که در آن از توابع ضرب و تقسیم استفاده شده که این جزئی از توابع استاندارد سیستمی نیستند و خود ما اونها رو نوشتیم و در یک هدر فایل اونها رو معرفی کردیم و بدنه توابع را نیز در فایل‌های کد جداگانه تعریف کردیم. حال برای بستن فایل کد کتابخانه‌ها می‌توانیم با دستور gcc آنها را به Object file تبدیل کنیم و اما حالا

چطور کامپایل را انجام دهیم؟

به‌صورت زیر:

این‌طوری به‌راحتی کدمون کامپایل میشه. حالا

اگه تعداد کتابخانه‌ها یا توابع زیاد بود چیکار کنیم؟

دوراه وجود داره: اول اینکه همهٔ توابع رو در یک هدر فایل تعریف کنیم و برای آن هدر فایل یک فایل کد ایجاد کرده و بدنه توابع را هم در آن تعریف کنیم و تا حدودی مشکلمون حل میشه اما من روش نوشتن توابع در فایل کدهای جداگانه را ترجیح میدم، چرا که توسعه و عیب‌یابی برنامه ساده‌تر میشه. حالا برای اینکه بتونیم مشکل اصلی رو حل کنیم، یک راهش آرشیو کردن فایل کدهای کتابخانه است که برای این کار می‌توانید از دستور زیر:

به صورت زیر استفاده کنیم:

معمولاً کتابخانه‌های آرشیو شده دارای پسوند “a.” هستند.

همچنین می‌توانید محتوای فایل آرشیو را با دستور زیر ببینید:

حالا میتونیم با خیال راحت کدمون را کامپایل کنیم:

توجه:فقط باید توجه داشته باشید که در این حالت ترتیبی که در دستور بالا رعایت کردم حتماً باید رعایت بشه.چرا؟زیرا در این حالت کامپایلر ابتدا باید سورس کد را خوانده و نیازهای آن را تشخیص دهد و در مرحله بعد از داخل فایل آرشیو کدهای موردنیاز را استخراج کند. حال اگر ترتیب بالا را رعایت نکنیم، ابتدا کامپایلر با فایل آرشیو مواجه می‌شود و چون توابع ناشناخته را هنوز تشخیص نداده استخراجی صورت نمی‌گیرد و در مرحله بعد با ارور مواجه می‌شویم. (البته دقت کنید فقط در این روش کامپایلر این‌گونه عمل می‌کند).

گاهی اوقات هم پیش میاد که کتابخانه ما در دایرکتوری دیگری قرار دارد که در این صورت برای شناساندن فایل آرشیو به کامپایلر باید ابتدا:

1.نام فایل را استاندارد کنیم:

مانند:

2. از دستور gcc به‌صورت زیر استفاده کنیم:

  توجه: فقط دقت داشته باشید که نام فایل آرشیو را باید بدون پیشوند lib و پسوند a. قرار دهید.

مانند

خب حالا که تا اینجا من رو همراهی کردید، باید بدونید که این فایل آرشیو همان کتابخانه استاتیک است.

(Static Library Vs Shared Library)کتابخانه استاتیک یا کتابخانه مشترک 

با کتابخانه‌های استاتیک که آشنا شدید و اما کتابخانه‌های مشترک:

این کتابخانه‌ها، کتابخانه‌هایی هستند که در فرایند کامپایل شناسایی می‌شوند؛ اما پیش‌پردازش نمی‌شوند و بدیهی است که کامپایل هم نمی‌شوند. پس کِی استفاده میشن؟

باید گفت هم‌زمان با اجرای برنامه‌هایی که در اونها از کتابخانه‌های مشترک استفاده می‌شود، برنامه‌ای به نام Dynamic Loader نیز اجرا می‌شود که وظیفه دارد هر کجا از برنامه که یکی از توابع کتابخانه‌های مشترک فراخوانی شده، آن کتابخانه را در داخل Ram قرار داده و به کپی برنامه در Ram متصل کند(Linking) تا برنامه اجرا شود.

خب ازآنجاکه کتابخانه‌های مشترک در برنامه‌نویسی میکروکنترلرها خیلی کاربرد ندارد، به تعریف آن بسنده می‌کنیم و اگر علاقه‌مند به دانستن بیشتر در مورد این کتابخانه‌ها هستید، می‌توانید به علامه دهر حضرت google مراجعه کرده و  مطالب مفید بسیاری در این باره بیابید.

انتشار مطالب با ذکر نام و آدرس وب سایت سیسوگ، بلامانع است.

شما نیز میتوانید یکی از نویسندگان سیسوگ باشید.   همکاری با سیسوگ

5 دیدگاه در “کامپایلر GCC چیست؟ + بررسی نحوه عملکرد | آموزش RTOS با STM32 قسمت 1

  1. Avatar for کاوه ناصح کاوه ناصح گفت:

    فوق العاده بود.
    من حدود ۲۰ ساله که امبدد کار میکنم. این مطالب بقدری شیوا و رسا بیان شدن که در این ۲۰ سال من ندیدم. با این که به این مطالب واقف بودم ولی از خوندش سیر نمی شدم

  2. Avatar for جاوید جاوید گفت:

    عالی بود مهندس

  3. Avatar for احسان پورعلی احسان پورعلی گفت:

    خیلی آموزنده

  4. Avatar for سینا مختاری سینا مختاری گفت:

    آقا عالی

  5. Avatar for رامین رامین گفت:

    عالیییی .
    تشکر از این مطلب بسیار خوب

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *