aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGlenn Elliott <gelliott@cs.unc.edu>2014-10-17 23:06:24 -0400
committerGlenn Elliott <gelliott@cs.unc.edu>2014-10-17 23:09:30 -0400
commit9214af4655bbb5b32621ad7495223a24967d0ee3 (patch)
treecce18f1e0abefef4fb492e30f2f64708daae43cc
parent123cac6d078cfd19ded5ae0342b12dd15c1d2254 (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.h14
-rw-r--r--include/config.h14
-rw-r--r--include/interrupts.h17
-rw-r--r--include/queuelock.h184
-rw-r--r--include/spinlock.h89
-rw-r--r--include/ticketlock.h13
-rw-r--r--src/pgm.cpp20
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
11typedef int seq_t; 11typedef 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
39static inline int cv_wait(cv_t* cv, ticketlock_t* l) 39static 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
55static inline int cv_wait_np(cv_t* cv, ticketlock_t* l, unsigned long *flags) 55static 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
6static 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
13static 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
22typedef uint64_t ptr_t;
23#else
24typedef uint32_t ptr_t;
25#endif
26
27typedef struct __queuenode
28{
29 volatile struct __queuenode* next;
30 volatile int blocked;
31} queuenode_t;
32
33typedef struct
34{
35 ptr_t tail;
36} queuelock_t;
37
38static const queuelock_t QUEUELOCK_INITIALIZER = {.tail = 0};
39
40static inline void ql_init(queuelock_t* q)
41{
42 *q = QUEUELOCK_INITIALIZER;
43}
44
45static 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
76static inline void ql_lock(queuelock_t* q, queuenode_t* self)
77{
78 __ql_lock(q, self);
79}
80
81static 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
106static inline void ql_unlock(queuelock_t* q, queuenode_t* self)
107{
108 __ql_unlock(q, self);
109}
110
111#ifndef PGM_PREEMPTIVE
112
113static 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
122static 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
138static 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*/
160static 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
166static 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
173static 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
179static 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"
12typedef ticketlock_t spinlock_t;
13static const spinlock_t PGM_SPINLOCK_INITIALIZER = TICKETLOCK_INITIALIZER;
14
15static inline void spin_init(spinlock_t* l)
16{
17 tl_init(l);
18}
19
20static inline void spin_lock(spinlock_t* l)
21{
22 tl_lock(l);
23}
24
25static inline void spin_unlock(spinlock_t* l)
26{
27 tl_unlock(l);
28}
29
30#ifndef PGM_PREEMPTIVE
31static inline void spin_init_np(spinlock_t* l)
32{
33 tl_init_np(l);
34}
35
36static inline void spin_lock_np(spinlock_t* l, long unsigned int* flags)
37{
38 tl_lock_np(l, flags);
39}
40
41static 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"
50typedef queuelock_t spinlock_t;
51static const spinlock_t PGM_SPINLOCK_INITIALIZER = QUEUELOCK_INITIALIZER;
52
53static inline void spin_init(spinlock_t* l)
54{
55 ql_init(l);
56}
57
58static inline void spin_lock(spinlock_t* l)
59{
60 ql_lock_no_nest(l);
61}
62
63static inline void spin_unlock(spinlock_t* l)
64{
65 ql_unlock_no_nest(l);
66}
67
68#ifndef PGM_PREEMPTIVE
69static inline void spin_init_np(spinlock_t* l)
70{
71 ql_init_np(l);
72}
73
74static inline void spin_lock_np(spinlock_t* l, long unsigned int* flags)
75{
76 ql_lock_no_nest_np(l, flags);
77}
78
79static 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
19typedef unsigned long long ticketdata_t; 20typedef 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
80static 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
87static inline void restore_flags(unsigned long flags)
88{
89 asm volatile("push %0 ; popf" : : "g" (flags) : "memory", "cc");
90}
91
92static inline void tl_lock_np(ticketlock_t* t, unsigned long* flags) 81static 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 @@
33typedef pthread_mutex_t pgm_lock_t; 33typedef pthread_mutex_t pgm_lock_t;
34typedef pthread_cond_t pgm_cv_t; 34typedef 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"
38typedef ticketlock_t pgm_lock_t; 38typedef spinlock_t pgm_lock_t;
39typedef cv_t pgm_cv_t; 39typedef 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