تجنُّب عكس الأولوية

توضّح هذه المقالة كيف يحاول نظام الصوت في Android تجنُّب عكس الأولوية، وتسلّط الضوء على التقنيات التي يمكنك استخدامها أيضًا.

قد تكون هذه التقنيات مفيدة لمطوّري تطبيقات الصوت العالية الأداء ومصنّعي المعدات الأصلية وموفّري نظام التشغيل على الشريحة الذين ينفّذون طبقة تجريد الأجهزة الصوتية. يُرجى العِلم أنّ تطبيق هذه التقنيات لا يضمن منع حدوث أخطاء أو أعطال أخرى، خاصةً إذا تم استخدامها خارج سياق الصوت. قد تختلف النتائج، وعليك إجراء التقييم والاختبار بنفسك.

الخلفية

تتم إعادة تصميم بنية خادم الصوت AudioFlinger في Android وتنفيذ برنامج العميل AudioTrack/AudioRecord للحد من وقت الاستجابة. بدأ هذا العمل في الإصدار 4.1 من نظام التشغيل Android، واستمر مع إجراء المزيد من التحسينات في الإصدارات 4.2 و4.3 و4.4 و5.0.

ولتحقيق وقت الاستجابة الأقصر هذا، كان من الضروري إجراء العديد من التغييرات في جميع أنحاء النظام. أحد التغييرات المهمة هو تخصيص موارد وحدة المعالجة المركزية (CPU) لسلاسل التعليمات البرمجية التي تتطلّب تنفيذًا سريعًا باستخدام سياسة جدولة أكثر قابلية للتوقّع. يسمح الجدولة الموثوقة بتقليل أحجام مخزن الصوت المؤقت وأعداده مع تجنُّب حالات نقص البيانات أو زيادتها.

انعكاس الأولوية

قلب الأولويات هو وضع تعذُّر كلاسيكي للأنظمة التي تعمل في الوقت الفعلي، حيث يتم حظر مهمة ذات أولوية أعلى لمدة غير محدودة في انتظار أن تحرر مهمة ذات أولوية أقل موردًا، مثل (حالة مشتركة محمية بواسطة) دالة استبعاد متبادل.

في نظام صوتي، يظهر انعكاس الأولوية عادةً على شكل خلل (نقرة أو صوت فرقعة أو انقطاع) أو تكرار الصوت عند استخدام المخازن المؤقتة الدائرية، أو تأخير في الاستجابة لأمر ما.

من الحلول الشائعة لتجنُّب مشكلة انعكاس الأولوية زيادة أحجام مخزن الصوت المؤقت. ومع ذلك، تؤدي هذه الطريقة إلى زيادة وقت الاستجابة وتخفي المشكلة فقط بدلاً من حلّها. من الأفضل فهم مشكلة انعكاس الأولوية وتجنُّبها، كما هو موضّح أدناه.

في عملية تنفيذ الصوت على Android، من المرجّح حدوث انعكاس الأولوية في هذه المواضع. لذا، عليك التركيز على ما يلي:

  • بين سلسلة المزج العادية وسلسلة المزج السريعة في AudioFlinger
  • بين سلسلة التعليمات الخاصة بمعالجة رد الاتصال في التطبيق لـ AudioTrack السريع وسلسلة التعليمات الخاصة بالخلاط السريع (لكل منهما أولوية أعلى، ولكن مع اختلاف بسيط في الأولويات)
  • بين سلسلة محادثات رد الاتصال في التطبيق لعملية AudioRecord سريعة وسلسلة محادثات الالتقاط السريع (مشابهة لما سبق)
  • ضمن تنفيذ طبقة تجريد الأجهزة (HAL) الصوتية، مثلاً للاتصال الهاتفي أو إلغاء الصدى
  • ضمن برنامج تشغيل الصوت في النواة
  • بين سلسلة استدعاء AudioTrack أو AudioRecord وسلاسل تطبيقات أخرى (هذا خارج نطاق سيطرتنا)

الحلول الشائعة

تشمل الحلول النموذجية ما يلي:

  • إيقاف المقاطعات
  • أقفال تبادل الاستبعاد المتبادل مع اكتساب الأولوية

لا يمكن إيقاف المقاطعات في مساحة مستخدم Linux، ولا تعمل مع المعالجات المتعددة المتماثلة (SMP).

لا يتم استخدام futexes (أقفال تبادل الاستبعاد السريع في مساحة المستخدم) التي تعتمد على مبدأ توريث الأولوية في نظام الصوت لأنّها ثقيلة نسبيًا، ولأنّها تعتمد على عميل موثوق به.

التقنيات التي يستخدمها Android

بدأت التجارب باستخدام "محاولة القفل" والقفل بمهلة زمنية. وهي صيغ غير حاصرة ومحدودة الحصر لعملية قفل التبادل الاستبعادي. كانت محاولة القفل والقفل مع المهلة تعمل بشكل جيد إلى حد ما، ولكنها كانت عرضة لبعض أوضاع الأعطال الغامضة: لم يكن من المضمون أن يتمكّن الخادم من الوصول إلى الحالة المشتركة إذا كان العميل مشغولاً، ويمكن أن تكون المهلة التراكمية طويلة جدًا إذا كان هناك تسلسل طويل من عمليات القفل غير ذات الصلة التي انتهت مهلتها.

نستخدم أيضًا عمليات ذرية مثل:

  • زيادة
  • استخدام "or" على مستوى البت
  • bitwise "and"

تعرض كل هذه الدوال القيمة السابقة وتتضمّن حواجز SMP اللازمة. أما العيب فهو أنّها قد تتطلّب عددًا غير محدود من عمليات إعادة المحاولة. في الواقع، تبيّن لنا أنّ عمليات إعادة المحاولة لا تشكّل مشكلة.

ملاحظة: إنّ العمليات الذرية وتفاعلاتها مع حواجز الذاكرة هي من المفاهيم التي يساء فهمها واستخدامها بشكل كبير. نضمّن هذه الطرق هنا من أجل تقديم معلومات شاملة، ولكن ننصحك أيضًا بقراءة المقالة مقدمة عن SMP لنظام التشغيل Android للحصول على مزيد من المعلومات.

ما زلنا نملك معظم الأدوات المذكورة أعلاه ونستخدمها، وقد أضفنا مؤخرًا الأساليب التالية:

  • استخدِم قوائم انتظار FIFO غير الحظرية ذات القارئ الواحد والكاتب الواحد للبيانات.
  • حاوِل نسخ الحالة بدلاً من مشاركتها بين الوحدات ذات الأولوية العالية والمنخفضة.
  • عندما يكون من الضروري مشاركة الحالة، يجب أن تقتصر الحالة على الكلمة ذات الحجم الأقصى التي يمكن الوصول إليها بشكل ذري في عملية واحدة للحافلة بدون إعادة محاولة.
  • بالنسبة إلى الحالات المعقّدة التي تتضمّن كلمات متعدّدة، استخدِم قائمة انتظار للحالات. قائمة انتظار الحالة هي في الأساس قائمة انتظار FIFO غير حظرية ذات قارئ واحد وكاتب واحد، وتُستخدَم للحالة بدلاً من البيانات، باستثناء أنّ الكاتب يدمج عمليات الدفع المتجاورة في عملية دفع واحدة.
  • يجب الانتباه إلى حواجز الذاكرة لضمان صحة SMP.
  • الثقة مع التحقّق: عند مشاركة الحالة بين العمليات، لا تفترض أنّ الحالة صحيحة التنسيق. على سبيل المثال، تأكَّد من أنّ الفهارس ضمن الحدود. ولا تكون عملية التحقّق هذه مطلوبة بين سلاسل المحادثات في العملية نفسها، أو بين العمليات التي تثق ببعضها البعض (والتي يكون لها عادةً المعرّف الفريد نفسه). لا يكون ذلك ضروريًا أيضًا بالنسبة إلى البيانات المشترَكة، مثل الصوت بتنسيق PCM، حيث لا يكون التلف مهمًا.

الخوارزميات غير الحظرية

الخوارزميات غير الحظرية كانت موضوعًا للعديد من الدراسات الحديثة. ولكن باستثناء قوائم انتظار FIFO التي تتضمّن قارئًا واحدًا وكاتبًا واحدًا، تبيّن لنا أنّها معقّدة وعُرضة للأخطاء.

اعتبارًا من الإصدار 4.2 من نظام التشغيل Android، يمكنك العثور على فئات القراءة والكتابة غير الحظرية والمفردة في المواقع التالية:

  • frameworks/av/include/media/nbaio/
  • frameworks/av/media/libnbaio/
  • frameworks/av/services/audioflinger/StateQueue*

وقد تم تصميمها خصيصًا لـ AudioFlinger وليست للاستخدام العام. تشتهر الخوارزميات غير الحظرية بصعوبة تصحيح أخطائها. يمكنك اعتبار هذا الرمز نموذجًا. يُرجى العلم أنّه قد تحدث أخطاء، ولا يمكن ضمان ملاءمة الفئات لأغراض أخرى.

بالنسبة إلى المطوّرين، يجب تعديل بعض نماذج رموز تطبيقات OpenSL ES لاستخدام خوارزميات غير حظر أو الرجوع إلى مكتبة مفتوحة المصدر غير تابعة لنظام Android.

لقد نشرنا مثالاً على تنفيذ FIFO غير محظور مصمّم خصيصًا لرمز التطبيق. يمكنك الاطّلاع على هذه الملفات في دليل مصدر النظام الأساسي frameworks/av/audio_utils:

الأدوات

حسب معلوماتنا، لا تتوفّر أدوات تلقائية للعثور على حالات عكس الأولوية، خاصةً قبل حدوثها. يمكن لبعض أدوات تحليل الرمز الثابت المستخدمة في الأبحاث العثور على حالات انعكاس الأولوية إذا كان بإمكانها الوصول إلى قاعدة الرموز البرمجية بأكملها. بالطبع، إذا كان الأمر يتضمّن رمز مستخدم عشوائيًا (كما هو الحال هنا بالنسبة إلى التطبيق) أو قاعدة رموز كبيرة (كما هو الحال بالنسبة إلى نواة Linux وبرامج تشغيل الأجهزة)، قد يكون التحليل الثابت غير عملي. الأهم هو قراءة الرمز البرمجي بعناية شديدة وفهم النظام بأكمله والتفاعلات بشكل جيد. تُعد أدوات مثل systrace و ps -t -p مفيدة في رصد مشكلة عكس الأولوية بعد حدوثها، ولكنها لا تخبرك بها مسبقًا.

كلمة أخيرة

بعد كل هذه المناقشة، لا تخف من استخدام عمليات الاستبعاد المتبادل. تُعدّ عمليات الاستبعاد المتبادل مفيدة للاستخدام العادي، وذلك عند استخدامها وتنفيذها بشكل صحيح في حالات الاستخدام العادية غير الحرجة من حيث الوقت. ولكن بين المهام ذات الأولوية العالية والمنخفضة، وفي الأنظمة الحساسة للوقت، من المرجّح أن تتسبّب عمليات الاستبعاد المتبادل في حدوث مشاكل.