رفع باگ Race Condition در بافر دایره‌ای UART | قسمت 26 آموزش Embedded C

embedded C 26
16 بازدید
۱۴۰۴-۰۶-۱۸
10 دقیقه
  • نویسنده: Alireza Abbasi
  • درباره نویسنده: ---

ارسال تابع

برای کار با حافظه بافر دایره‌ای، باید به ارسال تک‌تک کاراکترها با استفاده از نسخه دیگری از تابع myPutchar بازگردیم. در ادامه، کدی که وظیفه‌ی ارسال را بر عهده دارد، بررسی می‌کنیم:

توضیح خط به خط

  1. تا زمانی که فضای خالی وجود داشته باشد، منتظر بمان: این کد یک حلقه while است یعنی تا زمانی که تعداد کاراکترهای موجود در بافر (nCharacters) برابر با اندازه بافر (BUFFER_SIZE) باشد، اجرا می‌شود. به عبارتی، تا زمانی که بافر پر است، منتظر می‌مانیم.
  2. کاراکتر را درون بافر قرار بده: پس از اینکه فضای خالی در بافر پیدا شد، کاراکتر ch را در خانه‌ی بافر با اندیس putIndex قرار می‌دهیم.
  3. putIndex را که نشان‌دهنده‌ی خانه‌ی بعدی برای نوشتن کاراکتر است، افزایش می‌دهیم. اگر مقدار putIndex به اندازه بافر برسد، آن را به صفر بازنشانی می‌کنیم تا به ابتدای بافر برگردیم.
  4. تعداد کاراکترهای موجود در بافر را افزایش بده: با نوشتن کاراکتر جدید در بافر، تعداد کاراکترهای موجود (nCharacters) را یک واحد افزایش می‌دهیم.
  5. وقفه (interrupt) را فعال کن: در نهایت، وقفه‌ی ارسال (TXEIE) را برای USART فعال می‌کنیم. این وقفه زمانی رخ می‌دهد که بافر ارسال خالی شود و بتوان کاراکتر بعدی را ارسال کرد. اگر در حال ارسال باشیم، فعال‌سازی مجدد وقفه مشکلی ایجاد نمی‌کند.

روال وقفه

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

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

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

برنامه کامل

کد زیر برنامه کامل را نشان می دهد.

10.serial.buffer.bad/src/main.c

مشکل

هنگام اجرای برنامه، انتظار داریم خروجی زیر را مشاهده کنیم:

اما به جای آن، خروجی زیر را دریافت می‌کنیم:

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

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

باگ‌های زمانی (Timing bugs) بدنام‌اند که به‌سختی می‌توان آن‌ها را به‌صورت دستی و در لحظه ایجاد کرد، اما در این کد یکی از آن‌ها وجود دارد.

می‌دانیم که هر چه سیستم شلوغ‌تر باشد، احتمال وقوع خطا بیشتر می‌شود. به علاوه، سرنخ دیگری هم داریم: نتوانسته‌ایم خطا را در حین وقوع مشاهده کنیم، اما بعد از بررسی با دیباگر، متوجه شدیم که:

ساختار پیام‌ها و زمان‌بندی ارتباط در پروتکل KLine - قسمت سوم آموزش OBD II

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

دو راه برای رویارویی با این مشکل وجود دارد: نخست این‌که کد را ابزارگذاری (instrument) کنیم تا بفهمیم چه اتفاقی می‌افتد. دوم این‌که یک تحلیل بسیار دقیق روی تمام داده‌هایی که بین لایه بالایی و لایه پایینی به اشتراک گذاشته می‌شوند و کدی که این داده‌ها را تغییر می‌دهد انجام دهیم.

بیایید هر دو روش را انجام بدهیم.

ابزارگذاری کد (Instrumenting the Code)

ابزارگذاری (Instrumenting) کد یعنی اضافه کردن دستورهای دیباگ موقتی که کمک می‌کنند مشکل را پیدا کنیم. در بیشتر کدها، این یعنی نوشتن دستورهای printf برای چاپ داده‌های میانی که بتوانیم حین اجرای برنامه آن‌ها را بررسی کنیم. گاهی هم داده‌ها را داخل یک فایل لاگ ذخیره می‌کنیم تا بعد از وقوع مشکل، آن را تحلیل کنیم. هیچ‌کدام از این دو گزینه برای برنامه‌ی امبدد ما قابل استفاده نیستند. نمی‌توانیم از printf استفاده کنیم چون خروجی آن به کنسول سریال می‌رود، و این همان بخشی است که مشکل دارد. نمی‌توانیم هم لاگ‌فایل بنویسیم چون فایل‌سیستمی برای ذخیره اطلاعات لاگ نداریم.

ما به یک بافر log نیاز داریم که ۱۰۰ رویداد (event) آخر را ذخیره کند. هنگامی که با خطایی برخورد می‌کنیم، می‌توانیم به عقب برگردیم و رویدادها را بررسی کنیم تا ببینیم چه چیزی منجر به خطا شده است. این گزارش‌های log داده‌های مرتبط (getIndex، putIndex، nCharacters) و شماره‌ی خط فراخوانی به کد ثبت رویداد log را ذخیره می‌کند.

هنگامی که خطا رخ می‌دهد و فرصتی برای متوقف کردن برنامه در دیباگر پیدا می‌کنیم، می‌توانیم log را بررسی کنیم. اگر خوش‌شانس باشیم، باید بتوانیم چند ورودی log را پیدا کنیم که در خط X، با اطلاعات بافر سازگار بوده و در خط Y، خراب شده‌اند؛ این نشان می‌دهد که خطا بین خطوط X و Y رخ داده است.

کد ۱۰-۳ کد ثبت رویداد را نشان می‌دهد. این کد را بعد از سایر تعریف‌ها و اعلان‌های متغیر اضافه کنید.

کد ثبت‌کننده رویداد

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

حالا برای اینکه ببینیم آیا می‌توانیم خطا را پیدا کنیم، نیاز به چند فراخوانی به تابع debugEvent داریم. از آنجایی که nCharacters ما را کلافه کرده است، قبل و بعد از هر عملیاتی که روی nCharacters انجام می‌دهیم، یک فراخوانی به debugEvent قرار می‌دهیم:

همچنین یک بررسی سازگاری(consistency check) در ابتدای تابع myPutchar قرار می‌دهیم تا مطمئن شویم بافر سالم است. به طور خاص، اگر شرایطی را ببینیم که بافر ناسازگار باشد

بیایید برنامه را زیر دیباگر اجرا کنیم و یک نقطه توقف (breakpoint) روی Error_Handler بگذاریم تا ببینیم می‌توانیم خطا را بگیریم یا نه. در نهایت، به این نقطه توقف رسیدیم و با استفاده از دیباگر، مقدار debugEvents را بررسی کردیم. سپس با برگشتن به عقب در ردگیری‌ها (trace) توسط دیباگر، به مورد زیر رسیدیم:

  • Line 119: nCharacters == 3
  • Line 89: nCharacters == 3
  • Line 91: nCharacters == 2
  • Line 121: nCharacters == 4

چرا nCharacters بین دو رویداد آخر با ۲ پرش کرده است؟ خطوط مرتبط به شرح زیر هستند:

و:

مشکلی در خط ۹۰ یا ۱۲۰ وجود دارد، که این موضوع همچنین نکته مهمی را به ما می‌گوید. بین خط ۱۱۹ و قبل از خط ۱۲۱، یک وقفه (interrupt) رخ داده است. اکنون خطا را به چند خط کد و یک وقفه محدود کرده‌ایم. بیایید روش خود را تغییر دهیم و از تحلیل کد برای رسیدن به همین نتیجه استفاده کنیم. ما هر دو روش را بررسی می‌کنیم زیرا گاهی اوقات یک روش کار می‌کند و دیگری کار نمی‌کند.

تحلیل کد

راه دیگر برای فهمیدن مشکل، تحلیل اتفاقات در حال رخ دادن و تلاش برای شناسایی نقاط بالقوه‌ی مشکل است. تحلیل با شناسایی داده‌های مشترک بین لایه‌های بالا و پایین شروع می‌شود؛ به عبارت دیگر، بافر:

مقاومت‌ها را بهتر بشناسیم

از آنجایی که putIndex و getIndex هر کدام فقط توسط یک لایه (به ترتیب لایه بالا و لایه پایین) استفاده می‌شوند، نباید مشکلی ایجاد کنند. آرایه‌ی data توسط هر دو لایه به اشتراک گذاشته می‌شود، اما توسط لایه‌ی بالا نوشته شده و توسط لایه‌ی پایین خوانده می‌شود، بنابراین هر لایه در مورد این آرایه وظیفه‌ی متمایزی دارد. علاوه بر این، putIndex قسمتی از آرایه را که لایه‌ی بالا استفاده می‌کند، کنترل می‌کند و getIndex قسمتی را که لایه‌ی پایین استفاده می‌کند، کنترل می‌کند. آن‌ها به عناصر متفاوتی از آرایه اشاره می‌کنند و هیچ چیزی که وارد یا خارج از data می‌شود، نمی‌تواند بر روی ایندکس‌ها (indices) یا شمارنده‌ی کاراکتر تأثیر بگذارد. بنابراین، آرایه‌ی data مشکل نیست.

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

و دیگری در تابع myPutchar است:

این همان دو خطی است که کد ابزارگذاری‌شده‌ی ما نشان داد ممکن است مشکل‌ساز باشد.

بررسی دقیق کد

بیایید دقیقاً ببینیم چه اتفاقی می‌افتد زمانی که خط زیر اجرا می‌شود:

در اینجا کد اسمبلی (با توضیحات اضافه‌شده) برای این خط آمده است:

این کد، مقدار nCharacters را در رجیستر r3 بارگذاری می‌کند، آن را یک واحد افزایش می‌دهد و سپس مقدار جدید را دوباره در nCharacters ذخیره می‌کند. وقفه‌ها (interrupts) می‌توانند در هر زمانی رخ دهند، برای مثال درست بعد از اینکه مقدار در رجیستر r3 بارگذاری شد (فرض کنید nCharacters برابر با ۳ باشد)، که باعث رخ دادن موارد زیر می‌شود:

  • در خط ۴۰۶، مقدار فیلد nCharacters در ثبات r3 بارگذاری می‌شود (r3 == 3).
  • درست قبل از اجرای دستور خط ۴۰۷، یک وقفه رخ می‌دهد.
  • روال وقفه (Interrupt routine) مقدار nCharacters را می‌خواند (مقدار آن ۳ است).
  • این روال آن را یک واحد کم می‌کند، بنابراین مقدار nCharacters الان ۲ شده است.
  • روال وقفه تمام شده و کنترل به خط ۴۰۷ بازمی‌گردد.
  • دستور خط ۴۰۷ مقدار ۱ را به ثبات r3 اضافه کرده و نتیجه را در r2 قرار می‌دهد (r3 هنوز محتوی ۳ است، بنابراین r2 برابر ۴ می‌شود).
  • خط ۴۰۹ مقدار r2 را در nCharacters ذخیره می‌کند، حالا باید ۳ باشد ولی عدد ۴ نوشته می‌شود.
  • در خط ۴۰۷، برنامه فرض کرده که r3 حاوی مقدار به‌روز و درست nCharacters است، اما اینطور نیست.
  • این دقیقاً یک نمونه از باگ همزمانی (race condition) ناشی از وقفه وسط عملیات هست.

رجیستر r3 مقدار صحیح nCharacters را ندارد زیرا یک وقفه درست در زمان مناسب رخ داده و متغیر در همان زمان اصلاح شده است. ما در محافظت از سازگاری داده‌های مشترک شکست خوردیم. لایه‌ی بالا در حال تغییر دادن nCharacters بود در حالی که لایه‌ی پایین همزمان آن را تغییر می‌داد.

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

رفع خطا

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

مدت زمانی که وقفه‌ها خاموش هستند را کوتاه نگه دارید. اگر وقفه‌ها برای مدت طولانی خاموش شوند، ممکن است یک وقفه را از دست بدهید و داده‌ها را رها کنید.

در روال وقفه، ما مقدار nCharacters را کاهش می‌دهیم، پس آیا نیاز نداریم آن را با __disable_irq و __enable_irq محافظت کنیم؟ خیر، زیرا هنگامی که یک وقفه رخ می‌دهد، سیستم به طور خودکار مراحل زیر را انجام می‌دهد:

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

وقتی روال وقفه تمام می‌شود، سیستم این مراحل را انجام می‌دهد:

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

کارهای حسابداری زیادی در ابتدای و انتهای یک روال وقفه باید انجام شود. خوشبختانه، طراحان خانواده‌ی پردازنده‌ی ARM تصمیم گرفتند همه‌ی این کارها را در سخت‌افزار انجام دهند. ممکن است سایر پردازنده‌ها اینطور نباشند.

 

اطلاعات
16
0
0
لینک و اشتراک
profile

نویسنده: Alireza Abbasi

متخصص الکترونیک

ویراستار: MasoudHD
مقالات بیشتر
slide

پالت | بازار خرید و فروش قطعات الکترونیک

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

آیسی | موتور جستجوی قطعات الکترونیک

سامانه آی سی سیسوگ (Isee) قابلیتی جدید و کاربردی از سیسوگ است. در این سامانه سعی شده است که جستجو، انتخاب و خرید مناسب تر قطعات برای کاربران تسهیل شود. جستجو در آیسی
family

سیسوگ‌شاپ | فروشگاه محصولات Quectel

فروشگاه سیسوگ مجموعه ای متمرکز بر تکنولوژی های مبتنی بر IOT و ماژول های M2M نظیر GSM، GPS، LTE، NB-IOT، WiFi، BT و ... جایی که با تعامل فنی و سازنده، بهترین راهکارها انتخاب می شوند. برو به فروشگاه سیسوگ
family

سیسوگ فروم | محلی برای پاسخ پرسش‌های شما

دغدغه همیشگی فعالان تخصصی هر حوزه وجود بستری برای گفتگو و پرسش و پاسخ است. سیسوگ فروم یک انجمن آنلاین است که بصورت تخصصی امکان بحث، گفتگو و پرسش و پاسخ در حوزه الکترونیک را فراهم می‌کند. پرسش در سیسوگ فرم
family

سیکار | اولین مرجع متن باز ECU در ایران

بررسی و ارائه اطلاعات مربوط به ECU (واحد کنترل الکترونیکی) و نرم‌افزارهای متن باز مرتبط با آن برو به سیکار
become a writer

نویسنده شو !

سیسوگ با افتخار فضایی برای اشتراک گذاری دانش شماست. برای ما مقاله بنویسید.

ارسال مقاله
become a writer

نویسنده شو !

سیسوگ با افتخار فضایی برای اشتراک گذاری دانش شماست. برای ما مقاله بنویسید.

ارسال مقاله
خانواده سیسوگ
سیسوگ‌شاپ

فروشگاه محصولات Quectel

پالت
سیسوگ فروم

محلی برای پاسخ پرسش‌های شما

سیسوگ جابز
سیسوگ
سیسوگ فروم
سی‌کار

اولین مرجع متن باز ECU در ایران

سیسوگ مگ
آی‌سی

موتور جستجوی قطعات الکترونیکی

سیسوگ آکادمی
پالت

بازار خرید و فروش قطعات الکترونیک

دیدگاه ها

become a writer

نویسنده شو !

سیسوگ با افتخار فضایی برای اشتراک گذاری دانش شماست. برای ما مقاله بنویسید.

ارسال مقاله
become a writer

نویسنده شو !

سیسوگ با افتخار فضایی برای اشتراک گذاری دانش شماست. برای ما مقاله بنویسید.

ارسال مقاله