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