[Tux3] Design note: The get_segs interface

Daniel Phillips phillips at phunq.net
Tue Dec 16 16:07:18 PST 2008


The handling of data file extents in filemap.c has up till now been 
monolithic and far from crystal clear.  The new get_segs interface 
imposes some structure in a way that maps pretty well to both kernel 
and userspace.  This interface will play a central role in atomic 
commit, which is the "Christmas Project".

The job of get_segs is to take a logical range of file blocks and fill 
in a vector of physical extents that specify how the logical ranges 
maps to physical blocks.  For write, get_segs allocates blocks as 
necessary, and for read, indicates regions that map to holes in a 
sparse file.

A lot of the work get_segs has to do is similar or identical for read 
and write.  It probes a btree to find a dleaf containing extent 
entries, finds the right place in the dleaf, then scans through the 
existing extents, adding segments that are found to the list of output 
segments, or creating new ones as necessary.

Gaps found between existing segments are treated differently for read 
and write: read adds them as holes to the output segment list while 
write allocates new regions of blocks to fill the gap.

After completing a list of output segments, read is done, but write 
needs to update the data btree leaf with its changes.  At this point, 
it may discover that the new list of segments will not fit in the 
existing leaf, so it splits the leaf.  (While writing this post, I 
noticed a gigantic bug in filemap.c: as currently implemented, get_segs 
just repeats the whole scan and allocate, thus leaking all the blocks 
it allocated on the first attempt... moral of the story is, writing 
posts about computer code is good!)

After a list of physical extents is retrieved from get_segs, the caller 
does the necessary IO.  For userspace, this means calling 
diskread/diskwrite in a loop across segments, while for kernel, the 
block IO library fills in buffer details or attaches pages to a bio for 
asynchronous transfer to disk.  Holes on read are handled by filling 
the cached block with zero.

This strategy works pretty well for traditional write-in-place behavior.  
But we also need to support redirect-on-write, that is, reallocate any 
existing extents to new physical positions to avoid overwriting parts 
of an existing, consistent filesystem image on disk.  Redirect is 
required for snapshotting, and to support stronger-than-Posix semantics 
for atomic update of file data, along the lines of Ext3's data=journal 
mode.

To support redirect, get_segs has to allocate new extents not only for 
holes, but also to replace existing extents.  The replaced extents may 
have to be freed, but not immediately: those blocks must not be 
reallocated until after the current delta has fully arrived on disk.  
(When we have versioning, existing blocks are freed only when they 
become unused by any version.)  So get_segs will need a way of 
reporting the list of freed blocks, which it does not have yet.  But it 
also does not need it right now, because we do not have versioning at 
the moment, and a weaker form of atomic commit similar to Ext3's 
data=ordered mode will serve our immediate needs.

Just looking at the code (again, writing about this helps) get_segs 
could easily be refactored so that gaps are returned as holes for 
write, just like read, and the existing update_tree code can be pulled 
out into a separate function that calls get_segs, then does the 
write-specific processing.

In spirit, get_segs resembles the traditional ->get_block interface
used by the block IO library, but has an interface better suited to the 
problem rather than staying wedded to an ancient existing structure 
(buffer_head) as ->get_block does.  It works well as a way of gluing 
the traditional ->get_block interface onto our model, as can be seen 
from the implementation of tux3_get_block in filemap.c.

For mapping big regions with complex physical allocation patterns, 
get_segs provides a much more efficient interface than the existing 
block-at-a-time arrangement using buffer_heads, which was somewhat 
painfully extended over time to handle multiple contiguous blocks in 
certain circumstances.

The get_segs interface allows us to invert the traditional block library 
approach of calling ->get_blocks multiple times to build up a 
multi-block IO transfer.  Instead, we retrieve the physical mapping for 
a big logical region in one call to get_segs, with just one probe into 
the btree.

The current implementation of get_segs has a few deficiencies, still 
being worked on:

  - Existing extents that overlap the beginning or end of the logical
    region are not handled correctly on write.

  - The inner loop on extents always stops after the first extent,
    which was to keep it compatible with Hirofumi's functional
    implementation of tux3_get_block.  Now the original loop can be
    restored, and we just tell get_segs to return one segment at a
    time.

In fact, the segments I talk about here are really just extents.
When the interface settles down and proves itself out, the natural
thing to do is rename it as get_extents, which could potentially
evolve into a new, improved block library interface better suited
to modern filesystems.

Regards,

Daniel

_______________________________________________
Tux3 mailing list
Tux3 at tux3.org
http://mailman.tux3.org/cgi-bin/mailman/listinfo/tux3



More information about the Tux3 mailing list