This discussion has been locked.
You can no longer post new replies to this discussion. If you have a question you can start a new discussion

Atomicity

Is the operation below an atomic operation in a multitasking environment like RTX-51? If it is not, then what is supposed to do to make it an atomic one except using locking mechanisms?

void func()
{
  int locVal = globalVal++;
  ...
  ...
}

  • A 8-bit processor is not too good at copying a 16-bit value and then incrementing the source in a single step. Some processors (like a 8080 or Z80) have 16-bit extensions that are readily available but the 8051 is very limited. From the above code, it isn't obvious if the post-increment also needs to be atomic, but it is likely that it isn't.

    Looking at the assembler output is a great way to see if one C statement has been changed into multiple assembler statements - and to figure out if you will be in danger if an interrupt or task switch happens between any of these instructions.

    One way to protect code is to turn off the interrupts around a critical section. In some situations, you may get away with just deactivating a single interrupt source.

  • Thanks for your quick answer. I know that an assembler output would be a great tool to diagnose if the operation would be an atomic one or not and interrupt disabling/enabling mechanism could protect the code. I'm aware of all this stuff. I'm not after that. I'm after a more general info.

    Let me ask my question in that way: let my processor were
    a 32-bit one with flat memory and my RTOS were an advanced one like VxWorks. Would your answer then be the same?

    If it can't be completed in a single instruction then it is non-atomic? Is this the only way of diagnosing the atomicity in a code segment? If yes, which operations of a C/C++ compiler are atomic in general?

  • If it is more than 8 bits long and can't be completed in a single instruction and processed at different process levels then it is non-atomic

    which operations of a C/C++ compiler are atomic in general
    unanswerable

    Erik

  • thanks erik,

    so, only operations which are converted into a single instruction in machine level are atomic. Can the following one be an example of that?

    unsigned char val = 5;
    

    One last question: what do you mean by saying "processed at different process levels"?

  • This example is irrelevant. It may be one instruction to load the value 5 into a register and a second instruction to store the value.

    Or it may be one instruction to load the constant, a second instruction to compute the effective address of the variable (for example with some processors and stack-based variables) and a third instruction to store the value.

    But it isn't the number of instructions that is important in real life.

    Does it matter if you get an interrupt between en instruction to load a resgiter with the value 5 and an instruction to store the value? Not unless the interrupt destroys the register contents.

    In your last example, the question is: If an interrupt or other task reads your variable - what value will be read? Will it be the old value, or the new value (5) or a broken mix? In this case the store of the new value is atomic so another thread or interrupt will either see the old or the new value. In this case you got a atomic assign even if the source code had multiple instructions.

    Some processors have special instructions or instruction flag bits to "lock" the processor for one or more instructions - for example to allow two 16-bit stores to a 32-bit integer. This is similar to disabling interrupts, but with an automatic enable, and the program doesn't need to know if interrupts was enabled or disabled when processing the lock instruction or processing instructions with lock flags.

    Some processors have special hardware to create atomic updates to specific memory addresses. For example an assign to 16-bit timer register may be implemented with a 16-bit latch that requires the program to make two 8-bit assigns in a specific order. One of the 8-bit assigns just gets cached in the latch while the next 8-bit assign performs a full 16-bit transfer. The same thing exists for larger processors too - for example for a 16-bit processor to assign 32-bit timer values.

  • so, only operations which are converted into a single instruction in machine level are atomic
    I was not clear, I should have posted like this:
    If it the variable operated on is more than 8 bits long

    One last question: what do you mean by saying "processed at different process levels"?
    processed in main and an ISR or processed in 2 different ISRs ...

    Erik

  • Even with 8-bit variables, you can have problems with atomic operations.

    For example:

    ticket = next_ticket++;
    

    In this case, both the assign of the "ticket" variable, and the increment of "next_ticket" may be required to be performed in an unbroken sequence if there are more than one task - or a task and an interrupt - that needs to check out tickets.

    The above is similar to sempaphores and other synchronizing constructs where you may need an atomic test + write or test + increment or similar. Most modern processors have at least one special instruction for such purpose.

  • If it can't be completed in a single instruction then it is non-atomic?

    On the 8051: Yes. Other architectures may differ.

    If yes, which operations of a C/C++ compiler are atomic in general?

    None. Yes, none. You cannot assume that any statement in C will result in an atomic operation. Use proper locking mechanisms when not programming in assembly.

  • If it can't be completed in a single instruction then it is non-atomic?

    Let me throw one more thing into the mix. On the C166 architectire, the MULx and DIVx instructions are interruptible. The ISR must save the intermediate state of multiplication/division if it intends to execute MULx or DIVx. In a way, it means that even single instructions can be non-atomic.
    As for the whole discussion, I think the answers can only be as good as the questions. The concept of 'atomicity' is too vague to be applied to all CPU's in general and to the C programming language. You have to ask more specific questions.
    By the way, the language itself (C90 and C99) has no concept of 'atomicity'. Somewhat related are the keyword volatile and the concept of 'sequence points.'

  • You cannot assume that any statement in C will result in an atomic operation.

    Exactly right.
    Some compilers may provide language extensions which help achive atimicity. For example, Keil C166 compiler has the intrinsic functions _atomic_() and _endatomic_() which give access to the ATOMIC instruction of the CPU:
    http://www.keil.com/support/man/docs/c166/c166__atomic_.htm

  • _atomic_() isn't so much a compiler extension as a specific processor extension in the C166 processor.

    Most processors don't have any hardware support for atomic operations, so the compiler would have to disable interrupts. In that case, it is better to have the developer explicitly use any __dis() or similar intrinsic.

  • the only way to decide/achieve atomicity is knowledge.

    Trying to decide atomicity by looking at some C code is futile, if you have any doubt, you need look at the generated assembler.

    Erik

  • Most processors don't have any hardware support for atomic operations, so the compiler would have to disable interrupts.

    Unfortunately that only moves the problem elsewhere, but doesn't solve it. The trouble being that in general you have to save the previous state of the interrupt-enable flag before you disable interrupts, so you can clean up after yourself. In order for that saved state to be valid, these two operations have to be combined into an atomic step.

    I.e. you need an atomic instruction to work around the lack of an atomic instruction. Chicken/Egg, here we come!

  • You cannot assume that any statement in C will result in an atomic operation.

    That's not entirely true. C does provide __sigatomic_t in <signal.h> for exactly this purpose. There is a drawback though, in that interrupt handling is usually not mapped to the <signal.h> machinery.

  • I.e. you need an atomic instruction to work around the lack of an atomic instruction. Chicken/Egg, here we come!

    Only if you do things like disabling interrupts in an ISR without re-enabling them before leaving the ISR, or your processor has an interrupt system where there's a window after disabling interrupts in which interrupts can still occur.

    And even the venerable 8051 has an atomic "jump if bit is set and clear bit" instruction.