1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
|
/* This file contains the implementation of the three-level-lock. */
#include "fs.h"
#include "glo.h"
#include "tll.h"
#include "threads.h"
#include <assert.h>
static int tll_append(tll_t *tllp, tll_access_t locktype);
static int tll_append(tll_t *tllp, tll_access_t locktype)
{
struct worker_thread *queue;
assert(self != NULL);
assert(tllp != NULL);
assert(locktype != TLL_NONE);
/* Read-only and write-only requests go to the write queue. Read-serialized
* requests go to the serial queue. Then we wait for an event to signal it's
* our turn to go. */
queue = NULL;
if (locktype == TLL_READ || locktype == TLL_WRITE) {
if (tllp->t_write == NULL)
tllp->t_write = self;
else
queue = tllp->t_write;
} else {
if (tllp->t_serial == NULL)
tllp->t_serial = self;
else
queue = tllp->t_serial;
}
if (queue != NULL) { /* Traverse to end of queue */
while (queue->w_next != NULL) queue = queue->w_next;
queue->w_next = self;
}
self->w_next = NULL; /* End of queue */
/* Now wait for the event it's our turn */
worker_wait();
tllp->t_current = locktype;
tllp->t_status &= ~TLL_PEND;
tllp->t_owner = self;
if (tllp->t_current == TLL_READ) {
tllp->t_readonly++;
tllp->t_owner = NULL;
} else if (tllp->t_current == TLL_WRITE)
assert(tllp->t_readonly == 0);
/* Due to the way upgrading and downgrading works, read-only requests are
* scheduled to run after a downgraded lock is released (because they are
* queued on the write-only queue which has priority). This results from the
* fact that the downgrade operation cannot know whether the next locktype on
* the write-only queue is really write-only or actually read-only. However,
* that means that read-serialized requests stay queued, while they could run
* simultaneously with read-only requests. See if there are any and grant
* the head request access */
if (tllp->t_current == TLL_READ && tllp->t_serial != NULL) {
tllp->t_owner = tllp->t_serial;
tllp->t_serial = tllp->t_serial->w_next;
tllp->t_owner->w_next = NULL;
assert(!(tllp->t_status & TLL_PEND));
tllp->t_status |= TLL_PEND;
worker_signal(tllp->t_owner);
}
return(OK);
}
void tll_downgrade(tll_t *tllp)
{
/* Downgrade three-level-lock tll from write-only to read-serialized, or from
* read-serialized to read-only. Caveat: as we can't know whether the next
* lock type on the write queue is actually read-only or write-only, we can't
* grant access to that type. It will be granted access once we unlock. Also,
* because we apply write-bias, we can't grant access to read-serialized
* either, unless nothing is queued on the write-only stack. */
assert(self != NULL);
assert(tllp != NULL);
assert(tllp->t_owner == self);
switch(tllp->t_current) {
case TLL_WRITE: tllp->t_current = TLL_READSER; break;
case TLL_READSER:
/* If nothing is queued on write-only, but there is a pending lock
* requesting read-serialized, grant it and keep the lock type. */
if (tllp->t_write == NULL && tllp->t_serial != NULL) {
tllp->t_owner = tllp->t_serial;
tllp->t_serial = tllp->t_serial->w_next; /* Remove head */
tllp->t_owner->w_next = NULL;
assert(!(tllp->t_status & TLL_PEND));
tllp->t_status |= TLL_PEND;
worker_signal(tllp->t_owner);
} else {
tllp->t_current = TLL_READ;
tllp->t_owner = NULL;
}
tllp->t_readonly++; /* Either way, there's one more read-only lock */
break;
default: panic("VFS: Incorrect lock state");
}
if (tllp->t_current != TLL_WRITE && tllp->t_current != TLL_READSER)
assert(tllp->t_owner == NULL);
}
void tll_init(tll_t *tllp)
{
/* Initialize three-level-lock tll */
assert(tllp != NULL);
tllp->t_current = TLL_NONE;
tllp->t_readonly = 0;
tllp->t_status = TLL_DFLT;
tllp->t_write = NULL;
tllp->t_serial = NULL;
tllp->t_owner = NULL;
}
int tll_islocked(tll_t *tllp)
{
assert(tllp >= (tll_t *) PAGE_SIZE);
return(tllp->t_current != TLL_NONE);
}
int tll_locked_by_me(tll_t *tllp)
{
assert(tllp >= (tll_t *) PAGE_SIZE);
assert(self != NULL);
return(tllp->t_owner == self && !(tllp->t_status & TLL_PEND));
}
int tll_lock(tll_t *tllp, tll_access_t locktype)
{
/* Try to lock three-level-lock tll with type locktype */
assert(self != NULL);
assert(tllp >= (tll_t *) PAGE_SIZE);
assert(locktype != TLL_NONE);
self->w_next = NULL;
if (locktype != TLL_READ && locktype != TLL_READSER && locktype != TLL_WRITE)
panic("Invalid lock type %d\n", locktype);
/* If this locking has pending locks, we wait */
if (tllp->t_status & TLL_PEND)
return tll_append(tllp, locktype);
/* If we already own this lock don't lock it again and return immediately */
if (tllp->t_owner == self) {
assert(tllp->t_status == TLL_DFLT);
return(EBUSY);
}
/* If this lock is not accessed by anyone, locktype is granted off the bat */
if (tllp->t_current == TLL_NONE) {
tllp->t_current = locktype;
if (tllp->t_current == TLL_READ)
tllp->t_readonly = 1;
else { /* Record owner if locktype is read-serialized or write-only */
tllp->t_owner = self;
}
if (tllp->t_current == TLL_WRITE)
assert(tllp->t_readonly == 0);
return(OK);
}
/* If the current lock is write-only, we have to wait for that lock to be
* released (regardless of the value of locktype). */
if (tllp->t_current == TLL_WRITE)
return tll_append(tllp, locktype);
/* However, if it's not and we're requesting a write-only lock, we have to
* wait until the last read access is released (additional read requests
* after this write-only requests are to be queued) */
if (locktype == TLL_WRITE)
return tll_append(tllp, locktype);
/* We have to queue read and read-serialized requests if we have a write-only
* request queued ("write bias") or when a read-serialized lock is trying to
* upgrade to write-only. The current lock for this tll is either read or
* read-serialized. */
if (tllp->t_write != NULL || (tllp->t_status & TLL_UPGR)) {
assert(!(tllp->t_status & TLL_PEND));
return tll_append(tllp, locktype);
}
/* If this lock is in read-serialized mode, we can allow read requests and
* queue read-serialized requests */
if (tllp->t_current == TLL_READSER) {
if (locktype == TLL_READ && !(tllp->t_status & TLL_UPGR)) {
tllp->t_readonly++;
return(OK);
} else
return tll_append(tllp, locktype);
}
/* Finally, if the current lock is read-only, we can change it to
* read-serialized if necessary without a problem. */
tllp->t_current = locktype; /* Either read-only or read-serialized */
if (tllp->t_current == TLL_READ) { /* We now have an additional reader */
tllp->t_readonly++;
tllp->t_owner = NULL;
} else {
assert(tllp->t_current != TLL_WRITE);
tllp->t_owner = self; /* We now have a new owner */
self->w_next = NULL;
}
return(OK);
}
int tll_haspendinglock(tll_t *tllp)
{
/* Is someone trying to obtain a lock? */
assert(tllp != NULL);
/* Someone is trying to obtain a lock if either the write/read-only queue or
* the read-serialized queue is not empty. */
return(tllp->t_write != NULL || tllp->t_serial != NULL);
}
int tll_unlock(tll_t *tllp)
{
/* Unlock a previously locked three-level-lock tll */
int signal_owner = 0;
assert(self != NULL);
assert(tllp != NULL);
if (tllp->t_owner == NULL || tllp->t_owner != self) {
/* This unlock must have been done by a read-only lock */
tllp->t_readonly--;
assert(tllp->t_readonly >= 0);
assert(tllp->t_current == TLL_READ || tllp->t_current == TLL_READSER);
/* If a read-serialized lock is trying to upgrade and there are no more
* read-only locks, the lock can now be upgraded to write-only */
if ((tllp->t_status & TLL_UPGR) && tllp->t_readonly == 0)
signal_owner = 1;
}
if (tllp->t_owner == self && tllp->t_current == TLL_WRITE)
assert(tllp->t_readonly == 0);
if(tllp->t_owner == self || (tllp->t_owner == NULL && tllp->t_readonly == 0)){
/* Let another read-serialized or write-only request obtain access.
* Write-only has priority, but only after the last read-only access
* has left. Read-serialized access will only be granted if there is
* no pending write-only access request. */
struct worker_thread *new_owner;
new_owner = NULL;
tllp->t_owner = NULL; /* Remove owner of lock */
if (tllp->t_write != NULL) {
if (tllp->t_readonly == 0) {
new_owner = tllp->t_write;
tllp->t_write = tllp->t_write->w_next;
}
} else if (tllp->t_serial != NULL) {
new_owner = tllp->t_serial;
tllp->t_serial = tllp->t_serial->w_next;
}
/* New owner is head of queue or NULL if no proc is available */
if (new_owner != NULL) {
tllp->t_owner = new_owner;
tllp->t_owner->w_next = NULL;
assert(tllp->t_owner != self);
signal_owner = 1;
}
}
/* If no one is using this lock, mark it as not in use */
if (tllp->t_owner == NULL) {
if (tllp->t_readonly == 0)
tllp->t_current = TLL_NONE;
else
tllp->t_current = TLL_READ;
}
if (tllp->t_current == TLL_NONE || tllp->t_current == TLL_READ) {
if (!signal_owner) {
tllp->t_owner = NULL;
}
}
/* If we have a new owner or the current owner managed to upgrade its lock,
* tell it to start/continue running */
if (signal_owner) {
assert(!(tllp->t_status & TLL_PEND));
tllp->t_status |= TLL_PEND;
worker_signal(tllp->t_owner);
}
return(OK);
}
void tll_upgrade(tll_t *tllp)
{
/* Upgrade three-level-lock tll from read-serialized to write-only */
assert(self != NULL);
assert(tllp != NULL);
assert(tllp->t_owner == self);
assert(tllp->t_current != TLL_READ); /* i.e., read-serialized or write-only*/
if (tllp->t_current == TLL_WRITE) return; /* Nothing to do */
if (tllp->t_readonly != 0) { /* Wait for readers to leave */
assert(!(tllp->t_status & TLL_UPGR));
tllp->t_status |= TLL_UPGR;
worker_wait();
tllp->t_status &= ~TLL_UPGR;
tllp->t_status &= ~TLL_PEND;
assert(tllp->t_readonly == 0);
}
tllp->t_current = TLL_WRITE;
}
|