diff options
author | Glenn Elliott <gelliott@cs.unc.edu> | 2014-10-17 23:06:24 -0400 |
---|---|---|
committer | Glenn Elliott <gelliott@cs.unc.edu> | 2014-10-17 23:09:30 -0400 |
commit | 9214af4655bbb5b32621ad7495223a24967d0ee3 (patch) | |
tree | cce18f1e0abefef4fb492e30f2f64708daae43cc | |
parent | 123cac6d078cfd19ded5ae0342b12dd15c1d2254 (diff) |
Add queuelock_t, an MCS lock implementation.
This patch adds a queuelock_t spinlock. This lock
is a bit more efficient on systems with large
distributed caches (less bouncing of lock
cache lines). The ticketlock_t is remains
the default spinlock choice, however.
-rw-r--r-- | include/condvar.h | 14 | ||||
-rw-r--r-- | include/config.h | 14 | ||||
-rw-r--r-- | include/interrupts.h | 17 | ||||
-rw-r--r-- | include/queuelock.h | 184 | ||||
-rw-r--r-- | include/spinlock.h | 89 | ||||
-rw-r--r-- | include/ticketlock.h | 13 | ||||
-rw-r--r-- | src/pgm.cpp | 20 |
7 files changed, 323 insertions, 28 deletions
diff --git a/include/condvar.h b/include/condvar.h index cba7678..8ee3859 100644 --- a/include/condvar.h +++ b/include/condvar.h | |||
@@ -6,7 +6,7 @@ | |||
6 | #include <sys/syscall.h> | 6 | #include <sys/syscall.h> |
7 | #include <linux/futex.h> | 7 | #include <linux/futex.h> |
8 | 8 | ||
9 | #include "ticketlock.h" | 9 | #include "spinlock.h" |
10 | 10 | ||
11 | typedef int seq_t; | 11 | typedef int seq_t; |
12 | 12 | ||
@@ -36,32 +36,32 @@ static inline void cv_init_shared(cv_t* cv) | |||
36 | *cv = CV_INITIALIZER_PUBLIC; | 36 | *cv = CV_INITIALIZER_PUBLIC; |
37 | } | 37 | } |
38 | 38 | ||
39 | static inline int cv_wait(cv_t* cv, ticketlock_t* l) | 39 | static inline int cv_wait(cv_t* cv, spinlock_t* l) |
40 | { | 40 | { |
41 | int ret; | 41 | int ret; |
42 | int wait_mode = (cv->mode == CV_PRIVATE) ? FUTEX_WAIT_PRIVATE : FUTEX_WAIT; | 42 | int wait_mode = (cv->mode == CV_PRIVATE) ? FUTEX_WAIT_PRIVATE : FUTEX_WAIT; |
43 | seq_t seq = cv->seq; | 43 | seq_t seq = cv->seq; |
44 | 44 | ||
45 | ++cv->waiters; | 45 | ++cv->waiters; |
46 | tl_unlock(l); /* memory barrier */ | 46 | spin_unlock(l); /* memory barrier */ |
47 | ret = syscall(SYS_futex, &cv->seq, wait_mode, seq, NULL, NULL, 0); | 47 | ret = syscall(SYS_futex, &cv->seq, wait_mode, seq, NULL, NULL, 0); |
48 | tl_lock(l); | 48 | spin_lock(l); |
49 | --cv->waiters; | 49 | --cv->waiters; |
50 | 50 | ||
51 | return ret; | 51 | return ret; |
52 | } | 52 | } |
53 | 53 | ||
54 | #ifndef PGM_PREEMPTIVE | 54 | #ifndef PGM_PREEMPTIVE |
55 | static inline int cv_wait_np(cv_t* cv, ticketlock_t* l, unsigned long *flags) | 55 | static inline int cv_wait_np(cv_t* cv, spinlock_t* l, unsigned long *flags) |
56 | { | 56 | { |
57 | int ret; | 57 | int ret; |
58 | int wait_mode = (cv->mode == CV_PRIVATE) ? FUTEX_WAIT_PRIVATE : FUTEX_WAIT; | 58 | int wait_mode = (cv->mode == CV_PRIVATE) ? FUTEX_WAIT_PRIVATE : FUTEX_WAIT; |
59 | seq_t seq = cv->seq; | 59 | seq_t seq = cv->seq; |
60 | 60 | ||
61 | ++cv->waiters; | 61 | ++cv->waiters; |
62 | tl_unlock_np(l, *flags); /* memory barrier */ | 62 | spin_unlock_np(l, *flags); /* memory barrier */ |
63 | ret = syscall(SYS_futex, &cv->seq, wait_mode, seq, NULL, NULL, 0); | 63 | ret = syscall(SYS_futex, &cv->seq, wait_mode, seq, NULL, NULL, 0); |
64 | tl_lock_np(l, flags); | 64 | spin_lock_np(l, flags); |
65 | --cv->waiters; | 65 | --cv->waiters; |
66 | 66 | ||
67 | return ret; | 67 | return ret; |
diff --git a/include/config.h b/include/config.h index af41d42..41bfdb4 100644 --- a/include/config.h +++ b/include/config.h | |||
@@ -22,6 +22,18 @@ | |||
22 | #endif | 22 | #endif |
23 | 23 | ||
24 | /* | 24 | /* |
25 | Select the spinlock type. ticketlock_t is simple, | ||
26 | but queuelock_t is more cache efficient on systems | ||
27 | with large distributed caches (less bouncing of | ||
28 | cache lines). | ||
29 | - ticketlock_t = 0 | ||
30 | - queuelock_t = 1 | ||
31 | */ | ||
32 | #if (PGM_SYNC_METHOD == 1) | ||
33 | #define PGM_SPINLOCK_TYPE 0 | ||
34 | #endif | ||
35 | |||
36 | /* | ||
25 | Select the method tasks use for becoming non-preemptive | 37 | Select the method tasks use for becoming non-preemptive |
26 | while holding spinlocks. | 38 | while holding spinlocks. |
27 | - To allow preemption = 0 | 39 | - To allow preemption = 0 |
@@ -32,7 +44,7 @@ | |||
32 | 1) Options 1 and 2 require tasks to run as root. | 44 | 1) Options 1 and 2 require tasks to run as root. |
33 | 2) Disabling interrupts may be unsafe for IPCs | 45 | 2) Disabling interrupts may be unsafe for IPCs |
34 | that may block on write. (PGM uses non-blocking | 46 | that may block on write. (PGM uses non-blocking |
35 | writes whenever possible.) | 47 | writes whenever possible.) |
36 | */ | 48 | */ |
37 | #ifndef _USE_LITMUS | 49 | #ifndef _USE_LITMUS |
38 | #define PGM_NP_METHOD 0 | 50 | #define PGM_NP_METHOD 0 |
diff --git a/include/interrupts.h b/include/interrupts.h new file mode 100644 index 0000000..abd5346 --- /dev/null +++ b/include/interrupts.h | |||
@@ -0,0 +1,17 @@ | |||
1 | // Copyright (c) 2014, Glenn Elliott | ||
2 | // All rights reserved. | ||
3 | |||
4 | #pragma once | ||
5 | |||
6 | static inline unsigned long save_flags(void) | ||
7 | { | ||
8 | unsigned long flags; | ||
9 | asm volatile("pushf ; pop %0" : "=rm" (flags) : :"memory"); | ||
10 | return flags; | ||
11 | } | ||
12 | |||
13 | static inline void restore_flags(unsigned long flags) | ||
14 | { | ||
15 | asm volatile("push %0 ; popf" : : "g" (flags) : "memory", "cc"); | ||
16 | } | ||
17 | |||
diff --git a/include/queuelock.h b/include/queuelock.h new file mode 100644 index 0000000..1eab9a3 --- /dev/null +++ b/include/queuelock.h | |||
@@ -0,0 +1,184 @@ | |||
1 | // Copyright (c) 2014, Glenn Elliott | ||
2 | // All rights reserved. | ||
3 | |||
4 | // An implementation of an MCS (Mellor-Crummey and Scott) spinlock | ||
5 | |||
6 | #pragma once | ||
7 | |||
8 | #include <stdint.h> | ||
9 | |||
10 | #if defined(PGM_NP_INTERRUPTS) | ||
11 | #include <sys/io.h> | ||
12 | #endif | ||
13 | |||
14 | #if defined(PGM_NP_LITMUS) | ||
15 | #include "litmus.h" | ||
16 | #endif | ||
17 | |||
18 | #include "atomic.h" | ||
19 | #include "interrupts.h" | ||
20 | |||
21 | #if ULONG_MAX == 0xffffffffffffffffUL | ||
22 | typedef uint64_t ptr_t; | ||
23 | #else | ||
24 | typedef uint32_t ptr_t; | ||
25 | #endif | ||
26 | |||
27 | typedef struct __queuenode | ||
28 | { | ||
29 | volatile struct __queuenode* next; | ||
30 | volatile int blocked; | ||
31 | } queuenode_t; | ||
32 | |||
33 | typedef struct | ||
34 | { | ||
35 | ptr_t tail; | ||
36 | } queuelock_t; | ||
37 | |||
38 | static const queuelock_t QUEUELOCK_INITIALIZER = {.tail = 0}; | ||
39 | |||
40 | static inline void ql_init(queuelock_t* q) | ||
41 | { | ||
42 | *q = QUEUELOCK_INITIALIZER; | ||
43 | } | ||
44 | |||
45 | static inline void __ql_lock(queuelock_t* q, queuenode_t* self) | ||
46 | { | ||
47 | queuenode_t* prev; | ||
48 | |||
49 | /* prepare for enqueue */ | ||
50 | self->next = NULL; | ||
51 | self->blocked = 1; | ||
52 | |||
53 | /* enqueue at the end of the line */ | ||
54 | do | ||
55 | { | ||
56 | prev = (struct __queuenode*)((volatile ptr_t) q->tail); | ||
57 | } while(__sync_bool_compare_and_swap(&q->tail, (ptr_t)prev, (ptr_t)self)); | ||
58 | |||
59 | /* was there someone ahead of us? */ | ||
60 | if (prev) | ||
61 | { | ||
62 | /* tell the one ahead of us about ourselves */ | ||
63 | prev->next = self; | ||
64 | |||
65 | /* wait until unlock */ | ||
66 | while(self->blocked) | ||
67 | { | ||
68 | __sync_pause(); | ||
69 | } | ||
70 | } | ||
71 | |||
72 | /* make sure nothing leaks out (upward) of the critical section */ | ||
73 | __sync_synchronize(); | ||
74 | } | ||
75 | |||
76 | static inline void ql_lock(queuelock_t* q, queuenode_t* self) | ||
77 | { | ||
78 | __ql_lock(q, self); | ||
79 | } | ||
80 | |||
81 | static inline void __ql_unlock(queuelock_t* q, queuenode_t* self) | ||
82 | { | ||
83 | /* make sure nothing leaks out (downward) of the critical section */ | ||
84 | __sync_synchronize(); | ||
85 | |||
86 | /* try to dequeue from the end of the line */ | ||
87 | if (!self->next && __sync_bool_compare_and_swap(&q->tail, (ptr_t)self, (ptr_t)0)) | ||
88 | { | ||
89 | /* we really weren't at the end of the line! */ | ||
90 | |||
91 | /* wait until the one behind us tells us where they are */ | ||
92 | while(!self->next) | ||
93 | { | ||
94 | __sync_pause(); | ||
95 | } | ||
96 | } | ||
97 | |||
98 | /* tell the next one behind us that they may go */ | ||
99 | if(self->next) | ||
100 | { | ||
101 | /* unlock */ | ||
102 | self->next->blocked = 0; | ||
103 | } | ||
104 | } | ||
105 | |||
106 | static inline void ql_unlock(queuelock_t* q, queuenode_t* self) | ||
107 | { | ||
108 | __ql_unlock(q, self); | ||
109 | } | ||
110 | |||
111 | #ifndef PGM_PREEMPTIVE | ||
112 | |||
113 | static inline void ql_init_np(queuelock_t* q) | ||
114 | { | ||
115 | ql_init(q); | ||
116 | |||
117 | #if defined(PGM_NP_INTERRUPTS) | ||
118 | iopl(3); | ||
119 | #endif | ||
120 | } | ||
121 | |||
122 | static inline void ql_lock_np(queuelock_t* q, queuenode_t* self, unsigned long* flags) | ||
123 | { | ||
124 | #if defined(PGM_NP_INTERRUPTS) | ||
125 | /* save flags and disable interrupts */ | ||
126 | *flags = save_flags(); | ||
127 | asm volatile("cli": : :"memory"); | ||
128 | #elif defined(PGM_NP_LITMUS) | ||
129 | /* start non-preemption */ | ||
130 | __sync_synchronize(); | ||
131 | enter_np(); | ||
132 | __sync_synchronize(); | ||
133 | #endif | ||
134 | |||
135 | __ql_lock(q, self); | ||
136 | } | ||
137 | |||
138 | static inline void ql_unlock_np(queuelock_t* q, queuenode_t* self, unsigned long flags) | ||
139 | { | ||
140 | __ql_unlock(q, self); | ||
141 | |||
142 | #if defined(PGM_NP_INTERRUPTS) | ||
143 | /* re-enable flags before we exit */ | ||
144 | restore_flags(flags); | ||
145 | #elif defined(PGM_NP_LITMUS) | ||
146 | /* end non-preemption */ | ||
147 | __sync_synchronize(); | ||
148 | exit_np(); | ||
149 | __sync_synchronize(); | ||
150 | #endif | ||
151 | } | ||
152 | #endif | ||
153 | |||
154 | /* | ||
155 | Carrying around the queuenode_t is a pain for simple non-nested | ||
156 | critical sections. These varients of lock/unlock just puts the | ||
157 | wait node in thread-local storage. At most one non-nest queue | ||
158 | lock calll may be nested within other critical sections. | ||
159 | */ | ||
160 | static inline void ql_lock_no_nest(queuelock_t* q) | ||
161 | { | ||
162 | extern __thread queuenode_t thread_qnode; | ||
163 | ql_lock(q, &thread_qnode); | ||
164 | } | ||
165 | |||
166 | static inline void ql_unlock_no_nest(queuelock_t* q) | ||
167 | { | ||
168 | extern __thread queuenode_t thread_qnode; | ||
169 | ql_unlock(q, &thread_qnode); | ||
170 | } | ||
171 | |||
172 | #ifndef PGM_PREEMPTIVE | ||
173 | static inline void ql_lock_no_nest_np(queuelock_t* q, unsigned long* flags) | ||
174 | { | ||
175 | extern __thread queuenode_t thread_qnode; | ||
176 | ql_lock_np(q, &thread_qnode, flags); | ||
177 | } | ||
178 | |||
179 | static inline void ql_unlock_no_nest_np(queuelock_t* q, unsigned long flags) | ||
180 | { | ||
181 | extern __thread queuenode_t thread_qnode; | ||
182 | ql_unlock_np(q, &thread_qnode, flags); | ||
183 | } | ||
184 | #endif | ||
diff --git a/include/spinlock.h b/include/spinlock.h new file mode 100644 index 0000000..7220353 --- /dev/null +++ b/include/spinlock.h | |||
@@ -0,0 +1,89 @@ | |||
1 | // Copyright (c) 2014, Glenn Elliott | ||
2 | // All rights reserved. | ||
3 | |||
4 | #pragma once | ||
5 | |||
6 | #include "config.h" | ||
7 | |||
8 | #if PGM_SYNC_METHOD == 1 | ||
9 | |||
10 | #if PGM_SPINLOCK_TYPE == 0 /* ticket lock */ | ||
11 | #include "ticketlock.h" | ||
12 | typedef ticketlock_t spinlock_t; | ||
13 | static const spinlock_t PGM_SPINLOCK_INITIALIZER = TICKETLOCK_INITIALIZER; | ||
14 | |||
15 | static inline void spin_init(spinlock_t* l) | ||
16 | { | ||
17 | tl_init(l); | ||
18 | } | ||
19 | |||
20 | static inline void spin_lock(spinlock_t* l) | ||
21 | { | ||
22 | tl_lock(l); | ||
23 | } | ||
24 | |||
25 | static inline void spin_unlock(spinlock_t* l) | ||
26 | { | ||
27 | tl_unlock(l); | ||
28 | } | ||
29 | |||
30 | #ifndef PGM_PREEMPTIVE | ||
31 | static inline void spin_init_np(spinlock_t* l) | ||
32 | { | ||
33 | tl_init_np(l); | ||
34 | } | ||
35 | |||
36 | static inline void spin_lock_np(spinlock_t* l, long unsigned int* flags) | ||
37 | { | ||
38 | tl_lock_np(l, flags); | ||
39 | } | ||
40 | |||
41 | static inline void spin_unlock_np(spinlock_t* l, long unsigned int flags) | ||
42 | { | ||
43 | tl_unlock_np(l, flags); | ||
44 | } | ||
45 | #endif /* end !PGM_PREEMPTIVE */ | ||
46 | |||
47 | #elif PGM_SPINLOCK_TYPE == 1 /* queue lock */ | ||
48 | |||
49 | #include "queuelock.h" | ||
50 | typedef queuelock_t spinlock_t; | ||
51 | static const spinlock_t PGM_SPINLOCK_INITIALIZER = QUEUELOCK_INITIALIZER; | ||
52 | |||
53 | static inline void spin_init(spinlock_t* l) | ||
54 | { | ||
55 | ql_init(l); | ||
56 | } | ||
57 | |||
58 | static inline void spin_lock(spinlock_t* l) | ||
59 | { | ||
60 | ql_lock_no_nest(l); | ||
61 | } | ||
62 | |||
63 | static inline void spin_unlock(spinlock_t* l) | ||
64 | { | ||
65 | ql_unlock_no_nest(l); | ||
66 | } | ||
67 | |||
68 | #ifndef PGM_PREEMPTIVE | ||
69 | static inline void spin_init_np(spinlock_t* l) | ||
70 | { | ||
71 | ql_init_np(l); | ||
72 | } | ||
73 | |||
74 | static inline void spin_lock_np(spinlock_t* l, long unsigned int* flags) | ||
75 | { | ||
76 | ql_lock_no_nest_np(l, flags); | ||
77 | } | ||
78 | |||
79 | static inline void spin_unlock_np(spinlock_t* l, long unsigned int flags) | ||
80 | { | ||
81 | ql_unlock_no_nest_np(l, flags); | ||
82 | } | ||
83 | #endif /* end !PGM_PREEMPTIVE */ | ||
84 | |||
85 | #else | ||
86 | #error "Unknown spinlock configuration!" | ||
87 | #endif | ||
88 | |||
89 | #endif /* end PGM_SYNC_METHOD == 1 */ | ||
diff --git a/include/ticketlock.h b/include/ticketlock.h index 4c1035c..faef7d9 100644 --- a/include/ticketlock.h +++ b/include/ticketlock.h | |||
@@ -14,6 +14,7 @@ | |||
14 | #endif | 14 | #endif |
15 | 15 | ||
16 | #include "atomic.h" | 16 | #include "atomic.h" |
17 | #include "interrupts.h" | ||
17 | 18 | ||
18 | #if ULONG_MAX == 0xffffffffffffffffUL | 19 | #if ULONG_MAX == 0xffffffffffffffffUL |
19 | typedef unsigned long long ticketdata_t; | 20 | typedef unsigned long long ticketdata_t; |
@@ -77,18 +78,6 @@ static inline void tl_init_np(ticketlock_t *t) | |||
77 | #endif | 78 | #endif |
78 | } | 79 | } |
79 | 80 | ||
80 | static inline unsigned long save_flags(void) | ||
81 | { | ||
82 | unsigned long flags; | ||
83 | asm volatile("pushf ; pop %0" : "=rm" (flags) : :"memory"); | ||
84 | return flags; | ||
85 | } | ||
86 | |||
87 | static inline void restore_flags(unsigned long flags) | ||
88 | { | ||
89 | asm volatile("push %0 ; popf" : : "g" (flags) : "memory", "cc"); | ||
90 | } | ||
91 | |||
92 | static inline void tl_lock_np(ticketlock_t* t, unsigned long* flags) | 81 | static inline void tl_lock_np(ticketlock_t* t, unsigned long* flags) |
93 | { | 82 | { |
94 | #if defined(PGM_NP_INTERRUPTS) | 83 | #if defined(PGM_NP_INTERRUPTS) |
diff --git a/src/pgm.cpp b/src/pgm.cpp index 122775e..ff6c2f9 100644 --- a/src/pgm.cpp +++ b/src/pgm.cpp | |||
@@ -33,10 +33,14 @@ | |||
33 | typedef pthread_mutex_t pgm_lock_t; | 33 | typedef pthread_mutex_t pgm_lock_t; |
34 | typedef pthread_cond_t pgm_cv_t; | 34 | typedef pthread_cond_t pgm_cv_t; |
35 | #elif defined(PGM_USE_PGM_SYNC) | 35 | #elif defined(PGM_USE_PGM_SYNC) |
36 | #include "ticketlock.h" | 36 | #include "spinlock.h" |
37 | #include "condvar.h" | 37 | #include "condvar.h" |
38 | typedef ticketlock_t pgm_lock_t; | 38 | typedef spinlock_t pgm_lock_t; |
39 | typedef cv_t pgm_cv_t; | 39 | typedef cv_t pgm_cv_t; |
40 | |||
41 | #if PGM_SPINLOCK_TYPE == 1 | ||
42 | __thread queuenode_t thread_qnode; | ||
43 | #endif | ||
40 | #endif | 44 | #endif |
41 | 45 | ||
42 | #include "ring.h" | 46 | #include "ring.h" |
@@ -1420,13 +1424,13 @@ out: | |||
1420 | #elif defined(PGM_USE_PGM_SYNC) | 1424 | #elif defined(PGM_USE_PGM_SYNC) |
1421 | 1425 | ||
1422 | #ifdef PGM_PREEMPTIVE | 1426 | #ifdef PGM_PREEMPTIVE |
1423 | #define pgm_lock_init(l) tl_init((l)) | 1427 | #define pgm_lock_init(l) spin_init((l)) |
1424 | #define pgm_lock(l, flags) do { ((void)(flags)); tl_lock((l)); } while(0) | 1428 | #define pgm_lock(l, flags) do { ((void)(flags)); spin_lock((l)); } while(0) |
1425 | #define pgm_unlock(l, flags) do { ((void)(flags)); tl_unlock((l)); } while(0) | 1429 | #define pgm_unlock(l, flags) do { ((void)(flags)); spin_unlock((l)); } while(0) |
1426 | #else | 1430 | #else |
1427 | #define pgm_lock_init(l) tl_init_np((l)) | 1431 | #define pgm_lock_init(l) spin_init_np((l)) |
1428 | #define pgm_lock(l, flags) tl_lock_np((l), &(flags)) | 1432 | #define pgm_lock(l, flags) spin_lock_np((l), &(flags)) |
1429 | #define pgm_unlock(l, flags) tl_unlock_np((l), (flags)) | 1433 | #define pgm_unlock(l, flags) spin_unlock_np((l), (flags)) |
1430 | #endif | 1434 | #endif |
1431 | #define pgm_lock_destroy(l) (void)(l) | 1435 | #define pgm_lock_destroy(l) (void)(l) |
1432 | 1436 | ||