task/pthread_cancelpt: Move cancel point handling to libc, data to TLS

This moves task / thread cancel point logic from the NuttX kernel into
libc, while the data needed by the cancel point logic is moved to TLS.

The change is an enabler to move user-space APIs to libc as well, for
a coherent user/kernel separation.
This commit is contained in:
Ville Juven
2023-11-10 12:56:10 +02:00
committed by Xiang Xiao
parent 1e31ec8003
commit 0dedbcd4ae
24 changed files with 414 additions and 547 deletions
+2 -12
View File
@@ -97,22 +97,12 @@ static int group_cancel_children_handler(pid_t pid, FAR void *arg)
if (pid != (pid_t)((uintptr_t)arg))
{
/* Cancel this thread. This is a forced cancellation. Make sure that
* cancellation is not disabled by the task/thread. That bit will
* prevent pthread_cancel() or nxtask_delete() from doing what they
* need to do.
*/
rtcb = nxsched_get_tcb(pid);
if (rtcb != NULL)
{
/* This is a forced cancellation. Make sure that cancellation is
* not disabled by the task/thread. That bit would prevent
* pthread_cancel() or task_delete() from doing what they need
* to do.
*/
/* Cancel this thread. This is a forced cancellation. */
rtcb->flags &= ~TCB_FLAG_NONCANCELABLE;
rtcb->flags |= TCB_FLAG_FORCED_CANCEL;
/* 'pid' could refer to the main task of the thread. That pid
* will appear in the group member list as well!
+2 -5
View File
@@ -367,9 +367,7 @@ void nx_start(void)
*/
#ifdef CONFIG_SMP
g_idletcb[i].cmn.flags = (TCB_FLAG_TTYPE_KERNEL |
TCB_FLAG_NONCANCELABLE |
TCB_FLAG_CPU_LOCKED);
g_idletcb[i].cmn.flags = (TCB_FLAG_TTYPE_KERNEL | TCB_FLAG_CPU_LOCKED);
g_idletcb[i].cmn.cpu = i;
/* Set the affinity mask to allow the thread to run on all CPUs. No,
@@ -383,8 +381,7 @@ void nx_start(void)
g_idletcb[i].cmn.affinity =
(cpu_set_t)(CONFIG_SMP_DEFAULT_CPUSET & SCHED_ALL_CPUS);
#else
g_idletcb[i].cmn.flags = (TCB_FLAG_TTYPE_KERNEL |
TCB_FLAG_NONCANCELABLE);
g_idletcb[i].cmn.flags = TCB_FLAG_TTYPE_KERNEL;
#endif
#if CONFIG_TASK_NAME_SIZE > 0
+1 -3
View File
@@ -341,7 +341,7 @@ static void dump_task(FAR struct tcb_s *tcb, FAR void *arg)
#ifdef CONFIG_SMP
" %4d"
#endif
" %3d %-8s %-7s %c%c%c"
" %3d %-8s %-7s %c"
" %-18s"
" " SIGSET_FMT
" %p"
@@ -363,8 +363,6 @@ static void dump_task(FAR struct tcb_s *tcb, FAR void *arg)
TCB_FLAG_POLICY_SHIFT]
, g_ttypenames[(tcb->flags & TCB_FLAG_TTYPE_MASK)
>> TCB_FLAG_TTYPE_SHIFT]
, tcb->flags & TCB_FLAG_NONCANCELABLE ? 'N' : '-'
, tcb->flags & TCB_FLAG_CANCEL_PENDING ? 'P' : '-'
, tcb->flags & TCB_FLAG_EXIT_PROCESSING ? 'P' : '-'
, state
, SIGSET_ELEM(&tcb->sigprocmask)
-6
View File
@@ -448,12 +448,6 @@ int nx_pthread_create(pthread_trampoline_t trampoline, FAR pthread_t *thread,
#endif
}
#ifdef CONFIG_CANCELLATION_POINTS
/* Set the deferred cancellation type */
ptcb->cmn.flags |= TCB_FLAG_CANCEL_DEFERRED;
#endif
/* Get the assigned pid before we start the task (who knows what
* could happen to ptcb after this!).
*/
-5
View File
@@ -33,7 +33,6 @@ set(SRCS
task_recover.c
task_restart.c
task_spawnparms.c
task_setcancelstate.c
task_cancelpt.c
task_terminate.c
task_gettid.c
@@ -51,10 +50,6 @@ if(NOT CONFIG_BUILD_KERNEL)
list(APPEND SRCS task_spawn.c)
endif()
if(CONFIG_CANCELLATION_POINTS)
list(APPEND SRCS task_setcanceltype.c task_testcancel.c)
endif()
if(NOT CONFIG_BINFMT_DISABLE)
if(CONFIG_LIBC_EXECFUNCS)
list(APPEND SRCS task_execve.c task_posixspawn.c)
+2 -6
View File
@@ -21,8 +21,8 @@
CSRCS += task_create.c task_init.c task_setup.c task_activate.c
CSRCS += task_start.c task_delete.c task_exit.c task_exithook.c
CSRCS += task_getgroup.c task_getpid.c task_prctl.c task_recover.c
CSRCS += task_restart.c task_spawnparms.c task_setcancelstate.c
CSRCS += task_cancelpt.c task_terminate.c task_gettid.c exit.c
CSRCS += task_restart.c task_spawnparms.c task_cancelpt.c task_terminate.c
CSRCS += task_gettid.c exit.c
ifeq ($(CONFIG_SCHED_HAVE_PARENT),y)
CSRCS += task_getppid.c task_reparent.c
@@ -36,10 +36,6 @@ ifneq ($(CONFIG_BUILD_KERNEL),y)
CSRCS += task_spawn.c
endif
ifeq ($(CONFIG_CANCELLATION_POINTS),y)
CSRCS += task_setcanceltype.c task_testcancel.c
endif
ifneq ($(CONFIG_BINFMT_DISABLE),y)
ifeq ($(CONFIG_LIBC_EXECFUNCS),y)
CSRCS += task_execve.c task_posixspawn.c
+8 -240
View File
@@ -58,6 +58,7 @@
#include <nuttx/irq.h>
#include <nuttx/cancelpt.h>
#include <nuttx/tls.h>
#include "sched/sched.h"
#include "semaphore/semaphore.h"
@@ -65,245 +66,10 @@
#include "mqueue/mqueue.h"
#include "task/task.h"
#ifdef CONFIG_CANCELLATION_POINTS
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: enter_cancellation_point
*
* Description:
* Called at the beginning of the cancellation point to establish the
* cancellation point. This function does the following:
*
* 1. If deferred cancellation does not apply to this thread, nothing is
* done, otherwise, it
* 2. Sets state information in the caller's TCB and increments a nesting
* count.
* 3. If this is the outermost nesting level, it checks if there is a
* pending cancellation and, if so, calls either exit() or
* pthread_exit(), depending upon the type of the thread.
*
* Input Parameters:
* None
*
* Returned Value:
* true is returned if a cancellation is pending but cannot be performed
* now due to the nesting level.
*
****************************************************************************/
bool enter_cancellation_point(void)
{
FAR struct tcb_s *tcb = this_task();
bool ret = false;
/* Disabling pre-emption should provide sufficient protection. We only
* need the TCB to be stationary (no interrupt level modification is
* anticipated).
*
* REVISIT: is locking the scheduler sufficient in SMP mode?
*/
sched_lock();
/* If cancellation is disabled on this thread or if this thread is using
* asynchronous cancellation, then do nothing.
*
* Special case: if the cpcount count is greater than zero, then we are
* nested and the above condition was certainly true at the outermost
* nesting level.
*/
if (((tcb->flags & TCB_FLAG_NONCANCELABLE) == 0 &&
(tcb->flags & TCB_FLAG_CANCEL_DEFERRED) != 0) ||
tcb->cpcount > 0)
{
/* Check if there is a pending cancellation */
if ((tcb->flags & TCB_FLAG_CANCEL_PENDING) != 0)
{
/* Yes... return true (if we don't exit here) */
ret = true;
/* If there is a pending cancellation and we are at the outermost
* nesting level of cancellation function calls, then exit
* according to the type of the thread.
*/
if (tcb->cpcount == 0)
{
#ifndef CONFIG_DISABLE_PTHREAD
if ((tcb->flags & TCB_FLAG_TTYPE_MASK) ==
TCB_FLAG_TTYPE_PTHREAD)
{
pthread_exit(PTHREAD_CANCELED);
}
else
#endif
{
_exit(EXIT_FAILURE);
}
}
}
/* Otherwise, indicate that we are at a cancellation point by
* incrementing the nesting level of the cancellation point
* functions.
*/
DEBUGASSERT(tcb->cpcount < INT16_MAX);
tcb->cpcount++;
}
sched_unlock();
return ret;
}
/****************************************************************************
* Name: leave_cancellation_point
*
* Description:
* Called at the end of the cancellation point. This function does the
* following:
*
* 1. If deferred cancellation does not apply to this thread, nothing is
* done, otherwise, it
* 2. Clears state information in the caller's TCB and decrements a
* nesting count.
* 3. If this is the outermost nesting level, it checks if there is a
* pending cancellation and, if so, calls either exit() or
* pthread_exit(), depending upon the type of the thread.
*
* Input Parameters:
* None
*
* Returned Value:
* None
*
****************************************************************************/
void leave_cancellation_point(void)
{
FAR struct tcb_s *tcb = this_task();
/* Disabling pre-emption should provide sufficient protection. We only
* need the TCB to be stationary (no interrupt level modification is
* anticipated).
*
* REVISIT: is locking the scheduler sufficient in SMP mode?
*/
sched_lock();
/* If cancellation is disabled on this thread or if this thread is using
* asynchronous cancellation, then do nothing. Here we check only the
* nesting level: if the cpcount count is greater than zero, then the
* required condition was certainly true at the outermost nesting level.
*/
if (tcb->cpcount > 0)
{
/* Decrement the nesting level. If if would decrement to zero, then
* we are at the outermost nesting level and may need to do more.
*/
if (tcb->cpcount == 1)
{
/* We are no longer at the cancellation point */
tcb->cpcount = 0;
/* If there is a pending cancellation then just exit according to
* the type of the thread.
*/
if ((tcb->flags & TCB_FLAG_CANCEL_PENDING) != 0)
{
#ifndef CONFIG_DISABLE_PTHREAD
if ((tcb->flags & TCB_FLAG_TTYPE_MASK) ==
TCB_FLAG_TTYPE_PTHREAD)
{
pthread_exit(PTHREAD_CANCELED);
}
else
#endif
{
_exit(EXIT_FAILURE);
}
}
}
else
{
/* We are not at the outermost nesting level. Just decrment the
* nesting level count.
*/
tcb->cpcount--;
}
}
sched_unlock();
}
/****************************************************************************
* Name: check_cancellation_point
*
* Description:
* Returns true if:
*
* 1. Deferred cancellation does applies to this thread,
* 2. We are within a cancellation point (i.e., the nesting level in the
* TCB is greater than zero).
*
* Input Parameters:
* None
*
* Returned Value:
* true is returned if a cancellation is pending but cannot be performed
* now due to the nesting level.
*
****************************************************************************/
bool check_cancellation_point(void)
{
FAR struct tcb_s *tcb = this_task();
bool ret = false;
/* Disabling pre-emption should provide sufficient protection. We only
* need the TCB to be stationary (no interrupt level modification is
* anticipated).
*
* REVISIT: is locking the scheduler sufficient in SMP mode?
*/
sched_lock();
/* If cancellation is disabled on this thread or if this thread is using
* asynchronous cancellation, then return false.
*
* If the cpcount count is greater than zero, then we within a
* cancellation and will true if there is a pending cancellation.
*/
if (((tcb->flags & TCB_FLAG_NONCANCELABLE) == 0 &&
(tcb->flags & TCB_FLAG_CANCEL_DEFERRED) != 0) ||
tcb->cpcount > 0)
{
/* Check if there is a pending cancellation. If so, return true. */
ret = ((tcb->flags & TCB_FLAG_CANCEL_PENDING) != 0);
}
sched_unlock();
return ret;
}
#endif /* CONFIG_CANCELLATION_POINTS */
/****************************************************************************
* Name: nxnotify_cancellation
*
@@ -322,6 +88,7 @@ bool check_cancellation_point(void)
bool nxnotify_cancellation(FAR struct tcb_s *tcb)
{
FAR struct tls_info_s *tls = tls_get_info_pid(tcb->pid);
irqstate_t flags;
bool ret = false;
@@ -338,7 +105,8 @@ bool nxnotify_cancellation(FAR struct tcb_s *tcb)
/* Check to see if this task has the non-cancelable bit set. */
if ((tcb->flags & TCB_FLAG_NONCANCELABLE) != 0)
if ((tcb->flags & TCB_FLAG_FORCED_CANCEL) == 0 &&
(tls->tl_cpstate & CANCEL_FLAG_NONCANCELABLE) != 0)
{
/* Then we cannot cancel the thread now. Here is how this is
* supposed to work:
@@ -353,7 +121,7 @@ bool nxnotify_cancellation(FAR struct tcb_s *tcb)
* immediately, interrupting the thread with its processing."
*/
tcb->flags |= TCB_FLAG_CANCEL_PENDING;
tls->tl_cpstate |= CANCEL_FLAG_CANCEL_PENDING;
leave_critical_section(flags);
return true;
}
@@ -361,7 +129,7 @@ bool nxnotify_cancellation(FAR struct tcb_s *tcb)
#ifdef CONFIG_CANCELLATION_POINTS
/* Check if this task supports deferred cancellation */
if ((tcb->flags & TCB_FLAG_CANCEL_DEFERRED) != 0)
if ((tls->tl_cpstate & CANCEL_FLAG_CANCEL_ASYNC) == 0)
{
/* Then we cannot cancel the task asynchronously. */
@@ -369,13 +137,13 @@ bool nxnotify_cancellation(FAR struct tcb_s *tcb)
/* Mark the cancellation as pending. */
tcb->flags |= TCB_FLAG_CANCEL_PENDING;
tls->tl_cpstate |= CANCEL_FLAG_CANCEL_PENDING;
/* If the task is waiting at a cancellation point, then notify of the
* cancellation thereby waking the task up with an ECANCELED error.
*/
if (tcb->cpcount > 0)
if (tls->tl_cpcount > 0)
{
/* If the thread is blocked waiting for a semaphore, then the
* thread must be unblocked to handle the cancellation.
-11
View File
@@ -430,17 +430,6 @@ void nxtask_exithook(FAR struct tcb_s *tcb, int status)
return;
}
#ifdef CONFIG_CANCELLATION_POINTS
/* Mark the task as non-cancelable to avoid additional calls to exit()
* due to any cancellation point logic that might get kicked off by
* actions taken during exit processing.
*/
tcb->flags |= TCB_FLAG_NONCANCELABLE;
tcb->flags &= ~TCB_FLAG_CANCEL_PENDING;
tcb->cpcount = 0;
#endif
/* If the task was terminated by another task, it may be in an unknown
* state. Make some feeble effort to recover the state.
*/
-139
View File
@@ -1,139 +0,0 @@
/****************************************************************************
* sched/task/task_setcancelstate.c
*
* 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 <stdlib.h>
#include <pthread.h>
#include <sched.h>
#include <errno.h>
#include "sched/sched.h"
#include "task/task.h"
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: task_setcancelstate
*
* Description:
* The task_setcancelstate() function atomically both sets the calling
* task's cancellability state to the indicated state and returns the
* previous cancellability state at the location referenced by oldstate.
* Legal values for state are TASK_CANCEL_ENABLE and TASK_CANCEL_DISABLE.
*
* The cancellability state and type of any newly created tasks are
* TASK_CANCEL_ENABLE and TASK_CANCEL_DEFERRED respectively.
*
* Input Parameters:
* state - the new cancellability state, either TASK_CANCEL_ENABLE or
* TASK_CANCEL_DISABLE
* oldstate - The location to return the old cancellability state.
*
* Returned Value:
* Zero (OK) on success; ERROR is returned on any failure with the
* errno value set appropriately.
*
****************************************************************************/
int task_setcancelstate(int state, FAR int *oldstate)
{
FAR struct tcb_s *tcb = this_task();
int ret = OK;
/* Suppress context changes for a bit so that the flags are stable. (the
* flags should not change in interrupt handling).
*/
sched_lock();
/* Return the current state if so requested */
if (oldstate != NULL)
{
if ((tcb->flags & TCB_FLAG_NONCANCELABLE) != 0)
{
*oldstate = TASK_CANCEL_DISABLE;
}
else
{
*oldstate = TASK_CANCEL_ENABLE;
}
}
/* Set the new cancellation state */
if (state == TASK_CANCEL_ENABLE)
{
/* Clear the non-cancelable flag */
tcb->flags &= ~TCB_FLAG_NONCANCELABLE;
/* Check if a cancellation was pending */
if ((tcb->flags & TCB_FLAG_CANCEL_PENDING) != 0)
{
#ifdef CONFIG_CANCELLATION_POINTS
/* If we are using deferred cancellation? */
if ((tcb->flags & TCB_FLAG_CANCEL_DEFERRED) == 0)
#endif
{
/* No.. We are using asynchronous cancellation. If the
* cancellation was pending in this case, then just exit.
*/
tcb->flags &= ~TCB_FLAG_CANCEL_PENDING;
#ifndef CONFIG_DISABLE_PTHREAD
if ((tcb->flags & TCB_FLAG_TTYPE_MASK) ==
TCB_FLAG_TTYPE_PTHREAD)
{
pthread_exit(PTHREAD_CANCELED);
}
else
#endif
{
_exit(EXIT_FAILURE);
}
}
}
}
else if (state == TASK_CANCEL_DISABLE)
{
/* Set the non-cancelable state */
tcb->flags |= TCB_FLAG_NONCANCELABLE;
}
else
{
set_errno(EINVAL);
ret = ERROR;
}
sched_unlock();
return ret;
}
-128
View File
@@ -1,128 +0,0 @@
/****************************************************************************
* sched/task/task_setcanceltype.c
*
* 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 <stdlib.h>
#include <pthread.h>
#include <sched.h>
#include <errno.h>
#include "sched/sched.h"
#include "task/task.h"
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: task_setcanceltype
*
* Description:
* The task_setcanceltype() function atomically both sets the calling
* thread's cancellability type to the indicated type and returns the
* previous cancellability type at the location referenced by oldtype
* Legal values for type are TASK_CANCEL_DEFERRED and
* TASK_CANCEL_ASYNCHRONOUS.
*
* The cancellability state and type of any newly created threads,
* including the thread in which main() was first invoked, are
* TASK_CANCEL_ENABLE and TASK_CANCEL_DEFERRED respectively.
*
****************************************************************************/
int task_setcanceltype(int type, FAR int *oldtype)
{
FAR struct tcb_s *tcb = this_task();
int ret = OK;
/* Suppress context changes for a bit so that the flags are stable. (the
* flags should not change in interrupt handling).
*/
sched_lock();
/* Return the current type if so requested */
if (oldtype != NULL)
{
if ((tcb->flags & TCB_FLAG_CANCEL_DEFERRED) != 0)
{
*oldtype = TASK_CANCEL_DEFERRED;
}
else
{
*oldtype = TASK_CANCEL_ASYNCHRONOUS;
}
}
/* Set the new cancellation type */
if (type == TASK_CANCEL_ASYNCHRONOUS)
{
/* Clear the deferred cancellation bit */
tcb->flags &= ~TCB_FLAG_CANCEL_DEFERRED;
#ifdef CONFIG_CANCELLATION_POINTS
/* If we just switched from deferred to asynchronous type and if a
* cancellation is pending, then exit now.
*/
if ((tcb->flags & TCB_FLAG_CANCEL_PENDING) != 0 &&
(tcb->flags & TCB_FLAG_NONCANCELABLE) == 0)
{
tcb->flags &= ~TCB_FLAG_CANCEL_PENDING;
/* Exit according to the type of the thread */
#ifndef CONFIG_DISABLE_PTHREAD
if ((tcb->flags & TCB_FLAG_TTYPE_MASK) == TCB_FLAG_TTYPE_PTHREAD)
{
pthread_exit(PTHREAD_CANCELED);
}
else
#endif
{
_exit(EXIT_FAILURE);
}
}
#endif
}
#ifdef CONFIG_CANCELLATION_POINTS
else if (type == TASK_CANCEL_DEFERRED)
{
/* Set the deferred cancellation type */
tcb->flags |= TCB_FLAG_CANCEL_DEFERRED;
}
#endif
else
{
ret = EINVAL;
}
sched_unlock();
return ret;
}
-6
View File
@@ -409,12 +409,6 @@ static int nxthread_setup_scheduler(FAR struct tcb_s *tcb, int priority,
tcb->flags |= TCB_FLAG_SCHED_FIFO;
#endif
#ifdef CONFIG_CANCELLATION_POINTS
/* Set the deferred cancellation type */
tcb->flags |= TCB_FLAG_CANCEL_DEFERRED;
#endif
/* Save the task ID of the parent task in the TCB and allocate
* a child status structure.
*/
-52
View File
@@ -1,52 +0,0 @@
/****************************************************************************
* sched/task/task_testcancel.c
*
* 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 <sched.h>
#include <errno.h>
#include <nuttx/cancelpt.h>
#include "task/task.h"
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: task_testcancel
*
* Description:
* The task_testcancel() function creates a cancellation point in the
* calling thread. The task_testcancel() function has no effect if
* cancellability is disabled
*
****************************************************************************/
void task_testcancel(void)
{
enter_cancellation_point();
leave_cancellation_point();
}