data synchronization without using semapore in C
Asked Answered
S

3

1

I need to have data synchronization in my code. Currently I am accessing a global value inside interrupt and also in a local function which may corrupt the data if interrupt call is frequent. I need to avoid this scenario. I am not using operating system in my code, so I cannot use a semaphore. Using a similar locking method as semaphore might solve my problem.

Any help would be appreciated

Shudder answered 4/5, 2017 at 10:8 Comment(2)
Post your code.Annam
Tell us something about your hardware environment. If you have a single CPU core with one thread of execution, the easy way is to disable interrupts while you are doing the update. In a multiprocessor environment, the processor instruction set will normally have an atomic test and set instruction or equivalent.Silkstocking
R
4

Interrupts work differently than threads or processes - if a thread waits for a semaphore, it is simply not scheduled until either the semaphore gets available or, if given, the wait timeout elapses. In the mean while, other threads can be scheduled, one of those potentially giving back the semaphore.

This is not the case with interrupt service routines - these won't be interrupted by any thread scheduling (if at all, then only by other interrupts), but are executed until they return. So if a ISR gets to wait for a semaphore (or a similar mechanism, as you asked for), we are in a deadlock as the thread holding it can't be scheduled any more either to give the semaphore back...

So you need a totally different mechanism!

The usual way to do this is to disable the interrupt as long as your function needs to access the common data, and re-enable it afterwards (and you potentially need to do this within the ISR itself, too).

How? Well, OS/hardware specific - as long as you do not provide further details, I'm out here...

Just some hints yet: Keep the period of disabled interrupts as short as possible and make sure that the commonly accessed data is declared volatile!

Rocca answered 4/5, 2017 at 10:43 Comment(2)
Lock-free data structures can help, too!Childress
You don't have to wait in a forever-loop to grab a semaphore, you could as well just check if it is available. If not, the ISR will have to either discard the value or save it in a buffer.Vern
V
2

What you need is atomic access to the data. If it is a single variable and you can guarantee that access is atomic, then that is enough. However, this involves disassembling the C code and see what you ended up with. And even if the machine code ended up as atomic (single instruction), it won't be portable.

If you have a modern compiler with C11 support, you can declare the shared variable as _Atomic and that will solve the issue.

Another alternative is to simply shut off the particular interrupt during variable access in the caller. This will however disrupt real-time performance and you might miss out interrupts.

The universally "best" solution might be to invent a semaphore by yourself. Example:

// volatile to prevent dangerous compiler optimizations; does not solve re-entrancy
volatile uint32_t data;
volatile bool guard;

void ISR (void)
{
  if(!guard)
  {
    data = SOME_REGISTER;
  }
}

void main (void)
{
  ...
  guard = true;
  uint32_t local = data;
  guard = false;
}

In the above example no atomic access is guaranteed at all, not even to the guard variable. However, it is no longer necessary, because at the point where main() is about to read the data, the guard is guaranteed to be set. If the interrupt would kick in during the read, it wouldn't corrupt the data.

The only downside of this solution is that you will be missing out updating data when the guard is set. If this is an issue, you will have to implement some manner of temporary storage.

(Note that this code does not result in "memory barriers", so on complex multi-core processors, this method might not work and volatile will not necessarily result in a memory barrier. On ordinary microcontrollers it will work just fine though.)

Vern answered 4/5, 2017 at 13:58 Comment(5)
The above solution is fine. My actual scenario is that; I am holding the value of a ADC interrupt in a global variable and updating it to a local variable inside another sub function. So the function and the interrupt will be called frequently ; So we need to set and reset the guard variable in the interrupt too and provide a check in the function before the local variable tries to access the global variable. The issue arises when the local variable tries to access the global variable and interrupt happens the same time and value of global variable getting updated resulting in data corruption.Shudder
@Shudder The interrupt cannot get interrupted by the caller, so it is not necessary to set the guard there. (Unless you have a multicore solution with a second core executing the interrupt.)Vern
@Vern very thorough answer covering off all things to think aboutEulogistic
This solution has a chicken-and-egg problem: In the generic case, you cannot assume access to guard is atomic without disabling interrupts (in most cases, it will be, though). Back to square 1.Leuko
@Leuko No, because it doesn't matter if the access to guard is not atomic and interrupted - guard is not written to by any other code but main. In case it is interrupted, then the interrupt will execute and the main code gets back to setting that variable as if nothing happened. The only thing that matters is that at the point where main() is about to use the data variable, guard is guaranteed to be set, because the write to guard is sequenced before the read from data. This holds true in the generic case.Vern
E
1

It's probably as simple as this in your main code:

disable_interrupts();
value += 1;
enable_interrupts();

So you make sure the interrupt cannot fire while you're using the value in the main code.

Eulogistic answered 4/5, 2017 at 11:11 Comment(3)
If these functions change the global interrupt mask, then this is not a good idea. Ideally you should only disable the affected interrupt.Vern
@Vern There's not enough information in the question to provide the ideal answer.Eulogistic
Either this code disables the global interrupt mask or it doesn't - it isn't clear and that has nothing to do with the question. On several systems I have worked with, a macro with the exact name enable_interrupts() results in setting/clearing the global interrupt mask. Which is usually not a good idea unless you have perfect control over all interrupts in the MCU.Vern

© 2022 - 2024 — McMap. All rights reserved.