از وارونگی اولویت اجتناب کنید

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

این تکنیک‌ها ممکن است برای توسعه‌دهندگان برنامه‌های صوتی با کارایی بالا، تولیدکنندگان اصلی تجهیزات (OEM) و ارائه‌دهندگان SoC که در حال پیاده‌سازی HAL صوتی هستند، مفید باشد. لطفاً توجه داشته باشید که پیاده‌سازی این تکنیک‌ها تضمینی برای جلوگیری از اشکالات یا سایر خرابی‌ها، به ویژه اگر خارج از زمینه صوتی استفاده شوند، نیست. نتایج شما ممکن است متفاوت باشد و شما باید ارزیابی و آزمایش خود را انجام دهید.

پیشینه

سرور صوتی Android AudioFlinger و پیاده‌سازی کلاینت AudioTrack/AudioRecord برای کاهش تأخیر، در حال بازطراحی معماری هستند. این کار در اندروید ۴.۱ آغاز شد و با بهبودهای بیشتر در نسخه‌های ۴.۲، ۴.۳، ۴.۴ و ۵.۰ ادامه یافت.

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

وارونگی اولویت

وارونگی اولویت یک حالت خرابی کلاسیک در سیستم‌های بلادرنگ است که در آن یک وظیفه با اولویت بالاتر برای مدت نامحدودی مسدود می‌شود و منتظر می‌ماند تا یک وظیفه با اولویت پایین‌تر منبعی مانند (حالت مشترک محافظت شده توسط) یک mutex را آزاد کند.

در یک سیستم صوتی، وارونگی اولویت معمولاً به صورت یک قطعی (کلیک، پاپ، ترک اوت)، تکرار صدا هنگام استفاده از بافرهای دایره‌ای یا تأخیر در پاسخ به یک فرمان ظاهر می‌شود.

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

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

  • بین نخ میکسر معمولی و نخ میکسر سریع در AudioFlinger
  • بین نخ فراخوانی برنامه برای یک AudioTrack سریع و نخ میکسر سریع (هر دو اولویت بالاتری دارند، اما اولویت‌هایشان کمی متفاوت است)
  • بین نخ فراخوانی برنامه برای ضبط سریع صدا و نخ ضبط سریع (مشابه مورد قبلی)
  • در پیاده‌سازی لایه انتزاعی سخت‌افزار صوتی (HAL)، مثلاً برای تلفن یا حذف اکو
  • درون درایور صدا در هسته
  • بین نخ فراخوانی AudioTrack یا AudioRecord و سایر نخ‌های برنامه (این خارج از کنترل ما است)

راهکارهای رایج

راه حل های معمول عبارتند از:

  • غیرفعال کردن وقفه‌ها
  • موروثی‌سازی اولویت‌دار (mutexes)

غیرفعال کردن وقفه‌ها در فضای کاربری لینوکس امکان‌پذیر نیست و برای چندپردازنده‌ای متقارن (SMP) نیز کار نمی‌کند.

فوتکس‌های ارث‌بری اولویت‌دار (موتکس‌های فضای کاربری سریع) در سیستم صوتی استفاده نمی‌شوند زیرا نسبتاً سنگین هستند و به یک کلاینت قابل اعتماد متکی هستند.

تکنیک‌های مورد استفاده اندروید

آزمایش‌ها با "قفل کردن" و قفل کردن با timeout آغاز شد. اینها انواع مسدودکننده و محدودکننده‌ی عملیات قفل mutex هستند. قفل کردن و قفل کردن با timeout نسبتاً خوب کار می‌کردند، اما مستعد چند حالت خطای مبهم بودند: اگر کلاینت مشغول بود، تضمینی وجود نداشت که سرور بتواند به حالت مشترک دسترسی پیدا کند، و اگر توالی طولانی از قفل‌های نامرتبط وجود داشت که همگی به پایان می‌رسیدند، timeout تجمعی می‌توانست خیلی طولانی باشد.

ما همچنین از عملیات اتمی مانند موارد زیر استفاده می‌کنیم:

  • افزایش
  • بیتی "یا"
  • بیتی "و"

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

توجه: عملیات اتمی و تعامل آنها با موانع حافظه به طرز بدی بد فهمیده شده و به طور نادرست استفاده می‌شوند. ما این روش‌ها را برای کامل بودن در اینجا قرار می‌دهیم، اما توصیه می‌کنیم برای اطلاعات بیشتر، مقاله SMP Primer for Android را نیز مطالعه کنید.

ما هنوز بیشتر ابزارهای فوق را داریم و از آنها استفاده می‌کنیم و اخیراً این تکنیک‌ها را نیز اضافه کرده‌ایم:

  • برای داده‌ها از صف‌های FIFO تک‌خوان-تک‌نویس غیر مسدودشونده استفاده کنید.
  • سعی کنید به جای اشتراک‌گذاری وضعیت بین ماژول‌های با اولویت بالا و پایین، وضعیت را کپی کنید .
  • وقتی لازم است وضعیت به اشتراک گذاشته شود، وضعیت را به حداکثر اندازه کلمه‌ای که بتوان به صورت اتمی در عملیات تک باس و بدون تلاش مجدد به آن دسترسی داشت، محدود کنید.
  • برای وضعیت‌های پیچیده چند کلمه‌ای، از صف وضعیت استفاده کنید. صف وضعیت اساساً یک صف FIFO تک‌خوان و تک‌نویسِ غیر مسدودشونده است که برای وضعیت به جای داده استفاده می‌شود، با این تفاوت که نویسنده، پوش‌های مجاور را در یک پوش واحد ادغام می‌کند.
  • به موانع حافظه برای صحت SMP توجه کنید.
  • اعتماد کنید، اما تأیید کنید . هنگام اشتراک‌گذاری وضعیت بین فرآیندها، فرض نکنید که وضعیت به خوبی شکل گرفته است. برای مثال، بررسی کنید که شاخص‌ها در محدوده مجاز باشند. این تأیید بین نخ‌های یک فرآیند، بین فرآیندهای اعتماد متقابل (که معمولاً UID یکسانی دارند) لازم نیست. همچنین برای داده‌های مشترک مانند صدای PCM که خرابی در آنها بی‌اهمیت است، ضروری نیست.

الگوریتم‌های غیر مسدودکننده

الگوریتم‌های غیر مسدودکننده موضوع مطالعات اخیر بوده‌اند. اما به استثنای صف‌های FIFO تک‌خوان-تک‌نویس، متوجه شده‌ایم که آنها پیچیده و مستعد خطا هستند.

از اندروید ۴.۲ به بعد، می‌توانید کلاس‌های تک‌خوان/نویسنده‌ی غیر مسدودکننده‌ی ما را در این مکان‌ها پیدا کنید:

  • Frameworks/AV/Include/media/nbaio/
  • چارچوب‌ها/av/رسانه/libnbaio/
  • Frameworks/av/services/audiolinger/StateQueue*

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

برای توسعه‌دهندگان، برخی از کدهای نمونه برنامه OpenSL ES باید به‌روزرسانی شوند تا از الگوریتم‌های غیر مسدودکننده استفاده کنند یا به یک کتابخانه متن‌باز غیراندرویدی ارجاع دهند.

ما یک نمونه پیاده‌سازی FIFO بدون انسداد منتشر کرده‌ایم که به‌طور خاص برای کد برنامه طراحی شده است. این فایل‌ها را که در دایرکتوری منبع پلتفرم frameworks/av/audio_utils قرار دارند، مشاهده کنید:

ابزارها

تا آنجا که ما می‌دانیم، هیچ ابزار خودکاری برای یافتن وارونگی اولویت، به خصوص قبل از وقوع آن، وجود ندارد. برخی از ابزارهای تحلیل کد استاتیک تحقیقاتی، در صورت دسترسی به کل پایگاه کد، قادر به یافتن وارونگی‌های اولویت هستند. البته، اگر کد دلخواه کاربر درگیر باشد (همانطور که در اینجا برای برنامه وجود دارد) یا یک پایگاه کد بزرگ باشد (مانند هسته لینوکس و درایورهای دستگاه)، تحلیل استاتیک ممکن است غیرعملی باشد. مهمترین چیز این است که کد را با دقت بخوانید و درک خوبی از کل سیستم و تعاملات آن داشته باشید. ابزارهایی مانند systrace و ps -t -p برای مشاهده وارونگی اولویت پس از وقوع مفید هستند، اما از قبل به شما نمی‌گویند.

سخن پایانی

بعد از همه این بحث‌ها، از mutexeها نترسید. mutexeها در موارد استفاده معمولی، در صورتی که به درستی در موارد استفاده معمولی غیر حساس به زمان استفاده و پیاده‌سازی شوند، دوست شما هستند. اما بین وظایف با اولویت بالا و پایین و در سیستم‌های حساس به زمان، mutexeها احتمالاً مشکل‌ساز خواهند بود.