Journal/Paper.thy
changeset 207 d62b19b641c5
parent 206 3be0c4c034af
child 208 a5afc26b1d62
equal deleted inserted replaced
206:3be0c4c034af 207:d62b19b641c5
    82   priorities, this property can be violated. For this let $L$ be in the
    82   priorities, this property can be violated. For this let $L$ be in the
    83   possession of a lock for a resource that $H$ also needs. $H$ must
    83   possession of a lock for a resource that $H$ also needs. $H$ must
    84   therefore wait for $L$ to exit the critical section and release this
    84   therefore wait for $L$ to exit the critical section and release this
    85   lock. The problem is that $L$ might in turn be blocked by any thread
    85   lock. The problem is that $L$ might in turn be blocked by any thread
    86   with priority $M$, and so $H$ sits there potentially waiting
    86   with priority $M$, and so $H$ sits there potentially waiting
    87   indefinitely (consider the case where threads with propority $M$
    87   indefinitely (consider the case where threads with priority $M$
    88   continously need to be processed). Since $H$ is blocked by threads
    88   continuously need to be processed). Since $H$ is blocked by threads
    89   with lower priorities, the problem is called Priority Inversion. It
    89   with lower priorities, the problem is called Priority Inversion. It
    90   was first described in \cite{Lampson80} in the context of the Mesa
    90   was first described in \cite{Lampson80} in the context of the Mesa
    91   programming language designed for concurrent programming.
    91   programming language designed for concurrent programming.
    92  
    92  
    93   If the problem of Priority Inversion is ignored, real-time systems
    93   If the problem of Priority Inversion is ignored, real-time systems
    94   can become unpredictable and resulting bugs can be hard to diagnose.
    94   can become unpredictable and resulting bugs can be hard to diagnose.
    95   The classic example where this happened is the software that
    95   The classic example where this happened is the software that
    96   controlled the Mars Pathfinder mission in 1997 \cite{Reeves98}.  On
    96   controlled the Mars Pathfinder mission in 1997 \cite{Reeves98}.  On
    97   Earth the software run mostly without any problem, but once the
    97   Earth, the software ran mostly without any problem, but once the
    98   spacecraft landed on Mars, it shut down at irregular, but frequent,
    98   spacecraft landed on Mars, it shut down at irregular, but frequent,
    99   intervals leading to loss of project time as normal operation of the
    99   intervals. This led to loss of project time as normal operation of the
   100   craft could only resume the next day (the mission and data already
   100   craft could only resume the next day (the mission and data already
   101   collected were fortunately not lost, because of a clever system
   101   collected were fortunately not lost, because of a clever system
   102   design).  The reason for the shutdowns was that the scheduling
   102   design).  The reason for the shutdowns was that the scheduling
   103   software fell victim to Priority Inversion: a low priority thread
   103   software fell victim to Priority Inversion: a low priority thread
   104   locking a resource prevented a high priority thread from running in
   104   locking a resource prevented a high priority thread from running in
   188   still have Priority Inversion with $H'$ (which waits for the other
   188   still have Priority Inversion with $H'$ (which waits for the other
   189   resource). The correct behaviour for $L$ is to switch to the highest
   189   resource). The correct behaviour for $L$ is to switch to the highest
   190   remaining priority of the threads that it blocks.  A similar error
   190   remaining priority of the threads that it blocks.  A similar error
   191   is made in the textbook \cite[Section 2.3.1]{book} which specifies
   191   is made in the textbook \cite[Section 2.3.1]{book} which specifies
   192   for a process that inherited a higher priority and exits a critical
   192   for a process that inherited a higher priority and exits a critical
   193   section ``{\it it resumes the priority it had at the point of entry
   193   section that ``{\it it resumes the priority it had at the point of entry
   194   into the critical section}''.  This error can also be found in the
   194   into the critical section}''.  This error can also be found in the
   195   textbook \cite[Section 16.4.1]{LiYao03} where the authors write
   195   textbook \cite[Section 16.4.1]{LiYao03} where the authors write
   196   about this process: ``{\it its priority is immediately lowered to the level originally assigned}'';
   196   about this process: ``{\it its priority is immediately lowered to the level originally assigned}'';
   197   and also in the 
   197   and also in the 
   198   more recent textbook \cite[Page 119]{Laplante11} where the authors
   198   more recent textbook \cite[Page 119]{Laplante11} where the authors
   199   state: ``{\it when [the task] exits the critical section that caused
   199   state: ``{\it when [the task] exits the critical section that caused
   200   the block, it reverts to the priority it had when it entered that
   200   the block, it reverts to the priority it had when it entered that
   201   section}''. The textbook \cite[Page 286]{Liu00} contains a simlar
   201   section}''. The textbook \cite[Page 286]{Liu00} contains a similar
   202   flawed specification and even goes on to develop pseudo-code based
   202   flawed specification and even goes on to develop pseudo-code based
   203   on this flawed specification. Accordingly, the operating system
   203   on this flawed specification. Accordingly, the operating system
   204   primitives for inheritance and restoration of priorities in
   204   primitives for inheritance and restoration of priorities in
   205   \cite{Liu00} depend on maintaining a data structure called
   205   \cite{Liu00} depend on maintaining a data structure called
   206   \emph{inheritance log}. This log is maintained for every thread and
   206   \emph{inheritance log}. This log is maintained for every thread and
   207   broadly specified as containing ``{\it [h]istorical information on
   207   broadly specified as containing ``{\it [h]istorical information on
   208   how the thread inherited its current priority}'' \cite[Page
   208   how the thread inherited its current priority}'' \cite[Page
   209   527]{Liu00}. Unfortunately, the important information about actually
   209   527]{Liu00}. Unfortunately, the important information about actually
   210   computing the priority to be restored solely from this log is not
   210   computing the priority to be restored solely from this log is not
   211   explained in \cite{Liu00} but left as an ``{\it excercise}'' to the
   211   explained in \cite{Liu00} but left as an ``{\it exercise}'' to the
   212   reader.  As we shall see, a correct version of PIP does not need to
   212   reader.  As we shall see, a correct version of PIP does not need to
   213   maintain this (potentially expensive) log data structure at
   213   maintain this (potentially expensive) log data structure at
   214   all. Surprisingly also the widely read and frequently updated
   214   all. Surprisingly also the widely read and frequently updated
   215   textbook \cite{Silberschatz13} gives the wrong specification. On Page 254 the authors write: ``{\it Upon releasing the
   215   textbook \cite{Silberschatz13} gives the wrong specification. On Page 254 the authors write: ``{\it Upon releasing the
   216   lock, the [low-priority] thread will revert to its original
   216   lock, the [low-priority] thread will revert to its original
   220   While \cite{Laplante11,LiYao03,Liu00,book,Sha90,Silberschatz13} are
   220   While \cite{Laplante11,LiYao03,Liu00,book,Sha90,Silberschatz13} are
   221   the only formal publications we have found that specify the
   221   the only formal publications we have found that specify the
   222   incorrect behaviour, it seems also many informal descriptions of the
   222   incorrect behaviour, it seems also many informal descriptions of the
   223   PIP protocol overlook the possibility that another high-priority
   223   PIP protocol overlook the possibility that another high-priority
   224   process might wait for a low-priority process to finish.  A notable
   224   process might wait for a low-priority process to finish.  A notable
   225   exception is the texbook \cite{buttazzo}, which gives the correct
   225   exception is the textbook \cite{buttazzo}, which gives the correct
   226   behaviour of resetting the priority of a thread to the highest
   226   behaviour of resetting the priority of a thread to the highest
   227   remaining priority of the threads it blocks. This textbook also
   227   remaining priority of the threads it blocks. This textbook also
   228   gives an informal proof for the correctness of PIP in the style of
   228   gives an informal proof for the correctness of PIP in the style of
   229   Sha et al. Unfortunately, this informal proof is too vague to be
   229   Sha et al. Unfortunately, this informal proof is too vague to be
   230   useful for formalising the correctness of PIP and the specification
   230   useful for formalising the correctness of PIP and the specification
   247   for PIP being correct---a fact that has not been mentioned in the
   247   for PIP being correct---a fact that has not been mentioned in the
   248   literature and not been used in the reference implementation of PIP
   248   literature and not been used in the reference implementation of PIP
   249   in PINTOS.  This fact, however, is important for an efficient
   249   in PINTOS.  This fact, however, is important for an efficient
   250   implementation of PIP, because we can give the lock to the thread
   250   implementation of PIP, because we can give the lock to the thread
   251   with the highest priority so that it terminates more quickly.  We
   251   with the highest priority so that it terminates more quickly.  We
   252   are also being able to generalise the scheduler of Sha et
   252   are also able to generalise the scheduler of Sha et
   253   al.~\cite{Sha90} to the practically relevant case where critical
   253   al.~\cite{Sha90} to the practically relevant case where critical
   254   sections can overlap; see Figure~\ref{overlap} \emph{a)} below for
   254   sections can overlap; see Figure~\ref{overlap} \emph{a)} below for
   255   an example of this restriction. In the existing literature there is
   255   an example of this restriction. In the existing literature there is
   256   no proof and also no proving method that cover this generalised
   256   no proof and also no method for proving which covers this generalised
   257   case.
   257   case.
   258 
   258 
   259   \begin{figure}
   259   \begin{figure}
   260   \begin{center}
   260   \begin{center}
   261   \begin{tikzpicture}[scale=1]
   261   \begin{tikzpicture}[scale=1]
   330   \end{tabular}}
   330   \end{tabular}}
   331   \end{isabelle}
   331   \end{isabelle}
   332 
   332 
   333   \noindent
   333   \noindent
   334   whereby threads, priorities and (critical) resources are represented
   334   whereby threads, priorities and (critical) resources are represented
   335   as natural numbers. The event @{term Set} models the situation that
   335   as natural numbers. In what follows we shall use @{term cs} as a name for
       
   336   critical resources. The event @{term Set} models the situation that
   336   a thread obtains a new priority given by the programmer or
   337   a thread obtains a new priority given by the programmer or
   337   user (for example via the {\tt nice} utility under UNIX).  For states
   338   user (for example via the {\tt nice} utility under UNIX).  For states
   338   we define the following type-synonym:
   339   we define the following type-synonym:
   339 
   340 
   340   \begin{isabelle}\ \ \ \ \ %%%
   341   \begin{isabelle}\ \ \ \ \ %%%
   785   \end{tabular}
   786   \end{tabular}
   786   \end{isabelle}
   787   \end{isabelle}
   787 
   788 
   788   \noindent where @{text "SOME"} stands for Hilbert's epsilon and
   789   \noindent where @{text "SOME"} stands for Hilbert's epsilon and
   789   implements an arbitrary choice for the next waiting list. It just
   790   implements an arbitrary choice for the next waiting list. It just
   790   has to be a list of distinctive threads and contains the same
   791   has to be a list of distinct threads and contains the same
   791   elements as @{text "qs"} (essentially @{text "qs'"} can be any
   792   elements as @{text "qs"} (essentially @{text "qs'"} can be any
   792   reordering of the list @{text "qs"}). This gives for @{term V} the clause:
   793   reordering of the list @{text "qs"}). This gives for @{term V} the clause:
   793  
   794  
   794   \begin{isabelle}\ \ \ \ \ %%%
   795   \begin{isabelle}\ \ \ \ \ %%%
   795   \begin{tabular}{@ {}l}
   796   \begin{tabular}{@ {}l}
   800   \end{tabular}
   801   \end{tabular}
   801   \end{isabelle}
   802   \end{isabelle}
   802 
   803 
   803   Having the scheduler function @{term schs} at our disposal, we can
   804   Having the scheduler function @{term schs} at our disposal, we can
   804   ``lift'', or overload, the notions @{term waiting}, @{term holding},
   805   ``lift'', or overload, the notions @{term waiting}, @{term holding},
   805   @{term RAG}, %%@ {term dependants} 
   806   @{term RAG}, @{term "TDG"},  %%@ {term dependants} 
   806   and @{term cp} to operate on states only.
   807   and @{term cp} to operate on states only.
   807 
   808 
   808   \begin{isabelle}\ \ \ \ \ %%%
   809   \begin{isabelle}\ \ \ \ \ %%%
   809   \begin{tabular}{@ {}rcl}
   810   \begin{tabular}{@ {}rcl}
   810   @{thm (lhs) s_holding_abv}  & @{text "\<equiv>"} & @{thm (rhs) s_holding_abv[simplified wq_def]}\\
   811   @{thm (lhs) s_holding_abv}  & @{text "\<equiv>"} & @{thm (rhs) s_holding_abv[simplified wq_def]}\\
  1625 
  1626 
  1626   Because of these problems, we decided in our earlier paper
  1627   Because of these problems, we decided in our earlier paper
  1627   \cite{ZhangUrbanWu12} to leave out this property and let the
  1628   \cite{ZhangUrbanWu12} to leave out this property and let the
  1628   programmer take on the responsibility to program threads in such a
  1629   programmer take on the responsibility to program threads in such a
  1629   benign manner (in addition to causing no circularity in the
  1630   benign manner (in addition to causing no circularity in the
  1630   RAG). This leave-it-to-the-programmer was also the approach taken by
  1631   RAG). This leave-it-to-the-programmer approach was also taken by
  1631   Sha et al.~in their paper.  However, in this paper we can make an
  1632   Sha et al.~in their paper.  However, in this paper we can make an
  1632   improvement by establishing a finite bound on the duration of
  1633   improvement by establishing a finite bound on the duration of
  1633   Priority Inversion measured by the number of events.  The events can
  1634   Priority Inversion measured by the number of events.  The events can
  1634   be seen as a \textit{rough(!)} abstraction of the ``runtime
  1635   be seen as a \textit{rough(!)} abstraction of the ``runtime
  1635   behaviour'' of threads and also as an abstract notion of
  1636   behaviour'' of threads and also as an abstract notion of
  1660   require that the number of created threads is less than
  1661   require that the number of created threads is less than
  1661   a bound @{text "BC"}, that is 
  1662   a bound @{text "BC"}, that is 
  1662 
  1663 
  1663   \[@{text "len (filter isCreate es) < BC"}\;\]  
  1664   \[@{text "len (filter isCreate es) < BC"}\;\]  
  1664   
  1665   
  1665   wherby @{text es} is a list of events.
  1666   whereby @{text es} is a list of events.
  1666   \end{quote}
  1667   \end{quote}
  1667 
  1668 
  1668   \noindent Note that it is not enough to just to state that there are
  1669   \noindent Note that it is not enough to just state that there are
  1669   only finite number of threads created up until a single state @{text
  1670   only finite number of threads created up until a single state @{text
  1670   "s' @ s"} after @{text s}.  Instead, we need to put this bound on
  1671   "s' @ s"} after @{text s}.  Instead, we need to put this bound on
  1671   the @{text "Create"} events for all valid states after @{text s}.
  1672   the @{text "Create"} events for all valid states after @{text s}.
  1672   This ensures that no matter which ``future'' state is reached, the
  1673   This ensures that no matter which ``future'' state is reached, the
  1673   number of @{text "Create"}-events is finite. This bound @{text BC} is assumed 
  1674   number of @{text "Create"}-events is finite. This bound @{text BC} is assumed 
  1683   \end{isabelle}
  1684   \end{isabelle}
  1684 
  1685 
  1685   \noindent This set contains all threads that are not detached in
  1686   \noindent This set contains all threads that are not detached in
  1686   state @{text s}. According to our definiton of @{text "detached"},
  1687   state @{text s}. According to our definiton of @{text "detached"},
  1687   this means a thread in @{text "blockers"} either holds or waits for
  1688   this means a thread in @{text "blockers"} either holds or waits for
  1688   some resource in state @{text s} . Our Thm.~1 implies that any of
  1689   some resource in state @{text s} . Our Thm.~1 implies that only 
  1689   such threads can all potentially block @{text th} after state
  1690   these threads can all potentially block @{text th} after state
  1690   @{text s}. We need to make the following assumption about the
  1691   @{text s}. We need to make the following assumption about the
  1691   threads in the @{text "blockers"}-set:
  1692   threads in the @{text "blockers"}-set:
  1692 
  1693 
  1693   \begin{quote}
  1694   \begin{quote}
  1694   {\bf Assumptions on the threads {\boldmath{@{term "th' \<in> blockers"}}}:} 
  1695   {\bf Assumptions on the threads {\boldmath{@{term "th' \<in> blockers"}}}:} 
  1714   can be bounded by the number of actions the threads in @{text
  1715   can be bounded by the number of actions the threads in @{text
  1715   blockers} perform (i.e.~events) and how many threads are newly
  1716   blockers} perform (i.e.~events) and how many threads are newly
  1716   created.  To state our bound formally, we need to make a definition
  1717   created.  To state our bound formally, we need to make a definition
  1717   of what we mean by intermediate states between a state @{text s} and
  1718   of what we mean by intermediate states between a state @{text s} and
  1718   a future state after @{text s}; they will be the list of states
  1719   a future state after @{text s}; they will be the list of states
  1719   starting from @{text s} upto the state \mbox{@{text "es @ s"}}. For
  1720   starting from @{text s} up to the state \mbox{@{text "es @ s"}}. For
  1720   example, suppose $\textit{es} = [\textit{e}_n, \textit{e}_{n-1},
  1721   example, suppose $\textit{es} = [\textit{e}_n, \textit{e}_{n-1},
  1721   \ldots, \textit{e}_2, \textit{e}_1]$, then the intermediate states
  1722   \ldots, \textit{e}_2, \textit{e}_1]$, then the intermediate states
  1722   from @{text s} upto @{text "es @ s"} are
  1723   from @{text s} upto @{text "es @ s"} are
  1723 
  1724 
  1724   \begin{center}
  1725   \begin{center}
  1950   \end{isabelle}
  1951   \end{isabelle}
  1951 
  1952 
  1952   \noindent The first property is again telling us we do not need to
  1953   \noindent The first property is again telling us we do not need to
  1953   change the @{text RAG}.  The second shows that the @{term cp}-values
  1954   change the @{text RAG}.  The second shows that the @{term cp}-values
  1954   of all threads other than @{text th} are unchanged. The reason for
  1955   of all threads other than @{text th} are unchanged. The reason for
  1955   this is more subtle: Since @{text th} must be running, that is does
  1956   this is more subtle: Since @{text th} must be running, then it does
  1956   not wait for any resource to be released, it cannot be in any
  1957   not wait for any resource to be released and it cannot be in any
  1957   subtree of any other thread. So all current precedences of other
  1958   subtree of any other thread. So all current precedences of other
  1958   threads are unchanged.
  1959   threads are unchanged.
  1959 
  1960 
  1960   %The second
  1961   %The second
  1961   %however states that only threads that are \emph{not} dependants of @{text th} have their
  1962   %however states that only threads that are \emph{not} dependants of @{text th} have their
  2307   inductive method for protocol verification~\cite{Paulson98} is quite
  2308   inductive method for protocol verification~\cite{Paulson98} is quite
  2308   suitable for our formal model and proof. The traditional application
  2309   suitable for our formal model and proof. The traditional application
  2309   area of this method is security protocols. 
  2310   area of this method is security protocols. 
  2310 
  2311 
  2311   The second goal of our formalisation is to provide a specification for actually
  2312   The second goal of our formalisation is to provide a specification for actually
  2312   implementing PIP. Textbooks, for example \cite[Section 5.6.5]{Vahalia96},
  2313   implementing PIP. Textbooks, for example Vahalia \cite[Section 5.6.5]{Vahalia96},
  2313   explain how to use various implementations of PIP and abstractly
  2314   explain how to use various implementations of PIP and abstractly
  2314   discuss their properties, but surprisingly lack most details important for a
  2315   discuss their properties, but surprisingly lack most details important for a
  2315   programmer who wants to implement PIP (similarly Sha et al.~\cite{Sha90}).  
  2316   programmer who wants to implement PIP (similarly Sha et al.~\cite{Sha90}).  
  2316   That this is an issue in practice is illustrated by the
  2317   That this is an issue in practice is illustrated by the
  2317   email from Baker we cited in the Introduction. We achieved also this
  2318   email from Baker we cited in the Introduction. We achieved also this
  2318   goal: The formalisation allowed us to efficently implement our version
  2319   goal: The formalisation allowed us to efficently implement our version
  2319   of PIP on top of PINTOS \cite{PINTOS}, a simple instructional operating system for the x86 
  2320   of PIP on top of PINTOS, a simple instructional operating system for the x86 
  2320   architecture. It also gives the first author enough data to enable
  2321   architecture implemented by Pfaff \cite{PINTOS}. It also gives the first author enough data to enable
  2321   his undergraduate students to implement PIP (as part of their OS course).
  2322   his undergraduate students to implement PIP (as part of their OS course).
  2322   A byproduct of our formalisation effort is that nearly all
  2323   A byproduct of our formalisation effort is that nearly all
  2323   design choices for the implementation of PIP scheduler are backed up with a proved
  2324   design choices for the implementation of PIP scheduler are backed up with a proved
  2324   lemma. We were also able to establish the property that the choice of
  2325   lemma. We were also able to establish the property that the choice of
  2325   the next thread which takes over a lock is irrelevant for the correctness
  2326   the next thread which takes over a lock is irrelevant for the correctness
  2335   (the informal specification by Sha et al.~did not). 
  2336   (the informal specification by Sha et al.~did not). 
  2336   
  2337   
  2337 
  2338 
  2338   PIP is a scheduling algorithm for single-processor systems. We are
  2339   PIP is a scheduling algorithm for single-processor systems. We are
  2339   now living in a multi-processor world. Priority Inversion certainly
  2340   now living in a multi-processor world. Priority Inversion certainly
  2340   occurs also there, see for example \cite{Brandenburg11,Davis11}.  
  2341   occurs also there, see for example work by Brandenburg, and Davis and Burns \cite{Brandenburg11,Davis11}.  
  2341   However, there is very little ``foundational''
  2342   However, there is very little ``foundational''
  2342   work about PIP-algorithms on multi-processor systems.  We are not
  2343   work about PIP-algorithms on multi-processor systems.  We are not
  2343   aware of any correctness proofs, not even informal ones. There is an
  2344   aware of any correctness proofs, not even informal ones. There is an
  2344   implementation of a PIP-algorithm for multi-processors as part of the
  2345   implementation of a PIP-algorithm for multi-processors as part of the
  2345   ``real-time'' effort in Linux, including an informal description of the implemented scheduling
  2346   ``real-time'' effort in Linux, including an informal description of the implemented scheduling
  2346   algorithm given in \cite{LINUX}.  We estimate that the formal
  2347   algorithm given by Rostedt in \cite{LINUX}.  We estimate that the formal
  2347   verification of this algorithm, involving more fine-grained events,
  2348   verification of this algorithm, involving more fine-grained events,
  2348   is a magnitude harder than the one we presented here, but still
  2349   is a magnitude harder than the one we presented here, but still
  2349   within reach of current theorem proving technology. We leave this
  2350   within reach of current theorem proving technology. We leave this
  2350   for future work.
  2351   for future work.
  2351 
  2352 
  2352   To us, it seems sound reasoning about scheduling algorithms is fiendishly difficult
  2353   To us, it seems sound reasoning about scheduling algorithms is fiendishly difficult
  2353   if done informally by ``pencil-and-paper''. We infer this from the flawed proof
  2354   if done informally by ``pencil-and-paper''. We infer this from the flawed proof
  2354   in the paper by Sha et al.~\cite{Sha90} and also from \cite{Regehr} where Regehr 
  2355   in the paper by Sha et al.~\cite{Sha90} and also from \cite{Regehr} where Regehr 
  2355   points out an error in a paper about Preemption 
  2356   points out an error in a paper about Preemption 
  2356   Threshold Scheduling \cite{ThreadX}. The use of a theorem prover was
  2357   Threshold Scheduling by Wang and Saksena \cite{ThreadX}. The use of a theorem prover was
  2357   invaluable to us in order to be confident about the correctness of our reasoning 
  2358   invaluable to us in order to be confident about the correctness of our reasoning 
  2358   (for example no corner case can be overlooked).   
  2359   (for example no corner case can be overlooked).   
  2359   The most closely related work to ours is the formal verification in
  2360   The most closely related work to ours is the formal verification in
  2360   PVS of the Priority Ceiling Protocol done by Dutertre
  2361   PVS of the Priority Ceiling Protocol done by Dutertre~\cite{dutertre99b}---another solution to the Priority Inversion
  2361   \cite{dutertre99b}---another solution to the Priority Inversion
       
  2362   problem, which however needs static analysis of programs in order to
  2362   problem, which however needs static analysis of programs in order to
  2363   avoid it. There have been earlier formal investigations
  2363   avoid it. There have been earlier formal investigations
  2364   into PIP \cite{Faria08,Jahier09,Wellings07}, but they employ model
  2364   into PIP \cite{Faria08,Jahier09,Wellings07}, but they employ model
  2365   checking techniques. The results obtained by them apply,
  2365   checking techniques. The results obtained by them apply,
  2366   however, only to systems with a fixed size, such as a fixed number of 
  2366   however, only to systems with a fixed size, such as a fixed number of