Tracing di sistemi Embedded Linux – 2a parte – Scoprire i problemi di prestazioni in un gestore IRQ

Pubblicato il 16 novembre 2021

Nel mese di settembre è apparso il primo di una serie di articoli relativi al tracing di sistemi Linux embedded con Tracealyzer 4.4. Nella prima parte sono state esaminate le prestazioni del driver Linux. In questa seconda parte saranno analizzate le prestazioni di uno dei componenti chiave di ogni driver di dispositivi Linux, il gestore delle interruzioni (IRQ). Inoltre, verrà mostrato come individuare le problematiche e ottimizzare le prestazioni con il tool Tracealyzer for Linux di Percepio.

La prima cosa da ricordare quando di tratta di gestione degli interrupt è l’importanza di limitare il numero di operazioni a carico del gestore IRQ poiché esso mantiene il kernel, al pari dell’intero processore, in una condizione critica. Per fare un esempio, mentre il gestore IRQ è in esecuzione, tutti gli interrupt sono mascherati, cioè nessun altro interrupt può essere attivato, e lo schedulatore di Linux viene effettivamente messo in pausa. Quindi è necessario assicurarsi di fare solo il minimo indispensabile quando si serve un interrupt, come operazioni sui registri o spostamento dei dati nella memoria del processore. La gestione di altre operazioni come il trasferimento dei dati dovrebbe essere delegata a differenti meccanismi del kernel (come a esempio le funzioni tasklet). In questo esempio, la specifica del dispositivo prevede che un interrupt si attivi ogni 80 millisecondi, che rappresenta quindi il tempo massimo a disposizione del gestore IRQ per l’esecuzione.

Eliminare la necessità delle istruzioni print

Tracealyzer for Linux è estremamente utile per assicurare che il nostro gestore IRQ stia facendo il meno possibile. Per esempio, elimina la necessità di qualsiasi printks estranea (che come vedremo potrebbero effettivamente nascondere implementazioni errate) e di confronti di timestamp nei log del kernel, come pure quella di analizzare trace LTTng molto lunghi per valutare le prestazioni. Tracealyzer, invece, fornisce una panoramica chiara e dettagliata sul gestore IRQ.

Ritornando all’esempio del driver Linux per un dispositivo di acquisizione dati analizzato nel precedente post, un segnale dal GPIO informa il driver che i dati sono pronti per essere acquisiti.

Di seguito è descritto il nostro semplice gestore IRQ e il codice per registrarlo nel kernel:

static int __init mab_init(void)

{

  result = request_irq(irq_number, (irq_handler_t) mab_irq_handler,

  IRQF_TRIGGER_RISING, “mab_irq_handler”, NULL);

  return result;

}

 

static irq_handler_t mab_irq_handler(unsigned int irq,

void *device, struct pt_regs *regs)

{

  printk(KERN_INFO “MAB – got interrupt!\n”);

  return (irq_handler_t) IRQ_HANDLED;

}

Una chiamata prink registra semplicemente nel log del kernel che è stato ricevuto un interrupt e il valore restituito IRQ_HANDLED informa il kernel che l’interrupt è stato processato.

In ogni caso, prima di caricare il modulo del kernel e collegare il dispositivo, è necessario avviare LTTng sul target. Poichè in questo esempio si vuole istruire LTTng affinché acquisisca solamente i gestori IRQ, bisogna seguire una procedura leggermente differente rispetto a quello descritto nella guida “Getting Started With Tracealyzer for Linux” di Percepio ed eseguire invece il seguente set di comandi:

$> lttng create

$> lttng enable-event -k irq_*

$> lttng start

Dopo aver trasferito il trace prodotto da LTTng sulla macchina host ed aver avviato Tracealyzer, all’apertura dei grafici di “Trace View”, “Selection Details” (finestra in alto a destra) e “Actor Instance” comparirà la vista di figura 1. Nel grafico di “Actor Instance” è stato selezionato solo il gestore IRQ per focalizzare l’attenzione solo sulle informazioni rilevanti.

Fig. 1 – Il gestore IRQ è attivato all’incirca ogni 80 millisecondi come previsto

Come mostrato in figura 1, l’opzione “Periodicity – From Ready” è stata selezionata nella casella a discesa “View” per evidenziare che il gestore IRQ, come previsto, è stato avviato all’incirca ogni 80 millisecondi. Sfruttando la finestra “Selection Details” per un’analisi più approfondita e aprendo l’opzione “Execution Time”, si può vedere che il gestore IRQ ha impiegato in media 3,3 millisecondi per eseguire le istanze (fig. 2).

Fig. 2 – Il tempo di elaborazione medio con una chiamata printk è di 3,3 ms

Quando la chiamata printk viene rimossa, con il grafico “Actor Instances” visualizzato e la casella “View” ancora impostata su “Execution Time”, Tracealyzer ha evidenziato una significativa riduzione del tempo di elaborazione, passato da 3,3 ms (con printk) a 14 µs (senza printk) (Fig. 3).

Fig. 3 –  Quando la printk è rimossa, il tempo di esecuzione massimo si riduce a 14 µs.

Questa differenza dimostra chiaramente la notevole quantità di elaborazione coinvolta nella chiamata printk, la quale deve considerare una vasta gamma di casi differenti quando stampa sul log del kernel. Per ottenere le migliori prestazioni, l’ovvia conclusione è cercare di evitare l’uso di printk, o di uno qualunque dei suoi derivati, in un gestore IRQ.

La figura 3 mostra anche che il tempo di esecuzione tende a variare in modo irregolare. Sebbene vi siano differenze nell’ordine dei microsecondi, è comunque importante comprendere perché si verifichino tali fluttuazioni. La figura 4 aiuta a identificarne la causa.

Fig. 4 – L’opzione “Periodicity – From Read” aiuta a individuare le cause dei tempi di esecuzione così irregolari

Selezionando l’opzione “Periodicity – From Ready” nella vista “Actor Instances” di Tracealyzer, appare chiaro che qualcosa non quadra. Mentre la parte centrale dell’acquisizione mostra che il gestore IRQ è attivato ogni 80 ms come previsto, il numero di chiamate registrate è molto superiore sia all’inizio sia alla fine dell’acquisizione. La situazione diventa davvero bizzarra verso la fine dell’acquisizione, quando c’è un’istanza di esecuzione dopo 325 millisecondi.

Questo comportamento è spiegato dal fatto che nel gestore IRQ non viene interrotta l’attivazione dell’interrupt. Poichè l’interrupt è sempre presente, lo schedulatore Linux continua a concedere risorse di esecuzione al gestore IRQ: questo fenomeno negativo è noto come “trashing”.

printk maschera il bug

E’ interessante notare che la periodicità del gestore IRQ si stabilizza di nuovo dopo un certo periodo anche se non si è istruito il dispositivo a disattivare gli  interrupt. Da un esame più attento delle specifiche del dispositivo si può notare che un meccanismo fail-safe disattiva automaticamente l’interrupt dopo un certo periodo se non è stato riconosciuto sul bus I2C. Poiché nell’esempio attuale il tempo che intercorre tra le invocazioni del gestore IRQ con printk è di circa 80 millisecondi, il tempo di esecuzione si prolunga sino alla fase di disattivazione, mascherando il fatto che il codice non disattiva in modo adeguato l’interrupt.

Informazione sull’autore

Mohammed Billoo, fondatore di MAB Labs, LLC, può vantare un’esperienza di oltre 12 anni nella definizione di architetture, progettazione, implementazione e collaudo di software embedded, con una particolare enfasi su Linux embedded. La sua attività spazia dal bring-up di schede custom allo sviluppo di driver e di codice applicativo. Mohammed è un attivo contributore per il kernel Linux e partecipa a numerose attività nell’ambito dell’open source.

E’ professore aggiunto di Ingegneria Elettrica presso la “Cooper Union for the Advancement of Science and Art” dove insegna ai corsi di Logica digitale, Progettazione e Architetture di Computer.

Mohammed ha conseguito la laurea e il successivo master in Ingegneria Elettrica presso la stessa istituzione.

Questo è il primo di una serie di articoli focalizzati sull’uso di Tracealyzer per acquisire e analizzare la diagnostica del trace di tipo visuale per sistemi Linux embedded.

Il primo articolo è disponibile al seguente indirizzo:

Utilizzo di Tracealyzer con una distribuzione Linux basata su Yocto – Elettronica Plus (elettronica-plus.it)



Contenuti correlati

Scopri le novità scelte per te x