Forum: Projekte & Code Cmsis RTOS RTX: Stack Kontrolle


von Mehmet K. (mkmk)


Angehängte Dateien:

Lesenswert?

Servus allerseits

Ich benutze hin und wieder CMSIS-RTOS RTX; nicht aber Keil.
Waehrend man in uVision den Stack-Zustand sehr bequem kontrollieren 
kann, zappelt man ohne diese IDE wie ein Fisch auf dem Lande.
Zumindest erging es mir so.
Mit Hilfe von http://www.keil.com/support/docs/3735.htm habe ich mir 
eine Notlösung zusammengeschustert.

Die Grundsteine bilden
1
typedef enum _task_id_pool_t
2
{
3
    MAIN_TASK = 0,
4
    SCAN_TASK,
5
    BTN_TASK,
6
    BTN_SHUTDOWN_TASK,
7
    LED_TASK,
8
    ADC_TASK,
9
    SHT21_TASK,
10
    MHZ19_TASK,
11
    ANZ_TASKS,
12
} task_id_pool_t ;
--
1
struct stru_task_id_pool
2
{
3
    osThreadId  id_nr;
4
    const char *name;
5
    uint16_t   stk_size;
6
    uint16_t   stk_used;
7
    uint16_t   stk_left;
8
    uint16_t   proz_used;
9
} ;

und
1
stru_task_id_pool task_id_pool[ANZ_TASKS];

Einen Thread muss man nach Erschaffung in diesen pool eintragen. Z.Bsp.:
1
    button_id  = osThreadCreate(osThread(ButtonTask), NULL);
2
    task_id_pool[BTN_TASK].id_nr = button_id;
3
    task_id_pool[BTN_TASK].name  = "BTN_TASK";

In main() wird alle 500ms die Funktion CheckThreadStacks() aufgerufen.
Desweiteren füge ich überall dort, wo ich eine expliziete Kontrolle 
durchfühen will, diese Zeilen ein:
1
#if defined(DEBUG)
2
        CheckThreadStacks(ANZ_TASKS, (uint8_t *)&task_id_pool);
3
#endif

Hat mir mein Leben um einiges erleichert.

von Bernhard (Gast)


Lesenswert?

Hallo!
Vielen Dank für Deinen Code!
Ich habe genau das gleiche Problem und habe den Code implementiert.
Es sollte vielleicht erwähnt werden dass in RTX_Conf_CM.c die Option 
"OS_STKINIT" auf 1 stehen muss.
Der Code in main.cpp nach osKernelStart() wird nicht ausgeführt.
Dieser sollte ggf. in einem anderen Task stattfinden.

Eine Sache verstehe ich aber nicht ganz:

Das Programm erzeugt drei Tasks, einer davon wird aber nie bearbeitet:

TASK1: 0%
TASK2: 76%
TASK3: 60%

Bei TASK1 wird immer 0% stack usage angezeigt.

Irgendeine Idee?

Grüße,
Bernhard

von Bernhard (Gast)


Lesenswert?

Wenn ich 4 Tasks prüfen lasse obwohl nur 3 in der Liste sind, klappt 
auch die Anzeige des TASK1.
Kann es sein dass der Idle-Task irgendwo noch in die Quere kommt?

von Bernhard (Gast)


Lesenswert?

Ich habe in stack_check.c die Zeile

for (tcb_idx = 0; tcb_idx < max_tasks; tcb_idx++)

in

for (tcb_idx = 1; tcb_idx < max_tasks+1; tcb_idx++)

abgeändert.
[0] ist der Idle-Task und der interessiert uns nicht.

Jetzt siehts aus als würde es klappen.
Wie siehst Du das?

von Mehmet K. (mkmk)


Lesenswert?

Zuerst einmal ein kleiner Bug-Report, den ich, weil sich für den Code 
bis heute niemand interessiert hat, nicht nachgereicht habe, um kein 
unnötiges Rauschen im Forum zu erzeugen:

Zeile 51:
1
*stk_used =  (size - ((virgin + 1))) * sizeof(stk);

sollte lauten:
1
*stk_used =  (size - virgin) * sizeof(stk);


Das mit dem Idle-Task hat mir am Anfang auch sehr viel Kopfzerbrechen 
bereitet. Aber nachdem was ich so gelesen habe und selbst ausprobiert 
habe, scheint Idle nicht analysiert zu werden.
task_id_pool[0].name ist bei mir stets die MAIN_TASK; also der erste 
Eintrag im task_id_pool_t.
Desweiteren setze ich das Array immer grösser als in Wirklichkeit 
gebraucht.
z.Zt. habe ich 18 Task; gesetzt habe ich aber extern stru_task_id_pool 
task_id_pool[20]
Erstens, weil es nicht schadet; und zweitens, weil ich im Eifer des 
Gefechts bei einem neuen Task dazu neige, das korrekte Nachzuführen 
dieses Array zu vergessen.

Deshalb kann Deine Beanstandung
1
for (tcb_idx = 1; tcb_idx < max_tasks+1; tcb_idx++)
durchaus richtig sein; weil Du ja das Array immer korrekt Deinem 
tatsaechlichen Bedarf anpassen tust.
Werde es mal in den kommendan Tagen in Ruhe nochmals durchgen und Dir 
Bescheid geben.

Danke für Dein Feedback.

von Mehmet K. (mkmk)


Lesenswert?

Bernhard schrieb:
> Der Code in main.cpp nach osKernelStart() wird nicht ausgeführt.
> Dieser sollte ggf. in einem anderen Task stattfinden.

Bei mir ist das aber kein Problem. Normalerweise kommt ja nach 
osKernelStart() ein osDelay(osWaitForever), oder, wie in diesem Fall, 
ein Endlos-Loop.
Kannst Du mal Dein main() mal hier posten?

von Bernhard (Gast)


Lesenswert?

Danke für den Patch, den habe ich gerade implementiert!

Die for-Schleife die ich änderte betraf nicht die Struktur 
task_id_pool_t sondern die Task-Control-Blocks (TCB) des RTOS.
task_id_pool_t wird korrekt von 0 an ausgelesen (siehe Schleife in 
InsertValues) nur bei den TCBs ist TCB[0] offensichtlich der Idle-Task 
der uns ja nicht interessiert.
Jeder Task im System beginnt ab Index 1. Daher ist nach der Änderung 
auch die Ausgabe korrekt.

osKernelStart() initiiert das Task-Scheduling und kehrt nie zurück.

Trotzdem hier mein main()
1
int main(void)
2
{
3
4
  osKernelInitialize();
5
6
  // Init Hardware
7
  SYS_CPU_INIT();
8
9
  // Init debug UART
10
  dbg_init();
11
12
13
  // Init RTOS primitives
14
  TWI_RTOS_init();
15
  SSP_RTOS_init();
16
17
  // Create tasks
18
  diag_task_id = osThreadCreate(osThread(diag_task), NULL);
19
  ssp_task_id = osThreadCreate(osThread(ssp_task), NULL);
20
  main_task_id = osThreadCreate(osThread(main_task), NULL);
21
22
  task_id_pool[0].id_nr = main_task_id;
23
  task_id_pool[0].name  = "MAIN_TASK";
24
25
  task_id_pool[1].id_nr = ssp_task_id;
26
  task_id_pool[1].name  = "SSP_TASK";
27
28
  task_id_pool[2].id_nr = diag_task_id;
29
  task_id_pool[2].name  = "DIAG_TASK";
30
31
  // Start RTOS
32
  osKernelStart();
33
34
  for (;;);
35
}

Was die Größe der Arrays angeht: Ja, man kann bei genug RAM sicherlich 
gleich 10 oder mehr Einträge vorsehen. Das Problem dabei ist nur das 
wenn man sich um den Stack sorgen macht, meistens auch der RAM des 
Controllers reich befüllt ist.
Daher gehe ich auf Nummer sicher und definiere nur das was ich wirklich 
brauche.
Notfalls kann man ja auch mit einem

assert(max_tasks == (sizeof(task_id_pool)/sizeof(task_id_pool[0]));

schwerwiegende Fehler abfangen.

von Bernhard (Gast)


Angehängte Dateien:

Lesenswert?

Beiliegend meine aktuelle Arbeitskopie.

In main.c trägt man noch das ein, Beispiel für drei Tasks:
1
#include "stack_check.h"
2
3
task_id_pool_t task_id_pool[3];
4
5
int main(void)
6
{
7
 osKernelInitialize();
8
9
 task1_id = osThreadCreate(osThread(task1), NULL);
10
 task2_id = osThreadCreate(osThread(task2), NULL);
11
 task3_id = osThreadCreate(osThread(task3), NULL);
12
13
 task_id_pool[0].id_nr = task1_id;
14
 task_id_pool[0].name  = "TASK1";
15
16
 task_id_pool[1].id_nr = task2_id;
17
 task_id_pool[1].name  = "TASK2";
18
19
 task_id_pool[2].id_nr = task3_id;
20
 task_id_pool[2].name  = "TASK3";
21
22
 // Start RTOS
23
 osKernelStart();
24
25
 for (;;);
26
}

An beliebiger Stelle der Software veranlasst man die Prüfungen:
1
 CheckThreadStacks(3, task_id_pool);

Die Auslastung kann man sich hiermit anzeigen lassen:
1
 for (i = 0; i < 3; i++)
2
   printf("%s: %d%%\n", task_id_pool[i].name, task_id_pool[i].perc_used);

von Mehmet K. (mkmk)


Lesenswert?

Bernhard schrieb:
> osKernelStart() initiiert das Task-Scheduling und kehrt nie zurück.

Aus dem CMSIS-RTOS RTX  Version 4.80 Reference Manual:
https://www.keil.com/pack/doc/CMSIS/RTX/html/group___c_m_s_i_s___r_t_o_s___kernel_ctrl.html#gaab668ffd2ea76bb0a77ab0ab385eaef2

Returns
    status code that indicates the execution status of the function.

Start the RTOS Kernel and (...). The main thread will continue executing 
after osKernelStart.

Status and Error Codes
    osOK: the RTOS kernel has been successfully started.
    osErrorISR: osKernelStart cannot be called from interrupt service 
routines.

Also wenn bei Dir osKernelStart() nicht mehr zurückkehrt, muss irgendwas 
fehlgeschlagen haben.

von Bernhard (Gast)


Lesenswert?

@Mehmet: Danke für den Hinweis, wird geprüft. Kann sein dass ich hier 
was verwechselt habe da ich mehrere unterschiedliche RTOS nutze. Ich 
dies zu entschuldigen.

Könnte man den Code noch erweitern dass man den sonstigen noch freien 
Speicherplatz im RAM angezeigt bekommt?

von Mehmet K. (mkmk)


Lesenswert?

Soweit ich es verstanden habe, wird in RTX_Conf_CM.c der RAM-Bedarf für 
das RTOS bekanntgegeben - und das wird dann beim Linken bereitgestellt. 
Was die MCU sonst noch an RAM hat, interessiert das RTOS nicht.
Auch ist natürlich zu hinterfragen, welches RAM man eruieren will: 
Stack? Heap?

Mit Stack ist es realiv einfach: man ruft eine Funktion auf, in der man 
die Adresse einer lokalen Variablen sich anschaut.
Beim Heap ist es etwas komplizierter. Die nachfolgende Funktion ist 
übrigens nicht auf meinem Mist gewachsen; ich glaube stammt von meiner 
Arbeit mit ChibiOS/RT, als ich Probleme mit dem Heap hatte.
1
//========================
2
// CheckHeapSize
3
//========================
4
void CheckHeapSize(void)
5
{
6
    extern uint8_t __heap_base__[];
7
    extern uint8_t __heap_end__[];
8
    const uint32_t MIN_HEAP_SIZE = 4 * 4096;
9
10
    static uint8_t *nextmem;
11
    static uint8_t *endmem;
12
13
14
    nextmem = (uint8_t *)MEM_ALIGN_NEXT(__heap_base__);
15
    endmem  = (uint8_t *)MEM_ALIGN_PREV(__heap_end__);
16
    //lint -e{732}    Loss of sign (assignment) (int to unsigned long)
17
    App.heap_size = endmem - nextmem;
18
19
    assert_param(App.heap_size > MIN_HEAP_SIZE);
20
    if (App.heap_size < MIN_HEAP_SIZE)
21
    while(1)
22
    {
23
        ;
24
    }
25
}

heap_base und heap_end müssen im Linker-File definiert werden.

von Mehmet K. (mkmk)


Lesenswert?

Bernhard schrieb:
> Was die Größe der Arrays angeht: Ja, man kann bei genug RAM sicherlich
> gleich 10 oder mehr Einträge vorsehen. Das Problem dabei ist nur das
> wenn man sich um den Stack sorgen macht, meistens auch der RAM des
> Controllers reich befüllt ist.

Ich finde das keine so gute Idee. Wenn ich ein Projekt anwerfe, bei dem 
ich mir sicher bin, dass mir eine MCU mit 20kB RAM ausreicht, so löte 
ich auf die PCB, mit der ich meine Software entwickle, nichtsdestotrotz 
einen mit der max. erhaeltlichen Speichergrösse drauf.
Insbesondere bei Applikationen mir RTOS macht es echt keinen Spass, wenn 
man mit RAM geizig umgehen muss. Da sollte man schon mit der grossen 
Kelle ausschenken. Erst dann, wenn alles mehr oder weniger sauber 
laeuft, kann man dazu übergehen sich darüber Gedanken zu machen, wo ein 
Kaviarlöffel angebrachter waere.

von Bernhard (Gast)


Lesenswert?

Wenn ich ein Projekt selbst in der Hand hätte, dann würde ich auch einen 
Controller mit viel RAM nutzen.

Anders wenn man für eine vorgegebene Hardware eine Software entwickeln 
soll.
Derzeit hat mein Controller 8kB RAM. Andere pincompatible Controller mit 
mehr RAM gibts leider nicht.

von Mehmet K. (mkmk)


Lesenswert?

In der Tat kann man da nicht mit der Kelle hantieren :)
Noch ein Tipp: alles was nicht einer Task gehört, gehört der Main-Task. 
Wenn Du also waehend der Entwicklung noch RAM zu vergeben hast, dann 
vergebe es der Main-Task.
Das hatte ich anfaenglich nicht beachtet.


Eine Frage: ist es Dir gelungen in RTX_Conf_CM.c den Wert von OS_STKSIZE 
zu aendern? Da kann ich machen was ich will; es bleibt stur bei 200 
Bytes.

von Bernhard (Gast)


Lesenswert?

Der Wert von OS_STKSIZE steht bei mir fest auf 50.
Defaultwert sind 200 Words, also 400 Bytes.
Das funktioniert einwandfrei.

Gerade habe ich nochmal nachgeprüft, nach dem Aufruf von 
osKernelStart(); kehrt die Funktion definitiv nicht zurück.

Meine main() Funktion hatte ich oben schon gepostet.
Geprüft habe ich das folgendermaßen:
1
// Start RTOS
2
osKernelStart();
3
4
for (;;) {
5
 osDelay(500);
6
 CheckThreadStacks(3, task_id_pool);
7
}

Die Werte im task_id_pool stehen auch nach Minuten immernoch auf 0. Hier 
wird nichts berechnet.
Wenn ich die Funktion CheckThreadStacks in eine andere Task setze, dann 
klappts einwandfrei.

Ich kann übrigens auch ein NVIC_SystemReset(); nach dem osKernelStart(); 
setzen, das System läuft ohne Unterbrechung weiter...

von Mehmet K. (mkmk)


Lesenswert?

Bernhard schrieb:
> Der Wert von OS_STKSIZE steht bei mir fest auf 50.

Schon klar; ich wollte nur wissen, ob Du diesen Standartwert aendern 
kannst. Ich konnte es naemlich nicht.

Wenn Du aus dem osKernelStart() nicht mehr zurückkehrtst ... hast Du 
möglicherweise Tasks mit höherer Prioritaeten? In diesem Fall musst Du 
aber schon dafür sorgen, dass auch Tasks mit niedrigen Prioritaeten zum 
Zug kommen.

von Bernhard (Gast)


Lesenswert?

>Schon klar; ich wollte nur wissen, ob Du diesen Standartwert aendern
>kannst. Ich konnte es naemlich nicht.

Ja, den konnte ich ändern, funktioniert einwandfrei.
Umgebung: gcc auf Konsole :)

>Wenn Du aus dem osKernelStart() nicht mehr zurückkehrtst ... hast Du
>möglicherweise Tasks mit höherer Prioritaeten?

Die Tasks haben alle osPriorityNormal.

main() selbst ist bei mir kein eigener Task, das wäre nur bei z.B. Keil 
mit der Microlib so. Siehe RTX_CM_lib.h ab Zeile 254 (RTX v4.80)

von Pic T. (pic)


Lesenswert?

Wieviel tasks sowie welche IPC brauchst du? Eventuell habe ich was 
kleineres.

von Bernhard (Gast)


Lesenswert?

Eigentlich brauche ich nur 2 Tasks, ein Main-Task und ein Task als 
Treiber für einen Kommunikationschip. Als IPC nutze ich nur 
SendMessage/GetMessage

Im Grunde würde ein einfacher Task-Scheduler ohne Prioritätem schon 
reichen, CMSIS RTX war bisher die erste Wahl da gut dokumentiert, frei 
verwendbar und einfach zu implementieren.

von Mehmet K. (mkmk)


Lesenswert?

Bei 2 Tasks würde ich persönlich Protothreads benutzen.

von Pic T. (pic)


Lesenswert?

Ich bin noch 1 woche in Urlaub, errinnere mich eventuell wenn ich 
vergesse den code sowie die RAM Analyse zu posten, rtx MIT cmsis glue .h 
file

von Random .. (thorstendb) Benutzerseite


Lesenswert?

Hi,

ich hab ähnliches beim alten RTX gemacht. Mich interessierte aber nur 
die maximale Stack Auslastung zwecks Optimierung.

Dazu einfach die Stacks (User defined Stacks bei mir) vor der OS 
Initialisierung (im CMSIS RTOS in der SystemInit() bzw. pre-main) per 
memset auf 0xcc setzen und dann zur Laufzeit mit einen Memory Window 
beobachten.

: Bearbeitet durch User
von Bernhard (Gast)


Lesenswert?

Protothreads ist leider nicht verwendbar.
Ich brauche ein richtiges OS mit getrennt voneinander laufenden Tasks.
CMSIS RTX macht das eigentlich schon ganz gut, auch hinsichtlich 
Resourcenverbrauch.
Wenn jemand was anderes kennt bin ich gerne Interessiert.

von LTC1043 (Gast)


Lesenswert?

scmrtos ist ganz schön schlank :-) und vile Details elegant gelöst.

http://scmrtos.sourceforge.net/ScmRTOS
https://github.com/scmrtos/scmrtos

Cheers

von Andi (Gast)


Lesenswert?

Hallo,

ich nutze:

RTX 5.0.0
CMSIS-RTOS2  Version 2.1.1

gibt es hier auch eine Möglichkeit die Stack Kontrolle zu 
implementieren?

bei mir scheitert es daran das os_Stackinfo und os_active_TCB ( beide 
jeweils in der RTX_CM_lib.h definiert) unbekannt sind -->RTX_CM_lib.h 
gibts nicht bei der V2)

hat das schon mal jemand probiert?

Nutzt jemand die V2.1.1 und hat eine Lösung parat die proz. sich 
anzeigen zu lassen?

für die Stacksize gibt es ja eine implementierte Funktion.

Grüße

Andi

von Andreas H. (Firma: Infineon) (andreas11)


Lesenswert?

Andi schrieb:
> Hallo,
>
> ich nutze:
>
> RTX 5.0.0
> CMSIS-RTOS2  Version 2.1.1
>
> gibt es hier auch eine Möglichkeit die Stack Kontrolle zu
> implementieren?
>
> bei mir scheitert es daran das os_Stackinfo und os_active_TCB ( beide
> jeweils in der RTX_CM_lib.h definiert) unbekannt sind -->RTX_CM_lib.h
> gibts nicht bei der V2)
>
> hat das schon mal jemand probiert?
>
> Nutzt jemand die V2.1.1 und hat eine Lösung parat die proz. sich
> anzeigen zu lassen?
>
> für die Stacksize gibt es ja eine implementierte Funktion.
>
> Grüße
>
> Andi

Hallo,

bitte obigen Beitrag ignorieren. Ich hatte einen Denkfehler:) Hat sich 
alles geklärt...

Grüße

Andi

von Nop (Gast)


Lesenswert?

Mehmet K. schrieb:

> In main() wird alle 500ms die Funktion CheckThreadStacks() aufgerufen.

Ich sehe nicht so recht, wozu das überhaupt gut sein sein soll. Man muß 
sowieso auf maximale Stack Usage auslegen, und die durch Ausprobieren 
ermitteln zu wollen, ist grober Pfusch. Das ermittelt man durch den Call 
Graph.

von Andreas H. (Firma: Infineon) (andreas11)


Lesenswert?

Nop schrieb:
> Das ermittelt man durch den Call
> Graph.

und das soll wie genau funktionieren? Kannst du das bitte genauer 
erklären?

von Nop (Gast)


Lesenswert?

Andreas H. schrieb:

> und das soll wie genau funktionieren? Kannst du das bitte genauer
> erklären?

Mit Keil drückt man auf "build project" und guckt sich dann den Call 
Graph an. Der wirft einem automatisch die maximale Stacktiefe jeder 
Funktion mit Unteraufrufen aus. Der Bedarf eines Tasks ist dann die 
Tiefe der Entry-Funktion. Plus noch Sicherheitsspielraum, damit man 
nicht bei jeder kleinen Code-Änderung erneut gucken muß.

Beim GCC ist das etwas hakeliger. Man gibt -fstack-usage mit, bekommt 
dann aber nur pro Funktion den Bedarf ohne Aufrufe. Hab aber mal im Netz 
dazu ein Perl-Script gesehen, was das aufaddieren kann, solange man 
keine Rekursion verwendet. Oder, solange es ein überschaubares Projekt 
ist, kann man das von Hand machen. Muß man dann aber pro Aufruf noch ein 
paar Bytes extra einrechnen, das hängt von der Plattform und der 
verwendeten calling convention ab.

Mit reinem Testen wäre das nur dann akzeptabel, wenn man gleichzeitig 
100% Code-Coverage bekommt, denn sonst weiß man doch gar nicht, ob der 
Zweig mit der schlimmsten Tiefe überhaupt durchlaufen wurde (speziell, 
wenn es dabei um Fehlerbehandlung geht), und die Maximal-Zahl ist von 
fragwürdiger Bedeutung.

Ob der Stackbedarf für Interrupts auf die Task-Stacks addiert werden muß 
oder nicht, hängt davon ab, ob man keinen separaten Interrupt-Stack hat 
(dann ja) oder doch - dann nein, aber der dedizierte Interrupt-Stack muß 
groß genug sein für das worst-case-nesting. Jedenfalls darf man die 
nicht bei der Betrachtung vergessen.

Übrigens kann es hier fatal wirken, wenn man im Debug-Build brav testet 
und dann beim Release sich wundert, wieso es knallt. Na weil im 
Debug-Build mit O0 nicht ge-inlined wird, im Release mit O2 aber schon. 
Der Effekt kann der sein, daß zwei Stack-intensive Funktionen, die 
vorher nacheinander aufgerufen werden, dann in dieselbe Funktion 
rutschen. Je nachdem, was der Compiler dann macht, kann sich deren 
Stackbedarf dann addieren.

Das ist speziell wahrscheinlich, wenn man genau deswegen stackintensives 
Zeugs in eine eigene Funktion ausgelagert hat, die aber nur einmal 
aufgerufen wird. Abhilfe kann dann durchaus sein, an den richtigen 
Stellen mit noinline-Attributen das Inlining zu verhindern. Dazu muß man 
sich halt  den Call Graph bei der relevanten Optimierungsstufe angucken.

von Nop (Gast)


Lesenswert?

Nachtrag: das funktioniert natürlich nicht mehr, wenn man innerhalb der 
Tasks mit Funktionspointern arbeitet, die erst zur Laufzeit bestimmt 
werden. Was auch der Grund ist, wieso man das nach MISRA nicht darf, und 
dann muß man halt 100% Coverage hinbekommen. Oder man macht's nach dem 
Prinzip Hoffnung, wenn es letztlich egal ist, wie zuverlässig das Gerät 
am Ende arbeitet.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.