longjmp() at the end of an ISR ? Or Goto+flag ?

Go To Last Post
77 posts / 0 new

Pages

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

ISR+longjmp works as expected, in particular SREG and SP are saved in jmpbuf. And longjmp is designed to jump right out of a call tree (including any level of IRQs).
.
You are trying to implement timeout like in Java where an exception is thrown when a timer triggers.
.
The alternative would be to clutter up your funcs' code with tests an returns if the condition is true, which is not very nice either.
.
Advantage of longjmp is that it keeps your funcs clean and in addition is zero overhead until actually performed, and even then it's just around 40 ticks or so.
.
Main disadvantage of longjmp is that you need cleanup of your funcs internal state which you don't know, as you don't know when exactly the IRQ triggers.
.
And longjmp is not very common, hence there might be some bias against it.

avrfreaks does not support Opera. Profile inactive.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

setjmp/longjmp are often used in interactive programs to catch the interrupt key-sequence (e.g., Control-c): inside the REPL (read-eval-print-loop) one performs a setjmp(); inside the Control-c signal handler one performs a longjmp().  (By the way, the "modern" version of setjmp/longjmp is setcontext().)     This mechanism can also be used to implement co-routines, exception handlers, etc.

 

So I can see how the OP may have come to the solution in the first post.  And as odd as the code looks, I don't see why it would not work here.   But this style is not usual for embedded / real-time systems.

 

I did look at the implementation of setjmp in avr-libc (see attached).   The implementations is quite simple.  It saves the stack pointer and status register.  longjmp () restores the stack pointer and jmps to the setjmp() address.  It looks like any other registers will get trashed.

 

Looking at the original post -- not having read all the follow-ups -- it seems the OP wants to do a specific computation based on the value of critical_value and abort that computation when critical_value changes.  He has the choice of polling for critical value in the loop or using setjmp/longjmp.

 

 

Attachment(s): 

Last Edited: Sun. May 12, 2019 - 04:18 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The avr-libc routines also save/restore the call-saved registers, cf. the .irp code. setjmp behaves like a function, i.e. clobbers the call-used regs so that any local whose lifetime crosses setjmp will survive.

avrfreaks does not support Opera. Profile inactive.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

20 years ago I used an OS with preemptable tasks that used setjmp and longjmp to save and restore contexts.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

SprinterSB wrote:
The avr-libc routines also save/restore the call-saved registers, cf. the .irp code. setjmp behaves like a function, i.e. clobbers the call-used regs so that any local whose lifetime crosses setjmp will survive.

 

Thanks.  I missed that. 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

SprinterSB wrote:
ISR+longjmp works as expected, in particular SREG and SP are saved in jmpbuf. And longjmp is designed to jump right out of a call tree (including any level of IRQs).

 

It might be true for the given platform, no argument here. For another example, on x86 the `setjmp/longjmp` mechanism is sometimes successfully used to implement coroutines, i.e. to pass control between two different branches of call tree (basically, it is a side-jump, not a backwards jump). If done properly, it "works", even though it is not formally legal.

 

However, in general, as far as C standard library specification is concerned, it is illegal to `longjmp` out of signal handlers (or interrupt handlers). The behavior is undefined. There are several underlying reasonings behind this restriction, including (but not limited to) 1. the fact that the call tree for the handler might include several platform-dependent routines that cannot be safely jumped over, 2. `longjmp` is not an "async safe" function.

Last Edited: Mon. May 13, 2019 - 02:42 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

No.

 

Stop.

 

You don't know where your code was when your interrupt fired. If you leave the ISR via longjmp, you are not going back where you were before it was called. You're going to where setjmp was called.

 

Your problem isn't some subtlety of restoring things, it's that your idea makes no sense whatsoever. The idea of an interrupt is that your code is currently doing something, and then the interrupt happens, and when it's done, it resumes what it was doing previously. If you try to use a goto, or a longjmp, you are no longer resuming what you were doing previously.

 

 

Circumstances under which this would be reasonable: Nothing your program does matters to you, at all, and you don't care whether any of the code you wrote runs.

 

If it is possible that you care whether your program runs, then this is an idea which is completely, totally, wrong in every way.

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I guess the real solution here is to break up the long (50ms) work in main() to smaller steps that are also then conditional on some kind of "abandon because of interrupt" flag that is then set in the ISR.

 

(I often make this point but shouldn't all this have been captured in the design stages before implementation?)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

the_real_seebs wrote:
If you leave the ISR via longjmp, you are not going back where you were before it was called. You're going to where setjmp was called.

 

It can get more complicated when you have preemptible tasks.  The OS gets control after interrupts.  It runs the highest priority task that is ready to run.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

AndreyT wrote:

However, in general, as far as C standard library specification is concerned, it is illegal to `longjmp` out of signal handlers (or interrupt handlers). The behavior is undefined. There are several underlying reasonings behind this restriction, including (but not limited to) 1. the fact that the call tree for the handler might include several platform-dependent routines that cannot be safely jumped over, 2. `longjmp` is not an "async safe" function.

 

Are you sure about this?   I thought the issue was repeated signal handlers.  That is, if longjmp is used in signal handlers then signals must be blocked in the handlers.  Extending to interrupt handlers, interrupts must be disabled.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

MattRW wrote:

AndreyT wrote:

However, in general, as far as C standard library specification is concerned, it is illegal to `longjmp` out of signal handlers (or interrupt handlers). The behavior is undefined. There are several underlying reasonings behind this restriction, including (but not limited to) 1. the fact that the call tree for the handler might include several platform-dependent routines that cannot be safely jumped over, 2. `longjmp` is not an "async safe" function.

 

Are you sure about this?   I thought the issue was repeated signal handlers.  That is, if longjmp is used in signal handlers then signals must be blocked in the handlers.  Extending to interrupt handlers, interrupts must be disabled.

 

See the WG14 response to this matter:  http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1318.htm

 

What you are saying is similar to what the original C standard (C89/90) was saying (and might also rely on additional guarantees made by POSIX at that time)

 

"As it bypasses the usual function call and return mechanisms, the longjmp function shall execute correctly in contexts of interrupts, signals and any of their associated functions. However, if the longjmp function is invoked from a nested signal handler (that is, from a function invoked as a result of a signal raised during the handling of another signal), the behavior is undefined."

 

However, even this spec was challenged by Defect Report 152: http://www.open-std.org/jtc1/sc22/wg14/www/docs/dr_152.html

To which WG14 responded with

 

"The C Standard is clear enough as is. The longjmp function shall execute correctly when called from a non-nested signal handler invoked through calls to the raise or abort functions; if longjmp is called from a signal handler invoked by other means, or from a nested signal handler, the behavior is undefined."

 

(and the original wording was quietly removed from C standard).

 

So, according to this clarification from the very beginning using `longjmp` has only been allowed in signal handlers that were invoked manually, through `raise` or `abort` functions, not by "real" signals. 

 

The current C standard covers this issue with a more generic rule

 

"7.14.1.1 The signal function

5 If the signal occurs other than as the result of calling the abort or raise function, the behavior is undefined if the signal handler refers to any object with static or thread storage duration that is not a lock-free atomic object other than by assigning a value to an object declared as volatile sig_atomic_t, or the signal handler calls any function in the standard library other than the abort function, the _Exit function, the quick_exit function, or the signal function with the first argument equal to the signal number corresponding to the signal that caused the invocation of the handler."

 

As you can see, calling `longjmp` from asynchronous signal handlers remains illegal. Naturally, all this also applies to interrupt handlers invoked by actual interrupts.

Last Edited: Mon. May 13, 2019 - 10:57 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

"Dare to be naïve." - Buckminster Fuller

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

With AVRs and avr-gcc, I'd be surprised if longjump did not work almost as expected.

The lack of a RETI could be a problem, but since SREG gets restored, all should be ok.

We are not in the realm of C-standard signal-handlers.

We are closer to the realm of PINB |= 4 .

In any case, avr-gcc's ISRs are not C-standard things.

That said, ISR+longjump is not a documented avr-gcc thing either.

If ISR+longjump works for you,

my suggestion is to get the source for it.

Use said source instead of the library version.

That way you might not be affected by updates.

BTW my recollection is that the way to handle register-trashing

by longjmp is to call setjmp from a function that does nothing else

but return the value was wrong.

 

Edit: corrected BTW ....

"Demons after money.
Whatever happened to the still beating heart of a virgin?
No one has any standards anymore." -- Giles

Last Edited: Wed. May 15, 2019 - 11:01 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

AndreyT, Many thanks for the detailed explanation!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Just going to chime in and say...

 

Even if it works, calling longjmp is probably a bad idea. Not because it won't work correctly, but because it makes it harder for the maintenance programmer to understand.

 

In general, ISRs should just set state and return. Now, sometimes setting that state is a complicated business and makes the ISR take a bit longer than we'd hope, but it will still return to the main program.

 

If the main program can't handle things quickly enough (before another ISR arrives), then you can put more processing in the ISR- do the calculations etc, in there, and store the results in volatile variables, + ready flags etc. Be careful not to create race conditions, but the main code can always disable interrupts while it reads the volatile variables (copy them into locals, then re-enable).

 

For example, if reading serial data, you might create a buffer in ram and have the ISR just stash the byte (attiny1 USART does not have a hardware buffer of more than one byte) and move the counter on, the main program can then stay busy doing whatever it's doing until it's ready to read the serial data.

 

There might also be cases where calling longjmp from a ISR leaves the hardware in an unexpected state- but knowing that a ISR might do that, you can always disable interrupts while doing some hardware operations.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I found some of the RTOS code we used in 1996.  I think this is how some of it worked.  The maximum stack size for each task had to be specified.  At startup, space was reserved on the stack for each task.  Setjmp and longjmp were used to run each task in its own environment.   Our code ran on a TI DSP.  We could also run it on a PC for testing.

 

It was based on an RTOS developed by TI.  If you google for TI-RTOS you will get some hits.

Last Edited: Wed. May 15, 2019 - 05:33 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

TI-RTOS is one of the RTOS for an event processing framework.

QP/C: TI-RTOS Kernel (SYS/BIOS)

 

"Dare to be naïve." - Buckminster Fuller

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

skeeve wrote:

BTW my recollection is that the way to handle register-trashing

by longjmp is to call setjmp from a function that does nothing else

but return the value.

 

That wouldn't work because setjmp saves its calling environment, which includes the stack frame. If the function that calls setjmp returns, the calling environment is no longer valid. (The old vfork system call had the same constraint for similar reasons.)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

christop wrote:

 

That wouldn't work because setjmp saves its calling environment, which includes the stack frame. If the function that calls setjmp returns, the calling environment is no longer valid.

But the setjmp caller can call longjmp instead of return.

 

Here is the Scheduler we used.

 

/*f*****************************************************************************
*
*          void Scheduler(Process_s *NextProcess)
* DESCRIPTION: Scheduler() uses ANSI setjmp macro to preserve process context.
*              If NextProcess argument is NULL, it searches for the next higher
*              priority ready-to-run process and switch to it Or simply returns
*              to NullProcess if no ready-to-run process is found.  If not NULL,
*              it simply take the NextProcess agrument and switch to such
*              process. Scheduler() uses ANSI longjmp macro to restore context
*              and switch to scheduled process.
* RETURNS:       void.
* ASSUMES: NextProcess must be a valid process or NULL.
*          Interrupts are disabled.  They will be enabled in Longjmp when returning
*          to the next ready task.
* HISTORY: Created 03/28/96 by David Leung
*     03/28/96 DKL: Initial Start Coding.
*****************************************************************************f*/

void Scheduler(Process_s *next_process_p)
{
Process_s *process_p;

#ifndef _TMS320C5XX
  if (setjmp(currentProcess_p->context_a)==0)
#else
  if (rtmkSetjmp(currentProcess_p->context_a)==0)
#endif /* _TMS320C5XX */

  {
    if (next_process_p)
      currentProcess_p = next_process_p;
    else  {           /* determine the NextProcess */
      for(process_p = process_a; process_p->awaitingSig; process_p++)
         ;
      if(process_p->awaitingResource_p)   {
         if(process_p->awaitingResource_p->ownerProcess_p->awaitingSig  ||
            process_p->awaitingResource_p->ownerProcess_p->awaitingResource_p) {
            for(; process_p->awaitingSig  ||  process_p->awaitingResource_p;
               process_p++)
               ;
            }
         else
            process_p = process_p->awaitingResource_p->ownerProcess_p;
         }
      currentProcess_p = process_p;
      }
#ifndef _TMS320C5XX
  longjmp(currentProcess_p->context_a, 1);
#else
  rtmkLongjmp(currentProcess_p->context_a, 1);
#endif /* _TMS320C5XX */
  }
}

This brings back memories.  The scheduler searches the processes from highest to lowest priority.  Notice the "if(process_p->awaitingResource_p" stuff.  I added that code to eliminate priority inversion.  If a process is waiting for a resource, the scheduler now runs the process that has the resource locked.  

 

I never heard of "priority inversion" but I knew the original code didn't make sense.  When I told the guy who downloaded this RTOS from Dr. Dobbs journal of what I did, he knew the term "priority inversion".  He said that is what caused several spacecraft to be lost that NASA tried to send to mars.  NASA should have hired me.  Maybe NASA gets their code from Dr. Dobbs journal too.

 

Here are RTMK.c and RTMK.h.   I also zipped up a lot of stuff that has headers that RTMK uses.  7zip creates a file that is one third the size of Microsoft's zip, but this forum won't allow the filename extension .7z.   (500k vs. 1500k)  If anyone wants it, should I add a .txt to the .7z extension or upload the .zip file or put it on OneDrive?
 

Attachment(s): 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

steve17 wrote:
He said that is what caused several spacecraft to be lost that NASA tried to send to mars.
or go wonky after landing on Mars.

steve17 wrote:
NASA should have hired me.
May one be aware of what one wishes for.

 


What really happened to the software on the Mars Pathfinder spacecraft? | Rapita Systems

fyi

Because VxWorks contains a C language interpreter intended to allow developers to type in C expressions and functions to be executed during system debugging, it was possible to upload a short C program to the spacecraft, which when interpreted, changed the values of these variables [enable priority inheritance for 3 mutexes] from FALSE to TRUE. 

More than VxWorks have a C or C++ interpreter :

Solutions for Embedded Scripting in C/C++

...

  • Add a C/C++ interpreter into your application by compiling and linking with the library of our Embedded Ch.

...

though the OOTB RTOS is QNX or likely the configurable real-time part of a somewhat recent Linux kernel :

Embedded and embedding Ch - System Requirements

...

  • QNX 6.3.0 or above

...

The minimum disk space requirement for Embedded Ch with complete ISO C standard functions:

  • Less than 3 Mb.

 

"Dare to be naïve." - Buckminster Fuller

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

So what you (the OP) have, in effect is a process/task/thread that is running, and when an interrupt occurs, you want to abort that process and then restart it.

 

If I had to do this, I would implement it AS SUCH (in assembly language), saving the full context needed for the process involved, and restoring that context to restart the process.  This is "known science", used by various RTOS code. - no need to re-invent the wheel.    I would suspect that longjump() might miss some of the needed context, since it (probably?) operates at the "C" context level.  For example I wouldn't count on it restoring the "known zero" in R1, which will get trashed during a multi-byte multiply operation.

 

(On some architectures, it may be that longjump() does restore all necessary context to do this.  But from the quoted parts of the C specifications, it certainly seems that you can't count on it.)

(I'm not sure why people are so inclined to try to twist a HLL into doing work that a few relatively obvious lines of assembly language would accomplish.)

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

westfw wrote:

I would suspect that longjump() might miss some of the needed context, since it (probably?) operates at the "C" context level.  For example I wouldn't count on it restoring the "known zero" in R1, which will get trashed during a multi-byte multiply operation.

 

Easy enough to test. Just compile this and see what the .LST file looks like.

 

jmp_buf cpu_state;


void foo(void)
{
    longjmp(cpu_state,1);
}

void main(void)
{

	volatile int i;
	int j = 0;

    while (1)
    {
        if (setjmp(cpu_state) == 0)
        {
            foo();
        } else {
            //jump here from foo()
            i=j;
        }
    }
}

 

 

#1 This forum helps those that help themselves

#2 All grounds are not created equal

#3 How have you proved that your chip is running at xxMHz?

#4 "If you think you need floating point to solve the problem then you don't understand the problem. If you really do need floating point then you have a problem you do not understand." - Heater's ex-boss

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Restoring R1 is not needed because it can only be non-zero during a multi-instruction pattern. You cannot call longjmp (or setjmp for that matter) from within a multi-instruction pattern.
.
If you ever have R1 non-zero between multi-instruction patterns then your program ([inline] asm) is trash.
.
The only thing that does not work as expected are global registers: they are restored but should rather behave like static storage, i.e. retain their values across longjmp.
.
Restoring RAMPx is not needed because GCC assumes they are all 0 and ISR prologue inits them with 0 (except in the case when there is RAMPZ and no RAMPD, content of RAMPZ is of no concern then). (There are some GCC issues with RAMPx handling though, but that's not anything longjmp is to be blamed for.)
.
EIND is also not restored, but that's no issue because GCC assumes that EIND never changes during program execution anyway.

avrfreaks does not support Opera. Profile inactive.

Last Edited: Fri. May 17, 2019 - 11:06 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Fascinating discussion, but did no one notice that this is a seven-year necromancy started by @renkitch 25 posts ago?

"Experience is what enables you to recognise a mistake the second time you make it."

"Good judgement comes from experience.  Experience comes from bad judgement."

"Wisdom is always wont to arrive late, and to be a little approximate on first possession."

"When you hear hoofbeats, think horses, not unicorns."

"Fast.  Cheap.  Good.  Pick two."

"We see a lot of arses on handlebars around here." - [J Ekdahl]

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I did but folks seemed to be having so much fun ;-)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The use of longjmp etc. is timeless.  20 years ago I used an RTOS with preemptive tasks that used it.  I'm still wondering if it might be a good idea.

 

More difficult to explain is people posting C code when C was rendered obsolete when the C++ compiler was made available.  I continued using C back in 1985 although my OS9 OS came with a C++ compiler.

Pages