mirror of
https://github.com/apache/nuttx.git
synced 2026-08-14 08:53:15 +00:00
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
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>
274 lines
7.5 KiB
C
274 lines
7.5 KiB
C
/****************************************************************************
|
|
* libs/libc/semaphore/sem_wait.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 <errno.h>
|
|
#include <assert.h>
|
|
#include <sched.h>
|
|
|
|
#include <nuttx/sched.h>
|
|
#include <nuttx/init.h>
|
|
#include <nuttx/cancelpt.h>
|
|
#include <nuttx/semaphore.h>
|
|
#include <nuttx/atomic.h>
|
|
#include <nuttx/irq.h>
|
|
|
|
/****************************************************************************
|
|
* Public Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: sem_wait
|
|
*
|
|
* Description:
|
|
* This function attempts to lock the semaphore referenced by 'sem'. If
|
|
* the semaphore value is (<=) zero, then the calling task will not return
|
|
* until it successfully acquires the lock.
|
|
*
|
|
* Input Parameters:
|
|
* sem - Semaphore descriptor.
|
|
*
|
|
* Returned Value:
|
|
* This function is a standard, POSIX application interface. It returns
|
|
* zero (OK) if successful. Otherwise, -1 (ERROR) is returned and
|
|
* the errno value is set appropriately. Possible errno values include:
|
|
*
|
|
* - EINVAL: Invalid attempt to get the semaphore
|
|
* - EINTR: The wait was interrupted by the receipt of a signal.
|
|
*
|
|
****************************************************************************/
|
|
|
|
int sem_wait(FAR sem_t *sem)
|
|
{
|
|
int errcode;
|
|
int ret;
|
|
|
|
if (sem == NULL)
|
|
{
|
|
set_errno(EINVAL);
|
|
return ERROR;
|
|
}
|
|
|
|
/* sem_wait() is a cancellation point */
|
|
|
|
if (enter_cancellation_point())
|
|
{
|
|
#ifdef CONFIG_CANCELLATION_POINTS
|
|
/* If there is a pending cancellation, then do not perform
|
|
* the wait. Exit now with ECANCELED.
|
|
*/
|
|
|
|
errcode = ECANCELED;
|
|
goto errout_with_cancelpt;
|
|
#endif
|
|
}
|
|
|
|
/* Let nxsem_wait() do the real work */
|
|
|
|
ret = nxsem_wait(sem);
|
|
if (ret < 0)
|
|
{
|
|
errcode = -ret;
|
|
goto errout_with_cancelpt;
|
|
}
|
|
|
|
leave_cancellation_point();
|
|
return OK;
|
|
|
|
errout_with_cancelpt:
|
|
set_errno(errcode);
|
|
leave_cancellation_point();
|
|
return ERROR;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: nxsem_wait
|
|
*
|
|
* Description:
|
|
* This function attempts to lock the semaphore referenced by 'sem'. If
|
|
* the semaphore value is (<=) zero, then the calling task will not return
|
|
* until it successfully acquires the lock.
|
|
*
|
|
* This is an internal OS interface. It is functionally equivalent to
|
|
* sem_wait except that:
|
|
*
|
|
* - It is not a cancellation point, and
|
|
* - It does not modify the errno value.
|
|
*
|
|
* Input Parameters:
|
|
* sem - Semaphore descriptor.
|
|
*
|
|
* Returned Value:
|
|
* This is an internal OS interface and should not be used by applications.
|
|
* It follows the NuttX internal error return policy: Zero (OK) is
|
|
* returned on success. A negated errno value is returned on failure.
|
|
* Possible returned errors:
|
|
*
|
|
* - EINVAL: Invalid attempt to get the semaphore
|
|
* - EINTR: The wait was interrupted by the receipt of a signal.
|
|
*
|
|
****************************************************************************/
|
|
|
|
int nxsem_wait(FAR sem_t *sem)
|
|
{
|
|
bool fastpath = true;
|
|
bool mutex;
|
|
|
|
DEBUGASSERT(sem != NULL);
|
|
|
|
/* This API should not be called from the idleloop or interrupt */
|
|
|
|
#if defined(CONFIG_BUILD_FLAT) || defined(__KERNEL__)
|
|
DEBUGASSERT(!OSINIT_IDLELOOP() || !sched_idletask() ||
|
|
up_interrupt_context());
|
|
#endif
|
|
|
|
mutex = NXSEM_IS_MUTEX(sem);
|
|
|
|
/* Disable fast path if priority protection is enabled on the semaphore */
|
|
|
|
#ifdef CONFIG_PRIORITY_PROTECT
|
|
if ((sem->flags & SEM_PRIO_MASK) == SEM_PRIO_PROTECT)
|
|
{
|
|
fastpath = false;
|
|
}
|
|
#endif
|
|
|
|
/* Disable fast path on a counting semaphore with priority inheritance */
|
|
|
|
#ifdef CONFIG_PRIORITY_INHERITANCE
|
|
if (!mutex && (sem->flags & SEM_PRIO_MASK) != SEM_PRIO_NONE)
|
|
{
|
|
fastpath = false;
|
|
}
|
|
#endif
|
|
|
|
while (fastpath)
|
|
{
|
|
FAR atomic_t *val = mutex ? NXSEM_MHOLDER(sem) : NXSEM_COUNT(sem);
|
|
int32_t old = atomic_read(val);
|
|
int32_t new;
|
|
|
|
if (mutex)
|
|
{
|
|
if (old != NXSEM_NO_MHOLDER)
|
|
{
|
|
break;
|
|
}
|
|
|
|
new = NXSEM_MAKE_MHOLDER(_SCHED_GETTID());
|
|
}
|
|
else
|
|
{
|
|
if (old < 1)
|
|
{
|
|
break;
|
|
}
|
|
|
|
new = old - 1;
|
|
}
|
|
|
|
if (atomic_try_cmpxchg_acquire(val, &old, new))
|
|
{
|
|
return OK;
|
|
}
|
|
}
|
|
|
|
return nxsem_wait_slow(sem);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: nxsem_wait_uninterruptible
|
|
*
|
|
* Description:
|
|
* This function is wrapped version of nxsem_wait(), which is
|
|
* uninterruptible and convenient for use.
|
|
*
|
|
* Parameters:
|
|
* sem - Semaphore descriptor.
|
|
*
|
|
* Return Value:
|
|
* Zero(OK) - On success
|
|
* EINVAL - Invalid attempt to get the semaphore
|
|
* ECANCELED - May be returned if the thread is canceled while waiting.
|
|
*
|
|
****************************************************************************/
|
|
|
|
int nxsem_wait_uninterruptible(FAR sem_t *sem)
|
|
{
|
|
int ret;
|
|
|
|
do
|
|
{
|
|
/* Take the semaphore (perhaps waiting) */
|
|
|
|
ret = nxsem_wait(sem);
|
|
}
|
|
while (ret == -EINTR);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: nxsem_clockwait_uninterruptible
|
|
*
|
|
* Description:
|
|
* This function is wrapped version of nxsem_clockwait(), which is
|
|
* uninterruptible and convenient for use.
|
|
*
|
|
* Input Parameters:
|
|
* sem - Semaphore object
|
|
* clockid - The timing source to use in the conversion
|
|
* abstime - The absolute time to wait until a timeout is declared.
|
|
*
|
|
* Returned Value:
|
|
* EINVAL The sem argument does not refer to a valid semaphore. Or the
|
|
* thread would have blocked, and the abstime parameter specified
|
|
* a nanoseconds field value less than zero or greater than or
|
|
* equal to 1000 million.
|
|
* ETIMEDOUT The semaphore could not be locked before the specified timeout
|
|
* expired.
|
|
* EDEADLK A deadlock condition was detected.
|
|
* ECANCELED May be returned if the thread is canceled while waiting.
|
|
*
|
|
****************************************************************************/
|
|
|
|
int nxsem_clockwait_uninterruptible(FAR sem_t *sem, clockid_t clockid,
|
|
FAR const struct timespec *abstime)
|
|
{
|
|
int ret;
|
|
|
|
do
|
|
{
|
|
/* Take the semaphore (perhaps waiting) */
|
|
|
|
ret = nxsem_clockwait(sem, clockid, abstime);
|
|
}
|
|
while (ret == -EINTR);
|
|
|
|
return ret;
|
|
}
|