From 631071cdedcd412c1245d8e9cf6ecb7ec962b190 Mon Sep 17 00:00:00 2001 From: ligd Date: Sat, 25 Aug 2018 14:52:13 -0600 Subject: [PATCH] Squashed commit of the following: sched/wqueue: Modify high priority work queue to support multiple threads. sched/wqueue and libs/libc/wqueue: workqueues don't need set global data to zero since .bss is cleared automatically. Removing this unnecessary initialization also avoids the loss the work items queued before initialization. --- include/nuttx/wqueue.h | 6 ++ libs/libc/wqueue/work_usrthread.c | 1 - sched/Kconfig | 22 ++++- sched/wqueue/kwork_hpthread.c | 128 +++++++++++++++++++++--------- sched/wqueue/kwork_lpthread.c | 7 +- sched/wqueue/kwork_process.c | 11 +-- sched/wqueue/kwork_signal.c | 62 +++++++-------- sched/wqueue/wqueue.h | 9 ++- 8 files changed, 161 insertions(+), 85 deletions(-) diff --git a/include/nuttx/wqueue.h b/include/nuttx/wqueue.h index a3cd7b2493..1566638ce9 100644 --- a/include/nuttx/wqueue.h +++ b/include/nuttx/wqueue.h @@ -65,6 +65,8 @@ * (which runs at the lowest of priority and may not be appropriate * if memory reclamation is of high priority). If CONFIG_SCHED_HPWORK * is enabled, then the following options can also be used: + * CONFIG_SCHED_HPNTHREADS - The number of thread in the high-priority queue's + * thread pool. Default: 1 * CONFIG_SCHED_HPWORKPRIORITY - The execution priority of the high- * priority worker thread. Default: 224 * CONFIG_SCHED_HPWORKPERIOD - How often the worker thread checks for @@ -146,6 +148,10 @@ #ifdef CONFIG_SCHED_HPWORK +# ifndef CONFIG_SCHED_HPNTHREADS +# define CONFIG_SCHED_HPNTHREADS 1 +# endif + # ifndef CONFIG_SCHED_HPWORKPRIORITY # define CONFIG_SCHED_HPWORKPRIORITY 224 # endif diff --git a/libs/libc/wqueue/work_usrthread.c b/libs/libc/wqueue/work_usrthread.c index eceb57e0c6..c4b167952d 100644 --- a/libs/libc/wqueue/work_usrthread.c +++ b/libs/libc/wqueue/work_usrthread.c @@ -341,7 +341,6 @@ int work_usrstart(void) /* Initialize work queue data structures */ g_usrwork.delay = CONFIG_LIB_USRWORKPERIOD / USEC_PER_TICK; - dq_init(&g_usrwork.q); #ifdef CONFIG_BUILD_PROTECTED { diff --git a/sched/Kconfig b/sched/Kconfig index c023fb54f0..0be2069dd8 100644 --- a/sched/Kconfig +++ b/sched/Kconfig @@ -1302,6 +1302,24 @@ config SCHED_HPWORK if SCHED_HPWORK +config SCHED_HPNTHREADS + int "Number of high-priority worker threads" + default 1 + ---help--- + This options selects multiple, high-priority threads. This is + essentially a "thread pool" that provides multi-threaded servicing + of the high-priority work queue. This breaks the serialization + of the "queue" (hence, it is no longer a queue at all). + + CAUTION: Some drivers may use the work queue to serialize + operations. They may also use the high-priority work queue if it is + available. If there are multiple high-priority worker threads, then + this can result in the loss of that serialization. There may be + concurrent driver operations running on different HP threads and + this could lead to a failure. You may need to visit the use of the + HP work queue on your configuration is you select + CONFIG_SCHED_HPNTHREADS > 1 + config SCHED_HPWORKPRIORITY int "High priority worker thread priority" default 224 @@ -1370,8 +1388,8 @@ config SCHED_LPNTHREADS example). CAUTION: Some drivers may use the work queue to serialize - operations. The may also use the low-priority work queue if it is - available. If there are multiple low-priority worker thread, then + operations. They may also use the low-priority work queue if it is + available. If there are multiple low-priority worker threads, then this can result in the loss of that serialization. There may be concurrent driver operations running on different LP threads and this could lead to a failure. You may need to visit the use of the diff --git a/sched/wqueue/kwork_hpthread.c b/sched/wqueue/kwork_hpthread.c index a923ad1122..8ba3ba5ae2 100644 --- a/sched/wqueue/kwork_hpthread.c +++ b/sched/wqueue/kwork_hpthread.c @@ -1,7 +1,7 @@ /**************************************************************************** - * sched/wqueue/work_hothread.c + * sched/wqueue/work_hpthread.c * - * Copyright (C) 2009-2014 Gregory Nutt. All rights reserved. + * Copyright (C) 2009-2014, 2018 Gregory Nutt. All rights reserved. * Author: Gregory Nutt * * Redistribution and use in source and binary forms, with or without @@ -39,6 +39,9 @@ #include +#include +#include +#include #include #include #include @@ -56,7 +59,7 @@ * Public Data ****************************************************************************/ -/* The state of the kernel mode, high priority work queue. */ +/* The state of the kernel mode, high priority work queue(s). */ struct hp_wqueue_s g_hpwork; @@ -68,10 +71,10 @@ struct hp_wqueue_s g_hpwork; * Name: work_hpthread * * Description: - * This is the worker thread that performs the actions placed on the high + * These are the worker threads that performs the actions placed on the high * priority work queue. * - * This, along with the lower priority worker thread(s) are the kernel + * These, along with the lower priority worker thread(s) are the kernel * mode work queues (also build in the flat build). One of these threads * also performs periodic garbage collection (that would otherwise be * performed by the idle thread if CONFIG_SCHED_WORKQUEUE is not defined). @@ -92,31 +95,68 @@ struct hp_wqueue_s g_hpwork; static int work_hpthread(int argc, char *argv[]) { +#if CONFIG_SCHED_HPNTHREADS > 1 + int wndx; + pid_t me = getpid(); + int i; + + /* Find out thread index by search the workers in g_hpwork */ + + for (wndx = 0, i = 0; i < CONFIG_SCHED_HPNTHREADS; i++) + { + if (g_hpwork.worker[i].pid == me) + { + wndx = i; + break; + } + } + + DEBUGASSERT(i < CONFIG_SCHED_HPNTHREADS); +#endif + /* Loop forever */ for (; ; ) { -#ifndef CONFIG_SCHED_LPWORK - /* First, perform garbage collection. This cleans-up memory - * de-allocations that were queued because they could not be freed in - * that execution context (for example, if the memory was freed from - * an interrupt handler). - * - * NOTE: If the work thread is disabled, this clean-up is performed by - * the IDLE thread (at a very, very low priority). If the low-priority - * work thread is enabled, then the garbage collection is done on that - * thread instead. - */ +#if CONFIG_SCHED_HPNTHREADS > 1 + /* Thread 0 is special. Only thread 0 performs period garbage collection */ - sched_garbage_collection(); + if (wndx > 0) + { + /* The other threads will perform work, waiting indefinitely until + * signalled for the next work availability. + * + * The special value of zero for the poll period instructs work_process + * to wait indefinitely until a signal is received. + */ + + work_process((FAR struct kwork_wqueue_s *)&g_hpwork, 0, wndx); + } + else +#endif + { +#ifndef CONFIG_SCHED_LPWORK + /* First, perform garbage collection. This cleans-up memory + * de-allocations that were queued because they could not be freed in + * that execution context (for example, if the memory was freed from + * an interrupt handler). + * + * NOTE: If the work thread is disabled, this clean-up is performed by + * the IDLE thread (at a very, very low priority). If the low-priority + * work thread is enabled, then the garbage collection is done on that + * thread instead. + */ + + sched_garbage_collection(); #endif - /* Then process queued work. work_process will not return until: (1) - * there is no further work in the work queue, and (2) the polling - * period provided by g_hpwork.delay expires. - */ + /* Then process queued work. work_process will not return until: (1) + * there is no further work in the work queue, and (2) the polling + * period provided by g_hpwork.delay expires. + */ - work_process((FAR struct kwork_wqueue_s *)&g_hpwork, g_hpwork.delay, 0); + work_process((FAR struct kwork_wqueue_s *)&g_hpwork, g_hpwork.delay, 0); + } } return OK; /* To keep some compilers happy */ @@ -130,7 +170,7 @@ static int work_hpthread(int argc, char *argv[]) * Name: work_hpstart * * Description: - * Start the high-priority, kernel-mode work queue. + * Start the high-priority, kernel-mode worker thread(s) * * Input Parameters: * None @@ -144,31 +184,43 @@ static int work_hpthread(int argc, char *argv[]) int work_hpstart(void) { pid_t pid; + int wndx; /* Initialize work queue data structures */ - g_hpwork.delay = CONFIG_SCHED_HPWORKPERIOD / USEC_PER_TICK; - dq_init(&g_hpwork.q); + g_hpwork.delay = CONFIG_SCHED_HPWORKPERIOD / USEC_PER_TICK; - /* Start the high-priority, kernel mode worker thread */ + /* Don't permit any of the threads to run until we have fully initialized + * g_hpwork. + */ - sinfo("Starting high-priority kernel worker thread\n"); + sched_lock(); - pid = kthread_create(HPWORKNAME, CONFIG_SCHED_HPWORKPRIORITY, - CONFIG_SCHED_HPWORKSTACKSIZE, - (main_t)work_hpthread, - (FAR char * const *)NULL); + /* Start the high-priority, kernel mode worker thread(s) */ - DEBUGASSERT(pid > 0); - if (pid < 0) + sinfo("Starting high-priority kernel worker thread(s)\n"); + + for (wndx = 0; wndx < CONFIG_SCHED_HPNTHREADS; wndx++) { - serr("ERROR: kthread_create failed: %d\n", (int)pid); - return (int)pid; + pid = kthread_create(HPWORKNAME, CONFIG_SCHED_HPWORKPRIORITY, + CONFIG_SCHED_HPWORKSTACKSIZE, + (main_t)work_hpthread, + (FAR char * const *)NULL); + + DEBUGASSERT(pid > 0); + if (pid < 0) + { + serr("ERROR: kthread_create %d failed: %d\n", wndx, (int)pid); + sched_unlock(); + return (int)pid; + } + + g_hpwork.worker[wndx].pid = pid; + g_hpwork.worker[wndx].busy = true; } - g_hpwork.worker[0].pid = pid; - g_hpwork.worker[0].busy = true; - return pid; + sched_unlock(); + return g_hpwork.worker[0].pid; } #endif /* CONFIG_SCHED_HPWORK */ diff --git a/sched/wqueue/kwork_lpthread.c b/sched/wqueue/kwork_lpthread.c index 5d6911fef3..8da868015d 100644 --- a/sched/wqueue/kwork_lpthread.c +++ b/sched/wqueue/kwork_lpthread.c @@ -94,7 +94,7 @@ struct lp_wqueue_s g_lpwork; static int work_lpthread(int argc, char *argv[]) { -#if CONFIG_SCHED_LPNTHREADS > 0 +#if CONFIG_SCHED_LPNTHREADS > 1 int wndx; pid_t me = getpid(); int i; @@ -117,7 +117,7 @@ static int work_lpthread(int argc, char *argv[]) for (; ; ) { -#if CONFIG_SCHED_LPNTHREADS > 0 +#if CONFIG_SCHED_LPNTHREADS > 1 /* Thread 0 is special. Only thread 0 performs period garbage collection */ if (wndx > 0) @@ -184,10 +184,7 @@ int work_lpstart(void) /* Initialize work queue data structures */ - memset(&g_lpwork, 0, sizeof(struct kwork_wqueue_s)); - g_lpwork.delay = CONFIG_SCHED_LPWORKPERIOD / USEC_PER_TICK; - dq_init(&g_lpwork.q); /* Don't permit any of the threads to run until we have fully initialized * g_lpwork. diff --git a/sched/wqueue/kwork_process.c b/sched/wqueue/kwork_process.c index 5d678ce320..664cbb0e0e 100644 --- a/sched/wqueue/kwork_process.c +++ b/sched/wqueue/kwork_process.c @@ -1,7 +1,7 @@ /**************************************************************************** * sched/wqueue/work_process.c * - * Copyright (C) 2009-2014, 2016-2017 Gregory Nutt. All rights reserved. + * Copyright (C) 2009-2014, 2016-2018 Gregory Nutt. All rights reserved. * Author: Gregory Nutt * * Redistribution and use in source and binary forms, with or without @@ -215,12 +215,13 @@ void work_process(FAR struct kwork_wqueue_s *wqueue, clock_t period, int wndx) } } -#if defined(CONFIG_SCHED_LPWORK) && CONFIG_SCHED_LPNTHREADS > 0 +#if (defined(CONFIG_SCHED_HPWORK) && CONFIG_SCHED_HPNTHREADS > 1) \ + || (defined(CONFIG_SCHED_LPWORK) && CONFIG_SCHED_LPNTHREADS > 1) /* Value of zero for period means that we should wait indefinitely until * signalled. This option is used only for the case where there are - * multiple, low-priority worker threads. In that case, only one of - * the threads does the poll... the others simple. In all other cases - * period will be non-zero and equal to wqueue->delay. + * multiple worker threads. In that case, only one of the threads does + * the poll... the others simple. In all other cases period will be + * non-zero and equal to wqueue->delay. */ if (period == 0) diff --git a/sched/wqueue/kwork_signal.c b/sched/wqueue/kwork_signal.c index 73ca066a61..f7c3b82c91 100644 --- a/sched/wqueue/kwork_signal.c +++ b/sched/wqueue/kwork_signal.c @@ -1,7 +1,7 @@ /**************************************************************************** * sched/wqueue/work_signal.c * - * Copyright (C) 2014, 2016-2017 Gregory Nutt. All rights reserved. + * Copyright (C) 2014, 2016-2018 Gregory Nutt. All rights reserved. * Author: Gregory Nutt * * Redistribution and use in source and binary forms, with or without @@ -71,46 +71,25 @@ int work_signal(int qid) { - pid_t pid; + FAR struct kwork_wqueue_s *work; + int threads; + int i; /* Get the process ID of the worker thread */ #ifdef CONFIG_SCHED_HPWORK if (qid == HPWORK) { - pid = g_hpwork.worker[0].pid; + work = (FAR struct kwork_wqueue_s *)&g_hpwork; + threads = CONFIG_SCHED_HPNTHREADS; } else #endif #ifdef CONFIG_SCHED_LPWORK if (qid == LPWORK) { - int i; - - /* Find an IDLE worker thread */ - - for (i = 0; i < CONFIG_SCHED_LPNTHREADS; i++) - { - /* Is this worker thread busy? */ - - if (!g_lpwork.worker[i].busy) - { - /* No.. select this thread */ - - break; - } - } - - /* If all of the IDLE threads are busy, then just return successfully */ - - if (i >= CONFIG_SCHED_LPNTHREADS) - { - return OK; - } - - /* Otherwise, signal the first IDLE thread found */ - - pid = g_lpwork.worker[i].pid; + work = (FAR struct kwork_wqueue_s *)&g_lpwork; + threads = CONFIG_SCHED_LPNTHREADS; } else #endif @@ -118,9 +97,30 @@ int work_signal(int qid) return -EINVAL; } - /* Signal the worker thread */ + /* Find an IDLE worker thread */ - return nxsig_kill(pid, SIGWORK); + for (i = 0; i < threads; i++) + { + /* Is this worker thread busy? */ + + if (!work->worker[i].busy) + { + /* No.. select this thread */ + + break; + } + } + + /* If all of the IDLE threads are busy, then just return successfully */ + + if (i >= threads) + { + return OK; + } + + /* Otherwise, signal the first IDLE thread found */ + + return nxsig_kill(work->worker[i].pid, SIGWORK); } #endif /* CONFIG_SCHED_WORKQUEUE */ diff --git a/sched/wqueue/wqueue.h b/sched/wqueue/wqueue.h index e67976a11a..a84550a6e1 100644 --- a/sched/wqueue/wqueue.h +++ b/sched/wqueue/wqueue.h @@ -1,7 +1,7 @@ /**************************************************************************** * sched/wqueue/wqueue.h * - * Copyright (C) 2014, 2016 Gregory Nutt. All rights reserved. + * Copyright (C) 2014, 2016, 2018 Gregory Nutt. All rights reserved. * Author: Gregory Nutt * * Redistribution and use in source and binary forms, with or without @@ -88,11 +88,14 @@ struct hp_wqueue_s { clock_t delay; /* Delay between polling cycles (ticks) */ struct dq_queue_s q; /* The queue of pending work */ - struct kworker_s worker[1]; /* Describes the single high priority worker */ + + /* Describes each thread in the high priority queue's thread pool */ + + struct kworker_s worker[CONFIG_SCHED_HPNTHREADS]; }; #endif -/* This structure defines the state of one high-priority work queue. This +/* This structure defines the state of one low-priority work queue. This * structure must be cast compatible with kwork_wqueue_s */