Skip to content

Commit 050238a

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 050238a

File tree

3 files changed

+310
-0
lines changed

3 files changed

+310
-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: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
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 <pthread.h>
52+
#include <stdio.h>
53+
#include <stdlib.h>
54+
#include <unistd.h>
55+
56+
#ifdef __CHECKER__
57+
#define __rcu __attribute__((noderef, address_space(__rcu)))
58+
#define rcu_check_sparse(p, space) ((void) (((typeof(*p) space *) p) == p))
59+
#define __force __attribute__((force))
60+
#define rcu_uncheck(p) ((__typeof__(*p) __force *) p)
61+
#define rcu_check(p) ((__typeof__(*p) __force __rcu *) p)
62+
#else
63+
#define __rcu
64+
#define rcu_check_sparse(p, space)
65+
#define __force
66+
#define rcu_uncheck(p) p
67+
#define rcu_check(p) p
68+
#endif /* __CHECKER__ */
69+
70+
/* Avoid false sharing */
71+
#define __rcu_aligned __attribute__((aligned(128)))
72+
73+
struct rcu_node {
74+
unsigned int tid;
75+
int rcu_nesting[2];
76+
struct rcu_node *next;
77+
} __rcu_aligned;
78+
79+
struct rcu_data {
80+
unsigned int nr_thread;
81+
struct rcu_node *head;
82+
unsigned int rcu_thrd_nesting_idx;
83+
spinlock_t sp;
84+
};
85+
86+
/* Easy to use */
87+
#define __rcu_thrd_idx rcu_data.rcu_thrd_nesting_idx
88+
#define __rcu_thrd_nesting(ptr) \
89+
ptr->rcu_nesting[READ_ONCE(__rcu_thrd_idx) & 0x01]
90+
#define rcu_thrd_nesting __rcu_thrd_nesting(__rcu_per_thrd_ptr)
91+
92+
static struct rcu_data rcu_data = {.nr_thread = 0,
93+
.head = NULL,
94+
.rcu_thrd_nesting_idx = 0,
95+
.sp = SPINLOCK_INIT};
96+
static __thread struct rcu_node *__rcu_per_thrd_ptr;
97+
98+
static __inline__ struct rcu_node *__rcu_node_add(unsigned int tid)
99+
{
100+
struct rcu_node **indirect = &rcu_data.head;
101+
struct rcu_node *node;
102+
103+
node = (struct rcu_node *) malloc(sizeof(struct rcu_node));
104+
if (!node) {
105+
fprintf(stderr, "__rcu_node_add: malloc failed\n");
106+
abort();
107+
}
108+
109+
node->tid = tid;
110+
node->rcu_nesting[0] = 0;
111+
node->rcu_nesting[1] = 0;
112+
node->next = NULL;
113+
114+
spin_lock(&rcu_data.sp);
115+
116+
while (*indirect) {
117+
if ((*indirect)->tid == node->tid) {
118+
spin_unlock(&rcu_data.sp);
119+
free(node);
120+
return NULL;
121+
}
122+
indirect = &(*indirect)->next;
123+
}
124+
125+
*indirect = node;
126+
rcu_data.nr_thread++;
127+
128+
spin_unlock(&rcu_data.sp);
129+
130+
smp_mb();
131+
132+
return node;
133+
}
134+
135+
static __inline__ int rcu_init(void)
136+
{
137+
unsigned int tid = current_tid();
138+
139+
__rcu_per_thrd_ptr = __rcu_node_add(tid);
140+
141+
return (__rcu_per_thrd_ptr == NULL) ? -ENOMEM : 0;
142+
}
143+
144+
static __inline__ void rcu_clean(void)
145+
{
146+
struct rcu_node *node, *tmp;
147+
148+
spin_lock(&rcu_data.sp);
149+
150+
for (node = rcu_data.head; node != NULL; node = tmp) {
151+
tmp = node->next;
152+
free(node);
153+
}
154+
155+
rcu_data.head = NULL;
156+
rcu_data.nr_thread = 0;
157+
158+
spin_unlock(&rcu_data.sp);
159+
}
160+
161+
/* The per-thread reference count will only modified by their owner
162+
* thread but will read by other threads. So here we use WRITE_ONCE().
163+
*/
164+
static __inline__ void rcu_read_lock(void)
165+
{
166+
WRITE_ONCE(rcu_thrd_nesting, 1);
167+
}
168+
169+
static __inline__ void rcu_read_unlock(void)
170+
{
171+
WRITE_ONCE(rcu_thrd_nesting, 0);
172+
}
173+
174+
static __inline__ void synchronize_rcu(void)
175+
{
176+
struct rcu_node *node;
177+
178+
smp_mb();
179+
180+
spin_lock(&rcu_data.sp);
181+
182+
/* When the rcu_thrd_nesting is odd, which means that the LSB set 1,
183+
* that thread is in the read-side critical section. Also, we need
184+
* to skip the read side when it is in the new grace period.
185+
*/
186+
for (node = rcu_data.head; node != NULL; node = node->next) {
187+
while (READ_ONCE(__rcu_thrd_nesting(node)) & 0x1) {
188+
barrier();
189+
}
190+
}
191+
192+
/* Going to next grace period */
193+
__atomic_fetch_add(&__rcu_thrd_idx, 1, __ATOMIC_RELEASE);
194+
195+
spin_unlock(&rcu_data.sp);
196+
197+
smp_mb();
198+
}
199+
200+
#define rcu_dereference(p) \
201+
({ \
202+
__typeof__(*p) *__r_d_p = (__typeof__(*p) __force *) READ_ONCE(p); \
203+
rcu_check_sparse(p, __rcu); \
204+
__r_d_p; \
205+
})
206+
207+
#define rcu_assign_pointer(p, v) \
208+
({ \
209+
__typeof__(*p) *__r_a_p = \
210+
(__typeof__(*p) __force *) __atomic_exchange_n( \
211+
&(p), (__typeof__(*(p)) __force __rcu *) v, __ATOMIC_RELEASE); \
212+
rcu_check_sparse(p, __rcu); \
213+
__r_a_p; \
214+
})
215+
216+
#endif /* __THRD_RCU_H__ */

0 commit comments

Comments
 (0)