Tránh đảo ngược mức độ ưu tiên

Bài viết này giải thích cách hệ thống âm thanh của Android cố gắng tránh đảo ngược mức độ ưu tiên và nêu bật các kỹ thuật mà bạn cũng có thể sử dụng.

Những kỹ thuật này có thể hữu ích cho các nhà phát triển ứng dụng âm thanh hiệu suất cao, OEM và nhà cung cấp SoC đang triển khai HAL âm thanh. Xin lưu ý rằng việc triển khai các kỹ thuật này không đảm bảo sẽ tránh được trục trặc hoặc các lỗi khác, đặc biệt nếu được sử dụng bên ngoài ngữ cảnh âm thanh. Kết quả của bạn có thể khác nhau và bạn nên tiến hành đánh giá và kiểm tra của riêng mình.

Lý lịch

Máy chủ âm thanh Android AudioFlinger và ứng dụng khách AudioTrack/AudioRecord đang được tái kiến ​​trúc để giảm độ trễ. Công việc này bắt đầu trong Android 4.1 và tiếp tục với những cải tiến hơn nữa trong 4.2, 4.3, 4.4 và 5.0.

Để đạt được độ trễ thấp hơn này, cần có nhiều thay đổi trên toàn hệ thống. Một thay đổi quan trọng là gán tài nguyên CPU cho các luồng quan trọng về thời gian với chính sách lập lịch dễ dự đoán hơn. Lập kế hoạch đáng tin cậy cho phép giảm kích thước và số lượng bộ đệm âm thanh trong khi vẫn tránh chạy thiếu và tràn.

Đảo ngược ưu tiên

Đảo ngược mức độ ưu tiên là một chế độ lỗi cổ điển của các hệ thống thời gian thực, trong đó tác vụ có mức độ ưu tiên cao hơn bị chặn trong thời gian không giới hạn để chờ tác vụ có mức độ ưu tiên thấp hơn giải phóng tài nguyên, chẳng hạn như (trạng thái chia sẻ được bảo vệ bởi) một mutex .

Trong hệ thống âm thanh, đảo ngược mức độ ưu tiên thường biểu hiện dưới dạng trục trặc (nhấp, bật, thoát), âm thanh lặp lại khi sử dụng bộ đệm tròn hoặc chậm phản hồi lệnh.

Cách giải quyết phổ biến cho việc đảo ngược mức độ ưu tiên là tăng kích thước bộ đệm âm thanh. Tuy nhiên, phương pháp này làm tăng độ trễ và chỉ che giấu vấn đề thay vì giải quyết nó. Tốt hơn hết là bạn nên hiểu và ngăn chặn việc đảo ngược mức độ ưu tiên, như được trình bày bên dưới.

Trong quá trình triển khai âm thanh của Android, việc đảo ngược mức độ ưu tiên rất có thể xảy ra ở những nơi này. Và vì vậy bạn nên tập trung sự chú ý của mình vào đây:

  • giữa luồng trộn thông thường và luồng trộn nhanh trong AudioFlinger
  • giữa luồng gọi lại ứng dụng cho luồng AudioTrack nhanh và luồng trộn nhanh (cả hai đều có mức độ ưu tiên cao hơn nhưng mức độ ưu tiên hơi khác nhau)
  • giữa luồng gọi lại ứng dụng để có AudioRecord nhanh và luồng thu nhanh (tương tự như trước)
  • trong quá trình triển khai Lớp trừu tượng phần cứng âm thanh (HAL), ví dụ như để khử tiếng vang hoặc điện thoại
  • trong trình điều khiển âm thanh trong kernel
  • giữa chuỗi gọi lại AudioTrack hoặc AudioRecord và các chuỗi ứng dụng khác (điều này nằm ngoài tầm kiểm soát của chúng tôi)

Giải pháp chung

Các giải pháp điển hình bao gồm:

  • vô hiệu hóa ngắt
  • các mutex kế thừa ưu tiên

Việc vô hiệu hóa các ngắt không khả thi trong không gian người dùng Linux và không hoạt động đối với Bộ xử lý đa xử lý đối xứng (SMP).

Futex kế thừa ưu tiên (mutex không gian người dùng nhanh) không được sử dụng trong hệ thống âm thanh vì chúng tương đối nặng và vì chúng dựa vào một ứng dụng khách đáng tin cậy.

Các kỹ thuật được Android sử dụng

Thử nghiệm bắt đầu bằng "thử khóa" và khóa khi hết thời gian chờ. Đây là các biến thể chặn không chặn và chặn giới hạn của hoạt động khóa mutex. Thử khóa và khóa với thời gian chờ hoạt động khá tốt nhưng dễ mắc phải một số chế độ lỗi khó hiểu: máy chủ không được đảm bảo có thể truy cập trạng thái chia sẻ nếu máy khách bận và thời gian chờ tích lũy có thể quá lâu nếu có một chuỗi dài các khóa không liên quan đã hết thời gian chờ.

Chúng tôi cũng sử dụng các hoạt động nguyên tử như:

  • tăng
  • theo từng bit "hoặc"
  • theo chiều bit "và"

Tất cả những thứ này trả về giá trị trước đó và bao gồm các rào cản SMP cần thiết. Điểm bất lợi là chúng có thể yêu cầu số lần thử lại không giới hạn. Trong thực tế, chúng tôi nhận thấy rằng việc thử lại không thành vấn đề.

Lưu ý: Các hoạt động nguyên tử và sự tương tác của chúng với các rào cản bộ nhớ thường bị hiểu sai và sử dụng không chính xác. Chúng tôi đưa các phương pháp này vào đây để có đầy đủ nhưng khuyên bạn cũng nên đọc bài viết SMP Primer dành cho Android để biết thêm thông tin.

Chúng tôi vẫn có và sử dụng hầu hết các công cụ trên và gần đây đã thêm các kỹ thuật sau:

  • Sử dụng hàng đợi FIFO một trình đọc đơn không chặn cho dữ liệu.
  • Cố gắng sao chép trạng thái thay vì chia sẻ trạng thái giữa các mô-đun có mức độ ưu tiên cao và thấp.
  • Khi trạng thái cần được chia sẻ, hãy giới hạn trạng thái ở từ có kích thước tối đa có thể được truy cập nguyên tử trong hoạt động một bus mà không cần thử lại.
  • Đối với trạng thái nhiều từ phức tạp, hãy sử dụng hàng đợi trạng thái. Hàng đợi trạng thái về cơ bản chỉ là hàng đợi FIFO của một trình đọc đơn không chặn, được sử dụng cho trạng thái thay vì dữ liệu, ngoại trừ trình ghi thu gọn các lần đẩy liền kề thành một lần đẩy.
  • Hãy chú ý đến các rào cản bộ nhớ để đảm bảo tính chính xác của SMP.
  • Hãy tin tưởng, nhưng hãy xác minh . Khi chia sẻ trạng thái giữa các tiến trình, đừng cho rằng trạng thái đó được định dạng đúng. Ví dụ: kiểm tra xem các chỉ số có nằm trong giới hạn không. Việc xác minh này không cần thiết giữa các luồng trong cùng một quy trình, giữa các quy trình tin cậy lẫn nhau (thường có cùng UID). Nó cũng không cần thiết đối với dữ liệu được chia sẻ như âm thanh PCM khi lỗi không nghiêm trọng.

Thuật toán không chặn

Các thuật toán không chặn đã là chủ đề của nhiều nghiên cứu gần đây. Nhưng ngoại trừ hàng đợi FIFO dành cho một người đọc, chúng tôi nhận thấy chúng rất phức tạp và dễ xảy ra lỗi.

Bắt đầu từ Android 4.2, bạn có thể tìm thấy các lớp đọc/ghi đơn, không chặn của chúng tôi ở các vị trí sau:

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

Chúng được thiết kế dành riêng cho AudioFlinger và không có mục đích chung. Các thuật toán không chặn nổi tiếng là khó gỡ lỗi. Bạn có thể xem mã này như một mô hình. Nhưng hãy lưu ý rằng có thể có lỗi và các lớp học không được đảm bảo phù hợp với các mục đích khác.

Đối với các nhà phát triển, một số mã ứng dụng OpenSL ES mẫu phải được cập nhật để sử dụng các thuật toán không chặn hoặc tham chiếu thư viện nguồn mở không phải của Android.

Chúng tôi đã xuất bản một ví dụ triển khai FIFO không chặn được thiết kế riêng cho mã ứng dụng. Xem các tệp này nằm trong thư mục nguồn nền tảng frameworks/av/audio_utils :

Công cụ

Theo hiểu biết tốt nhất của chúng tôi, không có công cụ tự động nào để tìm kiếm sự đảo ngược mức độ ưu tiên, đặc biệt là trước khi điều đó xảy ra. Một số công cụ phân tích mã tĩnh nghiên cứu có khả năng tìm thấy các đảo ngược ưu tiên nếu có thể truy cập vào toàn bộ cơ sở mã. Tất nhiên, nếu có liên quan đến mã người dùng tùy ý (như ở đây dành cho ứng dụng) hoặc là một cơ sở mã lớn (như đối với nhân Linux và trình điều khiển thiết bị), thì việc phân tích tĩnh có thể không thực tế. Điều quan trọng nhất là phải đọc mã thật cẩn thận và nắm bắt tốt toàn bộ hệ thống cũng như các tương tác. Các công cụ như systraceps -t -p rất hữu ích để xem mức độ đảo ngược mức độ ưu tiên sau khi nó xảy ra nhưng không cho bạn biết trước.

Lời cuối cùng

Sau tất cả cuộc thảo luận này, đừng sợ mutexes. Mutexes là bạn của bạn trong mục đích sử dụng thông thường, khi được sử dụng và triển khai chính xác trong các trường hợp sử dụng thông thường không quan trọng về thời gian. Nhưng giữa các nhiệm vụ có mức độ ưu tiên cao và thấp và trong các hệ thống nhạy cảm với thời gian, các mutex có nhiều khả năng gây rắc rối hơn.