1 /* This file contains the implementation of the three-level-lock. */
9 static int tll_append(tll_t
*tllp
, tll_access_t locktype
);
11 static int tll_append(tll_t
*tllp
, tll_access_t locktype
)
13 struct worker_thread
*queue
;
17 assert(locktype
!= TLL_NONE
);
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
23 if (locktype
== TLL_READ
|| locktype
== TLL_WRITE
) {
24 if (tllp
->t_write
== NULL
)
27 queue
= tllp
->t_write
;
29 if (tllp
->t_serial
== NULL
)
30 tllp
->t_serial
= self
;
32 queue
= tllp
->t_serial
;
35 if (queue
!= NULL
) { /* Traverse to end of queue */
36 while (queue
->w_next
!= NULL
) queue
= queue
->w_next
;
39 self
->w_next
= NULL
; /* End of queue */
41 /* Now wait for the event it's our turn */
44 tllp
->t_current
= locktype
;
45 tllp
->t_status
&= ~TLL_PEND
;
48 if (tllp
->t_current
== TLL_READ
) {
51 } else if (tllp
->t_current
== TLL_WRITE
)
52 assert(tllp
->t_readonly
== 0);
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
);
74 void tll_downgrade(tll_t
*tllp
)
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. */
85 assert(tllp
->t_owner
== self
);
87 switch(tllp
->t_current
) {
88 case TLL_WRITE
: tllp
->t_current
= TLL_READSER
; break;
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. */
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
);
101 tllp
->t_current
= TLL_READ
;
102 tllp
->t_owner
= NULL
;
104 tllp
->t_readonly
++; /* Either way, there's one more read-only lock */
106 default: panic("VFS: Incorrect lock state");
109 if (tllp
->t_current
!= TLL_WRITE
&& tllp
->t_current
!= TLL_READSER
)
110 assert(tllp
->t_owner
== NULL
);
113 void tll_init(tll_t
*tllp
)
115 /* Initialize three-level-lock tll */
116 assert(tllp
!= NULL
);
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
;
126 int tll_islocked(tll_t
*tllp
)
128 assert(tllp
>= (tll_t
*) PAGE_SIZE
);
129 return(tllp
->t_current
!= TLL_NONE
);
132 int tll_locked_by_me(tll_t
*tllp
)
134 assert(tllp
>= (tll_t
*) PAGE_SIZE
);
135 assert(self
!= NULL
);
136 return(tllp
->t_owner
== self
&& !(tllp
->t_status
& TLL_PEND
));
139 int tll_lock(tll_t
*tllp
, tll_access_t locktype
)
141 /* Try to lock three-level-lock tll with type locktype */
143 assert(self
!= NULL
);
144 assert(tllp
>= (tll_t
*) PAGE_SIZE
);
145 assert(locktype
!= TLL_NONE
);
149 if (locktype
!= TLL_READ
&& locktype
!= TLL_READSER
&& locktype
!= TLL_WRITE
)
150 panic("Invalid lock type %d\n", locktype
);
152 /* If this locking has pending locks, we wait */
153 if (tllp
->t_status
& TLL_PEND
)
154 return tll_append(tllp
, locktype
);
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
);
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
;
170 if (tllp
->t_current
== TLL_WRITE
)
171 assert(tllp
->t_readonly
== 0);
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
);
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
);
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
);
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
)) {
202 return tll_append(tllp
, locktype
);
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 */
210 tllp
->t_owner
= NULL
;
212 assert(tllp
->t_current
!= TLL_WRITE
);
213 tllp
->t_owner
= self
; /* We now have a new owner */
220 int tll_haspendinglock(tll_t
*tllp
)
222 /* Is someone trying to obtain a lock? */
223 assert(tllp
!= NULL
);
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
);
230 int tll_unlock(tll_t
*tllp
)
232 /* Unlock a previously locked three-level-lock tll */
233 int signal_owner
= 0;
235 assert(self
!= NULL
);
236 assert(tllp
!= NULL
);
238 if (tllp
->t_owner
== NULL
|| tllp
->t_owner
!= self
) {
239 /* This unlock must have been done by a read-only lock */
241 assert(tllp
->t_readonly
>= 0);
242 assert(tllp
->t_current
== TLL_READ
|| tllp
->t_current
== TLL_READSER
);
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)
250 if (tllp
->t_owner
== self
&& tllp
->t_current
== TLL_WRITE
)
251 assert(tllp
->t_readonly
== 0);
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
;
260 tllp
->t_owner
= NULL
; /* Remove owner of lock */
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
;
267 } else if (tllp
->t_serial
!= NULL
) {
268 new_owner
= tllp
->t_serial
;
269 tllp
->t_serial
= tllp
->t_serial
->w_next
;
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
);
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
;
286 tllp
->t_current
= TLL_READ
;
289 if (tllp
->t_current
== TLL_NONE
|| tllp
->t_current
== TLL_READ
) {
291 tllp
->t_owner
= NULL
;
295 /* If we have a new owner or the current owner managed to upgrade its lock,
296 * tell it to start/continue running */
298 assert(!(tllp
->t_status
& TLL_PEND
));
299 tllp
->t_status
|= TLL_PEND
;
300 worker_signal(tllp
->t_owner
);
306 void tll_upgrade(tll_t
*tllp
)
308 /* Upgrade three-level-lock tll from read-serialized to write-only */
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
;
319 tllp
->t_status
&= ~TLL_UPGR
;
320 tllp
->t_status
&= ~TLL_PEND
;
321 assert(tllp
->t_readonly
== 0);
323 tllp
->t_current
= TLL_WRITE
;