80 locking a resource prevented a high priority thread from running in |
80 locking a resource prevented a high priority thread from running in |
81 time, leading to a system reset. Once the problem was found, it was |
81 time, leading to a system reset. Once the problem was found, it was |
82 rectified by enabling the \emph{Priority Inheritance Protocol} (PIP) |
82 rectified by enabling the \emph{Priority Inheritance Protocol} (PIP) |
83 \cite{Sha90}\footnote{Sha et al.~call it the \emph{Basic Priority |
83 \cite{Sha90}\footnote{Sha et al.~call it the \emph{Basic Priority |
84 Inheritance Protocol} \cite{Sha90} and others sometimes also call it |
84 Inheritance Protocol} \cite{Sha90} and others sometimes also call it |
85 \emph{Priority Boosting} or \emph{Priority Donation}.} in the scheduling software. |
85 \emph{Priority Boosting}, \emph{Priority Donation} or \emph{Priority Lending}.} |
|
86 in the scheduling software. |
86 |
87 |
87 The idea behind PIP is to let the thread $L$ temporarily inherit |
88 The idea behind PIP is to let the thread $L$ temporarily inherit |
88 the high priority from $H$ until $L$ leaves the critical section |
89 the high priority from $H$ until $L$ leaves the critical section |
89 unlocking the resource. This solves the problem of $H$ having to |
90 unlocking the resource. This solves the problem of $H$ having to |
90 wait indefinitely, because $L$ cannot be blocked by threads having |
91 wait indefinitely, because $L$ cannot be blocked by threads having |
175 when a resource is released is irrelevant for PIP being correct---a |
176 when a resource is released is irrelevant for PIP being correct---a |
176 fact that has not been mentioned in the literature and not been used |
177 fact that has not been mentioned in the literature and not been used |
177 in the reference implementation of PIP in PINTOS. This fact, however, is important |
178 in the reference implementation of PIP in PINTOS. This fact, however, is important |
178 for an efficient implementation of PIP, because we can give the lock |
179 for an efficient implementation of PIP, because we can give the lock |
179 to the thread with the highest priority so that it terminates more |
180 to the thread with the highest priority so that it terminates more |
180 quickly. |
181 quickly. We were also bale to generalise the scheduler of Sha |
|
182 et al \cite{Sha90} to the practically relevant case where critical |
|
183 sections can overlap. |
181 *} |
184 *} |
182 |
185 |
183 section {* Formal Model of the Priority Inheritance Protocol *} |
186 section {* Formal Model of the Priority Inheritance Protocol *} |
184 |
187 |
185 text {* |
188 text {* |
610 \begin{center} |
613 \begin{center} |
611 @{thm[mode=Rule] thread_V[where thread=th]} |
614 @{thm[mode=Rule] thread_V[where thread=th]} |
612 \end{center} |
615 \end{center} |
613 |
616 |
614 \noindent |
617 \noindent |
|
618 Note, however, that apart from the circularity condition, we do not make any |
|
619 assumption on how different resources can locked and released relative to each |
|
620 other. In our model it is possible that critical sections overlap. This is in |
|
621 contrast to Sha et al \cite{Sha90} who require that critical sections are |
|
622 properly nested. |
|
623 |
615 A valid state of PIP can then be conveniently be defined as follows: |
624 A valid state of PIP can then be conveniently be defined as follows: |
616 |
625 |
617 \begin{center} |
626 \begin{center} |
618 \begin{tabular}{c} |
627 \begin{tabular}{c} |
619 @{thm[mode=Axiom] vt_nil}\hspace{1cm} |
628 @{thm[mode=Axiom] vt_nil}\hspace{1cm} |
1244 \begin{center} |
1253 \begin{center} |
1245 \begin{tabular}{|l@ {\hspace{2mm}}|l@ {\hspace{2mm}}|} |
1254 \begin{tabular}{|l@ {\hspace{2mm}}|l@ {\hspace{2mm}}|} |
1246 \hline |
1255 \hline |
1247 {\bf Event} & {\bf PINTOS function} \\ |
1256 {\bf Event} & {\bf PINTOS function} \\ |
1248 \hline |
1257 \hline |
1249 @{text Create} & @{text "thread_create"}\\ |
1258 @{text Create} & @{ML_text "thread_create"}\\ |
1250 @{text Exit} & @{text "thread_exit"}\\ |
1259 @{text Exit} & @{ML_text "thread_exit"}\\ |
1251 @{text Set} & @{text "thread_set_priority"}\\ |
1260 @{text Set} & @{ML_text "thread_set_priority"}\\ |
1252 @{text P} & @{text "lock_acquire"}\\ |
1261 @{text P} & @{ML_text "lock_acquire"}\\ |
1253 @{text V} & @{text "lock_release"}\\ |
1262 @{text V} & @{ML_text "lock_release"}\\ |
1254 \hline |
1263 \hline |
1255 \end{tabular} |
1264 \end{tabular} |
1256 \end{center} |
1265 \end{center} |
1257 |
1266 |
1258 \noindent |
1267 \noindent |
1259 Our implicit assumption that every event is an atomic operation is ensured by the architecture of |
1268 Our implicit assumption that every event is an atomic operation is ensured by the architecture of |
1260 PINTOS. The case where an unlocked resource is given next to the waiting thread with the |
1269 PINTOS (which allows to disable interrupts when some operations are performed). The case where |
|
1270 an unlocked resource is given next to the waiting thread with the |
1261 highest precedence is realised in our implementation by priority queues. We implemented |
1271 highest precedence is realised in our implementation by priority queues. We implemented |
1262 them as \emph{Braun trees} \cite{Paulson96}, which provide efficient @{text "O(log n)"}-operations |
1272 them as \emph{Braun trees} \cite{Paulson96}, which provide efficient @{text "O(log n)"}-operations |
1263 for accessing and updating. Apart from having to implement relatively complex data\-structures in C |
1273 for accessing and updating. Apart from having to implement relatively complex data\-structures in C |
1264 using pointers, our experience with the implementation has been very positive: our specification |
1274 using pointers, our experience with the implementation has been very positive: our specification |
1265 and formalisation of PIP translates smoothly to an efficent implementation in PINTOS. |
1275 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}, |
|
1277 shown in Figure~\ref{code}. This function implements the operation that |
|
1278 the currently running thread asks for the lock of a specific resource. |
|
1279 In C such a lock is represented as a pointer to the structure {\tt lock} (Line 1). |
|
1280 |
|
1281 Lines 2 to 4 of {\tt lock\_aquire} contain diagnostic code: first we check that |
|
1282 the lock is a ``valid'' lock |
|
1283 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 |
|
1285 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 |
|
1287 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. |
|
1289 |
|
1290 |
1266 |
1291 |
1267 \begin{figure} |
1292 \begin{figure} |
1268 \begin{lstlisting} |
1293 \begin{lstlisting} |
1269 void lock_acquire (struct lock *lock) |
1294 void lock_acquire (struct lock *lock) |
1270 { ASSERT (lock != NULL); |
1295 { ASSERT (lock != NULL); |
1301 \end{lstlisting} |
1326 \end{lstlisting} |
1302 \caption{Our version of the {\tt lock\_release} function (implementing event @{text P}) in |
1327 \caption{Our version of the {\tt lock\_release} function (implementing event @{text P}) in |
1303 PINTOS.\label{code}} |
1328 PINTOS.\label{code}} |
1304 \end{figure} |
1329 \end{figure} |
1305 |
1330 |
1306 Let us illustrate that our specification translates relatively smoothly |
1331 |
1307 into C-code. The function {\tt lock\_aquire}, shown in Figure~\ref{code}, |
1332 Line 6 and 7 of {\tt lock\_aquire} make the operation of aquiring a lock atomic by disabling all |
1308 implements the operation that |
1333 interrupts, but saving them for resumption at the end of the function (Line 31). |
1309 the currently running thread asks for the lock of a specific resource. |
1334 In Line 8, the interesting code of the scheduler starts: we |
1310 In C such a lock is represented as a pointer to the structure {\tt lock}. Lines 2 to |
1335 test whether the lock is already taken (its value is then 0 indicating ``already |
1311 4 contain diagnostic code for PINTOS: first we check that the lock is a ``valid'' lock |
1336 taken'', and 1 for being ``free''). In case the lock is taken, we need to |
1312 by testing it is not {\tt NULL}; second we check that the code is not called |
1337 insert the current thread into the waiting queue of this lock (Line 9, |
1313 as part of an interrupt---aquiring a lock should have been only initiated by a |
1338 the waiting queue is referenced as @{ML_text "&lock-wq"}). |
1314 request from a (user) thread, not an interrupt; third we make sure the |
1339 Next we record that the current thread is waiting for the lock (Line 10). |
1315 current thread does not ask twice for a lock. These assertions are supposed |
|
1316 to be always satisfied because of the assumptions in PINTOS of how this code is called. |
|
1317 If not, then there is a bug in PINTOS. |
|
1318 |
|
1319 Line 6 and 7 make the operation of aquiring a lock atomic by disabling all |
|
1320 interrupts, but saving them for resumption at the ond of the function (Line 31). |
|
1321 In Line 8, the interesting code of this function starts: we |
|
1322 test whether lock is already taken (its value is 0 for indicating ``already |
|
1323 taken'' and 1 for being ``free''). In case the lock is taken, we need to |
|
1324 insert the current thread into the waiting queue of the lock (Line 9, |
|
1325 the waiting queue is referenced as @{ML_text "&lock-wq"}). The queues are |
|
1326 implemented as Braun Trees providing an heap interface, therefore |
|
1327 the function is called @{ML_text "heap_insert"}. Next we |
|
1328 record that the current thread is waiting for the lock (Line 10). |
|
1329 According to our specification, we need to ``chase'' the holders of locks |
1340 According to our specification, we need to ``chase'' the holders of locks |
1330 in the @{text RAG} (Resource Allocation Graph). In Lines 11 and 12 we |
1341 in the RAG (Resource Allocation Graph). For this we assign in Lines 11 and 12 |
1331 assign to the variable @{ML_text pt} the owner of the lock, and enter |
1342 assign the variable @{ML_text pt} ot the owner of the lock, and enter |
1332 a while-loop in Lines 13 to 24, which implements the ``chase''. |
1343 the while-loop in Lines 13 to 24. This loop implements the ``chase''. |
1333 |
1344 |
1334 *} |
1345 *} |
1335 |
1346 |
1336 section {* Conclusion *} |
1347 section {* Conclusion *} |
1337 |
1348 |
1364 goal: The formalisation allowed us to efficently implement our version |
1375 goal: The formalisation allowed us to efficently implement our version |
1365 of PIP on top of PINTOS \cite{PINTOS}, a simple instructional operating system for the x86 |
1376 of PIP on top of PINTOS \cite{PINTOS}, a simple instructional operating system for the x86 |
1366 architecture. It also gives the first author enough data to enable |
1377 architecture. It also gives the first author enough data to enable |
1367 his undergraduate students to implement PIP (as part of their OS course). |
1378 his undergraduate students to implement PIP (as part of their OS course). |
1368 A byproduct of our formalisation effort is that nearly all |
1379 A byproduct of our formalisation effort is that nearly all |
1369 design choices for the PIP scheduler are backed up with a proved |
1380 design choices for the implementation of PIP scheduler are backed up with a proved |
1370 lemma. We were also able to establish the property that the choice of |
1381 lemma. We were also able to establish the property that the choice of |
1371 the next thread which takes over a lock is irrelevant for the correctness |
1382 the next thread which takes over a lock is irrelevant for the correctness |
1372 of PIP. |
1383 of PIP. Moreover, we eliminated a crucial restriction present in |
|
1384 the proof of Sha et al.: they require that critical sections nest properly, |
|
1385 whereas our scheduler allows critical sections to overlap. This is the default |
|
1386 in implementations of PIP. |
1373 |
1387 |
1374 PIP is a scheduling algorithm for single-processor systems. We are |
1388 PIP is a scheduling algorithm for single-processor systems. We are |
1375 now living in a multi-processor world. Priority Inversion certainly |
1389 now living in a multi-processor world. Priority Inversion certainly |
1376 occurs also there. However, there is very little ``foundational'' |
1390 occurs also there. However, there is very little ``foundational'' |
1377 work about PIP-algorithms on multi-processor systems. We are not |
1391 work about PIP-algorithms on multi-processor systems. We are not |