mirror of
https://github.com/apache/nuttx.git
synced 2026-08-21 13:38:28 +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>
316 lines
9.2 KiB
C
316 lines
9.2 KiB
C
/****************************************************************************
|
|
* sched/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 <stdbool.h>
|
|
#include <errno.h>
|
|
#include <assert.h>
|
|
|
|
#include <nuttx/init.h>
|
|
#include <nuttx/irq.h>
|
|
#include <nuttx/arch.h>
|
|
#include <nuttx/mm/kmap.h>
|
|
#include <nuttx/signal.h>
|
|
|
|
#include "sched/sched.h"
|
|
#include "semaphore/semaphore.h"
|
|
|
|
/****************************************************************************
|
|
* Public Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: nxsem_wait_slow
|
|
*
|
|
* Description:
|
|
* This function attempts to lock the semaphore referenced by 'sem' in
|
|
* slow mode.
|
|
*
|
|
* 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_slow(FAR sem_t *sem)
|
|
{
|
|
#ifndef CONFIG_DISABLE_ALL_SIGNALS
|
|
sigset_t pendingset;
|
|
#endif
|
|
FAR struct tcb_s *rtcb = this_task();
|
|
irqstate_t flags;
|
|
int ret = OK;
|
|
bool unlocked;
|
|
FAR struct tcb_s *htcb = NULL;
|
|
bool mutex = NXSEM_IS_MUTEX(sem);
|
|
|
|
/* The following operations must be performed with interrupts
|
|
* disabled because nxsem_post() may be called from an interrupt
|
|
* handler.
|
|
*/
|
|
|
|
flags = enter_critical_section();
|
|
|
|
/* Make sure we were supplied with a valid semaphore. */
|
|
|
|
#ifndef CONFIG_DISABLE_ALL_SIGNALS
|
|
/* A signal can arrive before sem_wait transitions the task to
|
|
* TSTATE_WAIT_SEM. In that window, the wait cannot yet be aborted by
|
|
* sem_wait_irq(). If sem_wait then blocks without re-checking unmasked
|
|
* pending signals, it can sleep indefinitely and miss the interrupt.
|
|
*/
|
|
|
|
pendingset = nxsig_pendingset(rtcb);
|
|
nxsig_nandset(&pendingset, &pendingset, &rtcb->sigprocmask);
|
|
if (!sigisemptyset(&pendingset))
|
|
{
|
|
leave_critical_section(flags);
|
|
return -EINTR;
|
|
}
|
|
#endif
|
|
|
|
/* Check if the lock is available */
|
|
|
|
if (mutex)
|
|
{
|
|
uint32_t mholder;
|
|
|
|
/* We lock the mutex for us by setting the blocks bit,
|
|
* this is all that is needed if we block
|
|
*/
|
|
|
|
mholder = atomic_fetch_or(NXSEM_MHOLDER(sem), NXSEM_MBLOCKING_BIT);
|
|
|
|
/* Avoid mutex recursion, which is not allowed. The comparison uses
|
|
* the lock side's encoding so that ids of either sign compare the
|
|
* way they were stored.
|
|
*/
|
|
|
|
DEBUGASSERT((mholder & (~NXSEM_MBLOCKING_BIT)) !=
|
|
NXSEM_MAKE_MHOLDER(nxsched_gettid()));
|
|
|
|
if (NXSEM_MACQUIRED(mholder))
|
|
{
|
|
/* htcb gets NULL if
|
|
* - the only holder did exit (without posting first)
|
|
* - the mutex was reset before
|
|
* In both cases we simply acquire the mutex, thus recovering
|
|
* from these situations.
|
|
*/
|
|
|
|
htcb = nxsched_get_tcb(mholder & (~NXSEM_MBLOCKING_BIT));
|
|
}
|
|
|
|
unlocked = htcb == NULL;
|
|
}
|
|
else
|
|
{
|
|
unlocked = atomic_fetch_sub(NXSEM_COUNT(sem), 1) > 0;
|
|
}
|
|
|
|
if (unlocked)
|
|
{
|
|
/* 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);
|
|
}
|
|
|
|
leave_critical_section(flags);
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
/* For mutexes, we only add the holder to the tasks list at the
|
|
* time when a task blocks on the mutex, for priority restoration
|
|
*/
|
|
|
|
if (!mutex)
|
|
{
|
|
nxsem_add_holder(sem);
|
|
}
|
|
}
|
|
|
|
/* The semaphore is NOT available, We will have to block the
|
|
* current thread of execution.
|
|
*/
|
|
|
|
else
|
|
{
|
|
#ifdef CONFIG_PRIORITY_INHERITANCE
|
|
uint8_t prioinherit = sem->flags & SEM_PRIO_MASK;
|
|
#endif
|
|
|
|
/* First, verify that the task is not already waiting on a
|
|
* semaphore
|
|
*/
|
|
|
|
DEBUGASSERT(rtcb->waitobj == NULL);
|
|
|
|
#ifdef CONFIG_MM_KMAP
|
|
sem = kmm_map_user(rtcb, sem, sizeof(*sem));
|
|
#endif
|
|
|
|
/* Save the waited on semaphore in the TCB */
|
|
|
|
rtcb->waitobj = sem;
|
|
|
|
/* In case of a mutex, store the previous holder in the task's list */
|
|
|
|
if (mutex)
|
|
{
|
|
nxsem_add_holder_tcb(htcb, sem);
|
|
}
|
|
|
|
/* If priority inheritance is enabled, then check the priority of
|
|
* the holder of the semaphore.
|
|
*/
|
|
|
|
#ifdef CONFIG_PRIORITY_INHERITANCE
|
|
if (prioinherit == SEM_PRIO_INHERIT)
|
|
{
|
|
/* Disable context switching. The following operations must be
|
|
* atomic with regard to the scheduler.
|
|
*/
|
|
|
|
sched_lock();
|
|
|
|
/* Boost the priority of any threads holding a count on the
|
|
* semaphore.
|
|
*/
|
|
|
|
nxsem_boost_priority(sem);
|
|
}
|
|
#endif
|
|
|
|
/* Set the errno value to zero (preserving the original errno)
|
|
* value). We reuse the per-thread errno to pass information
|
|
* between sem_waitirq() and this functions.
|
|
*/
|
|
|
|
rtcb->errcode = OK;
|
|
|
|
/* Add the TCB to the prioritized semaphore wait queue, after
|
|
* checking this is not the idle task - descheduling that
|
|
* isn't going to end well.
|
|
*/
|
|
|
|
DEBUGASSERT(!is_idle_task(rtcb));
|
|
|
|
/* Remove the tcb task from the running list. */
|
|
|
|
nxsched_remove_self(rtcb);
|
|
|
|
/* Add the task to the specified blocked task list */
|
|
|
|
rtcb->task_state = TSTATE_WAIT_SEM;
|
|
nxsched_add_prioritized(rtcb, SEM_WAITLIST(sem));
|
|
|
|
/* Now, perform the context switch */
|
|
|
|
up_switch_context(this_task(), rtcb);
|
|
|
|
/* When we resume at this point, either (1) the semaphore has been
|
|
* assigned to this thread of execution, or (2) the semaphore wait
|
|
* has been interrupted by a signal or a timeout. We can detect
|
|
* these latter cases be examining the per-thread errno value.
|
|
*
|
|
* In the event that the semaphore wait was interrupted by a
|
|
* signal or a timeout, certain semaphore clean-up operations have
|
|
* already been performed (see sem_waitirq.c). Specifically:
|
|
*
|
|
* - nxsem_canceled() was called to restore the priority of all
|
|
* threads that hold a reference to the semaphore,
|
|
* - The semaphore count was decremented, and
|
|
* - tcb->waitobj was nullifed.
|
|
*
|
|
* It is necessary to do these things in sem_waitirq.c because a
|
|
* long time may elapse between the time that the signal was issued
|
|
* and this thread is awakened and this leaves a door open to
|
|
* several race conditions.
|
|
*/
|
|
|
|
/* Check if an error occurred while we were sleeping. Expected
|
|
* errors include EINTR meaning that we were awakened by a signal
|
|
* or ETIMEDOUT meaning that the timer expired for the case of
|
|
* sem_timedwait().
|
|
*
|
|
* If we were not awakened by a signal or a timeout, then
|
|
* nxsem_add_holder() was called by logic in sem_wait() fore this
|
|
* thread was restarted.
|
|
*/
|
|
|
|
ret = rtcb->errcode != OK ? -rtcb->errcode : OK;
|
|
|
|
#ifdef CONFIG_MM_KMAP
|
|
kmm_unmap(sem);
|
|
#endif
|
|
|
|
#ifdef CONFIG_PRIORITY_INHERITANCE
|
|
if (prioinherit != 0)
|
|
{
|
|
sched_unlock();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/* If this now holds the mutex, set the holder TID and the lock bit */
|
|
|
|
if (mutex && ret == OK)
|
|
{
|
|
uint32_t blocking_bit =
|
|
dq_empty(SEM_WAITLIST(sem)) ? 0 : NXSEM_MBLOCKING_BIT;
|
|
|
|
atomic_set(NXSEM_MHOLDER(sem), ((uint32_t)rtcb->pid) | blocking_bit);
|
|
}
|
|
|
|
leave_critical_section(flags);
|
|
return ret;
|
|
}
|