| #ifndef AOS_IPC_LIB_SYNC_H_ |
| #define AOS_IPC_LIB_SYNC_H_ |
| |
| #include <stddef.h> |
| #include <stdint.h> |
| |
| #ifdef __cplusplus |
| extern "C" { |
| #endif // __cplusplus |
| |
| // TODO(brians) add client requests to make helgrind useful with this code |
| // <http://www.valgrind.org/docs/manual/hg-manual.html#hg-manual.client-requests> |
| // and |
| // <http://www.valgrind.org/docs/manual/drd-manual.html#drd-manual.clientreqs> |
| // list the interesting ones |
| |
| // Have to remember to align structs containing it (recursively) to sizeof(int). |
| // Valid initial values for use with futex_ functions are 0 (unset) and 1 (set). |
| // The value should not be changed after multiple processes have started |
| // accessing an instance except through the functions declared in this file. |
| typedef uint32_t aos_futex __attribute__((aligned(sizeof(int)))); |
| |
| // For use with the condition_ functions. |
| // No initialization is necessary. |
| typedef aos_futex aos_condition; |
| |
| // For use with the mutex_ or death_notification_ functions. |
| // futex must be initialized to 0. |
| // No initialization is necessary for next and previous. |
| // Under ThreadSanitizer, pthread_mutex_init must be initialized to false. |
| // The recommended way to initialize one of these is by memset(3)ing the whole |
| // thing to 0 or using C++ () initialization to avoid depending on the |
| // implementation. |
| struct aos_mutex { |
| // 2 links to get O(1) adds and removes. |
| // This is &next of another element. |
| // next (might) have stuff |ed into it to indicate PI futexes and might also |
| // have an offset (see SetRobustListOffset); previous is an actual pointer |
| // without any of that. |
| // next has to stay the first element of this structure. |
| uintptr_t next; |
| struct aos_mutex *previous; |
| aos_futex futex; |
| #ifdef AOS_SANITIZER_thread |
| // Internal pthread mutex which is kept in sync with the actual mutex so tsan |
| // can understand what's happening and help catch bugs. |
| pthread_mutex_t pthread_mutex; |
| #ifndef __cplusplus |
| // TODO(brian): Remove this once the stupid C code is gone... |
| #define bool uint8_t |
| #endif |
| bool pthread_mutex_init; |
| #ifndef __cplusplus |
| #undef bool |
| #endif |
| #endif |
| }; |
| |
| // The mutex_ functions are designed to be used as mutexes. A mutex can only be |
| // unlocked from the same task which originally locked it. Also, if a task dies |
| // while holding a mutex, the next person who locks it will be notified. After a |
| // fork(2), any mutexes held will be held ONLY in the parent process. Attempting |
| // to unlock them from the child will give errors. |
| // Priority inheritance (aka priority inversion protection) is enabled. |
| |
| // All of these return 1 if the previous owner died with it held, 2 if |
| // interrupted by a signal, 3 if timed out, or 4 if an optional lock fails. Some |
| // of them (obviously) can never return some of those values. |
| // |
| // One of the highest priority processes blocked on a given mutex will be the |
| // one to lock it when it is unlocked. |
| int mutex_lock(struct aos_mutex *m) __attribute__((warn_unused_result)); |
| // Returns 2 if it timed out or 1 if interrupted by a signal. |
| int mutex_lock_timeout(struct aos_mutex *m, const struct timespec *timeout) |
| __attribute__((warn_unused_result)); |
| // Ignores signals (retries until something other than getting a signal |
| // happens). |
| int mutex_grab(struct aos_mutex *m) __attribute__((warn_unused_result)); |
| // LOG(FATAL)s for multiple unlocking. |
| void mutex_unlock(struct aos_mutex *m); |
| // Does not block waiting for the mutex. |
| int mutex_trylock(struct aos_mutex *m) __attribute__((warn_unused_result)); |
| #ifdef __cplusplus |
| // Returns whether or not the mutex is locked by this thread. |
| // There aren't very many valid uses for this function; the main ones are |
| // checking mutexes as they are destroyed to catch problems with that early and |
| // stack-based recursive mutex locking. |
| bool mutex_islocked(const aos_mutex *m); |
| |
| // The death_notification_ functions are designed for one thread to wait for |
| // another thread (possibly in a different process) to end. This can mean |
| // exiting gracefully or dying at any point. They use a standard aos_mutex, but |
| // this mutex may not be used with any of the mutex_ functions. |
| |
| // Initializes a variable which can be used to wait for this thread to die. |
| // This can only be called once after initializing *m to 0. |
| void death_notification_init(aos_mutex *m); |
| |
| // Manually triggers a death notification for this thread. |
| // This thread must have previously called death_notification_init(m). |
| void death_notification_release(aos_mutex *m); |
| |
| // Returns whether or not m is held by the current thread. |
| // This is mainly useful as a debug assertion. |
| inline bool death_notification_is_held(aos_mutex *m) { |
| return mutex_islocked(m); |
| } |
| #endif |
| |
| // The futex_ functions are similar to the mutex_ ones but different. |
| // They are designed for signalling when something happens (possibly to |
| // multiple listeners). A aos_futex manipulated with them can only be set or |
| // unset. Also, they can be set/unset/waited on from any task independently of |
| // who did something first and have no priority inversion protection. |
| // They return -1 for other error (which will be in errno from futex(2)). |
| // They have no spurious wakeups (because everybody always gets woken up). |
| // |
| // Another name for this kind of synchronization mechanism is a "notification". |
| // Python calls it an "event". |
| // |
| // They are different from the condition_ functions in that they do NOT work |
| // correctly as standard condition variables. While it is possible to keep |
| // track of the "condition" using the value part of the futex_* functions, the |
| // obvious implementation has basically the same race condition that condition |
| // variables are designed to prevent between somebody else grabbing the mutex |
| // and changing whether it's set or not and the futex_ function changing the |
| // futex's value. A futex is effectively a resettable condition variable with |
| // the condition being "has it been set"; if you have some other condition (for |
| // example messages are available to read on a queue), use the condition_ |
| // functions or there will be race conditions. |
| |
| // Wait for the futex to be set. Will return immediately if it's already set |
| // (after a syscall). |
| // Returns 0 if successful or it was already set, 1 if interrupted by a signal, |
| // or -1 with an error in errno. Can return 0 spuriously. |
| int futex_wait(aos_futex *m) __attribute__((warn_unused_result)); |
| // The same as futex_wait except returns 2 if it times out. |
| int futex_wait_timeout(aos_futex *m, const struct timespec *timeout) |
| __attribute__((warn_unused_result)); |
| |
| // Set the futex and wake up anybody waiting on it. |
| // Returns the number that were woken or -1 with an error in errno. |
| // |
| // This will always wake up all waiters at the same time and set the value to 1. |
| int futex_set(aos_futex *m); |
| // Same as above except lets something other than 1 be used as the final value. |
| int futex_set_value(aos_futex *m, aos_futex value); |
| // Unsets the futex (sets the value to 0). |
| // Returns 0 if it was set before and 1 if it wasn't. |
| // Can not fail. |
| int futex_unset(aos_futex *m); |
| |
| // The condition_ functions implement condition variable support. The API is |
| // similar to the pthreads api and works the same way. The same m argument must |
| // be passed in for all calls to all of the condition_ functions with a given c. |
| // They do have the potential for spurious wakeups. |
| |
| // Wait for the condition variable to be signalled. m will be unlocked |
| // atomically with actually starting to wait. m is guaranteed to be locked when |
| // this function returns. |
| // NOTE: The relocking of m is not atomic with stopping the actual wait and |
| // other process(es) may lock (+unlock) the mutex first. |
| // Returns 0 on success, 1 if the previous owner died or -1 if we timed out. |
| // Will only return -1 on timeout if end_time is not null. |
| int condition_wait(aos_condition *c, struct aos_mutex *m, |
| struct timespec *end_time) |
| __attribute__((warn_unused_result)); |
| // If any other processes are condition_waiting on c, wake 1 of them. Does not |
| // require m to be locked. |
| // NOTE: There is a small chance that this will wake more than just 1 waiter. |
| void condition_signal(aos_condition *c, struct aos_mutex *m); |
| // Wakes all processes that are condition_waiting on c. Does not require m to be |
| // locked. |
| void condition_broadcast(aos_condition *c, struct aos_mutex *m); |
| |
| #ifdef __cplusplus |
| } |
| |
| namespace aos { |
| namespace linux_code { |
| namespace ipc_lib { |
| |
| // Set the offset to use for putting addresses into the robust list. |
| // This is necessary to work around a kernel bug where it hangs when trying to |
| // deal with a futex on the robust list when its memory has been changed to |
| // read-only. |
| void SetRobustListOffset(ptrdiff_t offset); |
| |
| // Returns true if there are any mutexes still locked by this task. |
| // This is mainly useful for verifying tests don't mess up other ones by leaving |
| // now-freed but still locked mutexes around. |
| bool HaveLockedMutexes(); |
| |
| } // namespace ipc_lib |
| } // namespace linux_code |
| } // namespace aos |
| |
| #endif // __cplusplus |
| |
| #endif // AOS_IPC_LIB_SYNC_H_ |