165 \noindent |
165 \noindent |
166 {\bf Contributions:} There have been earlier formal investigations |
166 {\bf Contributions:} There have been earlier formal investigations |
167 into PIP \cite{Faria08,Jahier09,Wellings07}, but they employ model |
167 into PIP \cite{Faria08,Jahier09,Wellings07}, but they employ model |
168 checking techniques. This paper presents a formalised and |
168 checking techniques. This paper presents a formalised and |
169 mechanically checked proof for the correctness of PIP. For this we |
169 mechanically checked proof for the correctness of PIP. For this we |
170 needed to design a new correctness criterion. In contrast to model checking, our |
170 needed to design a new correctness criterion for PIP. In contrast to model checking, our |
171 formalisation provides insight into why PIP is correct and allows us |
171 formalisation provides insight into why PIP is correct and allows us |
172 to prove stronger properties that, as we will show, can help with an |
172 to prove stronger properties that, as we will show, can help with an |
173 efficient implementation of PIP in the educational PINTOS operating |
173 efficient implementation of PIP in the educational PINTOS operating |
174 system \cite{PINTOS}. For example, we found by ``playing'' with the |
174 system \cite{PINTOS}. For example, we found by ``playing'' with the |
175 formalisation that the choice of the next thread to take over a lock |
175 formalisation that the choice of the next thread to take over a lock |
345 \node (D) at (4,-0.7) [draw, rounded corners=1mm, rectangle, very thick] {@{text "th\<^isub>2"}}; |
345 \node (D) at (4,-0.7) [draw, rounded corners=1mm, rectangle, very thick] {@{text "th\<^isub>2"}}; |
346 \node (E) at (6,-0.7) [draw, circle, very thick, inner sep=0.4mm] {@{text "cs\<^isub>2"}}; |
346 \node (E) at (6,-0.7) [draw, circle, very thick, inner sep=0.4mm] {@{text "cs\<^isub>2"}}; |
347 \node (E1) at (6, 0.2) [draw, circle, very thick, inner sep=0.4mm] {@{text "cs\<^isub>3"}}; |
347 \node (E1) at (6, 0.2) [draw, circle, very thick, inner sep=0.4mm] {@{text "cs\<^isub>3"}}; |
348 \node (F) at (8,-0.7) [draw, rounded corners=1mm, rectangle, very thick] {@{text "th\<^isub>3"}}; |
348 \node (F) at (8,-0.7) [draw, rounded corners=1mm, rectangle, very thick] {@{text "th\<^isub>3"}}; |
349 |
349 |
|
350 \node (X) at (0,-2) [draw, rounded corners=1mm, rectangle, very thick] {@{text "th\<^isub>4"}}; |
|
351 \node (Y) at (2,-2) [draw, circle, very thick, inner sep=0.4mm] {@{text "cs\<^isub>4"}}; |
|
352 \node (Z) at (2,-2.9) [draw, circle, very thick, inner sep=0.4mm] {@{text "cs\<^isub>5"}}; |
|
353 \node (U1) at (4,-2) [draw, rounded corners=1mm, rectangle, very thick] {@{text "th\<^isub>5"}}; |
|
354 \node (U2) at (4,-2.9) [draw, rounded corners=1mm, rectangle, very thick] {@{text "th\<^isub>6"}}; |
|
355 \node (R) at (6,-2.9) [draw, circle, very thick, inner sep=0.4mm] {@{text "cs\<^isub>6"}}; |
|
356 |
350 \draw [<-,line width=0.6mm] (A) to node [pos=0.54,sloped,above=-0.5mm] {\fnt{}holding} (B); |
357 \draw [<-,line width=0.6mm] (A) to node [pos=0.54,sloped,above=-0.5mm] {\fnt{}holding} (B); |
351 \draw [->,line width=0.6mm] (C) to node [pos=0.4,sloped,above=-0.5mm] {\fnt{}waiting} (B); |
358 \draw [->,line width=0.6mm] (C) to node [pos=0.4,sloped,above=-0.5mm] {\fnt{}waiting} (B); |
352 \draw [->,line width=0.6mm] (D) to node [pos=0.4,sloped,below=-0.5mm] {\fnt{}waiting} (B); |
359 \draw [->,line width=0.6mm] (D) to node [pos=0.4,sloped,below=-0.5mm] {\fnt{}waiting} (B); |
353 \draw [<-,line width=0.6mm] (D) to node [pos=0.54,sloped,below=-0.5mm] {\fnt{}holding} (E); |
360 \draw [<-,line width=0.6mm] (D) to node [pos=0.54,sloped,below=-0.5mm] {\fnt{}holding} (E); |
354 \draw [<-,line width=0.6mm] (D) to node [pos=0.54,sloped,above=-0.5mm] {\fnt{}holding} (E1); |
361 \draw [<-,line width=0.6mm] (D) to node [pos=0.54,sloped,above=-0.5mm] {\fnt{}holding} (E1); |
355 \draw [->,line width=0.6mm] (F) to node [pos=0.45,sloped,below=-0.5mm] {\fnt{}waiting} (E); |
362 \draw [->,line width=0.6mm] (F) to node [pos=0.45,sloped,below=-0.5mm] {\fnt{}waiting} (E); |
|
363 |
|
364 \draw [->,line width=0.6mm] (U1) to node [pos=0.45,sloped,below=-0.5mm] {\fnt{}waiting} (Y); |
|
365 \draw [->,line width=0.6mm] (U2) to node [pos=0.45,sloped,below=-0.5mm] {\fnt{}waiting} (Z); |
|
366 \draw [<-,line width=0.6mm] (X) to node [pos=0.54,sloped,below=-0.5mm] {\fnt{}holding} (Z); |
|
367 \draw [<-,line width=0.6mm] (X) to node [pos=0.54,sloped,above=-0.5mm] {\fnt{}holding} (Y); |
|
368 \draw [<-,line width=0.6mm] (U2) to node [pos=0.54,sloped,above=-0.5mm] {\fnt{}holding} (R); |
356 \end{tikzpicture} |
369 \end{tikzpicture} |
357 \end{center} |
370 \end{center} |
|
371 \caption{An Instance of a Resource Allocation Graph (RAG).\label{RAGgraph}} |
|
372 \end{figure} |
358 |
373 |
359 \noindent |
374 \noindent |
360 We will design our scheduler |
375 We will design our scheduler |
361 so that every thread can be in the possession of several resources, that is |
376 so that every thread can be in the possession of several resources, that is |
362 it has potentially several incoming holding edges in the RAG, but has at most only one outgoing |
377 it has potentially several incoming holding edges in the RAG, but has at most one outgoing |
363 waiting edge. The reason is that when a thread asks for resource that is locked |
378 waiting edge. The reason is that when a thread asks for resource that is locked |
364 already, then the process is stopped and cannot ask for another resource. |
379 already, then the process is stopped and cannot ask for another resource. |
365 Clearly, also every resource can only have at most one outgoing holding edge---indicating |
380 Clearly, also every resource can only have at most one outgoing holding edge---indicating |
366 that the resource is locked. |
381 that the resource is locked. |
367 |
382 |
757 Inversion. For this we further have to assume that every thread |
773 Inversion. For this we further have to assume that every thread |
758 gives up its resources after a finite amount of time. We found that |
774 gives up its resources after a finite amount of time. We found that |
759 this assumption is awkward to formalise in our model. Therefore we |
775 this assumption is awkward to formalise in our model. Therefore we |
760 leave it out and let the programmer assume the responsibility to |
776 leave it out and let the programmer assume the responsibility to |
761 program threads in such a benign manner (in addition to causing no |
777 program threads in such a benign manner (in addition to causing no |
762 circularity in the @{text RAG}). In this detail, we do not |
778 circularity in the RAG). In this detail, we do not |
763 make any progress in comparison with the work by Sha et al. |
779 make any progress in comparison with the work by Sha et al. |
764 However, we are able to combine their two separate bounds into a |
780 However, we are able to combine their two separate bounds into a |
765 single theorem improving their bound. |
781 single theorem improving their bound. |
766 |
782 |
767 In what follows we will describe properties of PIP that allow us to prove |
783 In what follows we will describe properties of PIP that allow us to prove |
1240 (*>*) |
1256 (*>*) |
1241 text {* |
1257 text {* |
1242 \noindent |
1258 \noindent |
1243 As can be seen, a pleasing byproduct of our formalisation is that the properties in |
1259 As can be seen, a pleasing byproduct of our formalisation is that the properties in |
1244 this section closely inform an implementation of PIP, namely whether the |
1260 this section closely inform an implementation of PIP, namely whether the |
1245 @{text RAG} needs to be reconfigured or current precedences need to |
1261 RAG needs to be reconfigured or current precedences need to |
1246 be recalculated for an event. This information is provided by the lemmas we proved. |
1262 be recalculated for an event. This information is provided by the lemmas we proved. |
1247 We confirmed that our observations translate into practice by implementing |
1263 We confirmed that our observations translate into practice by implementing |
1248 our version of PIP on top of PINTOS, a small operating system written in C and used for teaching at |
1264 our version of PIP on top of PINTOS, a small operating system written in C and used for teaching at |
1249 Stanford University \cite{PINTOS}. To implement PIP, we only need to modify the kernel |
1265 Stanford University \cite{PINTOS}. To implement PIP, we only need to modify the kernel |
1250 functions corresponding to the events in our formal model. The events translate to the following |
1266 functions corresponding to the events in our formal model. The events translate to the following |
1271 highest precedence is realised in our implementation by priority queues. We implemented |
1287 highest precedence is realised in our implementation by priority queues. We implemented |
1272 them as \emph{Braun trees} \cite{Paulson96}, which provide efficient @{text "O(log n)"}-operations |
1288 them as \emph{Braun trees} \cite{Paulson96}, which provide efficient @{text "O(log n)"}-operations |
1273 for accessing and updating. Apart from having to implement relatively complex data\-structures in C |
1289 for accessing and updating. Apart from having to implement relatively complex data\-structures in C |
1274 using pointers, our experience with the implementation has been very positive: our specification |
1290 using pointers, our experience with the implementation has been very positive: our specification |
1275 and formalisation of PIP translates smoothly to an efficent implementation in PINTOS. |
1291 and formalisation of PIP translates smoothly to an efficent implementation in PINTOS. |
1276 Let us illustrate this with the C-implementation of the function {\tt lock\_aquire}, |
1292 Let us illustrate this with the C-code of the function {\tt lock\_aquire}, |
1277 shown in Figure~\ref{code}. This function implements the operation that |
1293 shown in Figure~\ref{code}. This function implements the operation that |
1278 the currently running thread asks for the lock of a specific resource. |
1294 the currently running thread asks for the lock of a resource. |
1279 In C such a lock is represented as a pointer to the structure {\tt lock} (Line 1). |
1295 In C such a lock is represented as a pointer to the structure {\tt lock} (Line 1). |
1280 |
1296 |
1281 Lines 2 to 4 of {\tt lock\_aquire} contain diagnostic code: first we check that |
1297 Lines 2 to 4 of {\tt lock\_aquire} contain diagnostic code: first, we check that |
1282 the lock is a ``valid'' lock |
1298 the lock is a ``valid'' lock |
1283 by testing it is not {\tt NULL}; second we check that the code is not called |
1299 by testing it is not {\tt NULL}; second, we check that the code is not called |
1284 as part of an interrupt---aquiring a lock should have been only initiated by a |
1300 as part of an interrupt---aquiring a lock should only be initiated by a |
1285 request from a (user) thread, not an interrupt; third we make sure the |
1301 request from a (user) thread, not an interrupt; third, we make sure the |
1286 current thread does not ask twice for a lock. These assertions are supposed |
1302 current thread does not ask twice for a lock. These assertions are supposed |
1287 to be satisfied because of the assumptions in PINTOS about how this code is called. |
1303 to be satisfied because of the assumptions in PINTOS about how this code is called. |
1288 If not, then the assertions indicate a bug in PINTOS. |
1304 If not, then the assertions indicate a bug in PINTOS. |
1289 |
1305 |
1290 |
1306 |
1297 ASSERT (!lock_held_by_current_thread (lock)); |
1313 ASSERT (!lock_held_by_current_thread (lock)); |
1298 |
1314 |
1299 enum intr_level old_level; |
1315 enum intr_level old_level; |
1300 old_level = intr_disable(); |
1316 old_level = intr_disable(); |
1301 if (lock->value == 0) { |
1317 if (lock->value == 0) { |
1302 heap_insert(thread_preced, &lock->wq, &thread_current()->helem); |
1318 heap_insert(thread_cprec, &lock->wq, &thread_current()->helem); |
1303 thread_current()->waiting = lock; |
1319 thread_current()->waiting = lock; |
1304 struct thread *pt; |
1320 struct thread *pt; |
1305 pt = lock->holder; |
1321 pt = lock->holder; |
1306 while (pt) { |
1322 while (pt) { |
1307 heap_update(lock_preced, &pt->held, &lock->helem); |
1323 heap_update(lock_cprec, &pt->held, &lock->helem); |
1308 if (!(update_cpreced(pt))) |
1324 if (!(update_cprec(pt))) |
1309 break; |
1325 break; |
1310 lock = pt->waiting; |
1326 lock = pt->waiting; |
1311 if (!lock) { |
1327 if (!lock) { |
1312 heap_update(higher_cpreced, &ready_heap, &pt->helem); |
1328 heap_update(higher_cprec, &ready_heap, &pt->helem); |
1313 break; |
1329 break; |
1314 }; |
1330 }; |
1315 heap_update(thread_preced, &lock->wq, &pt->helem); |
1331 heap_update(thread_cprec, &lock->wq, &pt->helem); |
1316 pt = lock->holder; |
1332 pt = lock->holder; |
1317 }; |
1333 }; |
1318 thread_block(); |
1334 thread_block(); |
1319 } else { |
1335 } else { |
1320 lock->value--; |
1336 lock->value--; |
1322 heap_insert(lock_prec, &thread_current()->held, &lock->helem); |
1338 heap_insert(lock_prec, &thread_current()->held, &lock->helem); |
1323 }; |
1339 }; |
1324 intr_set_level(old_level); |
1340 intr_set_level(old_level); |
1325 } |
1341 } |
1326 \end{lstlisting} |
1342 \end{lstlisting} |
1327 \caption{Our version of the {\tt lock\_release} function (implementing event @{text P}) in |
1343 \caption{Our version of the {\tt lock\_release} function in |
1328 PINTOS.\label{code}} |
1344 PINTOS. It implements the operation corresponding to a @{text P}-event.\label{code}} |
1329 \end{figure} |
1345 \end{figure} |
1330 |
1346 |
1331 |
1347 |
1332 Line 6 and 7 of {\tt lock\_aquire} make the operation of aquiring a lock atomic by disabling all |
1348 Line 6 and 7 of {\tt lock\_aquire} make the operation of aquiring a lock atomic by disabling all |
1333 interrupts, but saving them for resumption at the end of the function (Line 31). |
1349 interrupts, but saving them for resumption at the end of the function (Line 31). |
1334 In Line 8, the interesting code of the scheduler starts: we |
1350 In Line 8, the interesting code starts: we |
1335 test whether the lock is already taken (its value is then 0 indicating ``already |
1351 test whether the lock is already taken (its value is then 0 indicating ``already |
1336 taken'', and 1 for being ``free''). In case the lock is taken, we need to |
1352 taken'', or 1 for being ``free''). In case the lock is taken, we enter the |
1337 insert the current thread into the waiting queue of this lock (Line 9, |
1353 if-branch by first inserting the current thread into the waiting queue of this lock (Line 9, |
1338 the waiting queue is referenced as @{ML_text "&lock-wq"}). |
1354 the waiting queue is referenced as @{ML_text "&lock-wq"}). |
1339 Next we record that the current thread is waiting for the lock (Line 10). |
1355 Next we record that the current thread is waiting for the lock (Line 10). |
1340 According to our specification, we need to ``chase'' the holders of locks |
1356 According to our specification, we need to next ``chase'' the dependants |
1341 in the RAG (Resource Allocation Graph). For this we assign in Lines 11 and 12 |
1357 in the RAG (Resource Allocation Graph), but we can stop whenever there |
1342 assign the variable @{ML_text pt} ot the owner of the lock, and enter |
1358 is no change in the @{text cprec}. Befre we start the loop, we |
1343 the while-loop in Lines 13 to 24. This loop implements the ``chase''. |
1359 assign in Lines 11 and 12 assign the variable @{ML_text pt} to the owner |
|
1360 of the lock, and enter the while-loop in Lines 13 to 24. \ldots |
1344 |
1361 |
1345 *} |
1362 *} |
1346 |
1363 |
1347 section {* Conclusion *} |
1364 section {* Conclusion *} |
1348 |
1365 |