From fc73dfd36892547ef1ed7771d3d8f8ce5c4a2bf7 Mon Sep 17 00:00:00 2001 From: ligd Date: Tue, 11 Jun 2024 22:05:35 +0800 Subject: [PATCH] sleep: optimize sleep logic, to reduce the disable IRQ time Signed-off-by: ligd --- include/nuttx/signal.h | 49 +++++++ sched/signal/sig_nanosleep.c | 156 ++------------------- sched/signal/sig_timedwait.c | 258 ++++++++++++++++++++--------------- 3 files changed, 206 insertions(+), 257 deletions(-) diff --git a/include/nuttx/signal.h b/include/nuttx/signal.h index f9fef73441f..ada5eaa1c5a 100644 --- a/include/nuttx/signal.h +++ b/include/nuttx/signal.h @@ -439,6 +439,55 @@ int nxsig_kill(pid_t pid, int signo); #define nxsig_waitinfo(s,i) nxsig_timedwait(s,i,NULL) +/**************************************************************************** + * Name: nxsig_clockwait + * + * Description: + * This function selects the pending signal set specified by the argument + * set. If multiple signals are pending in set, it will remove and return + * the lowest numbered one. If no signals in set are pending at the time + * of the call, the calling process will be suspended until one of the + * signals in set becomes pending, OR until the process is interrupted by + * an unblocked signal, OR until the time interval specified by timeout + * (if any), has expired. If timeout is NULL, then the timeout interval + * is forever. + * + * If the info argument is non-NULL, the selected signal number is stored + * in the si_signo member and the cause of the signal is stored in the + * si_code member. The content of si_value is only meaningful if the + * signal was generated by sigqueue() (or nxsig_queue). + * + * This is an internal OS interface. It is functionally equivalent to + * sigtimedwait() except that: + * + * - It is not a cancellation point, and + * - It does not modify the errno value. + * + * Input Parameters: + * clockid - The ID of the clock to be used to measure the timeout. + * flags - Open flags. TIMER_ABSTIME is the only supported flag. + * rqtp - The amount of time to be suspended from execution. + * rmtp - If the rmtp argument is non-NULL, the timespec structure + * referenced by it is updated to contain the amount of time + * remaining in the interval (the requested time minus the time + * actually slept) + * + * Returned Value: + * This is an internal OS interface and should not be used by applications. + * A negated errno value is returned on failure. + * + * EAGAIN - wait time is zero. + * EINTR - The wait was interrupted by an unblocked, caught signal. + * + * Notes: + * This function should be called with critical section set. + * + ****************************************************************************/ + +int nxsig_clockwait(int clockid, int flags, + FAR const struct timespec *rqtp, + FAR struct timespec *rmtp); + /**************************************************************************** * Name: nxsig_timedwait * diff --git a/sched/signal/sig_nanosleep.c b/sched/signal/sig_nanosleep.c index 3c8c3c68861..d04a9ecd46e 100644 --- a/sched/signal/sig_nanosleep.c +++ b/sched/signal/sig_nanosleep.c @@ -93,112 +93,11 @@ int nxsig_nanosleep(FAR const struct timespec *rqtp, FAR struct timespec *rmtp) { - irqstate_t flags; - clock_t starttick; - sigset_t set; int ret; - /* Sanity check */ + ret = nxsig_clockwait(CLOCK_REALTIME, 0, rqtp, rmtp); - if (rqtp == NULL || rqtp->tv_nsec < 0 || rqtp->tv_nsec >= 1000000000) - { - return -EINVAL; - } - - /* If rqtp is zero, yield CPU and return - * Notice: The behavior of sleep(0) is not defined in POSIX, so there are - * different implementations: - * 1. In Linux, nanosleep(0) will call schedule() to yield CPU: - * https://elixir.bootlin.com/linux/latest/source/kernel/time/ - * hrtimer.c#L2038 - * 2. In BSD, nanosleep(0) will return immediately: - * https://github.com/freebsd/freebsd-src/blob/ - * 475fa89800086718bd9249fd4dc3f862549f1f78/crypto/openssh/ - * openbsd-compat/bsd-misc.c#L243 - */ - - if (rqtp->tv_sec == 0 && rqtp->tv_nsec == 0) - { - sched_yield(); - return OK; - } - - /* Get the start time of the wait. Interrupts are disabled to prevent - * timer interrupts while we do tick-related calculations before and - * after the wait. - */ - - flags = enter_critical_section(); - starttick = clock_systime_ticks(); - - /* Set up for the sleep. Using the empty set means that we are not - * waiting for any particular signal. However, any unmasked signal can - * still awaken nxsig_timedwait(). - */ - - sigemptyset(&set); - - /* nxsig_nanosleep is a simple application of nxsig_timedwait. */ - - ret = nxsig_timedwait(&set, NULL, rqtp); - - /* nxsig_timedwait() cannot succeed. It should always return error with - * either (1) EAGAIN meaning that the timeout occurred, or (2) EINTR - * meaning that some other unblocked signal was caught. - */ - - if (ret == -EAGAIN) - { - /* The timeout "error" is the normal, successful result */ - - leave_critical_section(flags); - return OK; - } - - /* If we get there, the wait has failed because we were awakened by a - * signal. Return the amount of "unwaited" time if rmtp is non-NULL. - */ - - if (rmtp) - { - clock_t elapsed; - clock_t remaining; - sclock_t ticks; - - /* REVISIT: The conversion from time to ticks and back could - * be avoided. clock_timespec_subtract() would be used instead - * to get the time difference. - */ - - /* First get the number of clock ticks that we were requested to - * wait. - */ - - ticks = clock_time2ticks(rqtp); - - /* Get the number of ticks that we actually waited */ - - elapsed = clock_systime_ticks() - starttick; - - /* The difference between the number of ticks that we were requested - * to wait and the number of ticks that we actually waited is that - * amount of time that we failed to wait. - */ - - if (elapsed >= (clock_t)ticks) - { - remaining = 0; - } - else - { - remaining = (clock_t)ticks - elapsed; - } - - clock_ticks2time(rmtp, remaining); - } - - leave_critical_section(flags); - return ret; + return ret == -EAGAIN ? 0 : ret; } /**************************************************************************** @@ -292,52 +191,17 @@ int clock_nanosleep(clockid_t clockid, int flags, return EINVAL; } - /* Check if absolute time is selected */ + /* Just a wrapper around nxsig_clockwait() */ - if ((flags & TIMER_ABSTIME) != 0) + ret = nxsig_clockwait(clockid, flags, rqtp, rmtp); + + /* Check if nxsig_clockwait() succeeded */ + + if (ret == -EAGAIN) { - struct timespec reltime; - struct timespec now; - irqstate_t irqstate; - - /* Calculate the relative time delay. We need to enter a critical - * section early to assure the relative time is valid from this - * point in time. - */ - - irqstate = enter_critical_section(); - ret = clock_gettime(clockid, &now); - if (ret < 0) - { - /* clock_gettime() sets the errno variable */ - - leave_critical_section(irqstate); - leave_cancellation_point(); - return -ret; - } - - clock_timespec_subtract(rqtp, &now, &reltime); - - /* Now that we have the relative time, the remaining operations - * are equivalent to nxsig_nanosleep(). - */ - - ret = nxsig_nanosleep(&reltime, rmtp); - leave_critical_section(irqstate); + ret = OK; } - else - { - /* In the relative time case, clock_nanosleep() is equivalent to - * nanosleep. In this case, it is a paper thin wrapper around - * nxsig_nanosleep(). - */ - - ret = nxsig_nanosleep(rqtp, rmtp); - } - - /* Check if nxsig_nanosleep() succeeded */ - - if (ret < 0) + else if (ret < 0) { /* If not return the errno */ diff --git a/sched/signal/sig_timedwait.c b/sched/signal/sig_timedwait.c index 3d5873a1e1e..43b61dc8585 100644 --- a/sched/signal/sig_timedwait.c +++ b/sched/signal/sig_timedwait.c @@ -203,6 +203,141 @@ void nxsig_wait_irq(FAR struct tcb_s *wtcb, int errcode) } #endif /* CONFIG_CANCELLATION_POINTS */ +/**************************************************************************** + * Name: nxsig_clockwait + * + * Description: + * This function selects the pending signal set specified by the argument + * set. If multiple signals are pending in set, it will remove and return + * the lowest numbered one. If no signals in set are pending at the time + * of the call, the calling process will be suspended until one of the + * signals in set becomes pending, OR until the process is interrupted by + * an unblocked signal, OR until the time interval specified by timeout + * (if any), has expired. If timeout is NULL, then the timeout interval + * is forever. + * + * If the info argument is non-NULL, the selected signal number is stored + * in the si_signo member and the cause of the signal is stored in the + * si_code member. The content of si_value is only meaningful if the + * signal was generated by sigqueue() (or nxsig_queue). + * + * This is an internal OS interface. It is functionally equivalent to + * sigtimedwait() except that: + * + * - It is not a cancellation point, and + * - It does not modify the errno value. + * + * Input Parameters: + * clockid - The ID of the clock to be used to measure the timeout. + * flags - Open flags. TIMER_ABSTIME is the only supported flag. + * rqtp - The amount of time to be suspended from execution. + * rmtp - If the rmtp argument is non-NULL, the timespec structure + * referenced by it is updated to contain the amount of time + * remaining in the interval (the requested time minus the time + * actually slept) + * + * Returned Value: + * This is an internal OS interface and should not be used by applications. + * A negated errno value is returned on failure. + * + * EAGAIN - wait time is zero. + * EINTR - The wait was interrupted by an unblocked, caught signal. + * + * Notes: + * This function should be called with critical section set. + * + ****************************************************************************/ + +int nxsig_clockwait(int clockid, int flags, + FAR const struct timespec *rqtp, + FAR struct timespec *rmtp) +{ + FAR struct tcb_s *rtcb = this_task(); + irqstate_t iflags; + clock_t expect; + clock_t stop; + + /* If rqtp is zero, yield CPU and return + * Notice: The behavior of sleep(0) is not defined in POSIX, so there are + * different implementations: + * 1. In Linux, nanosleep(0) will call schedule() to yield CPU: + * https://elixir.bootlin.com/linux/latest/source/kernel/time/ + * hrtimer.c#L2038 + * 2. In BSD, nanosleep(0) will return immediately: + * https://github.com/freebsd/freebsd-src/blob/ + * 475fa89800086718bd9249fd4dc3f862549f1f78/crypto/openssh/ + * openbsd-compat/bsd-misc.c#L243 + */ + + if (rqtp && rqtp->tv_sec == 0 && rqtp->tv_nsec == 0) + { + sched_yield(); + return -EAGAIN; + } + +#ifdef CONFIG_CANCELLATION_POINTS + /* nxsig_clockwait() is not a cancellation point, but it may be called + * from a cancellation point. So if a cancellation is pending, we + * must exit immediately without waiting. + */ + + if (check_cancellation_point()) + { + /* If there is a pending cancellation, then do not perform + * the wait. Exit now with ECANCELED. + */ + + return -ECANCELED; + } +#endif + + iflags = enter_critical_section(); + + if (rqtp) + { + /* Start the watchdog timer */ + + expect = clock_time2ticks(rqtp); + + if ((flags & TIMER_ABSTIME) == 0) + { + expect += clock_systime_ticks(); + } + + wd_start_absolute(&rtcb->waitdog, expect, nxsig_timeout, (wdparm_t)rtcb); + } + + /* Remove the tcb task from the ready-to-run list. */ + + nxsched_remove_self(rtcb); + + /* Add the task to the specified blocked task list */ + + rtcb->task_state = TSTATE_WAIT_SIG; + dq_addlast((FAR dq_entry_t *)rtcb, &g_waitingforsignal); + + /* Now, perform the context switch if one is needed */ + + up_switch_context(this_task(), rtcb); + + /* We no longer need the watchdog */ + + if (rqtp) + { + wd_cancel(&rtcb->waitdog); + stop = clock_systime_ticks(); + } + + leave_critical_section(iflags); + + if (rqtp && rmtp) + { + clock_ticks2time(rmtp, expect > stop ? expect - stop : 0); + } + + return 0; +} + /**************************************************************************** * Name: nxsig_timedwait * @@ -250,7 +385,6 @@ int nxsig_timedwait(FAR const sigset_t *set, FAR struct siginfo *info, sigset_t intersection; FAR sigpendq_t *sigpend; irqstate_t flags; - sclock_t waitticks; siginfo_t unbinfo; int ret; @@ -295,126 +429,28 @@ int nxsig_timedwait(FAR const sigset_t *set, FAR struct siginfo *info, /* Then dispose of the pending signal structure properly */ nxsig_release_pendingsignal(sigpend); - leave_critical_section(flags); } /* We will have to wait for a signal to be posted to this task. */ else { -#ifdef CONFIG_CANCELLATION_POINTS - /* nxsig_timedwait() is not a cancellation point, but it may be called - * from a cancellation point. So if a cancellation is pending, we - * must exit immediately without waiting. - */ - - if (check_cancellation_point()) - { - /* If there is a pending cancellation, then do not perform - * the wait. Exit now with ECANCELED. - */ - - leave_critical_section(flags); - return -ECANCELED; - } -#endif - rtcb->sigunbinfo = (info == NULL) ? &unbinfo : info; - /* Check if we should wait for the timeout */ + /* Save the set of pending signals to wait for */ - if (timeout != NULL) + rtcb->sigwaitmask = *set; + + leave_critical_section(flags); + + ret = nxsig_clockwait(CLOCK_REALTIME, 0, timeout, NULL); + if (ret < 0) { - /* Convert the timespec to system clock ticks, making sure that - * the resulting delay is greater than or equal to the requested - * time in nanoseconds. - */ - -#ifdef CONFIG_SYSTEM_TIME64 - waitticks = ((uint64_t)timeout->tv_sec * NSEC_PER_SEC + - (uint64_t)timeout->tv_nsec + NSEC_PER_TICK - 1) / - NSEC_PER_TICK; -#else - uint32_t waitmsec; - - DEBUGASSERT(timeout->tv_sec < UINT32_MAX / MSEC_PER_SEC); - waitmsec = timeout->tv_sec * MSEC_PER_SEC + - (timeout->tv_nsec + NSEC_PER_MSEC - 1) / NSEC_PER_MSEC; - waitticks = MSEC2TICK(waitmsec); -#endif - - if (waitticks > 0) - { - /* Save the set of pending signals to wait for */ - - rtcb->sigwaitmask = *set; - - /* Start the watchdog */ - - wd_start(&rtcb->waitdog, waitticks, - nxsig_timeout, (uintptr_t)rtcb); - - /* Now wait for either the signal or the watchdog, but - * first, make sure 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 ready-to-run list. */ - - nxsched_remove_self(rtcb); - - /* Add the task to the specified blocked task list */ - - rtcb->task_state = TSTATE_WAIT_SIG; - dq_addlast((FAR dq_entry_t *)rtcb, list_waitingforsignal()); - - /* Now, perform the context switch if one is needed */ - - up_switch_context(this_task(), rtcb); - - /* We no longer need the watchdog */ - - wd_cancel(&rtcb->waitdog); - } - else - { - rtcb->sigunbinfo = NULL; - - leave_critical_section(flags); - return -EAGAIN; - } + rtcb->sigunbinfo = NULL; + return ret; } - /* No timeout, just wait */ - - else - { - /* Save the set of pending signals to wait for */ - - rtcb->sigwaitmask = *set; - - /* And wait until one of the unblocked signals is posted, - * but first make sure 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_SIG; - dq_addlast((FAR dq_entry_t *)rtcb, list_waitingforsignal()); - - /* Now, perform the context switch */ - - up_switch_context(this_task(), rtcb); - } + flags = enter_critical_section(); /* We are running again, clear the sigwaitmask */ @@ -472,10 +508,10 @@ int nxsig_timedwait(FAR const sigset_t *set, FAR struct siginfo *info, } rtcb->sigunbinfo = NULL; - - leave_critical_section(flags); } + leave_critical_section(flags); + return ret; }