Implementación de semáforos en C y bajo nivel

Estaba pensando en cómo implementar semáforos (no binarios) usando menos código asm como sea posible.
No he tenido éxito en pensar y escribir sin usar un mutex, así que aquí está lo mejor que podía hacer hasta ahora:

Global:

#include <stdlib.h>
#include <pthread.h>
#include <stdatomic.h>
#include <stdbool.h>

typedef struct
{
    atomic_ullong    value;
    pthread_mutex_t *lock_op;
    bool             ready;
} semaphore_t;

typedef struct
{
    atomic_ullong   value;
    pthread_mutex_t lock_op;
    bool            ready;
} static_semaphore_t;

 /* use with static_semaphore_t */
#define SEMAPHORE_INITIALIZER(value) = {value, PTHREAD_MUTEX_INITIALIZER, true}


Funciones:

bool semaphore_init(semaphore_t *semaphore, unsigned long long init_value)
{   
    if(semaphore->ready) if(!(semaphore->lock_op = \
                             calloc(1, sizeof(pthread_mutex_t)))) return false;
    else                 pthread_mutex_destroy(semaphore->lock_op);   

    if(pthread_mutex_init(semaphore->lock_op, NULL))
            return false;

    semaphore->value = init_value;
    semaphore->ready = true;
    return true;
}

bool semaphore_wait(semaphore_t *semaphore)
{
    if(!semaphore->ready) return false;

    pthread_mutex_lock(&(semaphore->lock_op));
    while(!semaphore->value) __asm__ __volatile__("nop");
    (semaphore->value)--;
    pthread_mutex_unlock(&(semaphore->lock_op));
    return true;
}

bool semaphore_post(semaphore_t *semaphore)
{
    if(!semaphore->ready) return false;

    atomic_fetch_add(&(semaphore->value), (unsigned long long) 1);
    return true;
}


¿Es posible implementar un semáforo usando solo unas pocas líneas, con los elementos integrados atómicos o directamente en ensamblaje (ej.lock cmpxchg)?

Mirando la estructura sem_t de<bits/sempahore.h> incluido por<semaphore.h> me parece que se ha elegido un camino muy diferente ...

typedef union
{
    char __size[__SIZEOF_SEM_T];
    long int __align;
} sem_t;



ACTUALIZAR:

@PeterCordes ha propuesto una solución definitivamente mucho mejor, usando los atómicos, sin mutex, haciendo las verificaciones directamente en el valor del semáforo.

Todavía quiero comprender mejor las posibilidades de mejorar el código en términos de rendimiento, aprovechando las funciones de pausa incorporadas o las llamadas al kernel que evitan el desperdicio de la CPU, esperando que los recursos críticos estén disponibles.

También sería bueno tener una implementación estándar de mutexes y semáforos no binarios para la comparación.
Defutex (7) Yo leo:"El kernel de Linux proporciona futexes (" mutexes rápidos de espacio de usuario ") como un bloque de construcción para el bloqueo rápido de espacio de usuario y semáforos. Los futexes son muy básicos y se prestan bien para construir abstracciones de bloqueo de nivel superior como mutexes, variables de condición, bloqueos de lectura-escritura, barreras y semáforos ".

Respuestas a la pregunta(1)

Su respuesta a la pregunta