이 문서에서는 Android의 오디오 시스템에서 우선순위 역전을 회피하기 위해 시도하는 방법과 개발자가 사용할 수 있는 기법을 설명합니다.
이러한 기법은 고성능 오디오 앱 개발자, OEM, 오디오 HAL을 구현하는SoC 제공업체에 유용할 수 있습니다. 특히 오디오 컨텍스트 외부에서 사용하는 경우 이러한 기법을 구현해도 결함이나 장애를 방지하지 못할 수 있습니다. 결과는 다를 수 있으므로 자체적으로 평가하고 테스트해야 합니다.
백그라운드
Android AudioFlinger 오디오 서버 및 AudioTrack/AudioRecord 클라이언트 구현이 지연 시간을 줄이기 위해 다시 설계되고 있습니다. 이 작업은 Android 4.1에서 시작되었으며 4.2, 4.3, 4.4, 5.0에서 계속 개선되었습니다.
이러한 지연 시간을 줄이기 위해 시스템 전반에 걸쳐 많은 변화가 필요했습니다. 중요한 변경사항 중 하나는 더욱 더 예측 가능한 스케줄링 정책을 사용하여 시간이 중요한 스레드에 CPU 리소스를 할당하는 것입니다. 안정적인 스케줄링은 언더런과 오버런을 방지하면서 오디오 버퍼 사이즈와 개수를 줄여줍니다.
우선순위 역전
우선순위 역전은 실시간 시스템의 전형적인 실패 모드입니다. 여기서는 우선순위가 더 낮은 작업이 뮤텍스에 의해 보호되는 공유 상태와 같은 리소스를 방출할 때까지 우선순위가 더 높은 작업이 무기한 대기하며 차단됩니다.
오디오 시스템에서 우선순위 역전은 일반적으로 순환 버퍼가 사용되거나 명령어에 관한 응답이 지연될 때 결함(클릭, 팝, 드롭아웃), 반복 오디오로 나타납니다.
우선순위 역전의 일반적인 해결 방법은 오디오 버퍼 사이즈를 늘리는 것입니다. 그러나 이 방법은 지연 시간을 증가시키며, 문제를 해결하는 것이 아닌 숨기는 것에 불과합니다. 따라서 아래에 나와 있는 우선순위 역전을 이해하고 방지하는 것이 좋습니다.
Android 오디오 구현에서는 다음과 같은 위치에서 우선순위 역전이 가장 빈번하게 발생하므로 이에 주의를 기울여야 합니다.
- AudioFlinger의 일반 믹서 스레드와 빠른 믹서 스레드 사이
- 빠른 AudioTrack의 애플리케이션 콜백 스레드와 빠른 믹서 스레드 사이(둘 다 우선순위는 높지만 우선순위에 약간 차이가 있음)
- 빠른 AudioRecord의 애플리케이션 콜백 스레드와 빠른 캡처 스레드 사이(이전과 유사)
- 오디오 하드웨어 추상화 계층(HAL) 구현 내(예: 전화 통신 또는 에코 제거)
- 커널의 오디오 드라이버 내
- AudioTrack 또는 AudioRecord 콜백 스레드와 Google과 무관한 기타 앱 스레드 사이
일반적인 해결책
일반적인 해결책은 다음과 같습니다.
- 인터럽트 사용 중지
- 우선순위 상속 뮤텍스
인터럽트 사용 중지는 Linux 사용자 공간에서 지원하지 않으며 대칭형 다중 처리기(SMP)에서 작동하지 않습니다.
우선순위 상속 futex(fast user-space mutex)는 상대적으로 무겁고 신뢰할 수 있는 클라이언트에 의존하기 때문에 오디오 시스템에서 사용되지 않습니다.
Android에서 사용하는 기법
'잠금 시도' 및 시간 제한이 있는 잠금에 관한 실험이 시작되었습니다. 이는 뮤텍스 잠금 작업의 비 블로킹 및 제한 블로킹 변형입니다. 잠금 시도 및 시간 제한이 있는 잠금이 잘 작동했지만 여러 알 수 없는 실패 모드를 야기했습니다. 클라이언트 작업이 많은 경우 서버는 공유 상태에 액세스하지 못할 수 있으며 시간이 모두 초과된 무관한 잠금으로 구성된 긴 시퀀스가 있는 경우 누적 시간 제한이 너무 길어질 수 있습니다.
원자적 연산도 다음과 같이 사용합니다.
- 증가
- 비트 'or'
- 비트 'and'
이 모두가 이전 값을 반환하고 필요한 SMP 배리어를 포함합니다. 단점은 무한 재시도를 요구할 수 있다는 점이지만, 실제로 재시도는 문제가 되지 않는 것으로 나타났습니다.
참고: 원자적 연산과 메모리 배리어와의 상호작용은 곡해되고 오용되기 쉽습니다. 여기에 이러한 메서드가 모두 포함되어 있지만 자세한 내용은 Android용 SMP 기본 지침서를 참조하는 것이 좋습니다.
Google에서는 위의 도구 대부분을 보유하여 사용하며 최근에는 다음 기법을 추가했습니다.
- 데이터에 비 블로킹 단일 판독기 단일 작성기 FIFO 대기열을 사용합니다.
- 높은 우선순위 모듈 및 낮은 우선순위 모듈 간 상태를 공유하기보다는 복사하려고 시도합니다.
- 상태를 공유해야 하는 경우, 재시도 없이 하나의 버스 작업에서 원자적으로 액세스할 수 있는 최대 크기 단어로 상태를 제한합니다.
- 복잡한 여러 단어로 된 상태의 경우 상태 대기열을 사용합니다. 상태 대기열은 기본적으로 데이터가 아닌 상태에 사용되는 비 블로킹 단일 판독기 단일 작성기 FIFO 대기열입니다. 단, 작성기는 인접 푸시를 단일 푸시로 축소합니다.
- SMP 정확성에 관한 메모리 배리어에 주의합니다.
- 신뢰하되 검증합니다. 프로세스 간에 상태를 공유할 때 상태가 올바르다고 가정하지 않습니다. 예를 들어 색인이 범위 내에 있는지 확인합니다. 동일한 프로세스의 스레드 간, 일반적으로 동일한 UID를 가진 상호 신뢰 프로세스 간에는 이 검증이 필요하지 않습니다. 손상이 중요하지 않은 PCM 오디오 등의 공유 데이터에도 필요하지 않습니다.
비 블로킹 알고리즘
비 블로킹 알고리즘은 최근 연구의 주제였습니다. 하지만 단일 판독기 단일 작성기 FIFO 대기열을 제외하면 이 알고리즘은 복잡하고 오류가 발생하기 쉽습니다.
Android 4.2부터는 다음 위치에서 비 블로킹 단일 판독기/단일 작성기 클래스를 찾을 수 있습니다.
- frameworks/av/include/media/nbaio/
- frameworks/av/media/libnbaio/
- frameworks/av/services/audioflinger/StateQueue*
이는 AudioFlinger를 위해 특별히 설계되었으며 범용이 아닙니다. 비 블로킹 알고리즘은 디버그하기 어렵다고 알려져 있습니다. 이 코드를 모델로 볼 수 있습니다. 하지만 버그가 있을 수 있으며 클래스가 다른 목적에 적합하지 않을 수도 있습니다.
개발자는 비 블로킹 알고리즘을 사용하거나 Android가 아닌 오픈소스 라이브러리를 참조하도록 샘플 OpenSL ES 애플리케이션 코드 중 일부를 업데이트해야 합니다.
Google에서는 애플리케이션 코드용으로 고안된 비 블로킹 FIFO 구현 예를 게시했습니다. 플랫폼 소스 디렉터리(frameworks/av/audio_utils
)에 있는 다음 파일을 참조하세요.
도구
Google에서 확인한 바에 따르면 특히 우선순위 역전이 발생하기 전에 이를 발견하는 자동화 도구는 없습니다. 전체 코드베이스에 액세스할 수 있는 경우 일부 연구용 정적 코드 분석 도구가 우선순위 역전을 발견할 수 있습니다. 물론 임의의 사용자 코드가 포함(애플리케이션에서 사용하기 위해)되거나 대규모 코드베이스(Linux 커널 또는 기기 드라이버에서 사용하기 위해)인 경우 정적 분석은 비실용적일 수 있습니다. 가장 중요한 것은 코드를 매우 신중하게 읽어 전체 시스템과 상호작용을 정확히 파악하는 것입니다. systrace 및 ps -t -p
와 같은 도구는 우선순위 역전이 발생한 후에 이를 확인하는 데 유용하지만 사전에 알려주지는 않습니다.
마지막으로 짚고 넘어갈 내용
이러한 논의를 떠나서 뮤텍스를 두려워하지 마세요. 뮤텍스는 시간이 중요하지 않은 일반적인 사용 사례에서 올바르게 사용되고 구현될 때 일반적인 용도로는 문제없이 사용할 수 있습니다. 하지만 높은 우선순위와 낮은 우선순위 작업 사이에서 그리고 시간에 민감한 시스템에서는 뮤텍스가 문제를 일으킬 가능성이 큽니다.