Proiectarea planificatorului multi-task și a priorității pe 11 SKU-uri de pixuri de citit, cu tabele de latență, mitigarea inversiunii priorităților și diagrame de preempțiune pentru OEM-uri.

Publicat la: iul. 24, 2024 - 99 Vizualizări

Planificator multi-task și prioritate pentru pixuri de citit: comparație între FreeRTOS, Zephyr, AliOS și Linux CFS

Public țintă: Acest ghid este scris pentru inginerii de firmware, arhitecții RTOS și managerii de program OEM care trebuie să înțeleagă cum un stilou de citire își programează playerul audio, decodorul OID, gestionarul de butoane, stiva BLE și agentul OTA — și cum această planificare afectează latența vizibilă pentru utilizator, autonomia bateriei și tichetele de service. Se presupune că știți ce este un fir de execuție sau un task, dar nu este necesar să fiți expert într-un anumit RTOS.

Fiecare stilou de citire rulează mai multe taskuri în același timp, chiar și cel mai mic P2 cu cei 64 KB de Flash ai săi. Pe un T2 tipic s-ar putea găsi 24–38 de taskuri active: un task decodor OID, un task mixer audio, un task UI, un task supervizor Wi-Fi, un task server BLE GATT, un task de staging OTA, un task de monitorizare a bateriei, un task de gestionare a energiei și mai multe taskuri de interogare a senzorilor. Planificatorul decide care dintre ele rulează pe CPU la fiecare microsecundă.

Acest articol vă ghidează prin cele patru familii de planificatoare utilizate în cele 11 SKU-uri ale noastre (B1, B2, F8, K7, KF1, L5, P2, Q6, Q8, T2, Y6) — FreeRTOS, Zephyr, AliOS Things și Linux CFS — și arată atribuirea priorităților, latența de preempțiune, mitigarea inversiunii priorităților și politica de reducere a sarcinii pe care le folosim pentru fiecare.

1. Ce face de fapt un planificator al stiloului de citire

Un planificator are trei sarcini:

  1. Alege ce task rulează pe CPU în fiecare moment.
  2. Impune prioritățile — taskurile cu prioritate mai mare preiau controlul înaintea celor cu prioritate mai mică.
  3. Limitează latența dintre un eveniment (întrerupere, semnal) și trezirea taskului de răspuns.

La un stilou de citire, a treia sarcină este cea mai importantă. Un utilizator atinge cu stiloul un punct OID, senzorul OID declanșează o întrerupere DMA-complete, taskul decodor OID trebuie să se trezească în 1–4 ms pentru a decoda pagina, iar taskul player audio trebuie să înceapă streamingul în 50 ms. Dacă planificatorul nu poate limita această latență, utilizatorul aude o întârziere.

2. Cele patru familii de planificatoare pe care le livrăm

Planificator SO SKU-uri care îl utilizează Model de preempțiune Frecvența tick
FreeRTOS bazat pe tick FreeRTOS 10.4 B1, B2, P2 Preempțiune bazată pe priorități 1 kHz
Zephyr fără tick Zephyr 3.4 LTS K7, KF1 Prioritate + termen 100 Hz linie de bază, inactiv fără tick
AliOS Things AliOS Things 3.3 Q6, Q8 Preempțiune bazată pe priorități 1 kHz
Linux CFS Linux 5.10 / 5.15 F8, L5, T2, Y6 Completely Fair Scheduler cu grup RT 250 Hz / 1 kHz

Împărțirea două treimi RTOS, o treime Linux oglindește piața mai largă a stilourilor de citire. Alegerea planificatorului este determinată de alegerea SO-ului discutată în TC-097, dar politica de priorități este determinată de setul de funcționalități al SKU-ului.

3. Planificator FreeRTOS pe B1, B2, P2

FreeRTOS folosește un planificator preempitiv cu priorități fixe, cu opțiune de diviziune a timpului. Fiecare task are o prioritate de la 0 (cea mai mică) la configMAX_PRIORITIES - 1 (cea mai mare, de obicei 32 sau 56). Planificatorul rulează întotdeauna taskul gata cu cea mai mare prioritate; egalitățile se rezolvă prin felie de timp.

Pe firmware-ul 2.4.7 al B2, tabelul nostru de priorități este:

Prioritate Task Scop Stivă
7 (cea mai mare) audio_dma_isr Handler DMA-complete (amânat din ISR) 256 biți
6 oid_decode Decodare pagină OID 1.024 biți
5 audio_player Decodare MP3 + alimentare DAC 2.048 biți
4 button_handler Debounce + apăsare lungă 512 octeți
3 battery_monitor Sondaj indicator baterie 512 octeți
2 led_pattern Animație LED RGB 256 octeți
1 idle_hook Intrare idle fără tick-uri 128 octeți
0 ota_staging Recepție + scriere OTA 1.024 octeți

ISR-ul este amânat către un task de prioritate ridicată prin xQueueSendFromISR + taskNOTIFY astfel încât fluxul audio nu se blochează niciodată pe cod ISR lung. Prioritatea 7 înseamnă că handler-ul audio DMA rulează înaintea decodorului OID pentru următoarele 200 µs — important deoarece buffer-ul audio DMA trebuie reumplut în maximum 2 ms pentru a evita un glitch.

Sfat pentru cumpărător: Dacă vedeți o fișă tehnică pentru un stilou de citit care listează 4 sau mai puține priorități, fugiți. Stilourile de citit reale au nevoie de cel puțin 6.

4. Scheduler-ul Zephyr pe K7 și KF1

Zephyr folosește un scheduler bitmap multi-coadă, cu planificare opțională pe termen limită. Task-urile au o prioritate de la -16 (cea mai mică) la 15 (cea mai mare). Pe firmware-ul K7 1.9.5 și firmware-ul KF1 1.9.5, tabelul nostru de priorități este:

Prioritate Thread Scop
15 audio_stream_isr Audio DMA finalizat
14 oid_decode Decodare OID
13 audio_player Decodare MP3
12 ble_gatt Server BLE GATT
11 ble_adv Publicitate BLE
10 button_handler Butoane + apăsare lungă
9 battery_monitor Sondaj baterie
8 led_pattern Animație LED
7 sensor_calib Derivă calibrare senzor
6 fs_commit Commit sistem de fișiere
5 ota_staging Staging OTA
4 shell Shell de depanare
3 sys_workq Coadă de lucru sistem
2 idle Idle fără tick-uri

Modul idle fără tick-uri din Zephyr pe K7 reduce consumul de la 4,8 mA la 1,6 mA în stare inactivă. Aceasta este diferența de 3,2 mA care oferă modelului K7 autonomia de 6,0 h cu bateria Li-ion 14500 de 800 mAh.

K7 dispune și de un fir de execuție în modul deadline (audio_player_deadline) folosit atunci când OEM-ul are nevoie de planificare audio sub-milisecundă. Activăm acest lucru prin flag-ul de compilare firmware CONFIG_DEADLINE=y, care este activ în mod implicit pe KF1 și dezactivat pe K7 pentru a economisi 32 KB de Flash.

5. Planificatorul AliOS Things pe Q6 și Q8

AliOS Things 3.3 folosește un planificator preemptiv bazat pe priorități, foarte asemănător cu FreeRTOS. Acceptă până la 32 de priorități și are un nucleu krhino integrat pe care îl folosim din 2022. Tabela de priorități pentru Q6 \/ Q8 arată astfel:

Prioritate Sarcină Scop
7 (cea mai mare) audio_isr Transfer DMA audio încheiat
6 oid_decode Decodare OID
5 audio_player Decodare MP3 + DAC
4 btn_event Debounce buton
3 ble_gatt BLE GATT
3 ble_adv Publicitate BLE
2 pwr_mgr Manager alimentare
2 batt_poll Indicator baterie
1 led_pat Animație LED
0 ota_recv Receptor OTA

Planificatorul AliOS are o comutare de context ușor mai rapidă decât FreeRTOS pe Cortex-M4 deoarece AliOS folosește mai puține registre în contextul salvat. Măsurătoarea noastră este de 1,4 µs pe Q8 față de 2,0 µs pe B2 — semnificativ pentru întreruperile audio de prioritate mare.

6. Planificatorul Linux CFS pe F8, L5, T2, Y6

Linux folosește planificatorul Completely Fair Scheduler (CFS) pe F8, L5, T2 și Y6. CFS nu are priorități fixe în sensul RTOS. În schimb, dispune de:

  • Valori nice de la -20 (cea mai mare) la 19 (cea mai mică).
  • Grup de timp real (SCHED_RR, SCHED_FIFO) cu priorități 1–99.
  • cgroups pentru gruparea sarcinilor.
  • Planificare deadline (SCHED_DEADLINE) pentru sarcini hard-realime.

Pe firmware-ul L5 versiunea 4.2.0, tabelul nostru de priorități este:

Prioritate Linux Proces Descriere
SCHED_FIFO 90 audio-daemon Mixer audio + DAC
SCHED_FIFO 80 oid-decoder Decodare OID
SCHED_FIFO 70 wifi-supervisor Roaming Wi-Fi
SCHED_FIFO 60 ble-gatt BLE GATT
SCHED_RR 50 button-handler Buton + apăsare lungă
SCHED_RR 40 battery-monitor Indicator baterie
nice -10 led-pattern Animație LED
nice -5 ui-thread Randare UI
nice 0 asr-engine ASR pe dispozitiv
nice 5 logging-daemon Telemetrie
nice 10 cache-cleanup Curățare cache
nice 19 analytics Analiză anonimă

Daemon-ul audio rulează la SCHED_FIFO 90 pentru a garanta faptul că preia controlul în fața tuturor sarcinilor, cu excepția celor din kernel. Decodorul OID rulează la SCHED_FIFO 80 astfel încât poate prelua controlul de la motorul ASR. Motorul ASR rulează la nice 0 deoarece este orientat pe debit, nu pe latență.

Pre-emption latency, lower-priority → higher-priority (µs)

7. Inversiunea priorității: capcana

Inversiunea priorității este ceea ce se întâmplă atunci când o sarcină cu prioritate scăzută deține un mutex de care are nevoie o sarcină cu prioritate ridicată. Sarcina cu prioritate ridicată se blochează așteptând sarcina cu prioritate scăzută; între timp, o sarcină cu prioritate medie preia controlul de la sarcina cu prioritate scăzută. Sarcina cu prioritate ridicată rămâne blocată până când sarcina cu prioritate medie cedează.

Am întâlnit inversiunea priorității în teren o singură dată. Pe firmware-ul Q8 versiunea 2.6.4 (predecesorul versiunii 3.3.1), un mutex al UI era deținut de o sarcină de logging cu prioritate scăzută în timp ce playerul audio încerca să-l achiziționeze; playerul audio s-a blocat timp de 12 ms — perceptibil ca un glitch.

Măsura de mitigare pe care o livrăm pe fiecare SKU RTOS este mutex-uri cu moștenire de prioritate (numite și mutex-uri cu plafon de prioritate):

  • FreeRTOS utilizează xSemaphoreCreateMutex() (care acceptă moștenirea de prioritate în versiunile recente).
  • Zephyr dispune de k_mutex cu moștenire de prioritate activată implicit.
  • AliOS dispune de krhino_mutex_create() cu fanionul RHINO_MUTEX_PRIO_INHERIT.
  • Linux utilizează pthread_mutexattr_setprotocol(... PTHREAD_PRIO_INHERIT ...).

Daemonul audio L5 folosește mutexuri POSIX cu moștenire de prioritate în jurul handle-ului ALSA PCM. Decoderul video T2 procedează la fel în jurul handle-ului VPU.

8. Tickless Idle și Economie de Energie

Cea mai mare economie de energie pe care o poate oferi un planificator pe un SKU RTOS este tickless idle. Când nu există nimic de făcut, planificatorul dezactivează întreruperea SysTick, iar CPU-ul intră într-o așteptare de consum redus. Când survine următorul eveniment (expirarea unui timer, o întrerupere), planificatorul trezește CPU-ul și reprogramează SysTick.

Economiile tickless idle pe gama noastră (măsurate la 25 °C, 3,7 V):

SKU Curent inactiv cu tick Curent inactiv tickless Economie
B1 6,4 mA 2,1 mA 67 %
B2 5,9 mA 2,1 mA 64 %
K7 8,2 mA 2,8 mA 66 %
KF1 8,0 mA 2,7 mA 66 %
P2 4,2 mA 1,4 mA 67 %
Q6 6,6 mA 2,4 mA 64 %
Q8 6,4 mA 2,4 mA 62 %

SKU-urile Linux nu au tickless idle în sensul RTOS. Acestea dispun de guvernatori cpuidle care selectează un C-state mai profund când runqueue-ul este gol. F8 atinge 14 mA în inactivitate, L5 atinge 18 mA, T2 atinge 22 mA — în continuare de 4–10× mai mult decât inactivitatea RTOS.

9. Eliminarea Sarcinii și Comportamentul la Supraîncărcare

Când CPU-ul unui pen reader este suprîncărcat — de exemplu, decoderul OID declanșează 5 pagini în 200 ms pentru că utilizatorul răsfoiește o carte — planificatorul trebuie să renunțe la lucrări pentru a menține fluxul audio sănătos. Politica noastră de eliminare a sarcinii:

Condiție Acțiune
Adâncime coadă OID > 8 Elimină cel mai vechi, înregistrează un contor
Aproape de subalimentarea buffer-ului audio Crește scurt prioritatea task-ului OID
Adâncime coadă stivă BLE > 16 Deconectează, reconectează
Adâncime coadă TX Wi-Fi > 32 Elimină cadrele cu prioritate scăzută
Linux loadavg > 4 Limitare motor ASR la 50 % CPU
Baterie < 8 % Dezactivare Wi-Fi, reducere luminozitate LED, comutare pe audio mono

Trucul „creștere temporară a priorității sarcinii OID" este o creștere dinamică a priorității pe care o implementăm în sarcina playerului audio. Când bufferul audio este gol peste 75 %, sarcina audio trimite o notificare decoderului OID pentru a-i crește prioritatea pentru următoarele 50 ms. Acest lucru oferă 20–40 ms de rezervă la B2 în cele mai defavorabile scenarii.

10. Inspecția planificatorului și depanare

Fiecare SKU expune o API de inspecție a planificatorului prin USB sau BLE. API-ul returnează:

typedef struct {
    uint32_t task_id;
    char     name[16];
    uint8_t  priority;
    uint32_t stack_high_watermark;
    uint32_t runtime_us;
    uint32_t cpu_pct;
} task_stats_t;

OEM-urile pot interoga această API în timpul QA din fabrică pentru a captura o „amprentă a planificatorului" — o imagine a utilizării CPU pe sarcină. Am livrat peste 47.000 de astfel de amprente din 2023. Amprenta este neprețuită pentru diagnosticarea problemelor din teren: un stilou care indică battery_monitor consumând 28 % CPU în loc de 4 % așteptat are o eroare de polling undeva.

Pentru SKU-urile Linux, aceleași date sunt disponibile prin /proc/<pid>/stat și top -H. Includem o unealtă scheduler-diag în firmware-ul L5 4.2.0 care capturează amprenta într-un fișier JSON și o trimite prin Wi-Fi la cerere.

11. Cum să alegeți planificatorul potrivit pentru stiloul dumneavoastră de citit

Dacă sunteți un OEM care alege un planificator pentru un nou SKU de stilou de citit, iată regula empirică:

Scenariu de achiziție Planificator recomandat SKU sugerat
Sub 5 $, o singură limbă, doar atingere FreeRTOS bazat pe tick P2, B1
Gama medie bilingv, BLE Zephyr cu tickless K7, KF1
Gama medie cu OID personalizat, BLE AliOS Things Q6, Q8
Wi-Fi + ASR + video 720p Linux CFS cu SCHED_FIFO pentru audio L5
Apărare sau construcție premium, hard real-time Linux cu patch PREEMPT_RT T2
Premium K–12 cu streaming de conținut Linux CFS cu planificare deadline Y6

Sfat pentru cumpărător: Cereți întotdeauna OEM-ului două metrici ale planificatorului: latența de preempțiune în cel mai defavorabil caz de la o sarcină cu prioritate scăzută la una cu prioritate ridicată (µs) și curentul în repaus cu modul tickless activat (mA). Orice valoare peste 10 µs sau 4 mA în repaus este un semnal de alarmă pentru cazurile de utilizare a stilourilor de citit K–3.

Lecturi conexe

Solicitați o ofertă — Obțineți un răspuns rapid din fabrică

Frequently Asked Questions

Ce planificator (scheduler) folosește stiloul de citire B2?
Firmware-ul B2 2.4.7 utilizează planificatorul preemptiv bazat pe tick FreeRTOS 10.4, cu priorități 0–7. Sarcina audio DMA amânată prin ISR rulează la prioritatea 7, decodorul OID la prioritatea 6, iar playerul audio la prioritatea 5. Curentul în repaus este de 2,1 mA la 3,7 V, cu modul tickless idle activat.
Care este latența de preempțiune pe un stilou de citire cu RTOS?
Pe FreeRTOS B2 măsurăm 2,0 µs; pe Zephyr K7 măsurăm 1,8 µs; pe AliOS Q8 măsurăm 1,4 µs; pe Zephyr KF1 cu planificare deadline activată măsurăm 0,9 µs. SKU-urile Linux CFS (F8, L5, T2, Y6) măsoară 18–24 µs din cauza tick-ului planificatorului și a logicii runqueue-ului.
Cum preveniți inversiunea priorităților pe un stilou de citire?
Folosim mutex-uri cu moștenire de prioritate pe fiecare SKU RTOS: xSemaphoreCreateMutex pe FreeRTOS, k_mutex pe Zephyr, krhino_mutex_create cu flag-ul priority-inherit pe AliOS și pthread_mutexattr_setprotocol cu PTHREAD_PRIO_INHERIT pe Linux. Am avut un singur incident de inversiune a priorităților în teren de la începutul anului 2023, remediat în firmware-ul Q8 3.3.1.
Poate un stilou de citire să ruleze simultan sarcini RTOS și Linux?
Nu. Un stilou de citire rulează un singur SO la un moment dat. SKU-urile Linux (F8, L5, T2, Y6) pot găzdui un RTOS ca oaspete sub Jailhouse sau ca thread Xenomai, dar nu livrăm această configurație în producție în prezent. Costul din perspectiva validării și certificării este ridicat, iar beneficiul este redus.
Ce se întâmplă dacă planificatorul este suprasolicitat?
Politica noastră de descărcare a sarcinii (load-shedding) renunță la cea mai veche pagină OID când adâncimea cozii depășește 8, crește scurt prioritatea sarcinii OID când buffer-ul audio este gol peste 80 % (mai mult de 80 % gol), deconectează BLE dacă coada depășește 16 cadre, renunță la cadrele Wi-Fi de prioritate scăzută peste 32 de cadre și limitează motorul ASR la 50 % CPU dacă loadavg Linux depășește 4,0.
Coșul tău
Experiența ta pe acest site va fi îmbunătățită prin acceptarea cookie-urilor. Politica de Cookie-uri
Chat on WhatsApp