| /* |
| *------------------------------------------------------------------ |
| * svm_queue.c - unidirectional shared-memory queues |
| * |
| * Copyright (c) 2009-2019 Cisco and/or its affiliates. |
| * Licensed 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. |
| *------------------------------------------------------------------ |
| */ |
| |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <pthread.h> |
| #include <vppinfra/mem.h> |
| #include <vppinfra/format.h> |
| #include <vppinfra/cache.h> |
| #include <svm/queue.h> |
| #include <vppinfra/time.h> |
| #include <vppinfra/lock.h> |
| |
| svm_queue_t * |
| svm_queue_init (void *base, int nels, int elsize) |
| { |
| svm_queue_t *q; |
| pthread_mutexattr_t attr; |
| pthread_condattr_t cattr; |
| |
| q = (svm_queue_t *) base; |
| clib_memset (q, 0, sizeof (*q)); |
| |
| q->elsize = elsize; |
| q->maxsize = nels; |
| q->producer_evtfd = -1; |
| q->consumer_evtfd = -1; |
| |
| clib_memset (&attr, 0, sizeof (attr)); |
| clib_memset (&cattr, 0, sizeof (cattr)); |
| |
| if (pthread_mutexattr_init (&attr)) |
| clib_unix_warning ("mutexattr_init"); |
| if (pthread_mutexattr_setpshared (&attr, PTHREAD_PROCESS_SHARED)) |
| clib_unix_warning ("pthread_mutexattr_setpshared"); |
| if (pthread_mutexattr_setrobust (&attr, PTHREAD_MUTEX_ROBUST)) |
| clib_unix_warning ("setrobust"); |
| if (pthread_mutex_init (&q->mutex, &attr)) |
| clib_unix_warning ("mutex_init"); |
| if (pthread_mutexattr_destroy (&attr)) |
| clib_unix_warning ("mutexattr_destroy"); |
| if (pthread_condattr_init (&cattr)) |
| clib_unix_warning ("condattr_init"); |
| /* prints funny-looking messages in the Linux target */ |
| if (pthread_condattr_setpshared (&cattr, PTHREAD_PROCESS_SHARED)) |
| clib_unix_warning ("condattr_setpshared"); |
| if (pthread_cond_init (&q->condvar, &cattr)) |
| clib_unix_warning ("cond_init1"); |
| if (pthread_condattr_destroy (&cattr)) |
| clib_unix_warning ("cond_init2"); |
| |
| return (q); |
| } |
| |
| svm_queue_t * |
| svm_queue_alloc_and_init (int nels, int elsize, int consumer_pid) |
| { |
| svm_queue_t *q; |
| |
| q = clib_mem_alloc_aligned (sizeof (svm_queue_t) |
| + nels * elsize, CLIB_CACHE_LINE_BYTES); |
| clib_memset (q, 0, sizeof (*q)); |
| q = svm_queue_init (q, nels, elsize); |
| q->consumer_pid = consumer_pid; |
| |
| return q; |
| } |
| |
| /* |
| * svm_queue_free |
| */ |
| void |
| svm_queue_free (svm_queue_t * q) |
| { |
| (void) pthread_mutex_destroy (&q->mutex); |
| (void) pthread_cond_destroy (&q->condvar); |
| clib_mem_free (q); |
| } |
| |
| void |
| svm_queue_lock (svm_queue_t * q) |
| { |
| int rv = pthread_mutex_lock (&q->mutex); |
| if (PREDICT_FALSE (rv == EOWNERDEAD)) |
| pthread_mutex_consistent (&q->mutex); |
| } |
| |
| static int |
| svm_queue_trylock (svm_queue_t * q) |
| { |
| int rv = pthread_mutex_trylock (&q->mutex); |
| if (PREDICT_FALSE (rv == EOWNERDEAD)) |
| rv = pthread_mutex_consistent (&q->mutex); |
| return rv; |
| } |
| |
| void |
| svm_queue_unlock (svm_queue_t * q) |
| { |
| pthread_mutex_unlock (&q->mutex); |
| } |
| |
| int |
| svm_queue_is_full (svm_queue_t * q) |
| { |
| return q->cursize == q->maxsize; |
| } |
| |
| static inline void |
| svm_queue_send_signal_inline (svm_queue_t * q, u8 is_prod) |
| { |
| if (q->producer_evtfd == -1) |
| { |
| (void) pthread_cond_broadcast (&q->condvar); |
| } |
| else |
| { |
| int __clib_unused rv, fd; |
| u64 data = 1; |
| ASSERT (q->consumer_evtfd > 0 && q->producer_evtfd > 0); |
| fd = is_prod ? q->producer_evtfd : q->consumer_evtfd; |
| rv = write (fd, &data, sizeof (data)); |
| if (PREDICT_FALSE (rv < 0)) |
| clib_unix_warning ("signal write on %d returned %d", fd, rv); |
| } |
| } |
| |
| void |
| svm_queue_send_signal (svm_queue_t * q, u8 is_prod) |
| { |
| svm_queue_send_signal_inline (q, is_prod); |
| } |
| |
| static inline void |
| svm_queue_wait_inline (svm_queue_t * q) |
| { |
| if (q->producer_evtfd == -1) |
| { |
| pthread_cond_wait (&q->condvar, &q->mutex); |
| } |
| else |
| { |
| /* Fake a wait for event. We could use epoll but that would mean |
| * using yet another fd. Should do for now */ |
| u32 cursize = q->cursize; |
| svm_queue_unlock (q); |
| while (q->cursize == cursize) |
| CLIB_PAUSE (); |
| svm_queue_lock (q); |
| } |
| } |
| |
| void |
| svm_queue_wait (svm_queue_t * q) |
| { |
| svm_queue_wait_inline (q); |
| } |
| |
| static inline int |
| svm_queue_timedwait_inline (svm_queue_t * q, double timeout) |
| { |
| struct timespec ts; |
| ts.tv_sec = unix_time_now () + (u32) timeout; |
| ts.tv_nsec = (timeout - (u32) timeout) * 1e9; |
| |
| if (q->producer_evtfd == -1) |
| { |
| return pthread_cond_timedwait (&q->condvar, &q->mutex, &ts); |
| } |
| else |
| { |
| double max_time = unix_time_now () + timeout; |
| u32 cursize = q->cursize; |
| int rv; |
| |
| svm_queue_unlock (q); |
| while (q->cursize == cursize && unix_time_now () < max_time) |
| CLIB_PAUSE (); |
| rv = unix_time_now () < max_time ? 0 : ETIMEDOUT; |
| svm_queue_lock (q); |
| return rv; |
| } |
| } |
| |
| int |
| svm_queue_timedwait (svm_queue_t * q, double timeout) |
| { |
| return svm_queue_timedwait_inline (q, timeout); |
| } |
| |
| /* |
| * svm_queue_add_nolock |
| */ |
| int |
| svm_queue_add_nolock (svm_queue_t * q, u8 * elem) |
| { |
| i8 *tailp; |
| int need_broadcast = 0; |
| |
| if (PREDICT_FALSE (q->cursize == q->maxsize)) |
| { |
| while (q->cursize == q->maxsize) |
| svm_queue_wait_inline (q); |
| } |
| |
| tailp = (i8 *) (&q->data[0] + q->elsize * q->tail); |
| clib_memcpy_fast (tailp, elem, q->elsize); |
| |
| q->tail++; |
| q->cursize++; |
| |
| need_broadcast = (q->cursize == 1); |
| |
| if (q->tail == q->maxsize) |
| q->tail = 0; |
| |
| if (need_broadcast) |
| svm_queue_send_signal_inline (q, 1); |
| return 0; |
| } |
| |
| void |
| svm_queue_add_raw (svm_queue_t * q, u8 * elem) |
| { |
| i8 *tailp; |
| |
| tailp = (i8 *) (&q->data[0] + q->elsize * q->tail); |
| clib_memcpy_fast (tailp, elem, q->elsize); |
| |
| q->tail = (q->tail + 1) % q->maxsize; |
| q->cursize++; |
| |
| if (q->cursize == 1) |
| svm_queue_send_signal_inline (q, 1); |
| } |
| |
| |
| /* |
| * svm_queue_add |
| */ |
| int |
| svm_queue_add (svm_queue_t * q, u8 * elem, int nowait) |
| { |
| i8 *tailp; |
| int need_broadcast = 0; |
| |
| if (nowait) |
| { |
| /* zero on success */ |
| if (svm_queue_trylock (q)) |
| { |
| return (-1); |
| } |
| } |
| else |
| svm_queue_lock (q); |
| |
| if (PREDICT_FALSE (q->cursize == q->maxsize)) |
| { |
| if (nowait) |
| { |
| svm_queue_unlock (q); |
| return (-2); |
| } |
| while (q->cursize == q->maxsize) |
| svm_queue_wait_inline (q); |
| } |
| |
| tailp = (i8 *) (&q->data[0] + q->elsize * q->tail); |
| clib_memcpy_fast (tailp, elem, q->elsize); |
| |
| q->tail++; |
| q->cursize++; |
| |
| need_broadcast = (q->cursize == 1); |
| |
| if (q->tail == q->maxsize) |
| q->tail = 0; |
| |
| if (need_broadcast) |
| svm_queue_send_signal_inline (q, 1); |
| |
| svm_queue_unlock (q); |
| |
| return 0; |
| } |
| |
| /* |
| * svm_queue_add2 |
| */ |
| int |
| svm_queue_add2 (svm_queue_t * q, u8 * elem, u8 * elem2, int nowait) |
| { |
| i8 *tailp; |
| int need_broadcast = 0; |
| |
| if (nowait) |
| { |
| /* zero on success */ |
| if (svm_queue_trylock (q)) |
| { |
| return (-1); |
| } |
| } |
| else |
| svm_queue_lock (q); |
| |
| if (PREDICT_FALSE (q->cursize + 1 == q->maxsize)) |
| { |
| if (nowait) |
| { |
| svm_queue_unlock (q); |
| return (-2); |
| } |
| while (q->cursize + 1 == q->maxsize) |
| svm_queue_wait_inline (q); |
| } |
| |
| tailp = (i8 *) (&q->data[0] + q->elsize * q->tail); |
| clib_memcpy_fast (tailp, elem, q->elsize); |
| |
| q->tail++; |
| q->cursize++; |
| |
| if (q->tail == q->maxsize) |
| q->tail = 0; |
| |
| need_broadcast = (q->cursize == 1); |
| |
| tailp = (i8 *) (&q->data[0] + q->elsize * q->tail); |
| clib_memcpy_fast (tailp, elem2, q->elsize); |
| |
| q->tail++; |
| q->cursize++; |
| |
| if (q->tail == q->maxsize) |
| q->tail = 0; |
| |
| if (need_broadcast) |
| svm_queue_send_signal_inline (q, 1); |
| |
| svm_queue_unlock (q); |
| |
| return 0; |
| } |
| |
| /* |
| * svm_queue_sub |
| */ |
| int |
| svm_queue_sub (svm_queue_t * q, u8 * elem, svm_q_conditional_wait_t cond, |
| u32 time) |
| { |
| i8 *headp; |
| int need_broadcast = 0; |
| int rc = 0; |
| |
| if (cond == SVM_Q_NOWAIT) |
| { |
| /* zero on success */ |
| if (svm_queue_trylock (q)) |
| { |
| return (-1); |
| } |
| } |
| else |
| svm_queue_lock (q); |
| |
| if (PREDICT_FALSE (q->cursize == 0)) |
| { |
| if (cond == SVM_Q_NOWAIT) |
| { |
| svm_queue_unlock (q); |
| return (-2); |
| } |
| else if (cond == SVM_Q_TIMEDWAIT) |
| { |
| while (q->cursize == 0 && rc == 0) |
| rc = svm_queue_timedwait_inline (q, time); |
| |
| if (rc == ETIMEDOUT) |
| { |
| svm_queue_unlock (q); |
| return ETIMEDOUT; |
| } |
| } |
| else |
| { |
| while (q->cursize == 0) |
| svm_queue_wait_inline (q); |
| } |
| } |
| |
| headp = (i8 *) (&q->data[0] + q->elsize * q->head); |
| clib_memcpy_fast (elem, headp, q->elsize); |
| |
| q->head++; |
| /* $$$$ JFC shouldn't this be == 0? */ |
| if (q->cursize == q->maxsize) |
| need_broadcast = 1; |
| |
| q->cursize--; |
| |
| if (q->head == q->maxsize) |
| q->head = 0; |
| |
| if (need_broadcast) |
| svm_queue_send_signal_inline (q, 0); |
| |
| svm_queue_unlock (q); |
| |
| return 0; |
| } |
| |
| int |
| svm_queue_sub2 (svm_queue_t * q, u8 * elem) |
| { |
| int need_broadcast; |
| i8 *headp; |
| |
| svm_queue_lock (q); |
| if (q->cursize == 0) |
| { |
| svm_queue_unlock (q); |
| return -1; |
| } |
| |
| headp = (i8 *) (&q->data[0] + q->elsize * q->head); |
| clib_memcpy_fast (elem, headp, q->elsize); |
| |
| q->head++; |
| need_broadcast = (q->cursize == q->maxsize / 2); |
| q->cursize--; |
| |
| if (PREDICT_FALSE (q->head == q->maxsize)) |
| q->head = 0; |
| svm_queue_unlock (q); |
| |
| if (need_broadcast) |
| svm_queue_send_signal_inline (q, 0); |
| |
| return 0; |
| } |
| |
| int |
| svm_queue_sub_raw (svm_queue_t * q, u8 * elem) |
| { |
| int need_broadcast; |
| i8 *headp; |
| |
| if (PREDICT_FALSE (q->cursize == 0)) |
| { |
| while (q->cursize == 0) |
| ; |
| } |
| |
| headp = (i8 *) (&q->data[0] + q->elsize * q->head); |
| clib_memcpy_fast (elem, headp, q->elsize); |
| |
| need_broadcast = q->cursize == q->maxsize; |
| |
| q->head = (q->head + 1) % q->maxsize; |
| q->cursize--; |
| |
| if (PREDICT_FALSE (need_broadcast)) |
| svm_queue_send_signal_inline (q, 0); |
| |
| return 0; |
| } |
| |
| void |
| svm_queue_set_producer_event_fd (svm_queue_t * q, int fd) |
| { |
| q->producer_evtfd = fd; |
| } |
| |
| void |
| svm_queue_set_consumer_event_fd (svm_queue_t * q, int fd) |
| { |
| q->consumer_evtfd = fd; |
| } |
| |
| /* |
| * fd.io coding-style-patch-verification: ON |
| * |
| * Local Variables: |
| * eval: (c-set-style "gnu") |
| * End: |
| */ |