leOS - un semplice OS per schedulare piccoli task

PaoloP:
Un altro RTOS --> GitHub - BasicPI/Celina: RTOS :frowning:

Bene, vuol dire che l'argomento interessa. :wink:

leo72:

PaoloP:
Un altro RTOS --> GitHub - BasicPI/Celina: RTOS :frowning:

Bene, vuol dire che l'argomento interessa. :wink:

Ehi Leo fai la persona leale niente virus per il nuovo arrivato :stuck_out_tongue_closed_eyes:

Madwriter:

leo72:

PaoloP:
Un altro RTOS --> GitHub - BasicPI/Celina: RTOS :frowning:

Bene, vuol dire che l'argomento interessa. :wink:

Ehi Leo fai la persona leale niente virus per il nuovo arrivato :stuck_out_tongue_closed_eyes:

Ancora ]:smiley:
Ma la tua è una fissa eh :smiley:

leo72:

Madwriter:

leo72:

PaoloP:
Un altro RTOS --> GitHub - BasicPI/Celina: RTOS :frowning:

Bene, vuol dire che l'argomento interessa. :wink:

Ehi Leo fai la persona leale niente virus per il nuovo arrivato :stuck_out_tongue_closed_eyes:

Ancora ]:smiley:
Ma la tua è una fissa eh :smiley:

Non lo faccio più scusa però non inviarmi nessun virus :stuck_out_tongue_closed_eyes:
si cosi la smetti di vantarti :grin:
ps naturalmente scherzo :smiley:

L'altro giorno mi è venuta in mente una visione pazza, (si lo so mi capita e ormai ci ho fatto l'abitudine).

Io ho codice C simile al LeoOS, però uso una linked list per i task, quindi volendo posso anche inserire un task tra due task e ovviamente li posso anche spostare. Spostarli non comporta l'esecuzione di un task perchè esso dipende dal tempo che scorre, ma li si potrebbe ordinare in base ad valore appartenente al task, come avviene per i processi unix dove c'è il "nice".

La pensata pazza è stata quella di non realizzare per forza task a tempo ma anche task immediati e ogni task fa capo ad una macchina a stati, la pensata pazza è stata quella di inserire un task (task1) tra due (task0 e task1) che una volta eseguito elimina il task precedente o seguente a secondo di cosa c'è scritto nel task appena inserito. I task instantanei vengono eseguiti subito e possono modificare l'ordine di esecuzione dello "scheduler".

Non ho ancora ben chiaro dell'utilizzo di questa cosa, ma mi chiedevo con LeoOS posso inserire un task tra due o rimovere il primo e via discorrendo?

Perchè avrei intenzione di giocare un po con C++ le classi ecc usando LeoOS come base per i miei esperimenti folli.

Ciao.

se non ricordo male il LeoOS calcola il tempo necessario per eseguire il task, e se inferiore al tempo di attesa attuale viene settato il "trigger" di conseguenza.

Quindi all'avviarsi del trigger si cercano tutti i task che soddisfano la condizione (indipendentemente dalla posizione, anche se creare un'array/lista ordinata è semplice) e li esegue.

sono a "disfavore" dell'uso della lista, meglio un array dinamico con malloc/realloc (ho postato del codice mese scorso), ed inserimento ordinato (come per la tua lista)

Il leOS ed il leOS2 funzionano basandosi su un contatore hardware (timer o watchdog) che viene usato per generare un interrupt ad intervalli regolari (1 ms col leOS, 15/16 ms col watchdog nel leOS2).

Quando il timer va in overflow, viene incrementata una variabile che misura lo scorrere del tempo secondo l'unità e controlla, tutte le volte che viene chiamata l'ISR, se c'è un task che ha il tempo di attivazione uguale al valore attuale. Se sì, viene chiamata la corrispondente routine, poi il controllo torna allo scheduler, che prosegue l'analisi nell'elenco dei task.
Al termine, si esce dalla ISR.

La priorità di controllo è fatta sullo stato del task, se sospeso o eseguibile.
In caso sia sospeso, si passa al successivo. Se è invece eseguibile, si controlla se è venuto il suo tempo e, nel caso, viene eseguito. Quando il task termina, si controlla se era un task di tipo ONETIME, nel caso viene cancellato dallo scheduler, e la lista ricompattata in modo da non dover controllare celle della stessa vuote nelle successive chiamate dello scheduler.

I task vengono aggiunti allo scheduler a seconda del loro ordine di inserimento, per cui il task A, se inserito prima del task B, a parità di tempo di intervallo, sarà sempre eseguito prima di quest'ultimo.

Si può inserire un task tra 2 task già presenti perché appunto lo scheduler controlla tutte le volte l'elenco e non esegue solo il primo della lista.

Anzi, mauro, il tuo post mi ha fatto venire in mente che si potrebbe prevedere un valore di "nice" in modo che se due task arrivano ad essere eseguibili nello stesso istante, lo scheduler scelga quello con un valore di nice superiore.
Questo può capitare anche con 2 task che hanno intervalli differenti (minimo comune multiplo). Ad esempio, un task inserito per essere eseguito ogni 300 ms ed un task inserito per girare ogni 1000 ms, dopo 3000 ms saranno eseguibili nello stesso istante.

lesto:
sono a "disfavore" dell'uso della lista, meglio un array dinamico con malloc/realloc (ho postato del codice mese scorso), ed inserimento ordinato (come per la tua lista)

Sempre che questo non impatti negativamente sulle performance.
Tempo fa mi furono dati dei suggerimenti sul forum internazionale. Alcuni li ho seguiti, altri no. Quelli che non ho seguito, non l'ho fatto o per mia carenza di conoscenze del C/C++ o perché, all'atto pratico, mi aumentavano il consumo di risorse de leOS. Siccome il leOS gira anche su piccoli Tiny, l'impatto sulla Flash e/o RAM ha la priorità sull'eleganza del codice.

guarda che l'arry è sempre più efficiente nell'USO (sia lato uso CPU che uso RAM che dimensione sketch) della lista.

L'unico vantaggio della lista è che non occupa uno spazio di RAM contiguo, ed è (molto) più veloce negli inserimenti/rimozioni, specialmente se non in coda (l'array necessita di copiare tutti i dati fino all'elemneto da inserire, che va poi inserito, nel caso migliore in cui ha successo la realloc, nel caso peggiore devi riscopiare tutto.. ciò vuol dire un uso di RAM DOPPIO del necessario in fase di copia).
nella lista ogni "cella" può essere sparza per la RAM.. cosa che su un micro crea frammentazione della RAM.

Forse c'è un'incomprensione sui termini.
Quando parlo di elenco o lista di task, io non uso il template "list" ma un "array". Non lo alloco/disalloco dinamicamente ma lo lascio statico in memoria e ne riempio o svuoto le celle a seconda se devo inserire o rimuovere task.

ah, quindi c'è un numero massimo di task harcodato e la libreria usa una dimensione fissa di ram (a meno che l'array non sia di puntatori a una struttura/classe)

lesto:
ah, quindi c'è un numero massimo di task harcodato e la libreria usa una dimensione fissa di ram (a meno che l'array non sia di puntatori a una struttura/classe)

Definisco una struct contenente i dati di ogni task: puntatore alla funzione da eseguire, intervallo di esecuzione, tempo della prossima esecuzione, stato del task. Uso poi la struct come tipo dati per creare un array che conterrà i task, e poi gestisco i task modificando i dati nell'array.

Ma l'array viene dimensionato durante l'inizializzazione con un numero prefissato di task (fissato a 10 ma modificabile dall'utente) e non viene più cambiato durante il corso dell'elaborazione. In questo modo mi assicuro appunto che sia un blocco in Ram non frammentato: il compilatore dovrebbe infatti riservare uno spazio contiguo in memoria per memorizzare l'array.

Non litigate. Usare free su una struttura allocata prende il suo tempo, il mio codice alloca le struttura su cui inserire il task dinamicamente e quindi poi devo usare free per forza.

Mentre nel caso di LeOS, tutto è statico e quindi più rapido perché anche le struct sono statiche come l'array, quello che invece non capisco è come sia possibile inserire un task tra due task consecutivi, uno lo devi spostare copiando i dati nella struttura seguente e questo si richiede tempo, ma dipende da quanti dati si devono spostare. Oppure non ho capito come funziona LeOS.

PS: la parte della ISR la uso pure io e al tempo ho preso codice che hai postato e poi ci sono andato a prendere ancora da LeOS ecc. 8)

Comunque non ho ancora ben chiaro come sfruttare l'idea che mi e venuta e non so le... ho le idee confuse devo raggionarci su. :roll_eyes:

I task potrebbero essere più complessi fino a implementare una classe FSM, con le transizioni gli stati ecc, c'è chi per arduino ha scritto codice FSM con classi, ma non ho trovato mai il tempo per studiarla.

In ogni caso grazie per la discussione, ma se volete ancora commentare circa la mia idea folle vi seguo volentieri. :slight_smile:

Ciao.

Non sposti materialmente i task ma solo i dati che li riguardano nell'array.
I dati sono appunto il puntatore ed i timing relativi all'intervallo.

Tu quindi hai un array pluridimensionale, in ogni cella hai l'indirizzo del task. Per spostare un task, basta prendere i dati dei task da muovere e copiarli più avanti.
Se vedi, il leOS può cancellare dei task che sono anche inseriti in mezzo ad altri.

No leo ho una lista lincata, detta linked list.

Ogni elemento della lista è una struttura allocata dinamicamente con malloc, un campo della struttura e di tipo puntatore al stesso tipo di strutura

Quindi leggendo la prima struttura, questa ha un puntatore alla struttura seguente.

#define STOP 0
#define RUN  1

// serve a definire una funzione predisposta per essere un task
#define TASK(name) void name(volatile timer_t *const me)

// questa struttura viene creata dinamicamente chiamando la funzione timer_t *timer_create(uint16_t millis);
typedef struct timer_t timer_t;
struct timer_t {
    uint16_t ms;
    void (*timer_callback) (volatile timer_t *const me);
    int8_t state;
    volatile timer_t *next_timer;   // vedi questo punta a NULL se non ci sono altri elementi della lista lincata, e punta al prossimo elemntento della lista
se c'è, l'ultimo elemento inserito ovviamente punta a NULL
};


typedef struct sys_tick_t sys_tick_t;
struct sys_tick_t {
    uint16_t ms;
    unsigned long seconds;
    volatile timer_t *timer;
};

sys_tick_t* sys_tick_create();


timer_t *timer_create(uint16_t millis);


// serve ad impostare la funzione utente tramite il passagio di puntatore a funzione.
void timer_set_callback(volatile timer_t *const me,
                        void (*timer_callback) (volatile timer_t *const me));

double current_time();

void append_task(volatile timer_t *const timer);
void remove_task(timer_t *const timer);

Si usa così:

#include <mio.h>
#include <sys_timer.h>

#define LED_BLINK_10MS     GPIO_B5      // led sul piedino PB5

sys_tick_t  *system_tick;               // system timer pointer
timer_t     *timer_blink_10ms;          // puntatore a timer


TASK(blink_10ms)
{
    // 'me' is object, see the macro TASK
    gpio_flip(LED_BLINK_10MS);
    me->ms = 10;
}

static void ioinit()
{
    gpio_mode(LED_BLINK_10MS, out_mode);
}

int main()
{

    ioinit();
    // crea il timer di sistema, che scandisce un tick ogni millesimo di secondo
    system_tick = sys_tick_create();

    // crea una timer
    timer_blink_10ms = timer_create(10);
    // assegna al timer creato prima il task blink_10ms
    timer_set_callback(timer_blink_10ms, blink_10ms);
    // rende il timer pronto per essere avviato
    timer_blink_10ms->state = RUN;
    // aggiunge la funzione timer_blink_10ms al task manager
    append_task(timer_blink_10ms);
    // abilita globalmente l'interrupt, diversamente non funzionano il timer di sistema e la gestione dei task
    sei();

    while(1);
}

Questo è il modo di programmare con il C come se si trattasse di oggetti ed è usato da gtk, ma io l'ho appreso leggendo questo: Design Patterns for Embedded Systems in C: An Embedded Software Engineering ... - Bruce Powel Douglass - Google Libri

Ciao.

Ups... fraintendevo... pensavo continuassi a chiedermi come funzionava il leOS :sweat_smile:

leo72:
Tu quindi hai un array pluridimensionale, in ogni cella hai l'indirizzo del task.

questa è una cosa che vorrei chiarire. Tu haiun array di struct, o di puntatori a struct? nel primo caso l'array in se è grande ma non frammenta i dati, nelsecondo ogni elemento va allocato e quindi frammenti la ram ma non usi tutta la ram necessaria a tenee tutti i task ma solo quelli che usi

leo72:
Ups... fraintendevo... pensavo continuassi a chiedermi come funzionava il leOS :sweat_smile:

Anche io ho frainteso, tu scrivi:

Tu quindi hai un array pluridimensionale, in ogni cella hai l'indirizzo del task. Per spostare un task, basta prendere i dati dei task da muovere e copiarli più avanti.
Se vedi, il leOS può cancellare dei task che sono anche inseriti in mezzo ad altri.

E io lo inteso come una domanda ma il ? non c'è e quindi ho frainteso.

Comunque ho capito, in leOS c'è un array di struct composto di 10 elementi e se vuoi inserire taskA tra task0 e task1, devi spostare task1 più avanti compiando i dati ma se c'è anche task2 e 3 devi spostare questi copiandoli e questo si che prende tempo.

Ciao.

@lesto:
ho un array di struct. Solo un elemento dello struct è un puntatore.

@mauro:
esattamente