Forum: Mikrocontroller und Digitale Elektronik Stack Pointer auslesen und Manipulieren in C


von Basti B. (basti195) Benutzerseite


Lesenswert?

Hallo,
ich würde gerne eine "kleine Multithread" Einheit bauen, die in der Lage 
ist 2 kleine Programme nacheinander/ Parallel auszuführen.

Mir ist durchaus klar, dass es auch VIEL einfacher geht, aber darum geht 
es mir nicht!

Mein Ziel ist es am 2 Kleine Programme, jeweils in ihrer eigenen 
while(1) Schreibe,Parallele laufen zu lassen.

Die Idee ist das über ein TC-Interrupt die beiden Programme getriggert 
werden. Hierzu müsste es doch reichen den Stack-Pointer zu sichern, und 
die Rücksprung-Adresse von Programm 1 durch die von Programm 2 zu 
ersetzen.
Zusätzlich müssen die General-purpose Registers gesichert(und mit den 
gespeicherten von Programm 2 ersetzt) werden, damit keine Schritte/ 
Berechnungen verloren gehen.

Meine Frage bezieht sich nun auf 2 Dinge:
1. Stimmt meine Theorie
2. Wie kann ich die General-purpose Register auslesen und wieder 
beschreiben
gibt es für C überhaupt Befehle dazu?

Ich Verwende eine Cortex M4(SAM4SD32C)Prozessor.

: Bearbeitet durch User
von Falk B. (falk)


Lesenswert?

@ Sebastian B. (basti195)

>ich würde gerne eine "kleine Multithread" Einheit bauen, die in der Lage
>ist 2 kleine Programme nacheinander auszuführen.

Hmmm.

>Mein Ziel ist es am 2 Kleine Programme, jeweils in ihrer eigenen
>while(1) Schreibe,Parallele laufen zu lassen.

>Die Idee ist das über ein TC-Interrupt die beiden Programme getriggert
>werden. Hierzu müsste es doch reichen den Stack-Pointer zu sichern, und
>die Rücksprung-Adresse von Programm 1 durch die von Programm 2 zu
>ersetzen.

Das allein reicht nicht.

>Zusätzlich müssen die General-purpose Registers gesichert(und mit den
>gespeicherten von Programm 2 ersetzt) werden, damit keine Schritte/
>Berechnungen verloren gehen.

>1. Stimmt meine Theorie

Nein, denn sie ist mindestens grob unvollständig. Du hast nur den Hauch 
einer Skizze, kein Konzept.

>2. Wie kann ich die General-purpose Register auslesen und wieder
>beschreiben
>gibt es für C überhaupt Befehle dazu?

Nö. Darum macht man sowas eher in ASM.

Schau dir bestehende RTOSe an und denk drüber nach. Sooo einfach ist das 
nicht, wenn es nicht nur ein singuläres Gemurkse werden soll.

von Bernd K. (prof7bit)


Lesenswert?

Sebastian B. schrieb:
> 2. Wie kann ich die General-purpose Register auslesen und wieder
> beschreiben
> gibt es für C überhaupt Befehle dazu?

Die Sprachelemente von C wissen erstmal nichts (wollen absichtlich 
nichts wissen) von der darunterliegenden Hardware, sie sollen die 
Hardware vollständig abstrahieren.

von Wolfgang (Gast)


Lesenswert?

Bernd K. schrieb:
> Die Sprachelemente von C wissen erstmal nichts (wollen absichtlich
> nichts wissen) von der darunterliegenden Hardware, sie sollen die
> Hardware vollständig abstrahieren.

Eben, aber für die Taskumschaltung scheint es keine solche Abstraktion 
in C zu geben.

von (uint32_t*)0x01234567 (Gast)


Lesenswert?

Falk B. schrieb:
> Nö. Darum macht man sowas eher in ASM.

???
Mit nem C-Pointer kannst du quer duchr den Speicher alles 
manipulieren. Natürlich auch irgendwelche Register.

von Basti B. (basti195) Benutzerseite


Lesenswert?

Das Heißt es gibt keine Weg um das Speichern und Ersetzen der Register 
mit Inline-Assembler zu machen?

von ich (Gast)


Lesenswert?

Mit setjmp() und longjmp() kann man so Sachen basteln. Sollte Teil der C 
Standard Library sein.

https://en.wikipedia.org/wiki/Setjmp.h

von Programmierer (Gast)


Lesenswert?

Sebastian B. schrieb:
> Das Heißt es gibt keine Weg um das Speichern und Ersetzen der
> Register mit Inline-Assembler zu machen?

Klar geht das. Du musst aber noch richtige Stack Frames aufsetzen und 
die Register sichern. Die Rücksprungadresse kommt in das Stack Frame, 
das LR bekommt eine Spezial Adresse für den ISR Rücksprung. Lese mal 
genau das ARMv7M Architecture Manual. Die Architektur ist nämlich genau 
dafür gemacht, das zu können...

von Falk B. (falk)


Lesenswert?

@ (uint32_t*)0x01234567 (Gast)

>> Nö. Darum macht man sowas eher in ASM.

>???
>Mit nem C-Pointer kannst du quer duchr den Speicher alles
>manipulieren. Natürlich auch irgendwelche Register.

Auch Zitieren will gelernt sein.

>>2. Wie kann ich die General-purpose Register auslesen und wieder
>>beschreiben
>>gibt es für C überhaupt Befehle dazu?

>Nö. Darum macht man sowas eher in ASM.

Kaum. Es gibt in C KEINEN Direktzugriff auf den Stackpointer ala ASM. 
Nur weil der beim AVR als IO-Register exitiert und damit als SP(L/H) 
zugreifbar ist, ist das noch lange nicht allgemeingültig. CPU-Register 
sind im "normalen C" gar nicht sicht- und zugreifbar. Diverse Inline 
ASM-Hackereien haben mit "normalem C" nichts zu tun.

von Falk B. (falk)


Lesenswert?

@Sebastian B. (basti195)

>Das Heißt es gibt keine Weg um das Speichern und Ersetzen der Register
>mit Inline-Assembler zu machen?

Doch, den gibt es. Aber das ist ja ASM. Und Inline-ASM ist der 
beschwerlichste Weg, ASM zu nutzen. Ausser für ein paar kleine, pfiffige 
Makros tut sich das keiner an. Wenn schon, dann mit getrennten 
ASM-Dateien, die getrennt assembliert und dann zu deinem C-Projekt 
gelinkt werden.

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Hier ein Beispiel für einen Kontextwechsel - nichts anderes hast du ja 
vor - in C mit Inline-Assembler:
1
inline void __attribute__((naked)) MeineISR () {
2
  asm volatile ("cpsid i");
3
  
4
  // Save unsaved registers.
5
  asm volatile ("push {r4, r5, r6, r7, r8, r9, r10, r11, lr}" ::: "memory");
6
  
7
  // Store stack pointer for old thread.
8
  asm volatile ("str sp, [%0]" :: "r" (&currentThread->sp));
9
  
10
  // Update running thread.
11
  currentThread = getNextThread ();
12
  
13
  // Fetch stack pointer for new thread.
14
  asm volatile ("ldr sp, [%0]" :: "r" (&currentThread->sp));
15
  
16
  asm volatile ("cpsie i");
17
  
18
  // Load registers and return.
19
  asm volatile ("pop {r4, r5, r6, r7, r8, r9, r10, r11, pc}" ::: "memory");
20
}

Siehe auch:
http://infocenter.arm.com/help/topic/com.arm.doc.ddi0403e.b/index.html 
Ab Seite 592.
http://hardwarebug.org/2010/07/06/arm-inline-asm-secrets/
http://www.ibiblio.org/gferg/ldp/GCC-Inline-Assembly-HOWTO.html#ss5.3
http://www.ethernut.de/en/documents/arm-inline-asm.html
http://wiki.osdev.org/Inline_Assembly/Examples
http://www.osdever.net/tutorials/view/a-brief-tutorial-on-gcc-inline-asm

von Basti B. (basti195) Benutzerseite


Lesenswert?

Vielen Dank so weit :)
Was ist den der Genau unterschied zwischen Link Register und Stack 
Pointer?
Ist der Stack-Pointer der Punkt wo nach dem ISR hin gesprungen wird und 
das Link Register wo das Programm vor dem ISR war?

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Sebastian B. schrieb:
> Was ist den der Genau unterschied zwischen Link Register und Stack
> Pointer?
Uhoh, wenn du noch solche Fragen stellst solltest du ganz dringend das 
Architecture Reference Manual lesen, bevor du an so Dinge denkst wie 
einen Kontextwechsel zu programmieren!

von Falk B. (falk)


Lesenswert?

@ Niklas Gürtler (erlkoenig)

>> Was ist den der Genau unterschied zwischen Link Register und Stack
>> Pointer?
>Uhoh, wenn du noch solche Fragen stellst solltest du ganz dringend das
>Architecture Reference Manual lesen, bevor du an so Dinge denkst wie
>einen Kontextwechsel zu programmieren!

Der OP unterschätzt den Aufwand seiner Idee. Wer sowas machen will, muss 
seine CPU auf ASM-Ebene aus dem FF beherrschen, dazu noch C und die 
Verknüpfung von beidem. Das ist was für die hohen Semester.

In der Zeit hat man die meisten Probleme locker mit kooperativem 
Multitasking und einfachen Interrupts gelöst. Zumal die Idee, 
einfach zwei while(1) Schleifen "unsichtbar" zu multiplexen nur für 
triviale Sachen so einfach geht. In dem Moment, wo die beiden while(1) 
schleifen miteinenander kommunizieren müssen, wird es deutlich 
aufwändiger. Ohne gescheite Mechanismen ala Semaphoren, Mutexen etc. 
wird das nix.

von Jim M. (turboj)


Lesenswert?

Sebastian B. schrieb:
> Ich Verwende eine Cortex M4(SAM4SD32C)Prozessor.

Für Cortex M MCUs gibt es FreeRTOS, das enthält entsprechenden Code zur 
Taskumschaltung.

von Mikro 7. (mikro77)


Lesenswert?

Sebastian B. schrieb:

> ich würde gerne eine "kleine Multithread" Einheit bauen, die in der Lage
> ist 2 kleine Programme nacheinander/ Parallel auszuführen.

Hört sich so an, als ob du einen (minimalen) Scheduler bauen möchtest:
https://en.wikipedia.org/wiki/Scheduling_%28computing%29#Dispatcher

> Die Idee ist das über ein TC-Interrupt die beiden Programme getriggert
> werden. Hierzu müsste es doch reichen den Stack-Pointer zu sichern, und
> die Rücksprung-Adresse von Programm 1 durch die von Programm 2 zu
> ersetzen.

Trigger zum Auslösen des (Context) Switches hast du also. Du brauchst 
noch Funktionen zum Saven/Restoren des jeweiligen Contexts. Im minimalen 
Fall könnte ein Abbild der CPU Register ausreichen.

> Zusätzlich müssen die General-purpose Registers gesichert(und mit den
> gespeicherten von Programm 2 ersetzt) werden, damit keine Schritte/
> Berechnungen verloren gehen.
>
> Meine Frage bezieht sich nun auf 2 Dinge:
> 1. Stimmt meine Theorie

Grundsätzlich ja. Siehe auch o.g. Wikipedia Link.

> 2. Wie kann ich die General-purpose Register auslesen und wieder
> beschreiben
> gibt es für C überhaupt Befehle dazu?

Wenn, dann mit setjmp/longjmp wie vom Vorposter beschrieben.

von Guest (Gast)


Lesenswert?

Mal was aehnliches weil ich es vor Jahren versucht und nicht hinbekommen 
habe. Wie funktioniert das auf groesseren ARMs mit unterschiedlichen 
Register sets? Sobald der Cintext Change Interrupt aufgerufen wird, 
schaltet der Core ja in den Interrupt Mode und man hat keinen Zugriff 
mehr auf die Register des User Modes. Wechselt man waehrend dem 
Interrupt einfach in den Usermode zurueck oder gibt es eine cleverere 
Taktik?

von Basti B. (basti195) Benutzerseite


Lesenswert?

S. J. schrieb:
> Hört sich so an, als ob du einen (minimalen) Scheduler bauen möchtest

Genau so was in etwa habe ich vor :)

vielen dank an alle für die schnelle Hilfe.
Jetzt weiß ich(oder zumindest habe ich eine Idee °_^ )  wie in etwa ich 
vorgehen muss und wo ich mir weiteren Lese-Stoff zulegen muss.
vielen Dank
grüße
basti95

von (uint32_t*)0x01234567 (Gast)


Lesenswert?


von Noch einer (Gast)


Lesenswert?

Noch einfacher - Kooperatives Multitasking.

Nicht der Timer, deine Programme rufen der Scheduler auf. Da finden sich 
Beispiele, die mit setjump/longjump auskommen. Um interne Register 
brauchst du dich nicht zu kümmern.

von (prx) A. K. (prx)


Lesenswert?

Man kann bei den Cortex M zwischen Prozess- und Interrupt-Stack 
unterscheiden. Einem Thread-Scheduler kommt das sehr entgegen, da so 
nicht jeder Thread-Stack die Last von Interrupts tragen muss. Mit 
setjmp/longjmp geht das dann allerdings nicht.

Wenn Interrupts verschachtelt werden können, dann kann nicht jede ISR 
direkt den Thread umschalten, sondern nur diejenige, die direkt aus dem 
Thread-Kontext heraus aufgerufen wurde. Kein Problem, wenn alle auf 
einen anderen Thread umschaltenden Handler auf gleicher niedrigster 
Priorität laufen. Alternativ kann man bei verschachtelt aufgerufenen 
ISRs auch die PendSVC Exception nutzen. Der wird niedrigst priorisiert 
und wenn ein Thread-Switch ansteht aktiviert. Dessen Handler schaltet 
dann um.

von (prx) A. K. (prx)


Lesenswert?

Guest schrieb:
> Mal was aehnliches weil ich es vor Jahren versucht und nicht hinbekommen
> habe. Wie funktioniert das auf groesseren ARMs mit unterschiedlichen
> Register sets? Sobald der Cintext Change Interrupt aufgerufen wird,
> schaltet der Core ja in den Interrupt Mode und man hat keinen Zugriff
> mehr auf die Register des User Modes.

Beim ursprünglichen ARM Befehlssatz kann eine Variante der LDM/STM 
Befehle auf die Register des User-Modes zugreifen.

: Bearbeitet durch User
von Olaf (Gast)


Lesenswert?

Schau mal hier:

Beitrag "Neuer Multitasker Olix"

Ich habe sowas vor ein paar Jahren mal fuer einen M16C geschrieben. Das 
ist im Prinzip das Minimum das man braucht. Ausserdem habe ich mir Muehe 
gegeben es gut zu dokumentieren.

Da musst du nur noch den Assemblerteil selber schreiben. :-)

Allerdings, wie Falk schon sagte, man muss dazu den Prozessor gut kennen 
und sollte Assembler auch drauf haben.
Ich kann nur jedem mal empfehlen soetwas selber zu programmieren. Man 
bekommt da ein ganz gutes Gefuehl fuer einen Controller. .-)

Olaf

von eagle user (Gast)


Lesenswert?

Beim M4 musst du ggf. noch die Floating Point Register retten. Dadurch 
würde sich der Aufwand mehr als verdoppeln -- Entwicklung und vor allem 
Laufzeit. Das ist ein Grund mehr, auf Floating Point möglichst zu 
verzichten.

Wenn man sicher stellen könnte, dass die Register nur von einer Task 
benutzt werden, müssten sie nicht gerettet werden. Ob man wohl eine Task 
mit "-mcpu=cortex-m4" und den Rest mit " -mcpu=cortex-m3" übersetzen 
kann?

von (prx) A. K. (prx)


Lesenswert?

eagle user schrieb:
> Beim M4 musst du ggf. noch die Floating Point Register retten. Dadurch
> würde sich der Aufwand mehr als verdoppeln -- Entwicklung und vor allem
> Laufzeit.

Schema bei vielen Prozessoren, in Software oder direkt in Hardware: beim 
Kontextwechsel die FPU deaktivieren. Beim nächsten FPU-Befehl gibts eine 
Exception und darin kann der Wechsel des FPU-Kontextes falls nötig 
nachgeholt werden. Eine nicht verwendete FPU erzeugt so praktisch keine 
Kosten und wenn nur ein Thread sie nutzt wird nichts gewechselt.

Da das ein hinlänglich bekanntes Problem aller Prozessoren mit FPU ist 
hat ARM das bei den Cortex M automatisiert. Siehe Architecture Reference 
zu "Lazy context save of FP state".

> Das ist ein Grund mehr, auf Floating Point möglichst zu verzichten.

Nein. Das ist bloss ein Grund mehr, Doku zu lesen. ;-)

: Bearbeitet durch User
von Olaf (Gast)


Lesenswert?

> Beim M4 musst du ggf. noch die Floating Point Register retten. Dadurch
> würde sich der Aufwand mehr als verdoppeln

Ich wollte meinen Code irgendwann mal auf einen SH2A anpassen. Der 
duerfte durch seine sechzehn integrierten Registerbaenke ganz schoen 
flott sein.

Wobei ich mich gerade frage wie sich bei modernen Prozessoren wie dem 
SH2A oder auch den ARMs der Cache auswirkt. Ich koennte mir vorstellen 
das da ordentlich Leistung verloren geht weil der Inhalt des Cache dann 
sehr oft ungueltig wird.

Olaf

von (prx) A. K. (prx)


Lesenswert?

Olaf schrieb:
> Wobei ich mich gerade frage wie sich bei modernen Prozessoren wie dem
> SH2A oder auch den ARMs der Cache auswirkt. Ich koennte mir vorstellen
> das da ordentlich Leistung verloren geht

Wenn du auf sehr schnelle Ausführung von ISRs Wert legst und Cache 
Misses nicht zulässig sind, dann musst sehen, ob du deren Code im Cache 
festnageln kannst. Grad bei Controllern dürfte das über explizite 
Cache-Steuerung möglich sein.

Es gibt andererseits aber auch recht viele µCs, die eine Art Cache für 
langsamen Flash-Speicher haben, aber keinen Cache für das interne RAM 
benötigen. Bei denen legt man den Code der ISRs ins RAM.

> weil der Inhalt des Cache dann sehr oft ungueltig wird.

Nur wenn du eine MMU mit Adressumsetzung drin hast und der Cache die 
virtuellen Adressen taggt. Ohne Adressumsetzung (paging) oder bei 
physikalisch getaggten Caches (das ist die Regel) ändert der 
Kontextwechsel per se nichts am Cache.

Ohne Pinning des ISR-Codes ist das dann eine Wahrscheinlichkeitsrechnung 
zusammen mit Glück oder Pech der Adresslage der ISRs relativ zu anderem 
Code und der Assoziatität des Caches. Kann sein, dass eine häufig genug 
aufgerufene ISR im Cache quasi von selbst festgepinnt ist, nach 
grösserem Umbau des Codes aber stets rausfliegt.

: Bearbeitet durch User
von eagle user (Gast)


Lesenswert?

A. K. schrieb:

> "Lazy context save of FP state".

Danke für die Erklärung! Ein guter Foren-Beitrag sagt eben doch mehr als 
42 Seiten ARM-Doku ;)

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.