Use volatile data with methods not overloaded for volatile data

195 views Asked by At

I'm a bit at the end of my knowledge.

I have three processes communicating via a shared memory segment. Concurrent access is handled via correct locking (to avoid getting misunderstood here), so I'm pretty sure I'm using the volatile keyword in the way it is intended for.

My shared memory segment is cast to a volatile pointer to a struct, and I can operate on this. The struct has to be volatile, because I sometimes need to spin until some value on the shared memory changes - so not using volatile is not an option.

Now I am using an external C++-library (SystemC, but this should not matter here) from which my struct contains members of sc_time. Although I have access to the source of the library, I don't want to depend on modifications made by me, propably breaking stuff or running into maintenance hell.

Now this class "sc_time" has operators for comparison and assignment. These operators don't work with volatile sc_time - so far not surprising.

Now my question is: Is there a way converting away this volatile-ness without breaking the semantics? I can use the often mentioned const_cast<> or a simple C-cast, but what will the compiler do then? I could propably even just memcpy() the data - but then again, what will the result be?

Any suggestions will be welcome - I would have no problems at all using a C-only wrapper or any other method - as long as it works(tm), but my last resort would be some small memcpy-like assembler code for really reading the data - and that's something I'd like to avoid.

Thanks for your time reading this :-)

Edit: added small code snippet:

struct shared_memory{
     sc_time time1;
     sc_time time2;
     sc_time time3;
     ...
}

...

class foo 
{
    foo();   // attach shared memory and assign to *mem
    ...
    pthread_mutex_t mutex;
    volatile struct shared_memory *mem;
    ...
    void do_stuff();  // periodically called
};

void foo::do_stuff()
{
   ...
   lock_mutex(mutex);
   sc_time t1 = mem->time1;
   sc_time t2 = mem->time2;
   sc_time t3 = mem->time3;
   unlock_mutex(mutex);
   ... 
   while(t1 < t2 || t1 < t3){
       lock_mutex(mutex);
       t1 = mem->time1;
       t2 = mem->time2;
       t3 = mem->time3;
       unlock_mutex(mutex);
   }

}
2

There are 2 answers

15
code_fodder On

You can use const_cast to do away with the 'volatile' as well as the 'const' of variables.

Note: The variable within the function will be treated as non-volatile by the compiler :)

4
Jens Gustedt On

If you have to spin waiting for the changes of a variable, you are not locking correctly. Spin waiting always must assume atomic access to the data. This is not only about getting the latest version of data, this is also about coherence, data may be split over several cache lines for example, and you might just get the low bytes of an old version and the high bytes of an updated one.

For this reason the new C and C++ standards have concepts and tools for atomic access, use them if you may. If you don't have them, use compiler (or library) extension: all modern processors have atomic instructions and any decent compiler has an extension that provides such features.

Playing games with volatile is not a path you should go. If you cast away a volatile and pass such an object to a function that doesn't expect things to change under it, all things can happen. In particular, when the library that doesn't have the volatile is compiled optimizations that ruin your data can be performed. Don't do it.

Edit: Since you also tagged your question with gcc, for a good reason they have __sync_lock_test_and_set builtins since a loooong time as an extension. Use it. Maybe on your specific combination of architecture this will resolve in exactly the same code, but trust the gcc guys, they know when they have to add some assembler magic to give you guarantees about your data.