95 in the Mars Pathfinder mission, in Boeing's 787 Dreamliner, Honda's |
95 in the Mars Pathfinder mission, in Boeing's 787 Dreamliner, Honda's |
96 ASIMO robot, etc.), but also the POSIX 1003.1c Standard realised for |
96 ASIMO robot, etc.), but also the POSIX 1003.1c Standard realised for |
97 example in libraries for FreeBSD, Solaris and Linux. |
97 example in libraries for FreeBSD, Solaris and Linux. |
98 |
98 |
99 One advantage of PIP is that increasing the priority of a thread |
99 One advantage of PIP is that increasing the priority of a thread |
100 can be dynamically calculated by the scheduler. This is in contrast |
100 can be performed dynamically by the scheduler. This is in contrast |
101 to, for example, \emph{Priority Ceiling} \cite{Sha90}, another |
101 to, for example, \emph{Priority Ceiling} \cite{Sha90}, another |
102 solution to the Priority Inversion problem, which requires static |
102 solution to the Priority Inversion problem, which requires static |
103 analysis of the program in order to prevent Priority |
103 analysis of the program in order to prevent Priority |
104 Inversion. However, there has also been strong criticism against |
104 Inversion. However, there has also been strong criticism against |
105 PIP. For instance, PIP cannot prevent deadlocks when lock |
105 PIP. For instance, PIP cannot prevent deadlocks when lock |
1291 them as \emph{Braun trees} \cite{Paulson96}, which provide efficient @{text "O(log n)"}-operations |
1291 them as \emph{Braun trees} \cite{Paulson96}, which provide efficient @{text "O(log n)"}-operations |
1292 for accessing and updating. Apart from having to implement relatively complex data\-structures in C |
1292 for accessing and updating. Apart from having to implement relatively complex data\-structures in C |
1293 using pointers, our experience with the implementation has been very positive: our specification |
1293 using pointers, our experience with the implementation has been very positive: our specification |
1294 and formalisation of PIP translates smoothly to an efficent implementation in PINTOS. |
1294 and formalisation of PIP translates smoothly to an efficent implementation in PINTOS. |
1295 Let us illustrate this with the C-code of the function {\tt lock\_acquire}, |
1295 Let us illustrate this with the C-code of the function {\tt lock\_acquire}, |
1296 shown in Figure~\ref{code}. This function implements the operation that |
1296 shown in Figure~\ref{code}. This function implements the operation of requesting and, if free, |
1297 the currently running thread asks for the lock of a resource. |
1297 locking of a resource by the currently running thread. The convention in the PINTOS |
1298 In C such a lock is represented as a pointer to the structure {\tt lock} (Line 1). |
1298 code is use the terminology \emph{locks} rather than resources. |
1299 |
1299 A lock is represented as a pointer to the structure {\tt lock} (Line 1). |
1300 Lines 2 to 4 of {\tt lock\_acquire} contain diagnostic code: first, we check that |
1300 |
|
1301 Lines 2 to 4 of the function @{ML_text "lock_acquire"} contain diagnostic code: first, we check that |
1301 the lock is a ``valid'' lock |
1302 the lock is a ``valid'' lock |
1302 by testing it is not {\tt NULL}; second, we check that the code is not called |
1303 by testing whether it is not {\tt NULL}; second, we check that the code is not called |
1303 as part of an interrupt---acquiring a lock should only be initiated by a |
1304 as part of an interrupt---acquiring a lock should only be initiated by a |
1304 request from a (user) thread, not an interrupt; third, we make sure the |
1305 request from a (user) thread, not an interrupt; third, we make sure the |
1305 current thread does not ask twice for a lock. These assertions are supposed |
1306 current thread does not ask twice for a lock. These assertions are supposed |
1306 to be satisfied because of the assumptions in PINTOS about how this code is called. |
1307 to be satisfied because of the assumptions in PINTOS about how this code is called. |
1307 If not, then the assertions indicate a bug in PINTOS. |
1308 If not, then the assertions indicate a bug in PINTOS and the result will be |
|
1309 a \emph{kernel panic}. We took these three lines from the original code of |
|
1310 @{ML_text "lock_acquire"} in PINTOS. |
1308 |
1311 |
1309 |
1312 |
1310 |
1313 |
1311 \begin{figure} |
1314 \begin{figure} |
1312 \begin{lstlisting} |
1315 \begin{lstlisting} |
1349 |
1352 |
1350 |
1353 |
1351 Line 6 and 7 of {\tt lock\_acquire} make the operation of acquiring a lock atomic by disabling all |
1354 Line 6 and 7 of {\tt lock\_acquire} make the operation of acquiring a lock atomic by disabling all |
1352 interrupts, but saving them for resumption at the end of the function (Line 31). |
1355 interrupts, but saving them for resumption at the end of the function (Line 31). |
1353 In Line 8, the interesting code with respect to scheduling starts: we |
1356 In Line 8, the interesting code with respect to scheduling starts: we |
1354 test whether the lock is already taken (its value is then 0 indicating ``already |
1357 first check whether the lock is already taken (its value is then 0 indicating ``already |
1355 taken'', or 1 for being ``free''). In case the lock is taken, we enter the |
1358 taken'', or 1 for being ``free''). In case the lock is taken, we enter the |
1356 if-branch by first inserting the current thread into the waiting queue of this lock (Line 9). |
1359 if-branch inserting the current thread into the waiting queue of this lock (Line 9). |
1357 The waiting queue is referenced in the usual C-way as @{ML_text "&lock->wq"}. |
1360 The waiting queue is referenced in the usual C-way as @{ML_text "&lock->wq"}. |
1358 Next we record that the current thread is waiting for the lock (Line 10). |
1361 Next we record that the current thread is waiting for the lock (Line 10). |
1359 According to our specification, we need to next ``chase'' the dependants |
1362 Thus we established two pointers: one in the waiting queue of the lock pointing to the |
1360 in the RAG (Resource Allocation Graph), but we can stop the ``chase'' whenever |
1363 current thread, and the other from the currend thread pointing to the lock. |
1361 there is no change in the @{text cprec}. To start the loop, we |
1364 According to our specification in Section~\ref{model} and the properties we were able |
|
1365 to prove for @{text P}, we need to ``chase'' all the dependants |
|
1366 in the RAG (Resource Allocation Graph) and update their |
|
1367 current precedence, however we only have to do this as long as there is change in the |
|
1368 current precedence from one thread at hand to another. |
|
1369 |
|
1370 The ``chase'' is implemented in the while-loop in Lines 13 to 24. |
|
1371 To initialise the loop, we |
1362 assign in Lines 11 and 12 the variable @{ML_text pt} to the owner |
1372 assign in Lines 11 and 12 the variable @{ML_text pt} to the owner |
1363 of the lock, and then enter the while-loop in Lines 13 to 24. |
1373 of the lock. |
|
1374 Inside the loop, we first update the precedence of the lock held by @{ML_text pt} (Line 14). |
|
1375 Next, we check whether there is a change in the current precedence of @{ML_text pt}. If not, |
|
1376 then we leave the loop, since nothing else needs to be updated (Lines 15 and 16). |
|
1377 If there is a change, then we have to continue our ``chase''. We check what lock the |
|
1378 thread @{ML_text pt} is waiting for (Lines 17 and 18). If there is none, then |
|
1379 the thread @{ML_text pt} is ready (the ``chase'' is finished with finding a root). In this |
|
1380 case we update the ready-queue accordingly (Lines 19 and 20). If there is a lock @{ML_text pt} is |
|
1381 waiting for, we update the waiting queue for this lock and we continue the loop with |
|
1382 the holder of that lock |
|
1383 (Lines 22 and 23). After all current precedences have been updated, we finally need |
|
1384 to block the current thread, because the lock it asked for was taken (Line 25). |
|
1385 |
|
1386 If the lock the current thread asked for is \emph{not} taken, we proceed with the else-branch |
|
1387 (Lines 26 to 30). We first decrease the value of the lock to 0, meaning |
|
1388 it is taken now (Line 27). Second, we update the reference of the holder of |
|
1389 the lock (Line 28), and finally update the queue of locks the current |
|
1390 thread already possesses (Line 29). |
|
1391 The very last is to enable interrupts again thus leaving the protected section. |
1364 |
1392 |
1365 Inside the loop, we first update the precedence of the lock held by @{ML_text pt} (Line 14). |
1393 |
1366 Next, we test if there is a change in the current precedence of @{ML_text pt}. If not, |
|
1367 then we leave the loop since nothing else needs to be updated (Lines 15 and 16). |
|
1368 If there is a change, then we have to continue our ``chase''. We check the lock |
|
1369 @{ML_text pt} is waiting for (Lines 17 and 18). If there isn't one, the thread @{ML_text pt} |
|
1370 is ready (the ``chase'' finished with finding a root) and we update the ready-queue accordingly |
|
1371 (Lines 19 and 20). If there is a lock @{ML_text pt} is waiting for, we update the |
|
1372 waiting queue for this lock and we continue the loop with the holder of that lock |
|
1373 (Lines 22 and 23). Finally we need to block the current thread, since the lock |
|
1374 it asked for was taken (Line 25). |
|
1375 |
|
1376 If the lock was not taken, we take the else-branch (Lines 26 to 30). We first |
|
1377 decrease the value of the lock to 0, meaning it is taken now (Line 27). |
|
1378 We update the reference of holder of the lock (Line 28) and finally update |
|
1379 the queue of locks the current thread already possesses (Line 29). |
|
1380 Finally, we enable interrupts again, leaving the protected section of PINTOS. |
|
1381 Similar operations need to be implementated for the @{ML_text lock_release} function, which |
1394 Similar operations need to be implementated for the @{ML_text lock_release} function, which |
1382 we omit. |
1395 we however do not show. The reader should note that we did not verify our C-code. The |
|
1396 verification of the specification however provided us with the justification for designing |
|
1397 the C-code in this way. It gave us confidence for leaving the ``chase'' early whenever |
|
1398 there is no change in the calculated current precedence. |
1383 *} |
1399 *} |
1384 |
1400 |
1385 section {* Conclusion *} |
1401 section {* Conclusion *} |
1386 |
1402 |
1387 text {* |
1403 text {* |
1418 design choices for the implementation of PIP scheduler are backed up with a proved |
1434 design choices for the implementation of PIP scheduler are backed up with a proved |
1419 lemma. We were also able to establish the property that the choice of |
1435 lemma. We were also able to establish the property that the choice of |
1420 the next thread which takes over a lock is irrelevant for the correctness |
1436 the next thread which takes over a lock is irrelevant for the correctness |
1421 of PIP. Moreover, we eliminated a crucial restriction present in |
1437 of PIP. Moreover, we eliminated a crucial restriction present in |
1422 the proof of Sha et al.: they require that critical sections nest properly, |
1438 the proof of Sha et al.: they require that critical sections nest properly, |
1423 whereas our scheduler allows critical sections to overlap. This is the default |
1439 whereas our scheduler allows critical sections to overlap. |
1424 in implementations of PIP. |
|
1425 |
1440 |
1426 PIP is a scheduling algorithm for single-processor systems. We are |
1441 PIP is a scheduling algorithm for single-processor systems. We are |
1427 now living in a multi-processor world. Priority Inversion certainly |
1442 now living in a multi-processor world. Priority Inversion certainly |
1428 occurs also there. However, there is very little ``foundational'' |
1443 occurs also there. However, there is very little ``foundational'' |
1429 work about PIP-algorithms on multi-processor systems. We are not |
1444 work about PIP-algorithms on multi-processor systems. We are not |