Skip to content

Introducing k_threadlock A conditional low overhead "mutex" #92023

Open
@bjarki-andreasen

Description

@bjarki-andreasen

Problem Description

In zephyr, we have drivers and subsystems which may be used pre kernel, post kernel, with and without multithreading. In all of these scenarios, we need thread safety, however, in some of them, thread safety is a given, since we either only have one thread, in cases of pre kernel and no multithreading

This currently leads to drivers and subsystems needing to implement conditional locking themselves, typically only after its discovered that they needed it, as a driver's init level is changed for example.

The logic looks like this:

#if !CONFIG_NO_MULTITHREADING
if (!k_is_pre_kernel()) {
        (void)k_sem_take(&mysem, K_FOREVER);
}
#endif

...

#if !CONFIG_NO_MULTITHREADING
if (!k_is_pre_kernel()) {
        k_sem_give(&mysem);
}
#endif

We also have the "ISR" thread in any case, which we typically also want to sync with, so an adaptive solution to what follows is proposed as well :)

#if !CONFIG_NO_MULTITHREADING
static K_SEM_DEFINE(mysem, 0, 1);
#else
static atomic_t myatom = ATOM_INIT(0);
#endif

void myisr()
{
#if !CONFIG_NO_MULTITHREADING
        k_sem_give(&mysem);
#else
        atomic_set_bit(&myatom, 0);
#endif
}

void myapi()
{
#if !CONFIG_NO_MULTITHREADING
        k_sem_take(&mysem, K_FOREVER);
#else
        !WAIT_FOR(atomic_test_bit(&myatom, 0), 100000, NULL)
#endif
}

Proposed Change (Summary)

This could preferable be packaged into a new feature: The struct k_threadlock

which would make the code look like:

(void)k_threadlock_lock(&mythreadlock, K_FOREVER);

...

k_treadlock_unlock(&mythreadlock)

which would automatically adapt to the build configurations and kernel stage.

As inspired by @mathieuchopstm we could also consider a primitive which automatically adapts to waiting on something from an ISR using threads or polling, namely a k_threadawait() which would look like this:

#if !CONFIG_NO_MULTITHREADING
static K_SEM_DEFINE(mysem, 0, 1);
#else
static atomic_t myatom = ATOM_INIT(0);
#endif

void myisr()
{
#if !CONFIG_NO_MULTITHREADING
        k_sem_give(&mysem);
#else
        atomic_set_bit(&myatom, 0);
#endif
}

void myapi()
{
#if !CONFIG_NO_MULTITHREADING
        k_sem_take(&mysem, K_FOREVER);
#else
        !WAIT_FOR(atomic_test_bit(&myatom, 0), 100000, NULL)
#endif
}

with

static struct k_threadawait mythreadawait;

void myisr()
{
        k_threadawait_signal(&mythreadawait);
}

void myapi()
{
        k_threadawait_await(&mythreadawait, K_FOREVER);
}

Proposed Change (Detailed)

Simply package a k_sem and use it as a binary sem for locking, if required (!CONFIG_NO_MULTITHREADING). cheap and simple :)

Dependencies

No response

Concerns and Unresolved Questions

No response

Alternatives Considered

What we do now, technically a bit more efficient since we can avoid the check of k_is_pre_kernel() in some cases.

Metadata

Metadata

Labels

RFCRequest For Comments: want input from the communityarea: Kernel

Type

Projects

Status

No status

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions