From bba8541ad602bd86634dd054fa7fa35f33858191 Mon Sep 17 00:00:00 2001 From: Gregory Nutt Date: Sat, 8 Sep 2018 11:21:18 -0600 Subject: [PATCH] mm/iob: Add an IOB notifier that will send a signal to any registered threads that want to be notified when an IOB has been freed. This is an untested work-in-progress and is intended to be a part of a larger solution to correctly handling network poll operations. --- include/nuttx/mm/iob.h | 57 ++++++++ mm/iob/Kconfig | 20 +++ mm/iob/Make.defs | 4 + mm/iob/iob.h | 24 ++++ mm/iob/iob_free.c | 26 +++- mm/iob/iob_notify.c | 288 +++++++++++++++++++++++++++++++++++++++++ 6 files changed, 414 insertions(+), 5 deletions(-) create mode 100644 mm/iob/iob_notify.c diff --git a/include/nuttx/mm/iob.h b/include/nuttx/mm/iob.h index 0f4ddc0ecf..fd904ff22a 100644 --- a/include/nuttx/mm/iob.h +++ b/include/nuttx/mm/iob.h @@ -204,6 +204,63 @@ int iob_navail(void); FAR struct iob_s *iob_free(FAR struct iob_s *iob); +/**************************************************************************** + * Name: iob_notify_setup + * + * Description: + * Set up to notify the specified PID with the provided signal number. + * + * NOTE: To avoid race conditions, the caller should set the sigprocmask + * to block signal delivery. The signal will be delivered once the + * signal is removed from the sigprocmask. + * + * Input Parameters: + * pid - The PID to be notified. If a zero value is provided, then the + * PID of the calling thread will be used. + * signo - The signal number to use with the notification. + * + * Returned Value: + * > 0 - There are already free IOBs and this this number of free IOBs + * (CAUTION: This value is volatile). No signal notification + * will be provided. + * == 0 - There are no free IOBs. A signal will be sent to 'pid' when + * at least one IOB is available. That IOB is *not* reserved for + * the caller. Hence, due to race conditions, it could be taken + * by some other task. In that event, the caller should call + * sig_notify again. + * < 0 - An unexpected error occurred and no signal will be sent. The + * returned value is a negated errno value that indicates the + * nature of the failure. + * + ****************************************************************************/ + +#ifdef CONFIG_IOB_NOTIFIER +int iob_notify_setup(int pid, int signo); +#endif + +/**************************************************************************** + * Name: iob_notify_teardown + * + * Description: + * Eliminate an IOB notification previously setup by iob_notify_setup(). + * This function should only be called if the notification should be + * aborted prior to the notification. The notification will automatically + * be torn down after the signal is sent. + * + * Input Parameters: + * pid - The PID whose notification will be torn down. If a zero value + * is provided, then the PID of the calling thread will be used. + * + * Returned Value: + * Zero (OK) is returned on success; a negated errno value is returned on + * any failure. + * + ****************************************************************************/ + +#ifdef CONFIG_IOB_NOTIFIER +int iob_notify_teardown(int pid, int signo); +#endif + /**************************************************************************** * Name: iob_free_chain * diff --git a/mm/iob/Kconfig b/mm/iob/Kconfig index 62893db094..aa78d9d216 100644 --- a/mm/iob/Kconfig +++ b/mm/iob/Kconfig @@ -59,6 +59,26 @@ config IOB_THROTTLE I/O buffers will be denied to the read-ahead logic before TCP writes are halted. +config IOB_NOTIFIER + bool "Support IOB notifications" + default n + ---help--- + Enable building of IOB notifier logic that will send a signla to + a kernel thread when an IOB is available. This is is a general + purpose notifier, but was developed specifically to support poll() + logic where the poll must wait for an IOB to become available. + +config IOB_NWAITERS + int "Number of IOB waiters" + default 4 + range 1 32767 + depends on IOB_NOTIFIER + ---help--- + If CONFIG_IOB_NOTIFIER is selected, then a static array will be + allocated to hold information about pending notifications. This + then determines an upper limit for the number of waiters that can + be supported. + config IOB_DEBUG bool "Force I/O buffer debug" default n diff --git a/mm/iob/Make.defs b/mm/iob/Make.defs index 87d3b4614f..e709db759c 100644 --- a/mm/iob/Make.defs +++ b/mm/iob/Make.defs @@ -44,6 +44,10 @@ CSRCS += iob_initialize.c iob_pack.c iob_peek_queue.c iob_remove_queue.c CSRCS += iob_trimhead.c iob_trimhead_queue.c iob_trimtail.c CSRCS += iob_navail.c +ifeq ($(CONFIG_IOB_NOTIFIER),y) + CSRCS += iob_notify.c +endif + ifeq ($(CONFIG_DEBUG_FEATURES),y) CSRCS += iob_dump.c endif diff --git a/mm/iob/iob.h b/mm/iob/iob.h index 48aef8a946..74234fdef3 100644 --- a/mm/iob/iob.h +++ b/mm/iob/iob.h @@ -154,6 +154,30 @@ FAR struct iob_qentry_s *iob_tryalloc_qentry(void); FAR struct iob_qentry_s *iob_free_qentry(FAR struct iob_qentry_s *iobq); +/**************************************************************************** + * Name: iob_notify_signal + * + * Description: + * An IOB has become available. Signal all threads waiting for an IOB + * that an IOB is available. + * + * When an IOB becomes available, *all* of the waiters in this thread will + * be signaled. If there are multiple waiters then only the highest + * priority thread will get the IOB. Lower priority threads will need to + * call iob_notify once again. + * + * Input Parameters: + * None. + * + * Returned Value: + * None. + * + ****************************************************************************/ + +#ifdef CONFIG_IOB_NOTIFIER +void iob_notify_signal(void); +#endif + #endif /* CONFIG_MM_IOB */ #endif /* __MM_IOB_IOB_H */ diff --git a/mm/iob/iob_free.c b/mm/iob/iob_free.c index 2ae10e78e9..193a993673 100644 --- a/mm/iob/iob_free.c +++ b/mm/iob/iob_free.c @@ -1,7 +1,7 @@ /**************************************************************************** * mm/iob/iob_free.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 @@ -126,16 +126,32 @@ FAR struct iob_s *iob_free(FAR struct iob_s *iob) g_iob_freelist = iob; } - /* Signal that an IOB is available. If there is a thread waiting - * for an IOB, this will wake up exactly one thread. The semaphore - * count will correctly indicated that the awakened task owns an - * IOB and should find it in the committed list. + /* Signal that an IOB is available. If there is a thread blocked, + * waiting for an IOB, this will wake up exactly one thread. The + * semaphore count will correctly indicated that the awakened task + * owns an IOB and should find it in the committed list. */ nxsem_post(&g_iob_sem); #if CONFIG_IOB_THROTTLE > 0 nxsem_post(&g_throttle_sem); #endif + +#ifdef CONFIG_IOB_NOTIFIER + /* Check if the IOB was claimed by a thread that is blocked waiting + * for an IOB. + */ + + if (iob_navail() > 0) + { + /* Signal any threads that have requested a signal notification + * when an IOB becomes available. + */ + + iob_notify_signal(); + } +#endif + leave_critical_section(flags); /* And return the I/O buffer after the one that was freed */ diff --git a/mm/iob/iob_notify.c b/mm/iob/iob_notify.c new file mode 100644 index 0000000000..72e5915184 --- /dev/null +++ b/mm/iob/iob_notify.c @@ -0,0 +1,288 @@ +/**************************************************************************** + * mm/iob/iob_notify.c + * + * Copyright (C) 2018 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "iob.h" + +#ifdef CONFIG_IOB_NOTIFIER + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/* This is the saved information for one IOB notification */ + +struct iob_notify_s +{ + bool waiter; /* True if the waiter information is valid */ + uint8_t signo; /* The signal number to use when notifying the PID */ + pid_t pid; /* The PID to be notified when an IOB is available */ +}; + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/* This is a an array of threads waiting for an IOB. When an IOB becomes + * available, *all* of the waiters in this thread will be signaled and the + * entry will be marked invalid. If there are multiple waiters then only + * the highest priority thread will get the IOB. Lower priority threads + * will need to call iob_notify once again. + */ + +static struct iob_notify_s g_iob_notify[CONFIG_IOB_NWAITERS]; + +/* This semaphore is used as mutex to enforce mutually exlusive access to + * the g_io_notify[] array. + */ + +static sem_t g_notify_sem = SEM_INITIALIZER(1); + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: iob_notify_setup + * + * Description: + * Set up to notify the specified PID with the provided signal number. + * + * NOTE: To avoid race conditions, the caller should set the sigprocmask + * to block signal delivery. The signal will be delivered once the + * signal is removed from the sigprocmask. + * + * Input Parameters: + * pid - The PID to be notified. If a zero value is provided, then the + * PID of the calling thread will be used. + * signo - The signal number to use with the notification. + * + * Returned Value: + * > 0 - There are already free IOBs and this this number of free IOBs + * (CAUTION: This value is volatile). No signal notification + * will be provided. + * == 0 - There are no free IOBs. A signal will be sent to 'pid' when + * at least one IOB is available. That IOB is *not* reserved for + * the caller. Hence, due to race conditions, it could be taken + * by some other task. In that event, the caller should call + * sig_notify again. + * < 0 - An unexpected error occurred and no signal will be sent. The + * returned value is a negated errno value that indicates the + * nature of the failure. + * + ****************************************************************************/ + +int iob_notify_setup(int pid, int signo) +{ + int ret; + int i; + + /* If the pid is zero, then use the pid of the calling thread */ + + if (pid <= 0) + { + pid = getpid(); + } + + /* Get exclusive access to the notifier array */ + + ret = nxsem_wait(&g_notify_sem); + if (ret < 0) + { + return ret; + } + + /* If there are already free IOBs, then just return the number of free + * IOBs. + */ + + ret = iob_navail(); + if (ret <= 0) + { + /* Find a free entry in the g_iob_notify[] array */ + + ret = -ENOSPC; + for (i = 0; i < CONFIG_IOB_NWAITERS; i++) + { + if (!g_iob_notify[i].waiter) + { + g_iob_notify[i].waiter = true; + g_iob_notify[i].pid = pid; + g_iob_notify[i].signo = signo; + + ret = OK; + break; + } + } + } + + (void)nxsem_post(&g_notify_sem); + return ret; +} + +/**************************************************************************** + * Name: iob_notify_teardown + * + * Description: + * Eliminate an IOB notification previously setup by iob_notify_setup(). + * This function should only be called if the notification should be + * aborted prior to the notification. The notification will automatically + * be torn down after the signal is sent. + * + * Input Parameters: + * pid - The PID whose notification will be torn down. If a zero value + * is provided, then the PID of the calling thread will be used. + * + * Returned Value: + * Zero (OK) is returned on success; a negated errno value is returned on + * any failure. + * + ****************************************************************************/ + +int iob_notify_teardown(int pid, int signo) +{ + int ret; + int i; + + /* If the pid is zero, then use the pid of the calling thread */ + + if (pid <= 0) + { + pid = getpid(); + } + + /* Get exclusive access to the notifier array */ + + ret = nxsem_wait(&g_notify_sem); + if (ret < 0) + { + return ret; + } + + /* Find the entry matching this PID in the g_iob_notify[] array. We + * assume that there is only one. + */ + + ret = -ENOENT; + for (i = 0; i < CONFIG_IOB_NWAITERS; i++) + { + if (g_iob_notify[i].waiter && g_iob_notify[i].pid == pid) + { + g_iob_notify[i].waiter = false; + ret = OK; + break; + } + } + + (void)nxsem_post(&g_notify_sem); + return ret; +} + +/**************************************************************************** + * Name: iob_notify_signal + * + * Description: + * An IOB has become available. Signal all threads waiting for an IOB + * that an IOB is available. + * + * When an IOB becomes available, *all* of the waiters in this thread will + * be signaled. If there are multiple waiters then only the highest + * priority thread will get the IOB. Lower priority threads will need to + * call iob_notify once again. + * + * Input Parameters: + * None. + * + * Returned Value: + * None. + * + ****************************************************************************/ + +void iob_notify_signal(void) +{ + int ret; + int i; + + /* Get exclusive access to the notifier array */ + + ret = nxsem_wait(&g_notify_sem); + while (ret < 0) + { + DEBUGASSERT(ret == -EINTR || ret == -ECANCELED); + } + + /* Don't let any newly started threads block this thread until all of + * the notifications and been sent. + */ + + sched_lock(); + + /* Find the entry matching this PID in the g_iob_notify[] array. We + * assume that there is only one. + */ + + for (i = 0; i < CONFIG_IOB_NWAITERS; i++) + { + if (g_iob_notify[i].waiter) + { + /* Signal the waiter and free the entry */ + + (void)nxsig_kill(g_iob_notify[i].pid, g_iob_notify[i].signo); + g_iob_notify[i].waiter = false; + } + } + + sched_unlock(); + (void)nxsem_post(&g_notify_sem); +} + +#endif /* CONFIG_IOB_NOTIFIER */