Thread: Semaphore pre-initialisation

  1. #1
    Registered User awsdert's Avatar
    Join Date
    Jan 2015
    Posts
    1,733

    Semaphore pre-initialisation

    Let's say I have this:
    Code:
    #ifdef _WIN32
    typedef HANDLE sem;
    #define INVALID SEM NULL
    ...
    #else
    typedef sem_t sem;
    #define INVALID_SEM ...
    ...
    #endif
    sem global_sem = INVALID_SEM;
    int main(...) { ... }
    Is there any way to define INVALID_SEM in the second case? I've been trying to find info on that but all the search engines think I'm looking for is sem_init which is intended to be used during runtime, not compile time.

  2. #2
    Registered User
    Join Date
    Sep 2022
    Posts
    55
    The HANDLE represents a pointer to an opaque object while sem_t is an (implementation-defined) object type. However, as to my knowledge you wouldn't ever use a variable of type sem_t on a POSIX compliant OS as the creation of a semaphore returns a sem_t* and also functions to be used along with a semaphore expect a sem_t*. So, what's the point of defining your own sem type as sem_t rather than a pointer to sem_t?
    Last edited by aGerman; 02-23-2024 at 11:13 AM.

  3. #3
    Registered User awsdert's Avatar
    Join Date
    Jan 2015
    Posts
    1,733
    I'm writing a bare bones api wrapper that I can then use to make a cross-platform abi. Can't get around endianess and pointer sizes (though the PATH variable at least helps a tone in that respect) but the native os api can be wrapped by a platform ignorant abi. As for the *.exe vs *.elf vs *.etc, I'm resolving that with a combo of the linker and makefile on the objects produced just before the main extension. I'm also creating a main.o object to deal with native initialisation and then pass on the arguments converted to UTF-8 to a custom expectation, I'm thinking pawexec( ud, argc, argv ) for now, that's the rough format I'm giving the thread callbacks too as I want threads to not care if their parent is their own app or an external app.

    The basic idea is that paw could act as a pseudo shell env for libraries, thereby creating the option of hosting commands in app instead of via external shell. The pseudo shell thing is more a bonus addition since I'm already doing a custom abi. Anyways I want the initialiser because I'm accounting for situations that should be impossible but as with all rules there are exceptions. Sure I expect it to be initialised to 0 by the compiler but how do I know that initialisation to 0 doesn't in fact make it look like a valid sem_t and thus not become a subtle bug that could be uncaught until it does horrendous damage.

    For example file descriptors need to be initialised to -1, how do I know sem_t is not an object that uses a file descriptor inside it? In that case it being initialised to 0 actually gives a valid file descriptor since stdin uses 0 normally (if I remember correctly). Basically being able to directly tell the compiler how to pre-initialise the object so that lack of initialisation can be caught at the earliest stages would be possible.

    Additionally the native wrapper will be available for use by others so it's always best to assume the worst case scenario and work your way backwards.

    Edit: I just noticed I failed to address your "the creation of a semaphore returns a sem_t*", that only applies to sem_open(), not sem_init(). Since sem_init() is exposed to the user space I decided it best to just assume I'll need it at some point and start my wrapping from there

  4. #4
    Registered User
    Join Date
    Sep 2022
    Posts
    55
    Since sem_init() is exposed to the user space I decided it best to just assume I'll need it at some point and start my wrapping from there
    This might be an unfortunate choice then. Can't you just wrap sem_open() then? As I said HANDLE is a pointer to an opaque object. So you won't ever find something that represents a sem_t-like object in the Win32 API. Using sem_t instead of sem_t* would conflict with your cross-platform intention from the beginning, IMHO.

  5. #5
    Registered User awsdert's Avatar
    Join Date
    Jan 2015
    Posts
    1,733
    sem_open is next on the list to wrap but it doesn't resolve the need to invalidate the semaphore at compile time. For now I'm using this:
    Code:
    typedef sem_t npawsem;
    #define INVALID_NPAWSEM ((union{pawcu r[sizeof(sem_t)]; sem_t s;}){{0}}).s
    The underlying native wrapper that the public wrapper uses mandates GNUC23 minimum so I can make use of inline unions however this only solves half the problem, that being compile time pre-initialisation. This method doesn't account for possible file descriptors in the semaphore so I still need a more robust solution.

    Edit: btw still in the middle of bug fixing after removing some typedefs from the public headers after discovering intmax_t does in fact have ABI issues despite the efforts I've read of the C committee to maintain a fixed size for existing bindings. As a result I've yet to see if that define compiles or not.

  6. #6
    Registered User
    Join Date
    May 2012
    Location
    Arizona, USA
    Posts
    948
    Code:
    typedef sem_t *npawsem;
    #define INVALID SEM NULL
    Then make a function to allocate (with malloc() or shmget() or shm_open() or whatever, depending on how it's shared) and initialize (with sem_init()) a semaphore and return a pointer to it. Then you can abstract away creating unnamed (memory-based) semaphores that are either thread-shared or process-shared.

  7. #7
    Registered User awsdert's Avatar
    Join Date
    Jan 2015
    Posts
    1,733
    That's the same as
    Code:
    #define INVALID_NPAWSEM ((union{pawcu r[sizeof(sem_t)]; sem_t s;}){{0}}).s
    but with a runtime cost instead, not what I'm looking for. A use case for non-allocated semaphores would be a guarantee that the public reference does not randomly become invalid on a thread while it's passing the pointer to the public reference to try and lock it, so static initialisation is the only way to go there when trying to invalidated it prior to initialisation. I'll reiterate, I want COMPILE TIME INVALIDATION.

  8. #8
    Registered User
    Join Date
    May 2012
    Location
    Arizona, USA
    Posts
    948
    But that requires that you assume how sem_t is defined. POSIX doesn't say how the sem_t structure is defined. A semaphore with a value of 0 might be valid. On the other hand, a null pointer to a semaphore is always an invalid semaphore, regardless of how the sem_t structure is defined.

    Besides, how does a statically allocated semaphore object guarantee that it won't "randomly" become invalidated by some other thread? Couldn't some thread set a semaphore to INVALID_NPAWSEM at any time while another thread is using it?

  9. #9
    Registered User awsdert's Avatar
    Join Date
    Jan 2015
    Posts
    1,733
    gmail really needs to stop mark cboard mail as spam, anyways I ultimately found semget & co and now default to those instead since I can forcefully invalidate the semaphore at compile time by defining INVALID_NPAWSEM as -1. As far as "randomly become invalid" I meant that a pointer used as though it were valid can cause the app to crash. Worse still if the same expectations were applied in the kernel it could crash the entire kernel. If on the other hand I use something only the kernel can control the validity and type of then when a thread attempts to use a just invalidated semaphore the kernel will see the descriptor/handle (in the case of windows) as either not pointing to any object in the list of objects it has and thus flag as invalid or see it's pointing to something not a semaphore which will also cause it to flag as invalid. There's still an edge case of the hook being given to a new semaphore at which point the kernel can't detect the error but that's possibly catch-able with multiple copies of the hook.

    As far as posix not defining how sem_t is defined, that's fine as the header is always native. However because the header is always native posix should define a define for implementations to define for compile time invalidation. Something as simple as #define COMPILE_TIME_INVALID_SEM_T ... would've been fine. No need for code to look into the details of the semaphore. They just need to set their semaphore like this then:
    Code:
    sem_t s = COMPILE_TIME_INVALID_SEM_T;
    void *thread_foo(void*) { if ( sem_wait(s) != 0 && errno == EINVALID ) return NULL; ... }
    int main(...) { ... pthread_create(thread_foo,...); ... }
    Without the define it's ambiguous if main managed to somehow skip initialising s with sem_init(), it's entirely possible that during intial load of the executable the memory space s sits in just so happens to look like a valid semaphore which is definitely not thread safe and thus not the intent of sem_t.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Replies: 3
    Last Post: 11-14-2010, 11:14 AM
  2. incorrect initialisation
    By s_siouris in forum C Programming
    Replies: 2
    Last Post: 09-13-2006, 07:26 AM
  3. Tree Initialisation
    By kidburla in forum C Programming
    Replies: 1
    Last Post: 06-01-2006, 07:56 AM
  4. Initialisation of a struct
    By lzhaol in forum C++ Programming
    Replies: 4
    Last Post: 03-31-2006, 02:58 AM
  5. pointer initialisation
    By C_Coder in forum C Programming
    Replies: 2
    Last Post: 10-28-2001, 06:27 AM

Tags for this Thread