nuttx/sched/wdog/wd_start.c
ligd 7ff7f6ec21 wdog: optimize a bit speed in wd_start
After these wdog refactor:
We conducted a latency measurement using the rt-tests/cyclictest (commit cadd661) on an x86_64 NUC12 equipped with an i7-1255U processor and 16GB of LPDDR5 memory. The specific command used for this microbenchmark was cyclictest -q -l 100000 -h 30000, which is designed to assess the responsiveness of the cyclic timer.

The findings from our benchmark are summarized below, highlighting the minimum, median, and maximum latency values for each operating system tested:

Operating System	Minimum Latency (us)	Median Latency (us)	Maximum Latency (us)
Linux	            48	                    53	                410
PreemptRT	        6	                    57	                148
Xenomai	            53	                    53	                64
NuttX	            64	                    626	                1212
NuttX (refactor)	1	                    1	                3
In this table, "Min" indicates the shortest latency observed, "Median" represents the middle value of the latency distribution, and "Max" denotes the longest latency encountered.

The systems tested were as follows:

Linux: ACRN version 6.1.80 (commit f528146)
PreemptRT: Linux kernel 5.4.251 with the 5.4.254-rt85 patch applied
Xenomai: Linux kernel 5.4.251 patched with ipipe-core-5.4.239-x86-13
These results clearly demonstrate the varying performance of different operating systems in terms of timer latency, the refactored NuttX showing particularly low latency values.

Signed-off-by: ligd <liguiding1@xiaomi.com>
2024-09-10 23:32:30 +08:00

445 lines
13 KiB
C

/****************************************************************************
* sched/wdog/wd_start.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 <sys/types.h>
#include <stdint.h>
#include <stdbool.h>
#include <sys/param.h>
#include <unistd.h>
#include <sched.h>
#include <assert.h>
#include <debug.h>
#include <errno.h>
#include <nuttx/irq.h>
#include <nuttx/arch.h>
#include <nuttx/wdog.h>
#include "sched/sched.h"
#include "wdog/wdog.h"
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
#ifndef CONFIG_SCHED_CRITMONITOR_MAXTIME_WDOG
# define CONFIG_SCHED_CRITMONITOR_MAXTIME_WDOG 0
#endif
#if CONFIG_SCHED_CRITMONITOR_MAXTIME_WDOG > 0
# define CALL_FUNC(func, arg) \
do \
{ \
clock_t start; \
clock_t elapsed; \
start = perf_gettime(); \
func(arg); \
elapsed = perf_gettime() - start; \
if (elapsed > CONFIG_SCHED_CRITMONITOR_MAXTIME_WDOG) \
{ \
CRITMONITOR_PANIC("WDOG %p, %s IRQ, execute too long %ju\n", \
func, up_interrupt_context() ? \
"IN" : "NOT", (uintmax_t)elapsed); \
} \
} \
while (0)
#else
# define CALL_FUNC(func, arg) func(arg)
#endif
/****************************************************************************
* Private Data
****************************************************************************/
#ifdef CONFIG_SCHED_TICKLESS
static unsigned int g_wdtimernested;
#endif
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: wd_expiration
*
* Description:
* Check if the timer for the watchdog at the head of list is ready to
* run. If so, remove the watchdog from the list and execute it.
*
* Input Parameters:
* ticks - current time in ticks
*
* Returned Value:
* None
*
****************************************************************************/
static inline_function void wd_expiration(clock_t ticks)
{
FAR struct wdog_s *wdog;
irqstate_t flags;
wdentry_t func;
flags = enter_critical_section();
#ifdef CONFIG_SCHED_TICKLESS
/* Increment the nested watchdog timer count to handle cases where wd_start
* is called in the watchdog callback functions.
*/
g_wdtimernested++;
#endif
/* Process the watchdog at the head of the list as well as any
* other watchdogs that became ready to run at this time
*/
while (!list_is_empty(&g_wdactivelist))
{
wdog = list_first_entry(&g_wdactivelist, struct wdog_s, node);
/* Check if expected time is expired */
if (!clock_compare(wdog->expired, ticks))
{
break;
}
/* Remove the watchdog from the head of the list */
list_delete(&wdog->node);
/* Indicate that the watchdog is no longer active. */
func = wdog->func;
wdog->func = NULL;
/* Execute the watchdog function */
up_setpicbase(wdog->picbase);
CALL_FUNC(func, wdog->arg);
}
#ifdef CONFIG_SCHED_TICKLESS
/* Decrement the nested watchdog timer count */
g_wdtimernested--;
#endif
leave_critical_section(flags);
}
/****************************************************************************
* Name: wd_insert
*
* Description:
* Insert the timer into the global list to ensure that
* the list is sorted in increasing order of expiration absolute time.
*
* Input Parameters:
* wdog - Watchdog ID
* expired - expired absolute time in clock ticks
* wdentry - Function to call on timeout
* arg - Parameter to pass to wdentry
*
* Assumptions:
* wdog and wdentry is not NULL.
*
* Returned Value:
* None.
*
****************************************************************************/
static inline_function
void wd_insert(FAR struct wdog_s *wdog, clock_t expired,
wdentry_t wdentry, wdparm_t arg)
{
FAR struct wdog_s *curr;
/* Traverse the watchdog list */
list_for_every_entry(&g_wdactivelist, curr, struct wdog_s, node)
{
/* Until curr->expired has not timed out relative to expired */
if (!clock_compare(curr->expired, expired))
{
break;
}
}
/* There are two cases:
* - Traverse to the end, where curr == &g_wdactivelist.
* - Find a curr such that curr->expected has not timed out
* relative to expired.
* In either case 1 or 2, we just insert the wdog before curr.
*/
list_add_before(&curr->node, &wdog->node);
wdog->func = wdentry;
up_getpicbase(&wdog->picbase);
wdog->arg = arg;
wdog->expired = expired;
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: wd_start_absolute
*
* Description:
* This function adds a watchdog timer to the active timer queue. The
* specified watchdog function at 'wdentry' will be called from the
* interrupt level after the specified number of ticks has reached.
* Watchdog timers may be started from the interrupt level.
*
* Watchdog timers execute in the address environment that was in effect
* when wd_start() is called.
*
* Watchdog timers execute only once.
*
* To replace either the timeout delay or the function to be executed,
* call wd_start again with the same wdog; only the most recent wdStart()
* on a given watchdog ID has any effect.
*
* Input Parameters:
* wdog - Watchdog ID
* ticks - Absoulute time in clock ticks
* wdentry - Function to call on timeout
* arg - Parameter to pass to wdentry.
*
* NOTE: The parameter must be of type wdparm_t.
*
* Returned Value:
* Zero (OK) is returned on success; a negated errno value is return to
* indicate the nature of any failure.
*
* Assumptions:
* The watchdog routine runs in the context of the timer interrupt handler
* and is subject to all ISR restrictions.
*
****************************************************************************/
int wd_start_absolute(FAR struct wdog_s *wdog, clock_t ticks,
wdentry_t wdentry, wdparm_t arg)
{
irqstate_t flags;
bool reassess = false;
/* Verify the wdog and setup parameters */
if (wdog == NULL || wdentry == NULL)
{
return -EINVAL;
}
/* Calculate ticks+1, forcing the delay into a range that we can handle.
*
* NOTE that one is added to the delay. This is correct and must not be
* changed: The contract for the use wdog_start is that the wdog will
* delay FOR AT LEAST as long as requested, but may delay longer due to
* variety of factors. The wdog logic has no knowledge of the the phase
* of the system timer when it is started: The next timer interrupt may
* occur immediately or may be delayed for almost a full cycle. In order
* to meet the contract requirement, the requested time is also always
* incremented by one so that the delay is always at least as long as
* requested.
*
* There is extensive documentation about this time issue elsewhere.
*/
ticks++;
/* NOTE: There is a race condition here... the caller may receive
* the watchdog between the time that wd_start_absolute is called and
* the critical section is established.
*/
flags = enter_critical_section();
#ifdef CONFIG_SCHED_TICKLESS
/* We need to reassess timer if the watchdog list head has changed. */
if (WDOG_ISACTIVE(wdog))
{
reassess |= list_is_head(&g_wdactivelist, &wdog->node);
list_delete(&wdog->node);
wdog->func = NULL;
}
wd_insert(wdog, ticks, wdentry, arg);
if (!g_wdtimernested &&
(reassess || list_is_head(&g_wdactivelist, &wdog->node)))
{
/* Resume the interval timer that will generate the next
* interval event. If the timer at the head of the list changed,
* then this will pick that new delay.
*/
nxsched_reassess_timer();
}
#else
UNUSED(reassess);
/* Check if the watchdog has been started. If so, delete it. */
if (WDOG_ISACTIVE(wdog))
{
list_delete(&wdog->node);
wdog->func = NULL;
}
wd_insert(wdog, ticks, wdentry, arg);
#endif
leave_critical_section(flags);
return OK;
}
/****************************************************************************
* Name: wd_start
*
* Description:
* This function adds a watchdog timer to the active timer queue. The
* specified watchdog function at 'wdentry' will be called from the
* interrupt level after the specified number of ticks has elapsed.
* Watchdog timers may be started from the interrupt level.
*
* Watchdog timers execute in the address environment that was in effect
* when wd_start() is called.
*
* Watchdog timers execute only once.
*
* To replace either the timeout delay or the function to be executed,
* call wd_start again with the same wdog; only the most recent wdStart()
* on a given watchdog ID has any effect.
*
* Input Parameters:
* wdog - Watchdog ID
* delay - Delay count in clock ticks
* wdentry - Function to call on timeout
* arg - Parameter to pass to wdentry
*
* NOTE: The parameter must be of type wdparm_t.
*
* Returned Value:
* Zero (OK) is returned on success; a negated errno value is return to
* indicate the nature of any failure.
*
* Assumptions:
* The watchdog routine runs in the context of the timer interrupt handler
* and is subject to all ISR restrictions.
*
****************************************************************************/
int wd_start(FAR struct wdog_s *wdog, sclock_t delay,
wdentry_t wdentry, wdparm_t arg)
{
/* Verify the wdog and setup parameters */
if (delay < 0)
{
return -EINVAL;
}
return wd_start_absolute(wdog, clock_systime_ticks() + delay,
wdentry, arg);
}
/****************************************************************************
* Name: wd_timer
*
* Description:
* This function is called from the timer interrupt handler to determine
* if it is time to execute a watchdog function. If so, the watchdog
* function will be executed in the context of the timer interrupt
* handler.
*
* Input Parameters:
* ticks - If CONFIG_SCHED_TICKLESS is defined then the number of ticks
* in the interval that just expired is provided. Otherwise,
* this function is called on each timer interrupt and a value of one
* is implicit.
* noswitches - True: Can't do context switches now.
*
* Returned Value:
* If CONFIG_SCHED_TICKLESS is defined then the number of ticks for the
* next delay is provided (zero if no delay). Otherwise, this function
* has no returned value.
*
* Assumptions:
* Called from interrupt handler logic with interrupts disabled.
*
****************************************************************************/
#ifdef CONFIG_SCHED_TICKLESS
clock_t wd_timer(clock_t ticks, bool noswitches)
{
FAR struct wdog_s *wdog;
irqstate_t flags;
sclock_t ret;
/* Check if the watchdog at the head of the list is ready to run */
if (!noswitches)
{
wd_expiration(ticks);
}
flags = enter_critical_section();
/* Return the delay for the next watchdog to expire */
if (list_is_empty(&g_wdactivelist))
{
leave_critical_section(flags);
return 0;
}
/* Notice that if noswitches, expired - g_wdtickbase
* may get negative value.
*/
wdog = list_first_entry(&g_wdactivelist, struct wdog_s, node);
ret = wdog->expired - ticks;
leave_critical_section(flags);
/* Return the delay for the next watchdog to expire */
return MAX(ret, 1);
}
#else
void wd_timer(clock_t ticks)
{
/* Check if there are any active watchdogs to process */
wd_expiration(ticks);
}
#endif /* CONFIG_SCHED_TICKLESS */