nuttx/sched/semaphore/sem_trywait.c
Justin Hammond 31fbb99218
Some checks are pending
MemBrowse Memory Report / changes-filter (push) Waiting to run
MemBrowse Memory Report / load-targets (push) Waiting to run
MemBrowse Memory Report / identical (push) Blocked by required conditions
MemBrowse Memory Report / analyze (push) Blocked by required conditions
sched/semaphore: Keep a negative task id out of the mutex holder.
A mutex records its holder as a task id in the low 31 bits of a word
whose top bit means "someone is blocked on this".  The id was stored
without masking, so an id with its top bit set became a holder with the
blocking bit raised.

Task ids are normally small and positive, but not always.
nxsched_gettid() reports -ESRCH for a context that no longer maps to a
running task, and there is a window where that is exactly what the
running context is: nxtask_exit() marks the next task ready to run
while the dying task is still executing on its own stack, and only then
releases the TCB.  Freeing the group inside that release takes and
drops the group's mutexes, so the lock stores 0xfffffffd and the unlock
compares 0x7ffffffd, which are not equal.

With assertions enabled the unlock trips its holder check, and every
exit of a process that frees memory panics.  In a kernel build that is
every exit, so no program could be run twice, and running one at all
took the shell down with it.  Without assertions the failure is silent:
the accidental blocking bit sends the unlock looking for a waiter that
never existed.

Encode the id the same way everywhere it is stored or compared, so that
a lock and an unlock from one context agree whatever the id's sign.
The masked forms of -1 and -2 would alias the "no holder" and "reset"
values, but nxsched_gettid() yields only valid ids and -ESRCH.

mm_lock() already sidesteps this window with a note that gettid() may
return -ESRCH during a context switch; this gives the generic mutex the
same footing rather than a second special case.

Test case, on the EIC7700 EVB, which is a kernel build with assertions:

  nsh> hello
  Hello, World!!

Before, that printed and then panicked in sem_post, taking the shell
with it, every time.  After, five runs in a row complete and the shell
survives.  ps over telnet still completes.

Assisted-by: Claude:claude-opus-5
Signed-off-by: Justin Hammond <justin@dynam.ac>
2026-08-09 10:05:30 +08:00

137 lines
3.6 KiB
C

/****************************************************************************
* sched/semaphore/sem_trywait.c
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <stdbool.h>
#include <sched.h>
#include <assert.h>
#include <errno.h>
#include <nuttx/init.h>
#include <nuttx/irq.h>
#include <nuttx/arch.h>
#include "sched/sched.h"
#include "semaphore/semaphore.h"
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: nxsem_trywait_slow
*
* Description:
* This function locks the specified semaphore in slow mode.
*
* Input Parameters:
* sem - the semaphore descriptor
*
* Returned Value:
*
* EINVAL - Invalid attempt to get the semaphore
* EAGAIN - The semaphore is not available.
*
* Assumptions:
*
****************************************************************************/
int nxsem_trywait_slow(FAR sem_t *sem)
{
irqstate_t flags;
int ret = OK;
bool mutex = NXSEM_IS_MUTEX(sem);
FAR atomic_t *val = mutex ? NXSEM_MHOLDER(sem) : NXSEM_COUNT(sem);
int32_t old;
int32_t new;
/* The following operations must be performed with interrupts disabled
* because sem_post() may be called from an interrupt handler.
*/
flags = enter_critical_section();
/* If the semaphore is available, give it to the requesting task */
old = atomic_read(val);
do
{
if (mutex)
{
if (NXSEM_MACQUIRED(old))
{
ret = -EAGAIN;
break;
}
new = NXSEM_MAKE_MHOLDER(nxsched_gettid());
}
else
{
if (old <= 0)
{
ret = -EAGAIN;
break;
}
new = old - 1;
}
}
while (!atomic_try_cmpxchg_acquire(val, &old, new));
if (ret != -EAGAIN)
{
/* It is, let the task take the semaphore */
#ifdef CONFIG_PRIORITY_PROTECT
ret = nxsem_protect_wait(sem);
if (ret < 0)
{
if (mutex)
{
atomic_set(NXSEM_MHOLDER(sem), NXSEM_NO_MHOLDER);
}
else
{
atomic_fetch_add(NXSEM_COUNT(sem), 1);
}
}
else
#endif
{
if (!mutex)
{
nxsem_add_holder(sem);
}
}
}
/* Interrupts may now be enabled. */
leave_critical_section(flags);
return ret;
}