הימנעות מהיפוך עדיפות

במאמר הזה מוסבר איך מערכת האודיו של Android מנסה למנוע היפוך עדיפות, ומדגיש שיטות שאפשר להשתמש בהן גם כן.

הטכניקות האלה יכולות להועיל למפתחים של אפליקציות אודיו, יצרני ציוד מקורי (OEM) וספקי SoC שמטמיעים אודיו HAL. לתשומת ליבכם: כדי למנוע תקלות או כשלים אחרים, מחוץ להקשר של האודיו. התוצאות עשויות להשתנות, ועליך לנהל בעצמך במהלך ההערכה והבדיקה.

רקע

שרת האודיו Android AudioFlinger ו-AudioTrack/AudioRecord נבנו מחדש את הטמעות הלקוח כדי לקצר את זמן האחזור. העבודה הזו החלה ב-Android 4.1 והמשיכה עם שיפורים נוספים ב-4.2, ב-4.3, ב-4.4 וב-5.0.

כדי להגיע לזמן אחזור קצר זה, היה צורך בשינויים רבים במערכת. אחת שינוי חשוב הוא להקצות משאבי המעבד (CPU) לזמן קריטי שרשורים עם מדיניות תזמון יותר צפויה. תזמון אמין מאפשר להקטין את מאגר הנתונים הזמני של האודיו ואת הספירות תוך כדי תנועה כדי למנוע חריגות וחריגות.

היפוך העדיפות

היפוך העדיפות הוא מצב כשל קלאסי של מערכות בזמן אמת, משימה בעדיפות גבוהה יותר חסומה למשך זמן המתנה בלתי מוגבל אם משימה בעדיפות נמוכה יותר, תשחרר משאב כמו (משותף) מוגן על ידי) mutex.

במערכת אודיו, היפוך העדיפות בדרך כלל מתבטא תקלה (לחיצה, קופץ, נשירה), אודיו חוזר כש בתהליך אגירת נתונים מעגליים בשימוש, או עיכוב בתגובה לפקודה.

פתרון נפוץ להיפוך בעדיפות הוא הגדלת הגודל של מאגר הנתונים הזמני של האודיו. עם זאת, השיטה הזו מאריכה את זמן האחזור ורק מסתירה את הבעיה במקום לפתור אותה. עדיף להבין ולמנוע עדיפות בהיפוך, כפי שאפשר לראות בהמשך.

בהטמעת אודיו ב-Android, היפוך העדיפות הוא יתרחשו במקומות האלה. לכן עליכם להתמקד כאן:

  • בין שרשור במיקסר רגיל ושרשור מיקסר מהיר ב-AudioFlinger
  • בין ה-thread של קריאה חוזרת (callback) של האפליקציה ל-AudioTrack מהיר שרשורי מיקסר מהיר (לשניהם יש עדיפות גבוהה יותר, אבל מעט עדיפויות שונות)
  • בין ה-thread של הקריאה החוזרת לאפליקציה להקלטת AudioRecord מהירה צילום מהיר של שרשור (דומה לשרשור הקודם)
  • בהטמעה של Abstraction Layer (HAL) של חומרת אודיו, למשל: לביטול טלפוניה או הד
  • במנהל האודיו בליבה (kernel)
  • בין שרשור קריאה חוזרת (callback) של AudioTrack או AudioRecord לשרשורים אחרים של אפליקציות (אין לנו שליטה על כך)

פתרונות נפוצים

הפתרונות הטיפוסיים כוללים:

  • השבתת הפרעות
  • מוטקסים לירושה בעדיפות גבוהה

לא ניתן להשבית הפרעות במרחב המשתמשים ב-Linux, וגם לא פועלת במעבדים סימטריים מרובי (SMP).

ירושה של עדיפות futexes (השתקה מהירה של מרחב משתמש) אינם בשימוש במערכת האודיו מאחר שהם כבדים יחסית, ובגלל שהם מסתמכים על לקוח מהימן.

השיטות שמשמשות את Android

ניסויים שהתחילו ב'ניסיון נעילה' וננעל עם זמן קצוב לתפוגה. הנושאים האלה וריאנטים חסומים של מנעול mutex פעולה. האפשרות 'נעילה ונעילה' עם הזמן הקצוב טופלה בצורה די טובה, אבל שחשופים לכמה מצבי כשל לא ברורים: לא מובטח שהשרת יוכל לגשת למצב המשותף אם שהלקוח היה עסוק, והזמן המצטבר המצטבר עלול להיות ארוכים מדי אם היה רצף ארוך של מנעולים שאינם קשורים פג הזמן הקצוב.

אנחנו משתמשים גם פעולות אטומיות כמו:

  • הוסף
  • "או"
  • "ו-"

כל אלה מחזירות את הערך הקודם וכוללים את מחסומי SMP. החיסרון הוא שהם יכולים לדרוש ניסיונות חוזרים ללא הגבלה. בפועל, גילינו שהניסיונות החוזרים לא בעיה.

הערה: פעולות אטומיות והאינטראקציות שלהן עם מחסומי זיכרון שלא מובנים כל כך כראוי ונעשה בהם שימוש שגוי. אנחנו כוללים את השיטות האלה. כאן לצורך שלמות, אבל מומלץ לקרוא גם את המאמר SMP Primer ל-Android לקבלת מידע נוסף.

יש לנו עדיין את רוב הכלים האלה ואנחנו משתמשים בהם, ולאחרונה הוסיפו את הטכניקות האלה:

  • להשתמש בכותב יחיד, ללא חסימה, של קורא יחיד תורים ל-FIFO לנתונים.
  • אפשר לנסות עותק במדינה במקום שיתוף בין הערך הגבוה ל- מודולים בעדיפות נמוכה.
  • אם צריך לשתף את המדינה, יש להגביל את המדינה גודל מקסימלי מילה שאפשר לגשת אליו באופן אטומי בהפעלה של אוטובוס אחד בלי ניסיונות חוזרים.
  • למצב מורכב של מספר מילים, יש להשתמש בתור מצב. תור במדינה הוא למעשה ארגון FIFO של קורא יחיד ללא חסימה תור שמשמש למצב ולא לנתונים, חוץ מכיווץ מצב הכתיבה שזה דחוף לדחיפה אחת.
  • שימו לב אל מחסומי זיכרון לתיקון SMP.
  • אמינות, אבל אימות. בזמן השיתוף מצב בין תהליכים, מניחים שהמדינה במצב תקין. לדוגמה, בדקו שאינדקסים נמצאות בתוך גבולות. לא צריך את האימות הזה בין שרשורים באותו תהליך, בין תהליכים של אמון הדדי ( בדרך כלל יש לו אותו UID). אין צורך גם במקרה של נתונים כמו אודיו PCM שבו הפגמים הם חסרי השלכות.

אלגוריתמים ללא חסימה

אלגוריתמים ללא חסימה שעוסקים במחקרים רבים לאחרונה. אבל מלבד תורי FIFO לקורא יחיד של כותבים יחידים, גילינו שהם מורכבים ומובילים לשגיאות.

ב-Android מגרסה 4.2 אפשר למצוא קישורים כיתות קריאה יחידה/כתיבה במיקומים הבאים:

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

הם תוכננו במיוחד עבור AudioFlinger, לשימוש כללי. אלגוריתמים ללא חסימה ידועים לשמצה שקשה לבצע בו ניפוי באגים. אתם יכולים להסתכל על הקוד הזה בתור מודל. אבל להיות עשויים להיות באגים, והמחלקות לא מובטחות מתאים למטרות אחרות.

למפתחים: צריך לעדכן חלק מקוד האפליקציה לדוגמה של OpenSL ES ל- להשתמש באלגוריתמים שאינם חוסמים או לעיין בספריית קוד פתוח שאינה Android.

פרסמנו דוגמה להטמעת FIFO ללא חסימה שמיועדת ספציפית של האפליקציה. הקבצים הבאים נמצאים בספריית המקור של הפלטפורמה frameworks/av/audio_utils:

כלים

למיטב ידיעתנו, אין כלים אוטומטיים למצוא את היפוך העדיפות, במיוחד לפני שהוא קורה. במידה מסוימת מחקר, כלים לניתוח קוד סטטי המסוגלים למצוא עדיפות הפוכים אם אפשר לגשת ל-codebase כולו. כמובן, אם מעורב כאן קוד משתמש שרירותי (כפי שקורה כאן עבור האפליקציה) או שהוא בסיס קוד גדול (כמו ליבה ומנהלי התקנים של Linux), ניתוח סטטי עשוי להיות לא מעשי. הדבר הכי חשוב צריך לקרוא את הקוד בקפידה ולהבין טוב את כל המערכת ואינטראקציות. כלים כמו systrace וגם ps -t -p שימושיים להצגת היפוך בעדיפות גבוהה לאחר שהיא מתרחשת, אבל לא תודיע לכם מראש.

מילה אחרונה

אחרי כל הדיון הזה, אל תחששו מושמטים. השתקה הם חברים שלכם לשימוש רגיל, כאשר משתמשים בהם ומיישמים אותם בצורה נכונה בתרחישים לדוגמה רגילים שאינם קריטיים בזמן. אבל בין רמה גבוהה ל- במשימות בעדיפות נמוכה ובמערכות הרגישות לזמן, שעלולות לגרום לבעיות.