Skip to content

Commit b67ba09

Browse files
committed
Add thread-based RCU
Add the Linux Kernel style thread-based simple RCU. It supports sparse checking [1]. With sparse, it will report: main.c: note: in included file: thrd_rcu.h:95:42: warning: Using plain integer as NULL pointer thrd_rcu.h:95:42: warning: Using plain integer as NULL pointer It is from the pthread_mutex_t initializer, we can ignore it. [1] https://www.kernel.org/doc/html/latest/dev-tools/sparse.html
1 parent 5737c46 commit b67ba09

File tree

3 files changed

+309
-0
lines changed

3 files changed

+309
-0
lines changed

thrd_rcu/Makefile

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
CFLAGS = -Wall
2+
CFLAGS += -g
3+
CFLAGS += -D'READER_NUM=10'
4+
CFLAGS += -D'UPDATER_NUM=1'
5+
CFLAGS += -lpthread
6+
7+
all:
8+
$(CC) -o main main.c $(CFLAGS)
9+
10+
# Semantic Checker
11+
# https://www.kernel.org/doc/html/latest/dev-tools/sparse.html
12+
sparse:
13+
sparse main.c $(CFLAGS)
14+
15+
indent:
16+
clang-format -i *.[ch]
17+
18+
clean:
19+
rm -f main

thrd_rcu/main.c

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
#include <pthread.h>
2+
#include <stdio.h>
3+
#include <stdlib.h>
4+
5+
#include "thrd_rcu.h"
6+
7+
struct test {
8+
unsigned int count;
9+
};
10+
11+
static struct test __rcu *foo;
12+
13+
static void *reader_side(void *argv)
14+
{
15+
struct test __allow_unused *tmp;
16+
17+
if (rcu_init())
18+
abort();
19+
20+
rcu_read_lock();
21+
22+
tmp = rcu_dereference(foo);
23+
24+
printf("[reader %u] %u\n", current_tid(), tmp->count);
25+
26+
rcu_read_unlock();
27+
28+
pthread_exit(NULL);
29+
}
30+
31+
static void *updater_side(void *argv)
32+
{
33+
struct test *oldp;
34+
struct test *newval = (struct test *) malloc(sizeof(struct test));
35+
newval->count = current_tid();
36+
37+
printf("[updater %u]\n", newval->count);
38+
39+
oldp = rcu_assign_pointer(foo, newval);
40+
41+
synchronize_rcu();
42+
free(oldp);
43+
44+
pthread_exit(NULL);
45+
}
46+
47+
int main(int argc, char *argv[])
48+
{
49+
pthread_t reader[READER_NUM];
50+
pthread_t updater[UPDATER_NUM];
51+
int i;
52+
53+
foo = (struct test __rcu *) malloc(sizeof(struct test));
54+
rcu_uncheck(foo)->count = 0;
55+
56+
for (i = 0; i < READER_NUM / 2; i++)
57+
pthread_create(&reader[i], NULL, reader_side, NULL);
58+
59+
for (i = 0; i < UPDATER_NUM; i++)
60+
pthread_create(&updater[i], NULL, updater_side, NULL);
61+
62+
for (i = READER_NUM / 2; i < READER_NUM; i++)
63+
pthread_create(&reader[i], NULL, reader_side, NULL);
64+
65+
for (i = 0; i < READER_NUM; i++)
66+
pthread_join(reader[i], NULL);
67+
68+
for (i = 0; i < UPDATER_NUM; i++)
69+
pthread_join(updater[i], NULL);
70+
71+
free(rcu_uncheck(foo));
72+
rcu_clean();
73+
74+
return 0;
75+
}

thrd_rcu/thrd_rcu.h

Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
#ifndef __THRD_RCU_H__
2+
#define __THRD_RCU_H__
3+
4+
/* lock primitives from pthread and compiler primitives */
5+
6+
#include <pthread.h>
7+
8+
typedef pthread_mutex_t spinlock_t;
9+
10+
#define SPINLOCK_INIT PTHREAD_MUTEX_INITIALIZER
11+
12+
static __inline__ void spin_lock(spinlock_t *sp)
13+
{
14+
int ret;
15+
16+
ret = pthread_mutex_lock(sp);
17+
if (ret != 0) {
18+
fprintf(stderr, "spin_lock:pthread_mutex_lock %d\n", ret);
19+
abort();
20+
}
21+
}
22+
23+
static __inline__ void spin_unlock(spinlock_t *sp)
24+
{
25+
int ret;
26+
27+
ret = pthread_mutex_unlock(sp);
28+
if (ret != 0) {
29+
fprintf(stderr, "spin_unlock:pthread_mutex_unlock %d\n", ret);
30+
abort();
31+
}
32+
}
33+
34+
#define current_tid() (unsigned int) pthread_self()
35+
36+
#define ACCESS_ONCE(x) (*(volatile __typeof__(x) *) &(x))
37+
#define READ_ONCE(x) \
38+
({ \
39+
__typeof__(x) ___x = ACCESS_ONCE(x); \
40+
___x; \
41+
})
42+
#define WRITE_ONCE(x, val) \
43+
do { \
44+
ACCESS_ONCE(x) = (val); \
45+
} while (0)
46+
#define barrier() __asm__ __volatile__("" : : : "memory")
47+
#define __allow_unused __attribute__((unused))
48+
#define smp_mb() __asm__ __volatile__("mfence" : : : "memory")
49+
50+
#include <errno.h>
51+
#include <stdio.h>
52+
#include <stdlib.h>
53+
#include <unistd.h>
54+
55+
#ifdef __CHECKER__
56+
#define __rcu __attribute__((noderef, address_space(__rcu)))
57+
#define rcu_check_sparse(p, space) ((void) (((typeof(*p) space *) p) == p))
58+
#define __force __attribute__((force))
59+
#define rcu_uncheck(p) ((__typeof__(*p) __force *) p)
60+
#define rcu_check(p) ((__typeof__(*p) __force __rcu *) p)
61+
#else
62+
#define __rcu
63+
#define rcu_check_sparse(p, space)
64+
#define __force
65+
#define rcu_uncheck(p) p
66+
#define rcu_check(p) p
67+
#endif /* __CHECKER__ */
68+
69+
/* Avoid false sharing */
70+
#define __rcu_aligned __attribute__((aligned(128)))
71+
72+
struct rcu_node {
73+
unsigned int tid;
74+
int rcu_nesting[2];
75+
struct rcu_node *next;
76+
} __rcu_aligned;
77+
78+
struct rcu_data {
79+
unsigned int nr_thread;
80+
struct rcu_node *head;
81+
unsigned int rcu_thrd_nesting_idx;
82+
spinlock_t sp;
83+
};
84+
85+
/* Easy to use */
86+
#define __rcu_thrd_idx rcu_data.rcu_thrd_nesting_idx
87+
#define __rcu_thrd_nesting(ptr) \
88+
ptr->rcu_nesting[READ_ONCE(__rcu_thrd_idx) & 0x01]
89+
#define rcu_thrd_nesting __rcu_thrd_nesting(__rcu_per_thrd_ptr)
90+
91+
static struct rcu_data rcu_data = {.nr_thread = 0,
92+
.head = NULL,
93+
.rcu_thrd_nesting_idx = 0,
94+
.sp = SPINLOCK_INIT};
95+
static __thread struct rcu_node *__rcu_per_thrd_ptr;
96+
97+
static __inline__ struct rcu_node *__rcu_node_add(unsigned int tid)
98+
{
99+
struct rcu_node **indirect = &rcu_data.head;
100+
struct rcu_node *node;
101+
102+
node = (struct rcu_node *) malloc(sizeof(struct rcu_node));
103+
if (!node) {
104+
fprintf(stderr, "__rcu_node_add: malloc failed\n");
105+
abort();
106+
}
107+
108+
node->tid = tid;
109+
node->rcu_nesting[0] = 0;
110+
node->rcu_nesting[1] = 0;
111+
node->next = NULL;
112+
113+
spin_lock(&rcu_data.sp);
114+
115+
while (*indirect) {
116+
if ((*indirect)->tid == node->tid) {
117+
spin_unlock(&rcu_data.sp);
118+
free(node);
119+
return NULL;
120+
}
121+
indirect = &(*indirect)->next;
122+
}
123+
124+
*indirect = node;
125+
rcu_data.nr_thread++;
126+
127+
spin_unlock(&rcu_data.sp);
128+
129+
smp_mb();
130+
131+
return node;
132+
}
133+
134+
static __inline__ int rcu_init(void)
135+
{
136+
unsigned int tid = current_tid();
137+
138+
__rcu_per_thrd_ptr = __rcu_node_add(tid);
139+
140+
return (__rcu_per_thrd_ptr == NULL) ? -ENOMEM : 0;
141+
}
142+
143+
static __inline__ void rcu_clean(void)
144+
{
145+
struct rcu_node *node, *tmp;
146+
147+
spin_lock(&rcu_data.sp);
148+
149+
for (node = rcu_data.head; node != NULL; node = tmp) {
150+
tmp = node->next;
151+
free(node);
152+
}
153+
154+
rcu_data.head = NULL;
155+
rcu_data.nr_thread = 0;
156+
157+
spin_unlock(&rcu_data.sp);
158+
}
159+
160+
/* The per-thread reference count will only modified by their owner
161+
* thread but will read by other threads. So here we use WRITE_ONCE().
162+
*/
163+
static __inline__ void rcu_read_lock(void)
164+
{
165+
WRITE_ONCE(rcu_thrd_nesting, 1);
166+
}
167+
168+
static __inline__ void rcu_read_unlock(void)
169+
{
170+
WRITE_ONCE(rcu_thrd_nesting, 0);
171+
}
172+
173+
static __inline__ void synchronize_rcu(void)
174+
{
175+
struct rcu_node *node;
176+
177+
smp_mb();
178+
179+
spin_lock(&rcu_data.sp);
180+
181+
/* When the rcu_thrd_nesting is odd, which means that the LSB set 1,
182+
* that thread is in the read-side critical section. Also, we need
183+
* to skip the read side when it is in the new grace period.
184+
*/
185+
for (node = rcu_data.head; node != NULL; node = node->next) {
186+
while (READ_ONCE(__rcu_thrd_nesting(node)) & 0x1) {
187+
barrier();
188+
}
189+
}
190+
191+
/* Going to next grace period */
192+
__atomic_fetch_add(&__rcu_thrd_idx, 1, __ATOMIC_RELEASE);
193+
194+
spin_unlock(&rcu_data.sp);
195+
196+
smp_mb();
197+
}
198+
199+
#define rcu_dereference(p) \
200+
({ \
201+
__typeof__(*p) *__r_d_p = (__typeof__(*p) __force *) READ_ONCE(p); \
202+
rcu_check_sparse(p, __rcu); \
203+
__r_d_p; \
204+
})
205+
206+
#define rcu_assign_pointer(p, v) \
207+
({ \
208+
__typeof__(*p) *__r_a_p = \
209+
(__typeof__(*p) __force *) __atomic_exchange_n( \
210+
&(p), (__typeof__(*(p)) __force __rcu *) v, __ATOMIC_RELEASE); \
211+
rcu_check_sparse(p, __rcu); \
212+
__r_a_p; \
213+
})
214+
215+
#endif /* __THRD_RCU_H__ */

0 commit comments

Comments
 (0)