EmbeddedRelated.com
Forums
The 2024 Embedded Online Conference

Stack - interrupt, function call?

Started by splee2 5 years ago11 replieslatest reply 5 years ago5219 views

Hi,

I am confused about the stack.

I understand that in embedded C programming for MCU, when a function is called, a stack frame is created so that the following can be stored temporarily on the stack: return address, function parameters, return value, local variables, etc

How about when an interrupt happens, and the CPU registers need to be temporarily stored? Are they stored in the same stack memory? Or we are talking about two different stack at all?

Thank you

[ - ]
Reply by matthewbarrJanuary 5, 2019

The short answer is that it depends on the processor architecture, there is no one-size-fits-all answer to your question. One processor may save minimal architectural state on the stack and require the ISR (interrupt service routine) to save/restore additional state as required. Another may switch to an alternate copy of the register set during the ISR. You have to read about interrupt and exception handling for your particular processor architecture.

[ - ]
Reply by Jim_255January 5, 2019

You beat me to the punch by 20 seconds!! LOL Great minds think alike!

[ - ]
Reply by matthewbarrJanuary 5, 2019

We have a consensus!

[ - ]
Reply by QLJanuary 5, 2019

The YouTube video-course "Modern Embedded Systems Programming" explains the concepts you are confused about. Specifically, here are the lessons that answer your questions:

Lesson 8: Functions and the Stack:

Lesson 9: Modules, Recursion, AAPCS: 

Lesson 10: Stack Overflow and Other Pitfalls of Functions:

Lesson 0x10: Interrupts Part-1:

Enjoy!

[ - ]
Reply by Jim_255January 5, 2019

It actually depends on the MCU. In my 40 years in embedded firmware development I've worked with a great many MCUs. Some have a "special" Memory area for interrupt data storage,  some that have "limited" stack storage, some with a complete set of registers for use in interrupt handlers, and still others with a user stack for application use and a system stack for interrupt and Operating System use.

[ - ]
Reply by JackCrensJanuary 5, 2019

I'm a simple man, so I'm going to give you a simple answer: THERE IS ONLY ONE STACK.

I see that other folks disagree, and that's OK. Apparently there are "odd" CPUs that use special mechanisms for interrupts. I'm guessing that there may be RISC CPUs that do some bizarre things to create whole new stack frames for interrupts. 

I don't know about that. But the concept of a single stack has been the baseline idea since the days of the 8080 and 6800 microprocessors.  (The old IBM mainframes, by contrast, didn't have stacks at all!)

When an interrupt comes in, control of the CPU is vectored to the appropriate interrupt handler -- sometimes called an Interrupt Service Routine (ISR).  The job of the ISR is to preserve the CPU's state by pushing all appropriate registers onto the stack.  This includes the program counter.  

When the interrupt has been processed, the ISR must restore the state, and that allows the CPU to continue what it was doing. In effect, it's not even "aware" that it's been interrupted.

The same is true for function calls.  Whatever data is pushed onto the stack as the function is called, must be cleaned up (by either the function itself or its caller) before returning to the main control flow.

The old Motorola 68000 had neat instructions -- LINK and unlink (ULNK) to make this cleanup super-easy.

[ - ]
Reply by KocsonyaJanuary 5, 2019

"THERE IS ONLY ONE STACK"

I beg to differ.

Even on very old CPUs, like the m68k you mentioned, there were two stack pointers, the USP and the SSP (user and supervisor spack pointer). User code used the USP (as register A7) for function calls and local vars and whatnot. However, when any exception happened (interrupt or a fault) the CPU swithed to supervisor mode and remapped A7 to the the SSP before saving anything. All the embedded chips derived from the m68k (e.g. 683xx, ColdFire etc) have the same property.

This is also the case with the modern, cheap ARM-based embedded MCUs, from the veteran ARM7TDMI core to all current Cortex-Mx versions. In fact, the ARM7TDMI has more than two HW stack pointers, but let's not go into that. It should be noted that on the Cortex-Mx ARM decided to delay the SP switch when an exception hits because that gives some performance increase when implementing a context switch for multithreading. That was a mistake in my opinion, as it made recovery from (or even diagnostics of) certain fault exceptions impossible - but that's just my personal view, and it's off topic anyway.

What is on topic, though, is that I mentioned multithreading, which gives you the reason of having more than one stack even in userspace: each thread has its own stack and the context switch switches between those stacks. Multithreading is not some esoteric, desktop-system level thing. A rather long time ago I implemented some very simple co-operative multithreading on a 68HC11, an old 8-bitter from Motorola, and naturally each thread had its own stack, even though the CPU itself had only one SP register. I also do know for sure that I'm not the only one who used multithreading on 8-bit CPUs - in certain cases it makes the firmware so much cleaner, more readable and maintainable that it's worth every byte and clock cycle you spend on it.

So no, it's definitely not true that there's only one stack. You may work on a particular embedded gizmo that uses only one stack, but many other embedded gizmos (even low-end) may, and do, have several, implemented in HW or SW or both.

[ - ]
Reply by JackCrensJanuary 5, 2019

Ok, thanks for the reminder. I must admit, I had forgotten about the User vs. Supervisor mode.  Thanks for reminding me.

One of the things I really admired about the 68K and its ilk is how Motorola went so far out of its way to find out what sorts of constructs the compiler writers needed to generate efficient machine code from a C (or any other) high-level compiler. The LINK/ULNK was one such instruction. There were others, all aimed at making function calls very efficient.

There's a very good reason why I forgot about User mode:  As an embedded systems programmer, I never -- and WOULD never -- use it.

From the things you describe, I gather that Moto didn't just go to the compiler writers; they also went to the OS writers, asking what hardware constructs they needed to write efficient operating systems, including multi-tasking RTOSs, etc.  

In a project involving a 68332 (a wonderful chip!), I wrote my own RTOS which supported both multiple synchronous tasks (to process sensors and actuators) plus multiple asychronous tasks, driven by interrupts (to support user-based I/O actions like the essential ESTOP button as well as console I/O.  And no, I did _NOT_ use User mode!

The only place you and I seem to differ is in your mention of multi-threading. Surely multi-threading -- along with multi-user and multi-processor functions -- are _SOFTWARE_-implemented features, implemented as part of a general-purpose OS, and not hardware-supported features.

Jack


[ - ]
Reply by splee2January 5, 2019

Dear JackCrens,

I suppose you are saying function call and interrupt "shares" the same stack. Please kindly confirm my understanding is correct:

1. main program runs. Stack is empty

2. A function is called. A stack frame is thus created, and some data (function parameters, local variables, etc) are pushed on stack

3. BEFORE the function exits, IF an interrupt happens, then some CPU registers (R0, R1, ..) would be pushed (by hardware, or by user's interrupt routine) onto the SAME(?) stack

Up to this point, can I say the ONE stack consists of: some data from the function call + saved CPU registers value?

[ - ]
Reply by KocsonyaJanuary 5, 2019

Before the IRQ hits, you have a stack that looks like this:

SP-> 
  local data of the called function
  saved registers
  return address (and possibly a frame pointer)
  function parameters
  main()'s local data

Then the exception comes in. What happens at this point really depends on the core (and even what kind of exception was accepted).

For example, the Z80 has one stack pointer and an interrupt pushes nothing more than the return address to the very same stack:

SP->
  return address from ISR
  function local data
  ...

and even saving the status register is your responsibility in your interrupt handler routine - that was a common source of very hard to debug, misterious crashes on that chip.

The MC68HC11 has 2 8-bit accumulators, A and B, 2 16-bit index registers X and Y, a 16-bit stack pointer, a 16-bit PC and an 8-bit status register. When the CPU accepts an interrupt, it pushes all registers but the SP itself to the stack:

SP->
 status register
 B register
 ...
 PC (i.e. ISR return address) high byte
 PC low byte
 function local data
 ..

So far, just as you assumed, the function call and interrupt related stuff are on the same stack, on top of each other. But let's look at a different chip, the venerable MC68000 family. It has user mode and supervisor mode, each having its own SP and consequently 2 stacks. 

If your entire program runs in supervisor mode (which might make sense in an embedded system), then you will never use the USP (user SP), so your stack will look the same as with the previous chips, except that the m68k exception stack frames are variable size, depending on what kind of exception it accepted.

However, if main() is actually running as a user program, then it's a different story. Before the exception you have:

USP->
   function local data
   saved registers
   return address
   ...
 

SSP-> <empty>

Accepting any exception (interrupt or otherwise) puts the CPU to supervisor mode, so the active SP is SSP and thus the exception frame is saved there:

USP->
   function local data
   saved registers
   return address
   ...
 

SSP-> 
   exception stack frame

and if the ISR calls other functions all those function calls will build their frames on the supervisor stack; the stack pointed by USP won't be touched at all until the ISR returns. 

Furthermore, if nested interrupts are allowed (on the m68k and many other 16 and 32 bit CPUs ISRs can themselves be interrupted by a higher priority request) those exception frames plus stack usage by the relevamt ISRs will all be on the supevisor stack.

Now for a little twist, consider the Cortex-Mx chips. They are like the 68000, but there's a trick. When an exception is accepted, the exception stack frame is pushed to the current stack before the chip switches to the supervisor stack pointer (ARM calls the SSP "master SP" and the USP "program SP", but that's not important). So, assume that you have an interrupt. Its ISR starts running and then a higher priority IRQ is also accepted. Then your stacks look like this:

PSP->
   1st exception stack frame
   function local data
   saved registers
   return address
   ...
 

MSP-> 
   2nd exception stack frame
   1st ISR local vars
   1st ISR saved registers

So, it really depends on what chip you are talking about.

Hope that clarified things a bit.

[ - ]
Reply by JackCrensJanuary 5, 2019

Kocsonya has reminded me that the 68000 used two stacks, the User stack and the Supervisor stack.  This let folks writing operating systems separate user space from system space.  

But yes, I'm saying that, at least in embedded systems, you'd be working in system -- i.e., Supervisor -- mode, and using only the one stack. Stack frames for function calls get set up on the stack, and so does stuff pushed onto it by the interrupt handler.  The two sets of data SHOULD not interfere, since both the function and the ISR must preserve and restore the context (registers, flags, etc.) before they can change anything.

The old Intel 8080 had CPU registers A,B, C,D, H, L, and flags, plus the program counter (PC) and stack pointer (SP).  A CALL function would push the return address onto the stack, then jump to the function. A RET would pop the return address back into the PC.

Saving any other registers were the job of the function or interrupt handler.  You didn't have to preserve _ALL_ the registers, though -- only the ones that were changed. Even so, and even with the limited register count of the 8080, pushing and popping them took clock cycles that were precious.


In the Z80, designer Federico Faggin had a better idea: He gave the chip a whole new register set: A', B', etc. So interrupt handlers could simply switch to the alternate set.

It seemed like the perfect solution for system-level function calls and interrupt handlers, but AFAIK no operating system run on the Z80 ever used that feature.




The 2024 Embedded Online Conference