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

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

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

רקע כללי

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

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

היפוך עדיפות

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

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

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

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

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

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

הפתרונות האופייניים כוללים:

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

השבתת פסיקות אינה אפשרית במרחב משתמש לינוקס ואינה פועלת עבור מעבדים סימטריים (SMP).

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

טכניקות בשימוש אנדרואיד

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

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

  • תוֹסֶפֶת
  • קצת "או"
  • קצת "ו"

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

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

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

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

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

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

החל מאנדרואיד 4.2, אתה יכול למצוא את שיעורי הקורא/כותב הבלתי חוסמים שלנו במיקומים הבאים:

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

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

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

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

כלים

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

מילה אחרונה

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