├── test ├── .gitignore ├── Makefile ├── compat.h └── run.c ├── .gitignore ├── .gitmodules ├── Makefile.base ├── LICENSE ├── chanfd.h ├── README.md ├── configure └── chanfd.c /test/.gitignore: -------------------------------------------------------------------------------- 1 | test 2 | test9 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /Makefile 2 | *.a 3 | *.o 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "ck"] 2 | path = ck 3 | url = https://github.com/concurrencykit/ck.git 4 | -------------------------------------------------------------------------------- /Makefile.base: -------------------------------------------------------------------------------- 1 | CFLAGS += -g3 -ggdb -O3 2 | CFLAGS += -I. 3 | CFLAGS += -Wall 4 | CFLAGS += -Wextra 5 | CFLAGS += -Wmissing-declarations 6 | CFLAGS += -Wno-missing-field-initializers 7 | CFLAGS += -Wno-unused-parameter 8 | CFLAGS += -Wpointer-arith 9 | CFLAGS += -Wundef 10 | 11 | CHANFD_LIB := libchanfd.a 12 | CHANFD_OBJS := chanfd.o 13 | 14 | all: $(CHANFD_LIB) 15 | .PHONY: all clean 16 | 17 | $(CHANFD_LIB): $(CHANFD_OBJS) 18 | $(AR) crs $@ $^ 19 | 20 | clean: 21 | $(RM) $(CHANFD_OBJS) 22 | $(RM) $(CHANFD_LIB) 23 | 24 | distclean: clean 25 | $(RM) Makefile 26 | -------------------------------------------------------------------------------- /test/Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS := -g3 -ggdb -O2 -Wall -I.. 2 | LDFLAGS := -L.. 3 | 4 | LDLIBS := -lpthread 5 | 6 | CHANFD_LIB := ../libchanfd.a 7 | 8 | TESTS := test 9 | ifdef PLAN9 10 | TESTS += test9 11 | endif 12 | 13 | all: $(TESTS) 14 | 15 | run.o: run.c compat.h 16 | 17 | test: run.o 18 | $(CC) $(LDFLAGS) -o $@ $^ $(LDLIBS) $(CHANFD_LIB) 19 | 20 | run9.o: run.c compat.h 21 | 9c $(CFLAGS) -I$(PLAN9)/include -D__PLAN9__ $< -o $@ 22 | test9: run9.o 23 | 9l $(CFLAGS) -o $@ $(LDFLAGS) -L$(PLAN9)/lib $^ $(LDLIBS) -lthread 24 | 25 | clean: 26 | $(RM) *.o $(TESTS) 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014-2016, Emilio G. Cota 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | 8 | 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. 9 | 10 | 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 HOLDER 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. 11 | -------------------------------------------------------------------------------- /chanfd.h: -------------------------------------------------------------------------------- 1 | /* Licensed under Simplified BSD - see LICENSE file for details */ 2 | #ifndef CHANFD_H 3 | #define CHANFD_H 4 | 5 | #include 6 | #include 7 | 8 | struct chanfd *chanfd_create(size_t size, size_t n_elems); 9 | void chanfd_destroy(struct chanfd *); 10 | 11 | void chanfd_recv(struct chanfd *chan, void *data); 12 | void chanfd_send(struct chanfd *chan, const void *data); 13 | 14 | int chanfd_receiver_fd(struct chanfd *chan); 15 | int chanfd_sender_fd(struct chanfd *chan); 16 | 17 | bool chanfd_is_empty(struct chanfd *chan); 18 | 19 | /* below, some type-safe send/recv helpers */ 20 | 21 | static inline void chanfd_send_int(struct chanfd *channel, const int *elem) 22 | { 23 | chanfd_send(channel, elem); 24 | } 25 | 26 | static inline void chanfd_recv_int(struct chanfd *channel, int *elem) 27 | { 28 | chanfd_recv(channel, elem); 29 | } 30 | 31 | static inline void chanfd_send_uint(struct chanfd *channel, const unsigned int *elem) 32 | { 33 | chanfd_send(channel, elem); 34 | } 35 | 36 | static inline void chanfd_recv_uint(struct chanfd *channel, unsigned int *elem) 37 | { 38 | chanfd_recv(channel, elem); 39 | } 40 | 41 | /* use these to define your own */ 42 | #define CHANFD_INLINE_SEND_STRUCT(_func, _elem, _struct_name) \ 43 | static inline void _func(struct chanfd *channel, const struct _struct_name *_elem) \ 44 | { \ 45 | chanfd_send(channel, _elem); \ 46 | } 47 | 48 | #define CHANFD_INLINE_RECV_STRUCT(_func, _elem, _struct_name) \ 49 | static inline void _func(struct chanfd *channel, struct _struct_name *_elem) \ 50 | { \ 51 | chanfd_recv(channel, _elem); \ 52 | } 53 | 54 | #endif /* CHANFD_H */ 55 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # chanfd 2 | 3 | `chanfd` is a tiny C library that implements channels for inter-thread/process 4 | communication. These channels are similar to the channels in Plan9's 5 | [libthread](http://plan9.bell-labs.com/magic/man2html/2/thread) or 6 | [Go](https://golang.org/). `chanfd`, however, does not support co-routines. 7 | 8 | ### _I see. So is this yet another "Go Concurrency in C" project?_ 9 | Not quite. `chanfd` has a much narrower goal: it doesn't provide co-routines 10 | (it works with regular threads/processes), does not take over your `main()`, 11 | knows nothing about networking, and does not do any macro trickery to make 12 | C look like something else. 13 | 14 | `chanfd` is basically a simple wrapper around Linux' `eventfd(2)`, which 15 | means you can use standard polling calls such as `select(2)` or `epoll(2)` 16 | to wait on channels just like on any other file descriptors. 17 | 18 | ## API 19 | ```C 20 | struct chanfd *chanfd_create(size_t size, size_t n_elems); /* pass size=0 for unbuffered channel */ 21 | void chanfd_destroy(struct chanfd *); /* Note: no 'close' operation */ 22 | 23 | void chanfd_recv(struct chanfd *chan, void *data); /* see the header file for type-safe macros */ 24 | void chanfd_send(struct chanfd *chan, const void *data); 25 | 26 | int chanfd_receiver_fd(struct chanfd *chan); /* these can be used with select(2) etc. */ 27 | int chanfd_sender_fd(struct chanfd *chan); 28 | 29 | bool chanfd_is_empty(struct chanfd *chan); 30 | ``` 31 | `chanfd_send` and `chanfd_recv` are expensive; they issue syscalls that take at 32 | least one lock. However, `chanfd_is_empty` is fast and scalable since it only 33 | needs a SMP load fence and a load. 34 | 35 | ## Dependencies 36 | * Linux >= v2.6.30 37 | * [Concurrency Kit.](http://concurrencykit.org/) Run `./configure --help` for 38 | options regarding Concurrency Kit's installation. 39 | 40 | ## Historical Background on Channels 41 | [Bell Labs and CSP Threads](https://swtch.com/~rsc/thread/) by Russ Cox. 42 | 43 | ## Some Alternatives 44 | * [chan](https://github.com/tylertreat/chan) 45 | * [eb_chan](https://github.com/davekeck/eb_chan) 46 | * [libmill](http://libmill.org/) 47 | * [libtask](https://swtch.com/libtask/) 48 | * [Plan9's libthread](http://plan9.bell-labs.com/magic/man2html/2/thread) 49 | * or just use the [Go Programming Language](https://golang.org/) 50 | 51 | ## License 52 | Simplified BSD License -- see LICENSE. 53 | -------------------------------------------------------------------------------- /test/compat.h: -------------------------------------------------------------------------------- 1 | #ifndef COMPAT_H 2 | #define COMPAT_H 3 | 4 | #ifdef __PLAN9__ 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | typedef Channel __chan; 11 | 12 | static inline void __chan_send(__chan *chan, void *v) 13 | { 14 | send(chan, v); 15 | } 16 | 17 | static inline void __chan_recv(__chan *chan, void *v) 18 | { 19 | recv(chan, v); 20 | } 21 | 22 | static inline __chan *__chan_create(size_t size, size_t n_elems) 23 | { 24 | return chancreate(size, n_elems); 25 | } 26 | 27 | static inline int __thread_create(void (*func)(void *), void *arg, size_t stack_size) 28 | { 29 | proccreate(func, arg, stack_size); 30 | return 0; 31 | } 32 | 33 | #else /* chanfd */ 34 | 35 | #include 36 | #include 37 | 38 | #include 39 | 40 | typedef struct chanfd __chan; 41 | 42 | struct th_args { 43 | void (*func)(void *); 44 | void *arg; 45 | }; 46 | 47 | static inline void __chan_send(__chan *chan, void *v) 48 | { 49 | chanfd_send(chan, v); 50 | } 51 | 52 | static inline void __chan_recv(__chan *chan, void *v) 53 | { 54 | chanfd_recv(chan, v); 55 | } 56 | 57 | static inline __chan *__chan_create(size_t size, size_t n_elems) 58 | { 59 | return chanfd_create(size, n_elems); 60 | } 61 | 62 | static void *th_start(void *args) 63 | { 64 | struct th_args *argp = args; 65 | void *arg = argp->arg; 66 | void (*func)(void *) = argp->func; 67 | 68 | free(argp); 69 | func(arg); 70 | return NULL; 71 | } 72 | 73 | static inline int __thread_create(void (*func)(void *), void *arg, size_t stack_size) 74 | { 75 | struct th_args *argp; 76 | pthread_attr_t attr; 77 | pthread_t thread; 78 | int rc; 79 | 80 | rc = pthread_attr_init(&attr); 81 | if (rc) { 82 | errno = rc; 83 | return -1; 84 | } 85 | 86 | rc = pthread_attr_setstacksize(&attr, stack_size); 87 | if (rc) 88 | goto err; 89 | 90 | rc = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); 91 | if (rc) 92 | goto err; 93 | 94 | argp = malloc(sizeof(*argp)); 95 | if (argp == NULL) { 96 | rc = errno; 97 | goto err; 98 | } 99 | 100 | argp->func = func; 101 | argp->arg = arg; 102 | 103 | rc = pthread_create(&thread, &attr, th_start, argp); 104 | if (rc) 105 | goto err_create; 106 | 107 | pthread_attr_destroy(&attr); 108 | return 0; 109 | err_create: 110 | free(argp); 111 | err: 112 | pthread_attr_destroy(&attr); 113 | errno = rc; 114 | return -1; 115 | } 116 | 117 | #endif /* __PLAN9__ */ 118 | 119 | #endif /* COMPAT_H */ 120 | -------------------------------------------------------------------------------- /configure: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | EXIT_SUCCESS=0 4 | EXIT_FAILURE=1 5 | 6 | for option; do 7 | case "$option" in 8 | *=?*) 9 | value=`expr -- "$option" : '[^=]*=\(.*\)'` 10 | ;; 11 | *=) 12 | value= 13 | ;; 14 | *) 15 | value=yes 16 | ;; 17 | esac 18 | 19 | case "$option" in 20 | --help) 21 | echo "Usage: $0 [OPTIONS]" 22 | echo 23 | echo "Options:" 24 | echo " --ck-path=X Use concurrencykit's include files from X." 25 | echo " --fetch-ck Fetch concurrencykit as a submodule." 26 | echo 27 | echo "The following environment variables may be used:" 28 | echo " CC C compiler command" 29 | exit $EXIT_SUCCESS 30 | ;; 31 | --ck-path=*) 32 | CK_PATH="$value" 33 | ;; 34 | --fetch-ck) 35 | CK_SUBMODULE=1 36 | ;; 37 | --*) 38 | if test "$OPTION_CHECKING" -eq 1; then 39 | echo "$0 [--help]" 40 | echo "Unknown option $option" 41 | exit $EXIT_FAILURE 42 | fi 43 | ;; 44 | *=*) 45 | NAME=`expr -- "$option" : '\([^=]*\)='` 46 | eval "$NAME='$value'" 47 | export $NAME 48 | ;; 49 | *) 50 | echo "$0 [--help]" 51 | echo "Unknown option $option" 52 | exit $EXIT_FAILURE 53 | ;; 54 | esac 55 | done 56 | 57 | do_cc() { 58 | local compiler="$1" 59 | shift 60 | echo "$CC $compiler $@" 61 | "$CC" $compiler "$@" || return $? 62 | return 0 63 | } 64 | 65 | assert() 66 | { 67 | 68 | if test "$#" -eq 2; then 69 | fail=$2 70 | print=true 71 | elif test "$#" -eq 3; then 72 | fail=$3 73 | print=echo 74 | else 75 | echo "Usage: assert or assert " 1>&2 76 | exit $EXIT_FAILURE 77 | fi 78 | 79 | if test -z "$1"; then 80 | echo "failed [$fail]" 81 | exit $EXIT_FAILURE 82 | else 83 | ${print} "success [$1]" 84 | fi 85 | } 86 | 87 | CC=`which "${CC:-cc}"` 88 | if test -z "$CC" -o ! -x "$CC"; then 89 | CC=`which "${CC:-gcc}"` 90 | fi 91 | assert "$CC" "not found" 92 | 93 | GIT=`which git` 94 | assert "$GIT" "not found" 95 | 96 | cat << EOF > .1.c 97 | #include 98 | int main(void){return 0;} 99 | EOF 100 | 101 | if [ -z "$CK_PATH" ]; then 102 | if [ ! -z "$CK_SUBMODULE" ]; then 103 | git submodule init ck 104 | git submodule update ck 105 | cd ck && ./configure && make && cd .. 106 | CK_PATH=ck 107 | fi 108 | fi 109 | if [ ! -z "$CK_PATH" ]; then 110 | MYCFLAGS=-I"$CK_PATH/include" 111 | fi 112 | 113 | $CC $MYCFLAGS -o .1 .1.c 114 | r=$? 115 | rm -f .1.c .1 116 | 117 | if test "$r" -ne 0; then 118 | if [ -z "$CK_PATH" ]; then 119 | echo "concurrencykit headers not installed system-wide, and no --ck-path option provided. Consider calling configure with --fetch-ck." >& 2 120 | exit $EXIT_FAILURE 121 | else 122 | echo "Something went wrong wich concurrencykit. Most likely it was not built at '$CK_PATH'." >& 2 123 | exit $EXIT_FAILURE 124 | fi 125 | else 126 | echo "success [ck]" 127 | fi 128 | 129 | rm -f .1.c .1 130 | 131 | cat << EOF > .1.c 132 | #include 133 | int main(void){eventfd(0, EFD_CLOEXEC | EFD_SEMAPHORE);return 0;} 134 | EOF 135 | 136 | $CC $MYCFLAGS -o .1 .1.c 137 | r=$? 138 | rm -f .1.c .1 139 | 140 | if test "$r" -ne 0; then 141 | echo "eventfd(2) not found." >& 2 142 | exit $EXIT_FAILURE 143 | else 144 | echo "success [eventfd]" 145 | fi 146 | 147 | echo "CFLAGS :=" > Makefile 148 | if [ ! -z "$CK_PATH" ]; then 149 | echo "CFLAGS += -I\"$CK_PATH/include\"\n" >> Makefile 150 | fi 151 | cat Makefile.base >> Makefile 152 | 153 | echo "success" 154 | -------------------------------------------------------------------------------- /test/run.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "compat.h" 8 | 9 | #define N 16 10 | #define LIMIT 20000 11 | 12 | #define M 5 13 | 14 | static __chan *test1_rsp; 15 | static __chan *test2_chan; 16 | static __chan *buff_rsp; 17 | static int count; 18 | 19 | static inline void __chan_send_int(__chan *chan, int v) 20 | { 21 | __chan_send(chan, &v); 22 | } 23 | 24 | static void receiver_func(void *arg) 25 | { 26 | __chan *chan = arg; 27 | __chan *send_chan; 28 | int v; 29 | 30 | /* 31 | * Only one thread sends at a time, so we should be able to access 32 | * count safely without races. 33 | */ 34 | for (;;) { 35 | __chan_recv(chan, &v); 36 | 37 | v++; 38 | count++; 39 | 40 | send_chan = v == LIMIT ? test1_rsp : chan; 41 | __chan_send(send_chan, &v); 42 | 43 | if (send_chan == test1_rsp) 44 | return; 45 | } 46 | } 47 | 48 | /* 49 | * Test contention on the receiver end of a blocking channel. 50 | */ 51 | static bool test1(void) 52 | { 53 | __chan *chan; 54 | int i; 55 | int response; 56 | 57 | chan = __chan_create(sizeof(int), 0); 58 | if (chan == NULL) { 59 | perror(NULL); 60 | return false; 61 | } 62 | 63 | test1_rsp = __chan_create(sizeof(int), 0); 64 | if (chan == NULL) { 65 | perror(NULL); 66 | return false; 67 | } 68 | 69 | for (i = 0; i < N; i++) { 70 | if (__thread_create(receiver_func, chan, 64 * 1024)) { 71 | perror(NULL); 72 | return false; 73 | } 74 | } 75 | 76 | __chan_send_int(chan, 0); 77 | __chan_recv(test1_rsp, &response); 78 | 79 | return response == count && count == LIMIT; 80 | } 81 | 82 | static void test2_send_func(void *arg) 83 | { 84 | int i; 85 | 86 | for (i = 0; i <= LIMIT; i++) { 87 | __chan_send(test2_chan, &i); 88 | } 89 | } 90 | 91 | static void test2_recv_func(void *arg) 92 | { 93 | __chan *rsp = arg; 94 | int v = 0; 95 | 96 | while (v != LIMIT) { 97 | __chan_recv(test2_chan, &v); 98 | } 99 | __chan_send(rsp, &v); 100 | } 101 | 102 | /* 103 | * Test heavy contention on blocking send/receive. All N/N threads are trying 104 | * to send/receive at the same time. 105 | */ 106 | static bool test2(void) 107 | { 108 | __chan **responses; 109 | int i; 110 | 111 | test2_chan = __chan_create(sizeof(int), 0); 112 | if (test2_chan == NULL) { 113 | perror(NULL); 114 | return false; 115 | } 116 | 117 | responses = calloc(N, sizeof(__chan *)); 118 | if (responses == NULL) { 119 | perror(NULL); 120 | return false; 121 | } 122 | for (i = 0; i < N; i++) { 123 | responses[i] = __chan_create(sizeof(int), 0); 124 | if (responses[i] == NULL) { 125 | perror(NULL); 126 | return false; 127 | } 128 | } 129 | 130 | for (i = 0; i < N; i++) { 131 | if (__thread_create(test2_send_func, NULL, 64 * 1024)) { 132 | perror(NULL); 133 | return false; 134 | } 135 | 136 | if (__thread_create(test2_recv_func, responses[i], 64 * 1024)) { 137 | perror(NULL); 138 | return false; 139 | } 140 | } 141 | 142 | for (i = 0; i < N; i++) { 143 | int v; 144 | 145 | __chan_recv(responses[i], &v); 146 | if (v != LIMIT) 147 | return false; 148 | } 149 | return true; 150 | } 151 | 152 | static void buff_send(void *arg) 153 | { 154 | __chan *chan = arg; 155 | int i; 156 | 157 | for (i = 0; i <= LIMIT; i++) 158 | __chan_send(chan, &i); 159 | } 160 | 161 | static void buff_recv(void *arg) 162 | { 163 | __chan *chan = arg; 164 | int v = 0; 165 | 166 | while (v < LIMIT) 167 | __chan_recv(chan, &v); 168 | __chan_send(buff_rsp, &v); 169 | } 170 | 171 | /* 172 | * Test sender/receiver contention on a buffered channel. 173 | */ 174 | static bool test_buff(void) 175 | { 176 | __chan *chan; 177 | int total = 0; 178 | int rc; 179 | int v; 180 | int i; 181 | 182 | chan = __chan_create(sizeof(int), M); 183 | if (chan == NULL) 184 | return false; 185 | 186 | buff_rsp = __chan_create(sizeof(int), 0); 187 | if (chan == NULL) 188 | return false; 189 | 190 | for (i = 0; i < M; i++) { 191 | rc = __thread_create(buff_send, chan, 64 * 1024); 192 | if (rc) 193 | return false; 194 | rc = __thread_create(buff_recv, chan, 64 * 1024); 195 | if (rc) 196 | return false; 197 | } 198 | for (i = 0; i < M; i++) { 199 | __chan_recv(buff_rsp, &v); 200 | total += v; 201 | } 202 | 203 | return total == M * LIMIT; 204 | } 205 | 206 | #ifdef __PLAN9__ 207 | void threadmain(int argc, char *argv[]) 208 | #else 209 | int main(int argc, char *argv[]) 210 | #endif 211 | 212 | { 213 | assert(test1()); 214 | assert(test2()); 215 | assert(test_buff()); 216 | 217 | #ifndef __PLAN9__ 218 | return 0; 219 | #endif 220 | } 221 | -------------------------------------------------------------------------------- /chanfd.c: -------------------------------------------------------------------------------- 1 | /* Licensed under Simplified BSD - see LICENSE file for details */ 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | 17 | #include "chanfd.h" 18 | 19 | struct unbf_channel { 20 | int ack_fd; 21 | }; 22 | 23 | struct buff_channel { 24 | size_t in; 25 | size_t out; 26 | ck_spinlock_t lock; 27 | }; 28 | 29 | struct chanfd { 30 | uint64_t elems; 31 | uintptr_t datap; /* tagged pointer with 'buffered' bool */ 32 | size_t size; 33 | size_t max_elems; 34 | union { 35 | struct unbf_channel unbf; 36 | struct buff_channel buff; 37 | } chan; 38 | int sender_fd; 39 | int receiver_fd; 40 | } CK_CC_ALIGN(sizeof(uintptr_t)); 41 | 42 | static inline uint8_t *to_ptr(uintptr_t val) 43 | { 44 | return (uint8_t *)(val & ~1); 45 | } 46 | 47 | static inline bool is_buffered(struct chanfd *ch) 48 | { 49 | return ch->datap & 1; 50 | } 51 | 52 | static inline void up(int fd) 53 | { 54 | ssize_t n; 55 | uint64_t v = 1; 56 | 57 | n = write(fd, &v, sizeof(v)); 58 | assert(n == sizeof(v)); 59 | } 60 | 61 | static inline void down(int fd) 62 | { 63 | ssize_t n; 64 | uint64_t v; 65 | 66 | n = read(fd, &v, sizeof(v)); 67 | assert(n == sizeof(v)); 68 | } 69 | 70 | static inline int buff_channel_init(struct chanfd *ch) 71 | { 72 | struct buff_channel *bchan = &ch->chan.buff; 73 | 74 | ck_spinlock_init(&bchan->lock); 75 | return 0; 76 | } 77 | 78 | static inline int unbf_channel_init(struct chanfd *ch) 79 | { 80 | struct unbf_channel *uchan = &ch->chan.unbf; 81 | int flags = EFD_CLOEXEC | EFD_SEMAPHORE; 82 | 83 | uchan->ack_fd = eventfd(0, flags); 84 | if (CK_CC_UNLIKELY(uchan->ack_fd < 0)) 85 | return -1; 86 | return 0; 87 | } 88 | 89 | static inline void unbf_channel_destroy(struct chanfd *ch) 90 | { 91 | struct unbf_channel *uchan = &ch->chan.unbf; 92 | 93 | close(uchan->ack_fd); 94 | } 95 | 96 | static inline void buff_channel_destroy(struct chanfd *ch) 97 | { } 98 | 99 | /** 100 | * chanfd_create - create channel 101 | * @size: size of element to be exchanged through the channel 102 | * @n_elems: number of elements; 0 for unbuffered channel. 103 | * 104 | * Channels allow to explicitly transfer ownership of objects across 105 | * threads. Normally these objects are structs, so only a pointer to them 106 | * is passed through channels. Moreover, usually these structs are allocated 107 | * in the heap, since sharing stack data is harder to reason about. 108 | * 109 | * Receivers: they always block until there is data. 110 | * 111 | * Senders: in buffered channels (@n_elems > 0), they block only until their 112 | * data has been copied to the buffer. In unbuffered channels (@n_elems == 0), 113 | * they block until the receiver has received the value--i.e. "receiver 114 | * completes first." 115 | * 116 | * Returns a pointer to the newly created channel on success; NULL on error, 117 | * setting errno appropriately. 118 | * 119 | * Note: memory is allocated as MAP_SHARED so that forked processes can share 120 | * channels with their parents. 121 | * 122 | * See also: chanfd_destroy(), chanfd_send(), chanfd_recv() 123 | */ 124 | struct chanfd *chanfd_create(size_t size, size_t n_elems) 125 | { 126 | struct chanfd *chan; 127 | size_t max_elems = n_elems ? n_elems : 1; 128 | size_t bytes = max_elems * size; 129 | int flags = EFD_CLOEXEC | EFD_SEMAPHORE; 130 | uint8_t *data; 131 | int rc; 132 | 133 | chan = mmap(NULL, sizeof(*chan), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); 134 | if (CK_CC_UNLIKELY(chan == MAP_FAILED)) 135 | return NULL; 136 | 137 | data = mmap(NULL, bytes, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); 138 | if (CK_CC_UNLIKELY(data == MAP_FAILED)) 139 | goto err_chan_data; 140 | 141 | chan->datap = (uintptr_t)data; 142 | if (CK_CC_UNLIKELY(chan->datap & 1)) { 143 | fprintf(stderr, "%s:%d invalid 1-byte alignment of chanfd_channel struct\n", __func__, __LINE__); 144 | goto err_alignment; 145 | } 146 | if (n_elems > 0) 147 | chan->datap |= 1; 148 | 149 | chan->receiver_fd = eventfd(0, flags); 150 | if (CK_CC_UNLIKELY(chan->receiver_fd < 0)) 151 | goto err_receiver_fd; 152 | 153 | chan->sender_fd = eventfd(max_elems, flags); 154 | if (CK_CC_UNLIKELY(chan->sender_fd < 0)) 155 | goto err_sender_fd; 156 | 157 | chan->size = size; 158 | chan->max_elems = max_elems; 159 | 160 | if (is_buffered(chan)) 161 | rc = buff_channel_init(chan); 162 | else 163 | rc = unbf_channel_init(chan); 164 | if (CK_CC_UNLIKELY(rc)) 165 | goto err_union; 166 | 167 | return chan; 168 | err_union: 169 | close(chan->sender_fd); 170 | err_sender_fd: 171 | close(chan->receiver_fd); 172 | err_receiver_fd: 173 | err_alignment: 174 | munmap(to_ptr(chan->datap), bytes); 175 | err_chan_data: 176 | munmap(chan, sizeof(*chan)); 177 | return NULL; 178 | } 179 | 180 | /** 181 | * chanfd_destroy - destroy a channel 182 | * @chan: channel to destroy 183 | * 184 | * Frees all allocated data related to channel @chan. 185 | * 186 | * Note: calling this function while there are senders/receivers waiting 187 | * on the channel is a bug. 188 | * 189 | * See also: chanfd_create() 190 | */ 191 | void chanfd_destroy(struct chanfd *chan) 192 | { 193 | if (chan == NULL) 194 | return; 195 | 196 | if (is_buffered(chan)) 197 | buff_channel_destroy(chan); 198 | else 199 | unbf_channel_destroy(chan); 200 | 201 | munmap(to_ptr(chan->datap), chan->max_elems * chan->size); 202 | munmap(chan, sizeof(*chan)); 203 | } 204 | 205 | /** 206 | * chanfd_receiver_fd - obtain a channel's file descriptor for "receive" monitoring 207 | * @ch: channel to monitor 208 | * 209 | * The returned file descriptor can be used by I/O multiplexing functions 210 | * such as select(2). 211 | * 212 | * NOTE: the file descriptor _must_ be put in the "read" fd set of the monitoring 213 | * call--putting it on _any_ other fd set is a bug. 214 | * 215 | * If the file descriptor can be read, chanfd_recv() can then be called on 216 | * the channel without blocking. 217 | * 218 | * See also: chanfd_sender_fd() 219 | */ 220 | int chanfd_receiver_fd(struct chanfd *ch) 221 | { 222 | return ch->receiver_fd; 223 | } 224 | 225 | /** 226 | * chanfd_sender_fd - obtain a channel's file descriptor for "send" monitoring 227 | * @ch: channel to monitor 228 | * 229 | * The returned file descriptor can be used by I/O multiplexing functions 230 | * such as select(2). 231 | * 232 | * NOTE: As is the case for chanfd_receiver_fd(), the file descriptor _must_ be put 233 | * in the "read" fd set of the monitoring call--putting it on _any_ other 234 | * fd set is a bug. 235 | * 236 | * If the file descriptor can be read, chanfd_send() can then be called on 237 | * the channel without blocking. 238 | * 239 | * See also: chanfd_receiver_fd() 240 | */ 241 | int chanfd_sender_fd(struct chanfd *ch) 242 | { 243 | return ch->sender_fd; 244 | } 245 | 246 | static void unbf_channel_recv(struct chanfd *ch, void *data) 247 | { 248 | struct unbf_channel *uchan = &ch->chan.unbf; 249 | 250 | down(ch->receiver_fd); 251 | memcpy(data, to_ptr(ch->datap), ch->size); 252 | up(uchan->ack_fd); 253 | up(ch->sender_fd); 254 | } 255 | 256 | static inline void __inc(size_t *val, size_t max_elems) 257 | { 258 | *val += 1; 259 | if (*val == max_elems) 260 | *val = 0; 261 | } 262 | 263 | static inline void buff_channel_lock(struct chanfd *ch) 264 | { 265 | struct buff_channel *bchan = &ch->chan.buff; 266 | 267 | if (ch->max_elems != 1) 268 | ck_spinlock_lock(&bchan->lock); 269 | } 270 | 271 | static inline void buff_channel_unlock(struct chanfd *ch) 272 | { 273 | struct buff_channel *bchan = &ch->chan.buff; 274 | 275 | if (ch->max_elems != 1) 276 | ck_spinlock_unlock(&bchan->lock); 277 | } 278 | 279 | static void buff_channel_recv(struct chanfd *ch, void *data) 280 | { 281 | struct buff_channel *bchan = &ch->chan.buff; 282 | 283 | down(ch->receiver_fd); 284 | 285 | buff_channel_lock(ch); 286 | memcpy(data, to_ptr(ch->datap) + bchan->out * ch->size, ch->size); 287 | __inc(&bchan->out, ch->max_elems); 288 | buff_channel_unlock(ch); 289 | 290 | up(ch->sender_fd); 291 | } 292 | 293 | /** 294 | * chanfd_recv - receive data from channel 295 | * @ch: channel to receive from 296 | * @data: pointer to where the data should be copied to 297 | * 298 | * Blocks until there is data in the channel. The amount of bytes copied 299 | * to @data is determined upon channel creation with chanfd_create(). 300 | * 301 | * See also: chanfd_receiver_fd() 302 | */ 303 | void chanfd_recv(struct chanfd *ch, void *data) 304 | { 305 | if (is_buffered(ch)) 306 | buff_channel_recv(ch, data); 307 | else 308 | unbf_channel_recv(ch, data); 309 | ck_pr_dec_64(&ch->elems); 310 | ck_pr_fence_store(); 311 | } 312 | 313 | static void unbf_channel_send(struct chanfd *ch, const void *data) 314 | { 315 | struct unbf_channel *uchan = &ch->chan.unbf; 316 | 317 | down(ch->sender_fd); 318 | memcpy(to_ptr(ch->datap), data, ch->size); 319 | up(ch->receiver_fd); 320 | down(uchan->ack_fd); 321 | } 322 | 323 | static void buff_channel_send(struct chanfd *ch, const void *data) 324 | { 325 | struct buff_channel *bchan = &ch->chan.buff; 326 | 327 | down(ch->sender_fd); 328 | 329 | buff_channel_lock(ch); 330 | memcpy(to_ptr(ch->datap) + bchan->in * ch->size, data, ch->size); 331 | __inc(&bchan->in, ch->max_elems); 332 | buff_channel_unlock(ch); 333 | 334 | up(ch->receiver_fd); 335 | } 336 | 337 | /** 338 | * chanfd_is_empty - check whether a channel is empty 339 | * @chan: channel to be checked 340 | * 341 | * Performs a fast, lockless peek at a channel to see whether any data has 342 | * been sent through it. 343 | * 344 | * An alternative is to perform a select()/poll()/epoll() call on the 345 | * channel's file descriptor. This is more powerful (e.g. timeout, ability 346 | * to wait on several channels) but is significantly slower (syscall + locks 347 | * within the kernel). 348 | * 349 | * Returns true when no data has been sent through the channel, false otherwise. 350 | */ 351 | bool chanfd_is_empty(struct chanfd *chan) 352 | { 353 | ck_pr_fence_load(); 354 | return !ck_pr_load_64(&chan->elems); 355 | } 356 | 357 | /** 358 | * chanfd_send - send data to channel 359 | * @ch: channel to send to 360 | * @data: pointer to where the data should be copied from 361 | * 362 | * This function behaves differently depending on whether @ch is buffered 363 | * or not; this is determined upon channel creation with chanfd_create(). 364 | * 365 | * When sending on a buffered channel, the sender blocks only until its data 366 | * can be copied to the buffer. When sending on an unbuffered channel, the 367 | * sender will block until its data has been received with chanfd_recv(). 368 | * 369 | * Returns 0 on success; -1 on error, setting errno appropriately. 370 | * 371 | * See also: chanfd_sender_fd() 372 | */ 373 | void chanfd_send(struct chanfd *ch, const void *data) 374 | { 375 | if (is_buffered(ch)) 376 | buff_channel_send(ch, data); 377 | else 378 | unbf_channel_send(ch, data); 379 | ck_pr_inc_64(&ch->elems); 380 | ck_pr_fence_store(); 381 | } 382 | --------------------------------------------------------------------------------