Journal/Paper.thy
changeset 11 8e02fb168350
parent 8 5ba3d79622da
child 12 85116bc854c0
equal deleted inserted replaced
10:242a781135ba 11:8e02fb168350
    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);
  1286       if (!lock) {
  1311       if (!lock) {
  1287         heap_update(higher_cpreced, &ready_heap, &pt->helem);
  1312         heap_update(higher_cpreced, &ready_heap, &pt->helem);
  1288         break;
  1313         break;
  1289       };
  1314       };
  1290       heap_update(thread_preced, &lock->wq, &pt->helem);
  1315       heap_update(thread_preced, &lock->wq, &pt->helem);
  1291       pt = lock -> holder;
  1316       pt = lock->holder;
  1292     };
  1317     };
  1293     thread_block();
  1318     thread_block();
  1294   } else {
  1319   } else {
  1295     lock->value--;
  1320     lock->value--;
  1296     lock->holder = thread_current();
  1321     lock->holder = thread_current();
  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