% to get hardcopy of this lecture, you need the following files (plus the
% normal latex and tex base directories):
%     lecture.tex:     latex source file
%     macros.tex:      figure macros
%     psfig.tex:       postscript figure (psfig) macro definition
%     pocs-header.sty: Principles of Computer Systems lecture latex header
%
% run the following programs:
% latex lecture
%   -> Creates lecture.dvi, plus latex intermediate files
% latex lecture
%   -> Run a second time to get cross-references right
% dvi2ps lecture |  egrep -v '^showpage$' > lecture.ps
%   -> Merges the figures with the text, can also pipe to lpr
%
\documentstyle[12pt,pocs-header]{article}
\Scribe{Saeed Jaffer}
\Lecturer{Butler Lampson}
\LectureNumber{4}
\LectureDate{September 23, 1991}
\include{macros} % used to include figures in this document
\begin{document}
\MakeScribeTop

\section{File Systems Continued}

Today's topics:

\begin{itemize}
\item Allocation
\item Directories
\item Transactions
\end{itemize}

\section{Allocation}

Whenever a file is extended or truncated or is deleted or added, the
free space on the disk is either increased or decreased.  Free space is
defined as all of the blocks that are not reachable from the
directory.  

The questions we are addressing here:
\begin{itemize}
\item How do we find space on disk?
\item What data structures are appropriate?
\item How do you deal with failures?
\end{itemize}

A reasonable invariant is that a block is free if and only if it is
not reachable.  If one can find the disk address within a directory
then the memory is reachable.  We don't want to allocate memory that's
reachable because then we'll overwrite the current (valid) data.

\subsection{Bit Table}
One representation scheme to manage free space is to use a bit table,
call it {\tt Reachable:SEQ[BOOL]}, with one bit for each block on the
disk.  Each bit is a boolean indicating whether the block is free or
allocated.  One advantage of this scheme is that the table is of a
fixed size for a given disk.  However, this strategy proves
inefficient if only a few blocks are free, since most of the
space allocated for the table is wasted.

We need to perform two updates to the data structures: the status of
Reachable, and the disk addresses of the file's extents.  This is a
problem since the disk may crash between the two atomic operations.
For example, if we first update the file and then update the table, a
crash between them allows the new blocks of the file to be allocated
to a second file.  If the order is reversed, then the table could say
the blocks are in use when they are not.  Either way we cannot easily
implement the invariant:\\

\hspace*{1cm}{\tt Free[da] <=> ~Reachable[da]} \\

The main point behind all this is to try and find a free
block to allocate.  Let's try a slightly weaker invariant: \\

\hspace*{1cm}{\tt Free[da] => ~Reachable[da]} \\

\noindent With this invariant, it is OK to update the table and then
update the file.  After a while, this solution will take up a LOT of
space, since it periodically loses blocks.  The logical solution is to
garbage collect the blocks, which ensures that all unreachable blocks
are marked free.  (Crashing during Garbage Collection could be
problematic.)

The lesson: a weaker invariant often provides adequate functionality
with higher performance.


\section{Directories}

Directories are simply functions from pathnames to files (PN $->$ F).
One way to implement this is with {\em Encode} and {\em Decode}
functions where encode is a mapping from the internal to the external
representation (often a sequence of bytes that has to go out over a
wire), and decode is a mapping back from the external to internal
representation.  Note that t = {\em Decode}({\em Encode}(t)) for all t.

Some possible examples:
\begin{enumerate}
\item Internal: a list of real numbers \\
      External: a list containing the size of the numbers and then the
                numbers themselves.  	
\item Internal: a tree containing a node and two leaves	\\
      External: represent as an S-expression: 1(2 3) 
\item Internal: a network 		
      External: an S-expression using UID's for nodes that have
		already been defined.
\end{enumerate}

One scheme for a directory is to use an external representation
composed of a list of pairs of pathnames and disk addresses.  However,
this requires sequential scanning of possibly the entire directory to
locate a particular pair.  

One solution to the search problem is to sort the pairs and use a
binary search.  Another solution is to use B-trees which were in
originally devised for just this purpose.  However, in this case, we
must carefully examine atomicity to ensure that writes leave the
directory in a known state.

\begin{description}

\item[Table (Map):]
\begin{verbatim}
     Dir = SET[Pair]
     Pair = RECORD[pn, e]
\end{verbatim}

\begin{itemize}
 \item[] We need to store Dir on disk; how do we represent it as a disk
block?  How do we make it a sequence of bytes?

\item[]
\begin{verbatim}
      [pn, e]
       ^   ^              _________________________________
       |   |              | string | re | string | re . . .
       |  10 bytes        ---------------------------------
       |
      string
\end{verbatim}
Parsing this becomes a problem because of the string's length,
so let's store the string's length:
\begin{verbatim}
      _______________________________________________
      |    string     | re |    string     | re . . .
      -----------------------------------------------
      l,c0,c2...c(l-1)  10   l,c0,...c(l-1)  10
\end{verbatim}
Here {\tt l} is the length of the string.
\end{itemize}

\item[General Encoding/Decoding \\]
\begin{tt}
\hspace*{1cm} Encode(t) --> Data \\
\hspace*{1cm} Decode(data) --> T  RAISES failed \\
\end{tt}
For example,
\begin{verbatim}
    Pair: r1, r2
                   ___                      ___
                r1 | INT.Encode(r1.size) + r1 |   <-- Type of Encoding
                r2 | INT.Encode(r2.size) + r2 |
                   ---                      ---
\end{verbatim}

This a part of a general encoding technique called {\em TLV}\/
encoding; TLV stands for Type-Length-Value.  The idea is that you always
encode an items type (in a fixed number of bytes), then encode its
length (also in a fixed number of bytes), and then encode its value in
the specified number of bytes.  If you know a lot about what you're
coding, you can make a more specific coding scheme.  If not, you could use
something like this (TLV) general purpose coding scheme.


When we update we can't follow the simple algorithm of removing the entry
in the directory and updating the rest, since crashes would cause
inconsistency in such a scheme.  The solution is to add a byte to the
representation that indicates if the directory entry is in use.  Thus
to update a file we would add the new entry to the end and then mark
the old entry not in use.  We would periodically clean up the unused
blocks in the middle.  Problems: 1) If the unused entry spans 2 data blocks.
2) Renaming -- we want to make it atomic.

\end{description}

\section{Transactions}

How do you make them atomic in the presence of crashes?
\begin{verbatim}
        VS : Value space

        SS : States space

        A : Actions 
                         SS  ----->  ( SS , V )
                      initial         new   returned
                       state        state   values
\end{verbatim}

\noindent The action is a coding of the actual function.  We need to manipulate
the actions.

\begin{center}
\begin{tt}
\begin{tabular}{|l|l|p{3in}|}
\hline
Do & << vs := a(vs) >> & Do performs its actions on the volatile
state.\\ \hline  
Commit & << ss := vs >> & The volatile state that Do has been changing
becomes the stable state.\\ \hline 
Crash & << vs := ss >>  & The volatile state (i.e., the contents of memory)
is lost.\\\hline
\multicolumn{3}{c}{Crashes can only occur outside of the atomicity brackets.}
\end{tabular}
\end{tt}
\end{center}

All actions are done atomically to the volatile state.  When the volatile state
becomes an acceptable state, a commit can be done which sets the
stable state to be the volatile state.  When a crash occurs all changes made
since the last commit are lost, but all changes made before the last commit
are safe.  A crash cannot occur during a commit due to the atomicity
brackets. 

\subsection{File System Recovery Scheme Implementation}

The implementation of a recovery scheme is far more complicated than
its specification.  The main problem lies in atomicity -- the only
atomic actions that a real disk can perform is a single block write.
Since most updates, and certainly almost all commits, require more than
one block update, it is difficult to satisfy the atomicity specs in a
real implementation.  The solution to this problem is to divide the
complex actions into a log which is a series of atomic updates.  The
partitioning of the original problem leads to recovery problems if a
crash occurs while performing the series of atomic updates.  The
implementation takes care of that within its Redo procedure.

We'll use a LOG to record a sequence of the past actions.
An update is a single atomic action; a log is a sequence of updates.
We don't care about volatile state anymore, just the log and the stable state.
\begin{verbatim}
                        ss       =      ss        +      sl

                   stable state  =  stable state  +  stable log
                     of spec           of impl


                        vs       =      ss        +      vl

                  volatile state =  stable state  +  volatile log
                     of spec           of impl
\end{verbatim}

\noindent Implementation: \\
\begin{verbatim}
        Do(a) -> V =
            << VAR v, l |
                 (ss + vl + l, v) = a.meaning(ss + vl)
                 vl := vl + l;                      
                 RET V; >>

        Commit () = << sl := vl >>; vl := L{}; Redo ()
\end{verbatim}

\noindent Redo gets stable log into stable state

\begin{verbatim}
        Crash () = VAR l | vl := l
                   vl := L{}; Redo()

        Redo ()        = VAR l := sl |
                  DO l # L{} =>
                      << ss := l.head(ss) >>; l := l.tail
                  OD
                  sl := L{}
\end{verbatim}

We have to restrict updates so that this scheme will work.

\begin{verbatim}
     sl = 1 2 3 4 5 6

     ss = 1 2 3 # 1 # 1 2 3 4 5 # 1 2 # 1 2 3 4 5 6  (update complete)

     (# implies crash and Redo)
\end{verbatim}

\noindent Simple rule required: \\
\begin{verbatim}
      (ALL s, l | s + l + l = s + l)
\end{verbatim}

\noindent Applying a log to state+log must be just like applying the
log. (This property is called {\em idempotency}\/; an operation is
idempotent if applying it a second time has no effect.) How do we
achieve this?

\begin{itemize}
\item Make all updates writes (this can cause more problems)
\item Attach unique identifiers to each update and record which
      updates have been applied.
\item Instead of storing all performed UIDs, store the largest
      completed UID per disk block.  So when you complete
      another update within a data block, increment the
      largest UID.
\end{itemize}

\end{document}