Commit e0484344 authored by David Howells's avatar David Howells

fscache: Rewrite documentation

Rewrite the fscache documentation.

Changes
=======
ver #3:
 - The volume coherency data is now an arbitrarily-sized blob, not a u64.

ver #2:
 - Put quoting around some bits of C being referred to in the docs[1].
 - Stripped the markup off the ref to the netfs lib doc[2].
Signed-off-by: default avatarDavid Howells <dhowells@redhat.com>
Reviewed-by: default avatarJeff Layton <jlayton@kernel.org>
cc: linux-cachefs@redhat.com
Link: https://lore.kernel.org/r/20211130175119.63d0e7aa@canb.auug.org.au/ [1]
Link: https://lore.kernel.org/r/20211130162311.105fcfa5@canb.auug.org.au/ [2]
Link: https://lore.kernel.org/r/163819672252.215744.15454333549935901588.stgit@warthog.procyon.org.uk/ # v1
Link: https://lore.kernel.org/r/163906986754.143852.17703291789683936950.stgit@warthog.procyon.org.uk/ # v2
Link: https://lore.kernel.org/r/163967193834.1823006.15991526817786159772.stgit@warthog.procyon.org.uk/ # v3
Link: https://lore.kernel.org/r/164021585970.640689.3162537597817521032.stgit@warthog.procyon.org.uk/ # v4
parent 1702e797
.. SPDX-License-Identifier: GPL-2.0
==========================
FS-Cache Cache backend API
==========================
=================
Cache Backend API
=================
The FS-Cache system provides an API by which actual caches can be supplied to
FS-Cache for it to then serve out to network filesystems and other interested
parties.
parties. This API is used by::
This API is declared in <linux/fscache-cache.h>.
#include <linux/fscache-cache.h>.
Initialising and Registering a Cache
====================================
To start off, a cache definition must be initialised and registered for each
cache the backend wants to make available. For instance, CacheFS does this in
the fill_super() operation on mounting.
The cache definition (struct fscache_cache) should be initialised by calling::
void fscache_init_cache(struct fscache_cache *cache,
struct fscache_cache_ops *ops,
const char *idfmt,
...);
Where:
* "cache" is a pointer to the cache definition;
* "ops" is a pointer to the table of operations that the backend supports on
this cache; and
* "idfmt" is a format and printf-style arguments for constructing a label
for the cache.
The cache should then be registered with FS-Cache by passing a pointer to the
previously initialised cache definition to::
int fscache_add_cache(struct fscache_cache *cache,
struct fscache_object *fsdef,
const char *tagname);
Two extra arguments should also be supplied:
* "fsdef" which should point to the object representation for the FS-Cache
master index in this cache. Netfs primary index entries will be created
here. FS-Cache keeps the caller's reference to the index object if
successful and will release it upon withdrawal of the cache.
* "tagname" which, if given, should be a text string naming this cache. If
this is NULL, the identifier will be used instead. For CacheFS, the
identifier is set to name the underlying block device and the tag can be
supplied by mount.
This function may return -ENOMEM if it ran out of memory or -EEXIST if the tag
is already in use. 0 will be returned on success.
Unregistering a Cache
=====================
A cache can be withdrawn from the system by calling this function with a
pointer to the cache definition::
void fscache_withdraw_cache(struct fscache_cache *cache);
In CacheFS's case, this is called by put_super().
Security
Overview
========
The cache methods are executed one of two contexts:
(1) that of the userspace process that issued the netfs operation that caused
the cache method to be invoked, or
(2) that of one of the processes in the FS-Cache thread pool.
In either case, this may not be an appropriate context in which to access the
cache.
The calling process's fsuid, fsgid and SELinux security identities may need to
be masqueraded for the duration of the cache driver's access to the cache.
This is left to the cache to handle; FS-Cache makes no effort in this regard.
Interaction with the API is handled on three levels: cache, volume and data
storage, and each level has its own type of cookie object:
Control and Statistics Presentation
===================================
======================= =======================
COOKIE C TYPE
======================= =======================
Cache cookie struct fscache_cache
Volume cookie struct fscache_volume
Data storage cookie struct fscache_cookie
======================= =======================
The cache may present data to the outside world through FS-Cache's interfaces
in sysfs and procfs - the former for control and the latter for statistics.
Cookies are used to provide some filesystem data to the cache, manage state and
pin the cache during access in addition to acting as reference points for the
API functions. Each cookie has a debugging ID that is included in trace points
to make it easier to correlate traces. Note, though, that debugging IDs are
simply allocated from incrementing counters and will eventually wrap.
A sysfs directory called /sys/fs/fscache/<cachetag>/ is created if CONFIG_SYSFS
is enabled. This is accessible through the kobject struct fscache_cache::kobj
and is for use by the cache as it sees fit.
The cache backend and the network filesystem can both ask for cache cookies -
and if they ask for one of the same name, they'll get the same cookie. Volume
and data cookies, however, are created at the behest of the filesystem only.
Relevant Data Structures
========================
Cache Cookies
=============
* Index/Data file FS-Cache representation cookie::
Caches are represented in the API by cache cookies. These are objects of
type::
struct fscache_cookie {
struct fscache_object_def *def;
struct fscache_netfs *netfs;
void *netfs_data;
...
};
The fields that might be of use to the backend describe the object
definition, the netfs definition and the netfs's data for this cookie.
The object definition contain functions supplied by the netfs for loading
and matching index entries; these are required to provide some of the
cache operations.
* In-cache object representation::
struct fscache_object {
int debug_id;
enum {
FSCACHE_OBJECT_RECYCLING,
...
} state;
spinlock_t lock
struct fscache_cache *cache;
struct fscache_cookie *cookie;
struct fscache_cache {
void *cache_priv;
unsigned int debug_id;
char *name;
...
};
Structures of this type should be allocated by the cache backend and
passed to FS-Cache when requested by the appropriate cache operation. In
the case of CacheFS, they're embedded in CacheFS's internal object
structures.
There are a few fields that the cache backend might be interested in. The
``debug_id`` can be used in tracing to match lines referring to the same cache
and ``name`` is the name the cache was registered with. The ``cache_priv``
member is private data provided by the cache when it is brought online. The
other fields are for internal use.
The debug_id is a simple integer that can be used in debugging messages
that refer to a particular object. In such a case it should be printed
using "OBJ%x" to be consistent with FS-Cache.
Each object contains a pointer to the cookie that represents the object it
is backing. An object should retired when put_object() is called if it is
in state FSCACHE_OBJECT_RECYCLING. The fscache_object struct should be
initialised by calling fscache_object_init(object).
Registering a Cache
===================
When a cache backend wants to bring a cache online, it should first register
the cache name and that will get it a cache cookie. This is done with::
* FS-Cache operation record::
struct fscache_cache *fscache_acquire_cache(const char *name);
struct fscache_operation {
atomic_t usage;
struct fscache_object *object;
unsigned long flags;
#define FSCACHE_OP_EXCLUSIVE
void (*processor)(struct fscache_operation *op);
void (*release)(struct fscache_operation *op);
...
};
This will look up and potentially create a cache cookie. The cache cookie may
have already been created by a network filesystem looking for it, in which case
that cache cookie will be used. If the cache cookie is not in use by another
cache, it will be moved into the preparing state, otherwise it will return
busy.
FS-Cache has a pool of threads that it uses to give CPU time to the
various asynchronous operations that need to be done as part of driving
the cache. These are represented by the above structure. The processor
method is called to give the op CPU time, and the release method to get
rid of it when its usage count reaches 0.
If successful, the cache backend can then start setting up the cache. In the
event that the initialisation fails, the cache backend should call::
An operation can be made exclusive upon an object by setting the
appropriate flag before enqueuing it with fscache_enqueue_operation(). If
an operation needs more processing time, it should be enqueued again.
void fscache_relinquish_cookie(struct fscache_cache *cache);
to reset and discard the cookie.
* FS-Cache retrieval operation record::
struct fscache_retrieval {
struct fscache_operation op;
struct address_space *mapping;
struct list_head *to_do;
...
};
Bringing a Cache Online
=======================
A structure of this type is allocated by FS-Cache to record retrieval and
allocation requests made by the netfs. This struct is then passed to the
backend to do the operation. The backend may get extra refs to it by
calling fscache_get_retrieval() and refs may be discarded by calling
fscache_put_retrieval().
Once the cache is set up, it can be brought online by calling::
A retrieval operation can be used by the backend to do retrieval work. To
do this, the retrieval->op.processor method pointer should be set
appropriately by the backend and fscache_enqueue_retrieval() called to
submit it to the thread pool. CacheFiles, for example, uses this to queue
page examination when it detects PG_lock being cleared.
int fscache_add_cache(struct fscache_cache *cache,
const struct fscache_cache_ops *ops,
void *cache_priv);
The to_do field is an empty list available for the cache backend to use as
it sees fit.
This stores the cache operations table pointer and cache private data into the
cache cookie and moves the cache to the active state, thereby allowing accesses
to take place.
* FS-Cache storage operation record::
Withdrawing a Cache From Service
================================
struct fscache_storage {
struct fscache_operation op;
pgoff_t store_limit;
...
};
The cache backend can withdraw a cache from service by calling this function::
A structure of this type is allocated by FS-Cache to record outstanding
writes to be made. FS-Cache itself enqueues this operation and invokes
the write_page() method on the object at appropriate times to effect
storage.
void fscache_withdraw_cache(struct fscache_cache *cache);
This moves the cache to the withdrawn state to prevent new cache- and
volume-level accesses from starting and then waits for outstanding cache-level
accesses to complete.
Cache Operations
================
The cache must then go through the data storage objects it has and tell fscache
to withdraw them, calling::
The cache backend provides FS-Cache with a table of operations that can be
performed on the denizens of the cache. These are held in a structure of type:
void fscache_withdraw_cookie(struct fscache_cookie *cookie);
::
on the cookie that each object belongs to. This schedules the specified cookie
for withdrawal. This gets offloaded to a workqueue. The cache backend can
test for completion by calling::
struct fscache_cache_ops
bool fscache_are_objects_withdrawn(struct fscache_cookie *cache);
* Name of cache provider [mandatory]::
Once all the cookies are withdrawn, a cache backend can withdraw all the
volumes, calling::
const char *name
void fscache_withdraw_volume(struct fscache_volume *volume);
This isn't strictly an operation, but should be pointed at a string naming
the backend.
to tell fscache that a volume has been withdrawn. This waits for all
outstanding accesses on the volume to complete before returning.
When the the cache is completely withdrawn, fscache should be notified by
calling::
* Allocate a new object [mandatory]::
void fscache_cache_relinquish(struct fscache_cache *cache);
struct fscache_object *(*alloc_object)(struct fscache_cache *cache,
struct fscache_cookie *cookie)
to clear fields in the cookie and discard the caller's ref on it.
This method is used to allocate a cache object representation to back a
cookie in a particular cache. fscache_object_init() should be called on
the object to initialise it prior to returning.
This function may also be used to parse the index key to be used for
multiple lookup calls to turn it into a more convenient form. FS-Cache
will call the lookup_complete() method to allow the cache to release the
form once lookup is complete or aborted.
Volume Cookies
==============
Within a cache, the data storage objects are organised into logical volumes.
These are represented in the API as objects of type::
* Look up and create object [mandatory]::
struct fscache_volume {
struct fscache_cache *cache;
void *cache_priv;
unsigned int debug_id;
char *key;
unsigned int key_hash;
...
u8 coherency_len;
u8 coherency[];
};
void (*lookup_object)(struct fscache_object *object)
There are a number of fields here that are of interest to the caching backend:
This method is used to look up an object, given that the object is already
allocated and attached to the cookie. This should instantiate that object
in the cache if it can.
* ``cache`` - The parent cache cookie.
The method should call fscache_object_lookup_negative() as soon as
possible if it determines the object doesn't exist in the cache. If the
object is found to exist and the netfs indicates that it is valid then
fscache_obtained_object() should be called once the object is in a
position to have data stored in it. Similarly, fscache_obtained_object()
should also be called once a non-present object has been created.
* ``cache_priv`` - A place for the cache to stash private data.
If a lookup error occurs, fscache_object_lookup_error() should be called
to abort the lookup of that object.
* ``debug_id`` - A debugging ID for logging in tracepoints.
* ``key`` - A printable string with no '/' characters in it that represents
the index key for the volume. The key is NUL-terminated and padded out to
a multiple of 4 bytes.
* Release lookup data [mandatory]::
* ``key_hash`` - A hash of the index key. This should work out the same, no
matter the cpu arch and endianness.
void (*lookup_complete)(struct fscache_object *object)
* ``coherency`` - A piece of coherency data that should be checked when the
volume is bound to in the cache.
This method is called to ask the cache to release any resources it was
using to perform a lookup.
* ``coherency_len`` - The amount of data in the coherency buffer.
* Increment object refcount [mandatory]::
Data Storage Cookies
====================
struct fscache_object *(*grab_object)(struct fscache_object *object)
A volume is a logical group of data storage objects, each of which is
represented to the network filesystem by a cookie. Cookies are represented in
the API as objects of type::
This method is called to increment the reference count on an object. It
may fail (for instance if the cache is being withdrawn) by returning NULL.
It should return the object pointer if successful.
struct fscache_cookie {
struct fscache_volume *volume;
void *cache_priv;
unsigned long flags;
unsigned int debug_id;
unsigned int inval_counter;
loff_t object_size;
u8 advice;
u32 key_hash;
u8 key_len;
u8 aux_len;
...
};
The fields in the cookie that are of interest to the cache backend are:
* Lock/Unlock object [mandatory]::
* ``volume`` - The parent volume cookie.
void (*lock_object)(struct fscache_object *object)
void (*unlock_object)(struct fscache_object *object)
* ``cache_priv`` - A place for the cache to stash private data.
These methods are used to exclusively lock an object. It must be possible
to schedule with the lock held, so a spinlock isn't sufficient.
* ``flags`` - A collection of bit flags, including:
* FSCACHE_COOKIE_NO_DATA_TO_READ - There is no data available in the
cache to be read as the cookie has been created or invalidated.
* Pin/Unpin object [optional]::
* FSCACHE_COOKIE_NEEDS_UPDATE - The coherency data and/or object size has
been changed and needs committing.
int (*pin_object)(struct fscache_object *object)
void (*unpin_object)(struct fscache_object *object)
* FSCACHE_COOKIE_LOCAL_WRITE - The netfs's data has been modified
locally, so the cache object may be in an incoherent state with respect
to the server.
These methods are used to pin an object into the cache. Once pinned an
object cannot be reclaimed to make space. Return -ENOSPC if there's not
enough space in the cache to permit this.
* FSCACHE_COOKIE_HAVE_DATA - The backend should set this if it
successfully stores data into the cache.
* FSCACHE_COOKIE_RETIRED - The cookie was invalidated when it was
relinquished and the cached data should be discarded.
* Check coherency state of an object [mandatory]::
* ``debug_id`` - A debugging ID for logging in tracepoints.
int (*check_consistency)(struct fscache_object *object)
* ``inval_counter`` - The number of invalidations done on the cookie.
This method is called to have the cache check the saved auxiliary data of
the object against the netfs's idea of the state. 0 should be returned
if they're consistent and -ESTALE otherwise. -ENOMEM and -ERESTARTSYS
may also be returned.
* ``advice`` - Information about how the cookie is to be used.
* Update object [mandatory]::
* ``key_hash`` - A hash of the index key. This should work out the same, no
matter the cpu arch and endianness.
int (*update_object)(struct fscache_object *object)
* ``key_len`` - The length of the index key.
This is called to update the index entry for the specified object. The
new information should be in object->cookie->netfs_data. This can be
obtained by calling object->cookie->def->get_aux()/get_attr().
* ``aux_len`` - The length of the coherency data buffer.
Each cookie has an index key, which may be stored inline to the cookie or
elsewhere. A pointer to this can be obtained by calling::
* Invalidate data object [mandatory]::
void *fscache_get_key(struct fscache_cookie *cookie);
int (*invalidate_object)(struct fscache_operation *op)
The index key is a binary blob, the storage for which is padded out to a
multiple of 4 bytes.
This is called to invalidate a data object (as pointed to by op->object).
All the data stored for this object should be discarded and an
attr_changed operation should be performed. The caller will follow up
with an object update operation.
Each cookie also has a buffer for coherency data. This may also be inline or
detached from the cookie and a pointer is obtained by calling::
fscache_op_complete() must be called on op before returning.
void *fscache_get_aux(struct fscache_cookie *cookie);
* Discard object [mandatory]::
void (*drop_object)(struct fscache_object *object)
Cookie Accounting
=================
This method is called to indicate that an object has been unbound from its
cookie, and that the cache should release the object's resources and
retire it if it's in state FSCACHE_OBJECT_RECYCLING.
Data storage cookies are counted and this is used to block cache withdrawal
completion until all objects have been destroyed. The following functions are
provided to the cache to deal with that::
This method should not attempt to release any references held by the
caller. The caller will invoke the put_object() method as appropriate.
void fscache_count_object(struct fscache_cache *cache);
void fscache_uncount_object(struct fscache_cache *cache);
void fscache_wait_for_objects(struct fscache_cache *cache);
The count function records the allocation of an object in a cache and the
uncount function records its destruction. Warning: by the time the uncount
function returns, the cache may have been destroyed.
* Release object reference [mandatory]::
The wait function can be used during the withdrawal procedure to wait for
fscache to finish withdrawing all the objects in the cache. When it completes,
there will be no remaining objects referring to the cache object or any volume
objects.
void (*put_object)(struct fscache_object *object)
This method is used to discard a reference to an object. The object may
be freed when all the references to it are released.
Cache Management API
====================
The cache backend implements the cache management API by providing a table of
operations that fscache can use to manage various aspects of the cache. These
are held in a structure of type::
* Synchronise a cache [mandatory]::
struct fscache_cache_ops {
const char *name;
...
};
void (*sync)(struct fscache_cache *cache)
This contains a printable name for the cache backend driver plus a number of
pointers to methods to allow fscache to request management of the cache:
This is called to ask the backend to synchronise a cache with its backing
device.
* Set up a volume cookie [optional]::
void (*acquire_volume)(struct fscache_volume *volume);
* Dissociate a cache [mandatory]::
This method is called when a volume cookie is being created. The caller
holds a cache-level access pin to prevent the cache from going away for
the duration. This method should set up the resources to access a volume
in the cache and should not return until it has done so.
void (*dissociate_pages)(struct fscache_cache *cache)
If successful, it can set ``cache_priv`` to its own data.
This is called to ask a cache to perform any page dissociations as part of
cache withdrawal.
* Clean up volume cookie [optional]::
* Notification that the attributes on a netfs file changed [mandatory]::
void (*free_volume)(struct fscache_volume *volume);
int (*attr_changed)(struct fscache_object *object);
This method is called when a volume cookie is being released if
``cache_priv`` is set.
This is called to indicate to the cache that certain attributes on a netfs
file have changed (for example the maximum size a file may reach). The
cache can read these from the netfs by calling the cookie's get_attr()
method.
The cache may use the file size information to reserve space on the cache.
It should also call fscache_set_store_limit() to indicate to FS-Cache the
highest byte it's willing to store for an object.
* Look up a cookie in the cache [mandatory]::
This method may return -ve if an error occurred or the cache object cannot
be expanded. In such a case, the object will be withdrawn from service.
bool (*lookup_cookie)(struct fscache_cookie *cookie);
This operation is run asynchronously from FS-Cache's thread pool, and
storage and retrieval operations from the netfs are excluded during the
execution of this operation.
This method is called to look up/create the resources needed to access the
data storage for a cookie. It is called from a worker thread with a
volume-level access pin in the cache to prevent it from being withdrawn.
True should be returned if successful and false otherwise. If false is
returned, the withdraw_cookie op (see below) will be called.
* Reserve cache space for an object's data [optional]::
If lookup fails, but the object could still be created (e.g. it hasn't
been cached before), then::
int (*reserve_space)(struct fscache_object *object, loff_t size);
void fscache_cookie_lookup_negative(
struct fscache_cookie *cookie);
This is called to request that cache space be reserved to hold the data
for an object and the metadata used to track it. Zero size should be
taken as request to cancel a reservation.
can be called to let the network filesystem proceed and start downloading
stuff whilst the cache backend gets on with the job of creating things.
This should return 0 if successful, -ENOSPC if there isn't enough space
available, or -ENOMEM or -EIO on other errors.
If successful, ``cookie->cache_priv`` can be set.
The reservation may exceed the current size of the object, thus permitting
future expansion. If the amount of space consumed by an object would
exceed the reservation, it's permitted to refuse requests to allocate
pages, but not required. An object may be pruned down to its reservation
size if larger than that already.
* Withdraw an object without any cookie access counts held [mandatory]::
* Request page be read from cache [mandatory]::
void (*withdraw_cookie)(struct fscache_cookie *cookie);
int (*read_or_alloc_page)(struct fscache_retrieval *op,
struct page *page,
gfp_t gfp)
This method is called to withdraw a cookie from service. It will be
called when the cookie is relinquished by the netfs, withdrawn or culled
by the cache backend or closed after a period of non-use by fscache.
This is called to attempt to read a netfs page from the cache, or to
reserve a backing block if not. FS-Cache will have done as much checking
as it can before calling, but most of the work belongs to the backend.
The caller doesn't hold any access pins, but it is called from a
non-reentrant work item to manage races between the various ways
withdrawal can occur.
If there's no page in the cache, then -ENODATA should be returned if the
backend managed to reserve a backing block; -ENOBUFS or -ENOMEM if it
didn't.
The cookie will have the ``FSCACHE_COOKIE_RETIRED`` flag set on it if the
associated data is to be removed from the cache.
If there is suitable data in the cache, then a read operation should be
queued and 0 returned. When the read finishes, fscache_end_io() should be
called.
The fscache_mark_pages_cached() should be called for the page if any cache
metadata is retained. This will indicate to the netfs that the page needs
explicit uncaching. This operation takes a pagevec, thus allowing several
pages to be marked at once.
* Change the size of a data storage object [mandatory]::
The retrieval record pointed to by op should be retained for each page
queued and released when I/O on the page has been formally ended.
fscache_get/put_retrieval() are available for this purpose.
void (*resize_cookie)(struct netfs_cache_resources *cres,
loff_t new_size);
The retrieval record may be used to get CPU time via the FS-Cache thread
pool. If this is desired, the op->op.processor should be set to point to
the appropriate processing routine, and fscache_enqueue_retrieval() should
be called at an appropriate point to request CPU time. For instance, the
retrieval routine could be enqueued upon the completion of a disk read.
The to_do field in the retrieval record is provided to aid in this.
This method is called to inform the cache backend of a change in size of
the netfs file due to local truncation. The cache backend should make all
of the changes it needs to make before returning as this is done under the
netfs inode mutex.
If an I/O error occurs, fscache_io_error() should be called and -ENOBUFS
returned if possible or fscache_end_io() called with a suitable error
code.
The caller holds a cookie-level access pin to prevent a race with
withdrawal and the netfs must have the cookie marked in-use to prevent
garbage collection or culling from removing any resources.
fscache_put_retrieval() should be called after a page or pages are dealt
with. This will complete the operation when all pages are dealt with.
* Invalidate a data storage object [mandatory]::
* Request pages be read from cache [mandatory]::
bool (*invalidate_cookie)(struct fscache_cookie *cookie);
int (*read_or_alloc_pages)(struct fscache_retrieval *op,
struct list_head *pages,
unsigned *nr_pages,
gfp_t gfp)
This is called when the network filesystem detects a third-party
modification or when an O_DIRECT write is made locally. This requests
that the cache backend should throw away all the data in the cache for
this object and start afresh. It should return true if successful and
false otherwise.
This is like the read_or_alloc_page() method, except it is handed a list
of pages instead of one page. Any pages on which a read operation is
started must be added to the page cache for the specified mapping and also
to the LRU. Such pages must also be removed from the pages list and
``*nr_pages`` decremented per page.
On entry, new I O/operations are blocked. Once the cache is in a position
to accept I/O again, the backend should release the block by calling::
If there was an error such as -ENOMEM, then that should be returned; else
if one or more pages couldn't be read or allocated, then -ENOBUFS should
be returned; else if one or more pages couldn't be read, then -ENODATA
should be returned. If all the pages are dispatched then 0 should be
returned.
void fscache_resume_after_invalidation(struct fscache_cookie *cookie);
If the method returns false, caching will be withdrawn for this cookie.
* Request page be allocated in the cache [mandatory]::
int (*allocate_page)(struct fscache_retrieval *op,
struct page *page,
gfp_t gfp)
* Prepare to make local modifications to the cache [mandatory]::
This is like the read_or_alloc_page() method, except that it shouldn't
read from the cache, even if there's data there that could be retrieved.
It should, however, set up any internal metadata required such that
the write_page() method can write to the cache.
void (*prepare_to_write)(struct fscache_cookie *cookie);
If there's no backing block available, then -ENOBUFS should be returned
(or -ENOMEM if there were other problems). If a block is successfully
allocated, then the netfs page should be marked and 0 returned.
This method is called when the network filesystem finds that it is going
to need to modify the contents of the cache due to local writes or
truncations. This gives the cache a chance to note that a cache object
may be incoherent with respect to the server and may need writing back
later. This may also cause the cached data to be scrapped on later
rebinding if not properly committed.
* Request pages be allocated in the cache [mandatory]::
* Begin an operation for the netfs lib [mandatory]::
int (*allocate_pages)(struct fscache_retrieval *op,
struct list_head *pages,
unsigned *nr_pages,
gfp_t gfp)
bool (*begin_operation)(struct netfs_cache_resources *cres,
enum fscache_want_state want_state);
This is an multiple page version of the allocate_page() method. pages and
nr_pages should be treated as for the read_or_alloc_pages() method.
This method is called when an I/O operation is being set up (read, write
or resize). The caller holds an access pin on the cookie and must have
marked the cookie as in-use.
If it can, the backend should attach any resources it needs to keep around
to the netfs_cache_resources object and return true.
* Request page be written to cache [mandatory]::
If it can't complete the setup, it should return false.
int (*write_page)(struct fscache_storage *op,
struct page *page);
The want_state parameter indicates the state the caller needs the cache
object to be in and what it wants to do during the operation:
This is called to write from a page on which there was a previously
successful read_or_alloc_page() call or similar. FS-Cache filters out
pages that don't have mappings.
* ``FSCACHE_WANT_PARAMS`` - The caller just wants to access cache
object parameters; it doesn't need to do data I/O yet.
This method is called asynchronously from the FS-Cache thread pool. It is
not required to actually store anything, provided -ENODATA is then
returned to the next read of this page.
* ``FSCACHE_WANT_READ`` - The caller wants to read data.
If an error occurred, then a negative error code should be returned,
otherwise zero should be returned. FS-Cache will take appropriate action
in response to an error, such as withdrawing this object.
* ``FSCACHE_WANT_WRITE`` - The caller wants to write to or resize the
cache object.
If this method returns success then FS-Cache will inform the netfs
appropriately.
Note that there won't necessarily be anything attached to the cookie's
cache_priv yet if the cookie is still being created.
* Discard retained per-page metadata [mandatory]::
Data I/O API
============
void (*uncache_page)(struct fscache_object *object, struct page *page)
A cache backend provides a data I/O API by through the netfs library's ``struct
netfs_cache_ops`` attached to a ``struct netfs_cache_resources`` by the
``begin_operation`` method described above.
This is called when a netfs page is being evicted from the pagecache. The
cache backend should tear down any internal representation or tracking it
maintains for this page.
See the Documentation/filesystems/netfs_library.rst for a description.
FS-Cache Utilities
==================
Miscellaneous Functions
=======================
FS-Cache provides some utilities that a cache backend may make use of:
* Note occurrence of an I/O error in a cache::
void fscache_io_error(struct fscache_cache *cache)
void fscache_io_error(struct fscache_cache *cache);
This tells FS-Cache that an I/O error occurred in the cache. After this
has been called, only resource dissociation operations (object and page
release) will be passed from the netfs to the cache backend for the
specified cache.
This tells FS-Cache that an I/O error occurred in the cache. This
prevents any new I/O from being started on the cache.
This does not actually withdraw the cache. That must be done separately.
* Note cessation of caching on a cookie due to failure::
* Invoke the retrieval I/O completion function::
void fscache_end_io(struct fscache_retrieval *op, struct page *page,
int error);
This is called to note the end of an attempt to retrieve a page. The
error value should be 0 if successful and an error otherwise.
* Record that one or more pages being retrieved or allocated have been dealt
with::
void fscache_retrieval_complete(struct fscache_retrieval *op,
int n_pages);
This is called to record the fact that one or more pages have been dealt
with and are no longer the concern of this operation. When the number of
pages remaining in the operation reaches 0, the operation will be
completed.
* Record operation completion::
void fscache_op_complete(struct fscache_operation *op);
This is called to record the completion of an operation. This deducts
this operation from the parent object's run state, potentially permitting
one or more pending operations to start running.
* Set highest store limit::
void fscache_set_store_limit(struct fscache_object *object,
loff_t i_size);
This sets the limit FS-Cache imposes on the highest byte it's willing to
try and store for a netfs. Any page over this limit is automatically
rejected by fscache_read_alloc_page() and co with -ENOBUFS.
* Mark pages as being cached::
void fscache_mark_pages_cached(struct fscache_retrieval *op,
struct pagevec *pagevec);
This marks a set of pages as being cached. After this has been called,
the netfs must call fscache_uncache_page() to unmark the pages.
* Perform coherency check on an object::
enum fscache_checkaux fscache_check_aux(struct fscache_object *object,
const void *data,
uint16_t datalen);
This asks the netfs to perform a coherency check on an object that has
just been looked up. The cookie attached to the object will determine the
netfs to use. data and datalen should specify where the auxiliary data
retrieved from the cache can be found.
One of three values will be returned:
FSCACHE_CHECKAUX_OKAY
The coherency data indicates the object is valid as is.
FSCACHE_CHECKAUX_NEEDS_UPDATE
The coherency data needs updating, but otherwise the object is
valid.
FSCACHE_CHECKAUX_OBSOLETE
The coherency data indicates that the object is obsolete and should
be discarded.
* Initialise a freshly allocated object::
void fscache_object_init(struct fscache_object *object);
This initialises all the fields in an object representation.
* Indicate the destruction of an object::
void fscache_object_destroyed(struct fscache_cache *cache);
This must be called to inform FS-Cache that an object that belonged to a
cache has been destroyed and deallocated. This will allow continuation
of the cache withdrawal process when it is stopped pending destruction of
all the objects.
* Indicate negative lookup on an object::
void fscache_object_lookup_negative(struct fscache_object *object);
This is called to indicate to FS-Cache that a lookup process for an object
found a negative result.
This changes the state of an object to permit reads pending on lookup
completion to go off and start fetching data from the netfs server as it's
known at this point that there can't be any data in the cache.
This may be called multiple times on an object. Only the first call is
significant - all subsequent calls are ignored.
* Indicate an object has been obtained::
void fscache_obtained_object(struct fscache_object *object);
This is called to indicate to FS-Cache that a lookup process for an object
produced a positive result, or that an object was created. This should
only be called once for any particular object.
This changes the state of an object to indicate:
(1) if no call to fscache_object_lookup_negative() has been made on
this object, that there may be data available, and that reads can
now go and look for it; and
(2) that writes may now proceed against this object.
* Indicate that object lookup failed::
void fscache_object_lookup_error(struct fscache_object *object);
This marks an object as having encountered a fatal error (usually EIO)
and causes it to move into a state whereby it will be withdrawn as soon
as possible.
* Indicate that a stale object was found and discarded::
void fscache_object_retrying_stale(struct fscache_object *object);
This is called to indicate that the lookup procedure found an object in
the cache that the netfs decided was stale. The object has been
discarded from the cache and the lookup will be performed again.
* Indicate that the caching backend killed an object::
void fscache_object_mark_killed(struct fscache_object *object,
enum fscache_why_object_killed why);
This is called to indicate that the cache backend preemptively killed an
object. The why parameter should be set to indicate the reason:
void fscache_caching_failed(struct fscache_cookie *cookie);
FSCACHE_OBJECT_IS_STALE
- the object was stale and needs discarding.
This notes that a the caching that was being done on a cookie failed in
some way, for instance the backing storage failed to be created or
invalidation failed and that no further I/O operations should take place
on it until the cache is reset.
FSCACHE_OBJECT_NO_SPACE
- there was insufficient cache space
* Count I/O requests::
FSCACHE_OBJECT_WAS_RETIRED
- the object was retired when relinquished.
void fscache_count_read(void);
void fscache_count_write(void);
FSCACHE_OBJECT_WAS_CULLED
- the object was culled to make space.
These record reads and writes from/to the cache. The numbers are
displayed in /proc/fs/fscache/stats.
* Count out-of-space errors::
* Get and release references on a retrieval record::
void fscache_count_no_write_space(void);
void fscache_count_no_create_space(void);
void fscache_get_retrieval(struct fscache_retrieval *op);
void fscache_put_retrieval(struct fscache_retrieval *op);
These record ENOSPC errors in the cache, divided into failures of data
writes and failures of filesystem object creations (e.g. mkdir).
These two functions are used to retain a retrieval record while doing
asynchronous data retrieval and block allocation.
* Count objects culled::
void fscache_count_culled(void);
* Enqueue a retrieval record for processing::
This records the culling of an object.
void fscache_enqueue_retrieval(struct fscache_retrieval *op);
* Get the cookie from a set of cache resources::
This enqueues a retrieval record for processing by the FS-Cache thread
pool. One of the threads in the pool will invoke the retrieval record's
op->op.processor callback function. This function may be called from
within the callback function.
struct fscache_cookie *fscache_cres_cookie(struct netfs_cache_resources *cres)
Pull a pointer to the cookie from the cache resources. This may return a
NULL cookie if no cookie was set.
* List of object state names::
const char *fscache_object_states[];
API Function Reference
======================
For debugging purposes, this may be used to turn the state that an object
is in into a text string for display purposes.
.. kernel-doc:: include/linux/fscache-cache.h
.. SPDX-License-Identifier: GPL-2.0
===============================================
CacheFiles: CACHE ON ALREADY MOUNTED FILESYSTEM
===============================================
===================================
Cache on Already Mounted Filesystem
===================================
.. Contents:
......
......@@ -10,25 +10,25 @@ Overview
This facility is a general purpose cache for network filesystems, though it
could be used for caching other things such as ISO9660 filesystems too.
FS-Cache mediates between cache backends (such as CacheFS) and network
FS-Cache mediates between cache backends (such as CacheFiles) and network
filesystems::
+---------+
| | +--------------+
| NFS |--+ | |
| | | +-->| CacheFS |
+---------+ | +----------+ | | /dev/hda5 |
| | | | +--------------+
+---------+ +-->| | |
| | | |--+
| AFS |----->| FS-Cache |
| | | |--+
+---------+ +-->| | |
| | | | +--------------+
+---------+ | +----------+ | | |
| | | +-->| CacheFiles |
| ISOFS |--+ | /var/cache |
| | +--------------+
| | +--------------+
| NFS |--+ | |
| | | +-->| CacheFS |
+---------+ | +----------+ | | /dev/hda5 |
| | | | +--------------+
+---------+ +-------------->| | |
| | +-------+ | |--+
| AFS |----->| | | FS-Cache |
| | | netfs |-->| |--+
+---------+ +-->| lib | | | |
| | | | | | +--------------+
+---------+ | +-------+ +----------+ | | |
| | | +-->| CacheFiles |
| 9P |--+ | /var/cache |
| | +--------------+
+---------+
Or to look at it another way, FS-Cache is a module that provides a caching
......@@ -84,101 +84,62 @@ then serving the pages out of that cache rather than the netfs inode because:
one-off access of a small portion of it (such as might be done with the
"file" program).
It instead serves the cache out in PAGE_SIZE chunks as and when requested by
the netfs('s) using it.
It instead serves the cache out in chunks as and when requested by the netfs
using it.
FS-Cache provides the following facilities:
(1) More than one cache can be used at once. Caches can be selected
* More than one cache can be used at once. Caches can be selected
explicitly by use of tags.
(2) Caches can be added / removed at any time.
* Caches can be added / removed at any time, even whilst being accessed.
(3) The netfs is provided with an interface that allows either party to
* The netfs is provided with an interface that allows either party to
withdraw caching facilities from a file (required for (2)).
(4) The interface to the netfs returns as few errors as possible, preferring
* The interface to the netfs returns as few errors as possible, preferring
rather to let the netfs remain oblivious.
(5) Cookies are used to represent indices, files and other objects to the
netfs. The simplest cookie is just a NULL pointer - indicating nothing
cached there.
(6) The netfs is allowed to propose - dynamically - any index hierarchy it
desires, though it must be aware that the index search function is
recursive, stack space is limited, and indices can only be children of
indices.
(7) Data I/O is done direct to and from the netfs's pages. The netfs
indicates that page A is at index B of the data-file represented by cookie
C, and that it should be read or written. The cache backend may or may
not start I/O on that page, but if it does, a netfs callback will be
invoked to indicate completion. The I/O may be either synchronous or
asynchronous.
(8) Cookies can be "retired" upon release. At this point FS-Cache will mark
them as obsolete and the index hierarchy rooted at that point will get
recycled.
(9) The netfs provides a "match" function for index searches. In addition to
saying whether a match was made or not, this can also specify that an
entry should be updated or deleted.
(10) As much as possible is done asynchronously.
FS-Cache maintains a virtual indexing tree in which all indices, files, objects
and pages are kept. Bits of this tree may actually reside in one or more
caches::
FSDEF
|
+------------------------------------+
| |
NFS AFS
| |
+--------------------------+ +-----------+
| | | |
homedir mirror afs.org redhat.com
| | |
+------------+ +---------------+ +----------+
| | | | | |
00001 00002 00007 00125 vol00001 vol00002
| | | | |
+---+---+ +-----+ +---+ +------+------+ +-----+----+
| | | | | | | | | | | | |
PG0 PG1 PG2 PG0 XATTR PG0 PG1 DIRENT DIRENT DIRENT R/W R/O Bak
| |
PG0 +-------+
| |
00001 00003
|
+---+---+
| | |
PG0 PG1 PG2
In the example above, you can see two netfs's being backed: NFS and AFS. These
have different index hierarchies:
* The NFS primary index contains per-server indices. Each server index is
indexed by NFS file handles to get data file objects. Each data file
objects can have an array of pages, but may also have further child
objects, such as extended attributes and directory entries. Extended
attribute objects themselves have page-array contents.
* The AFS primary index contains per-cell indices. Each cell index contains
per-logical-volume indices. Each of volume index contains up to three
indices for the read-write, read-only and backup mirrors of those volumes.
Each of these contains vnode data file objects, each of which contains an
array of pages.
The very top index is the FS-Cache master index in which individual netfs's
have entries.
Any index object may reside in more than one cache, provided it only has index
children. Any index with non-index object children will be assumed to only
reside in one cache.
* There are three types of cookie: cache, volume and data file cookies.
Cache cookies represent the cache as a whole and are not normally visible
to the netfs; the netfs gets a volume cookie to represent a collection of
files (typically something that a netfs would get for a superblock); and
data file cookies are used to cache data (something that would be got for
an inode).
* Volumes are matched using a key. This is a printable string that is used
to encode all the information that might be needed to distinguish one
superblock, say, from another. This would be a compound of things like
cell name or server address, volume name or share path. It must be a
valid pathname.
* Cookies are matched using a key. This is a binary blob and is used to
represent the object within a volume (so the volume key need not form
part of the blob). This might include things like an inode number and
uniquifier or a file handle.
* Cookie resources are set up and pinned by marking the cookie in-use.
This prevents the backing resources from being culled. Timed garbage
collection is employed to eliminate cookies that haven't been used for a
short while, thereby reducing resource overload. This is intended to be
used when a file is opened or closed.
A cookie can be marked in-use multiple times simultaneously; each mark
must be unused.
* Begin/end access functions are provided to delay cache withdrawal for the
duration of an operation and prevent structs from being freed whilst
we're looking at them.
* Data I/O is done by asynchronous DIO to/from a buffer described by the
netfs using an iov_iter.
* An invalidation facility is available to discard data from the cache and
to deal with I/O that's in progress that is accessing old data.
* Cookies can be "retired" upon release, thereby causing the object to be
removed from the cache.
The netfs API to FS-Cache can be found in:
......@@ -189,11 +150,6 @@ The cache backend API to FS-Cache can be found in:
Documentation/filesystems/caching/backend-api.rst
A description of the internal representations and object state machine can be
found in:
Documentation/filesystems/caching/object.rst
Statistical Information
=======================
......@@ -201,333 +157,162 @@ Statistical Information
If FS-Cache is compiled with the following options enabled::
CONFIG_FSCACHE_STATS=y
CONFIG_FSCACHE_HISTOGRAM=y
then it will gather certain statistics and display them through a number of
proc files.
then it will gather certain statistics and display them through:
/proc/fs/fscache/stats
----------------------
/proc/fs/fscache/stats
This shows counts of a number of events that can happen in FS-Cache:
This shows counts of a number of events that can happen in FS-Cache:
+--------------+-------+-------------------------------------------------------+
|CLASS |EVENT |MEANING |
+==============+=======+=======================================================+
|Cookies |idx=N |Number of index cookies allocated |
+ +-------+-------------------------------------------------------+
| |dat=N |Number of data storage cookies allocated |
|Cookies |n=N |Number of data storage cookies allocated |
+ +-------+-------------------------------------------------------+
| |spc=N |Number of special cookies allocated |
+--------------+-------+-------------------------------------------------------+
|Objects |alc=N |Number of objects allocated |
+ +-------+-------------------------------------------------------+
| |nal=N |Number of object allocation failures |
| |v=N |Number of volume index cookies allocated |
+ +-------+-------------------------------------------------------+
| |avl=N |Number of objects that reached the available state |
+ +-------+-------------------------------------------------------+
| |ded=N |Number of objects that reached the dead state |
+--------------+-------+-------------------------------------------------------+
|ChkAux |non=N |Number of objects that didn't have a coherency check |
| |vcol=N |Number of volume index key collisions |
+ +-------+-------------------------------------------------------+
| |ok=N |Number of objects that passed a coherency check |
+ +-------+-------------------------------------------------------+
| |upd=N |Number of objects that needed a coherency data update |
+ +-------+-------------------------------------------------------+
| |obs=N |Number of objects that were declared obsolete |
+--------------+-------+-------------------------------------------------------+
|Pages |mrk=N |Number of pages marked as being cached |
| |unc=N |Number of uncache page requests seen |
| |voom=N |Number of OOM events when allocating volume cookies |
+--------------+-------+-------------------------------------------------------+
|Acquire |n=N |Number of acquire cookie requests seen |
+ +-------+-------------------------------------------------------+
| |nul=N |Number of acq reqs given a NULL parent |
+ +-------+-------------------------------------------------------+
| |noc=N |Number of acq reqs rejected due to no cache available |
+ +-------+-------------------------------------------------------+
| |ok=N |Number of acq reqs succeeded |
+ +-------+-------------------------------------------------------+
| |nbf=N |Number of acq reqs rejected due to error |
+ +-------+-------------------------------------------------------+
| |oom=N |Number of acq reqs failed on ENOMEM |
+--------------+-------+-------------------------------------------------------+
|Lookups |n=N |Number of lookup calls made on cache backends |
|LRU |n=N |Number of cookies currently on the LRU |
+ +-------+-------------------------------------------------------+
| |neg=N |Number of negative lookups made |
| |exp=N |Number of cookies expired off of the LRU |
+ +-------+-------------------------------------------------------+
| |pos=N |Number of positive lookups made |
| |rmv=N |Number of cookies removed from the LRU |
+ +-------+-------------------------------------------------------+
| |crt=N |Number of objects created by lookup |
| |drp=N |Number of LRU'd cookies relinquished/withdrawn |
+ +-------+-------------------------------------------------------+
| |tmo=N |Number of lookups timed out and requeued |
| |at=N |Time till next LRU cull (jiffies) |
+--------------+-------+-------------------------------------------------------+
|Invals |n=N |Number of invalidations |
+--------------+-------+-------------------------------------------------------+
|Updates |n=N |Number of update cookie requests seen |
+ +-------+-------------------------------------------------------+
| |nul=N |Number of upd reqs given a NULL parent |
| |rsz=N |Number of resize requests |
+ +-------+-------------------------------------------------------+
| |run=N |Number of upd reqs granted CPU time |
| |rsn=N |Number of skipped resize requests |
+--------------+-------+-------------------------------------------------------+
|Relinqs |n=N |Number of relinquish cookie requests seen |
+ +-------+-------------------------------------------------------+
| |nul=N |Number of rlq reqs given a NULL parent |
| |rtr=N |Number of rlq reqs with retire=true |
+ +-------+-------------------------------------------------------+
| |wcr=N |Number of rlq reqs waited on completion of creation |
| |drop=N |Number of cookies no longer blocking re-acquisition |
+--------------+-------+-------------------------------------------------------+
|AttrChg |n=N |Number of attribute changed requests seen |
+ +-------+-------------------------------------------------------+
| |ok=N |Number of attr changed requests queued |
+ +-------+-------------------------------------------------------+
| |nbf=N |Number of attr changed rejected -ENOBUFS |
|NoSpace |nwr=N |Number of write requests refused due to lack of space |
+ +-------+-------------------------------------------------------+
| |oom=N |Number of attr changed failed -ENOMEM |
| |ncr=N |Number of create requests refused due to lack of space |
+ +-------+-------------------------------------------------------+
| |run=N |Number of attr changed ops given CPU time |
| |cull=N |Number of objects culled to make space |
+--------------+-------+-------------------------------------------------------+
|Allocs |n=N |Number of allocation requests seen |
|IO |rd=N |Number of read operations in the cache |
+ +-------+-------------------------------------------------------+
| |ok=N |Number of successful alloc reqs |
+ +-------+-------------------------------------------------------+
| |wt=N |Number of alloc reqs that waited on lookup completion |
+ +-------+-------------------------------------------------------+
| |nbf=N |Number of alloc reqs rejected -ENOBUFS |
+ +-------+-------------------------------------------------------+
| |int=N |Number of alloc reqs aborted -ERESTARTSYS |
+ +-------+-------------------------------------------------------+
| |ops=N |Number of alloc reqs submitted |
+ +-------+-------------------------------------------------------+
| |owt=N |Number of alloc reqs waited for CPU time |
+ +-------+-------------------------------------------------------+
| |abt=N |Number of alloc reqs aborted due to object death |
+--------------+-------+-------------------------------------------------------+
|Retrvls |n=N |Number of retrieval (read) requests seen |
+ +-------+-------------------------------------------------------+
| |ok=N |Number of successful retr reqs |
+ +-------+-------------------------------------------------------+
| |wt=N |Number of retr reqs that waited on lookup completion |
+ +-------+-------------------------------------------------------+
| |nod=N |Number of retr reqs returned -ENODATA |
+ +-------+-------------------------------------------------------+
| |nbf=N |Number of retr reqs rejected -ENOBUFS |
+ +-------+-------------------------------------------------------+
| |int=N |Number of retr reqs aborted -ERESTARTSYS |
+ +-------+-------------------------------------------------------+
| |oom=N |Number of retr reqs failed -ENOMEM |
+ +-------+-------------------------------------------------------+
| |ops=N |Number of retr reqs submitted |
+ +-------+-------------------------------------------------------+
| |owt=N |Number of retr reqs waited for CPU time |
+ +-------+-------------------------------------------------------+
| |abt=N |Number of retr reqs aborted due to object death |
+--------------+-------+-------------------------------------------------------+
|Stores |n=N |Number of storage (write) requests seen |
+ +-------+-------------------------------------------------------+
| |ok=N |Number of successful store reqs |
+ +-------+-------------------------------------------------------+
| |agn=N |Number of store reqs on a page already pending storage |
+ +-------+-------------------------------------------------------+
| |nbf=N |Number of store reqs rejected -ENOBUFS |
+ +-------+-------------------------------------------------------+
| |oom=N |Number of store reqs failed -ENOMEM |
+ +-------+-------------------------------------------------------+
| |ops=N |Number of store reqs submitted |
+ +-------+-------------------------------------------------------+
| |run=N |Number of store reqs granted CPU time |
+ +-------+-------------------------------------------------------+
| |pgs=N |Number of pages given store req processing time |
+ +-------+-------------------------------------------------------+
| |rxd=N |Number of store reqs deleted from tracking tree |
+ +-------+-------------------------------------------------------+
| |olm=N |Number of store reqs over store limit |
+--------------+-------+-------------------------------------------------------+
|VmScan |nos=N |Number of release reqs against pages with no |
| | |pending store |
+ +-------+-------------------------------------------------------+
| |gon=N |Number of release reqs against pages stored by |
| | |time lock granted |
+ +-------+-------------------------------------------------------+
| |bsy=N |Number of release reqs ignored due to in-progress store|
+ +-------+-------------------------------------------------------+
| |can=N |Number of page stores cancelled due to release req |
+--------------+-------+-------------------------------------------------------+
|Ops |pend=N |Number of times async ops added to pending queues |
+ +-------+-------------------------------------------------------+
| |run=N |Number of times async ops given CPU time |
+ +-------+-------------------------------------------------------+
| |enq=N |Number of times async ops queued for processing |
+ +-------+-------------------------------------------------------+
| |can=N |Number of async ops cancelled |
+ +-------+-------------------------------------------------------+
| |rej=N |Number of async ops rejected due to object |
| | |lookup/create failure |
+ +-------+-------------------------------------------------------+
| |ini=N |Number of async ops initialised |
+ +-------+-------------------------------------------------------+
| |dfr=N |Number of async ops queued for deferred release |
+ +-------+-------------------------------------------------------+
| |rel=N |Number of async ops released |
| | |(should equal ini=N when idle) |
+ +-------+-------------------------------------------------------+
| |gc=N |Number of deferred-release async ops garbage collected |
+--------------+-------+-------------------------------------------------------+
|CacheOp |alo=N |Number of in-progress alloc_object() cache ops |
+ +-------+-------------------------------------------------------+
| |luo=N |Number of in-progress lookup_object() cache ops |
+ +-------+-------------------------------------------------------+
| |luc=N |Number of in-progress lookup_complete() cache ops |
+ +-------+-------------------------------------------------------+
| |gro=N |Number of in-progress grab_object() cache ops |
+ +-------+-------------------------------------------------------+
| |upo=N |Number of in-progress update_object() cache ops |
+ +-------+-------------------------------------------------------+
| |dro=N |Number of in-progress drop_object() cache ops |
+ +-------+-------------------------------------------------------+
| |pto=N |Number of in-progress put_object() cache ops |
+ +-------+-------------------------------------------------------+
| |syn=N |Number of in-progress sync_cache() cache ops |
+ +-------+-------------------------------------------------------+
| |atc=N |Number of in-progress attr_changed() cache ops |
+ +-------+-------------------------------------------------------+
| |rap=N |Number of in-progress read_or_alloc_page() cache ops |
+ +-------+-------------------------------------------------------+
| |ras=N |Number of in-progress read_or_alloc_pages() cache ops |
+ +-------+-------------------------------------------------------+
| |alp=N |Number of in-progress allocate_page() cache ops |
+ +-------+-------------------------------------------------------+
| |als=N |Number of in-progress allocate_pages() cache ops |
+ +-------+-------------------------------------------------------+
| |wrp=N |Number of in-progress write_page() cache ops |
+ +-------+-------------------------------------------------------+
| |ucp=N |Number of in-progress uncache_page() cache ops |
+ +-------+-------------------------------------------------------+
| |dsp=N |Number of in-progress dissociate_pages() cache ops |
+--------------+-------+-------------------------------------------------------+
|CacheEv |nsp=N |Number of object lookups/creations rejected due to |
| | |lack of space |
+ +-------+-------------------------------------------------------+
| |stl=N |Number of stale objects deleted |
+ +-------+-------------------------------------------------------+
| |rtr=N |Number of objects retired when relinquished |
+ +-------+-------------------------------------------------------+
| |cul=N |Number of objects culled |
| |wr=N |Number of write operations in the cache |
+--------------+-------+-------------------------------------------------------+
Netfslib will also add some stats counters of its own.
/proc/fs/fscache/histogram
--------------------------
Cache List
==========
::
FS-Cache provides a list of cache cookies:
cat /proc/fs/fscache/histogram
JIFS SECS OBJ INST OP RUNS OBJ RUNS RETRV DLY RETRIEVLS
===== ===== ========= ========= ========= ========= =========
/proc/fs/fscache/cookies
This shows the breakdown of the number of times each amount of time
between 0 jiffies and HZ-1 jiffies a variety of tasks took to run. The
columns are as follows:
This will look something like::
========= =======================================================
COLUMN TIME MEASUREMENT
========= =======================================================
OBJ INST Length of time to instantiate an object
OP RUNS Length of time a call to process an operation took
OBJ RUNS Length of time a call to process an object event took
RETRV DLY Time between an requesting a read and lookup completing
RETRIEVLS Time between beginning and end of a retrieval
========= =======================================================
# cat /proc/fs/fscache/caches
CACHE REF VOLS OBJS ACCES S NAME
======== ===== ===== ===== ===== = ===============
00000001 2 1 2123 1 A default
Each row shows the number of events that took a particular range of times.
Each step is 1 jiffy in size. The JIFS column indicates the particular
jiffy range covered, and the SECS field the equivalent number of seconds.
where the columns are:
======= ===============================================================
COLUMN DESCRIPTION
======= ===============================================================
CACHE Cache cookie debug ID (also appears in traces)
REF Number of references on the cache cookie
VOLS Number of volumes cookies in this cache
OBJS Number of cache objects in use
ACCES Number of accesses pinning the cache
S State
NAME Name of the cache.
======= ===============================================================
The state can be (-) Inactive, (P)reparing, (A)ctive, (E)rror or (W)ithdrawing.
Object List
Volume List
===========
If CONFIG_FSCACHE_OBJECT_LIST is enabled, the FS-Cache facility will maintain a
list of all the objects currently allocated and allow them to be viewed
through::
FS-Cache provides a list of volume cookies:
/proc/fs/fscache/objects
/proc/fs/fscache/volumes
This will look something like::
[root@andromeda ~]# head /proc/fs/fscache/objects
OBJECT PARENT STAT CHLDN OPS OOP IPR EX READS EM EV F S | NETFS_COOKIE_DEF TY FL NETFS_DATA OBJECT_KEY, AUX_DATA
======== ======== ==== ===== === === === == ===== == == = = | ================ == == ================ ================
17e4b 2 ACTV 0 0 0 0 0 0 7b 4 0 0 | NFS.fh DT 0 ffff88001dd82820 010006017edcf8bbc93b43298fdfbe71e50b57b13a172c0117f38472, e567634700000000000000000000000063f2404a000000000000000000000000c9030000000000000000000063f2404a
1693a 2 ACTV 0 0 0 0 0 0 7b 4 0 0 | NFS.fh DT 0 ffff88002db23380 010006017edcf8bbc93b43298fdfbe71e50b57b1e0162c01a2df0ea6, 420ebc4a000000000000000000000000420ebc4a0000000000000000000000000e1801000000000000000000420ebc4a
VOLUME REF nCOOK ACC FL CACHE KEY
======== ===== ===== === == =============== ================
00000001 55 54 1 00 default afs,example.com,100058
where the first set of columns before the '|' describe the object:
where the columns are:
======= ===============================================================
COLUMN DESCRIPTION
======= ===============================================================
OBJECT Object debugging ID (appears as OBJ%x in some debug messages)
PARENT Debugging ID of parent object
STAT Object state
CHLDN Number of child objects of this object
OPS Number of outstanding operations on this object
OOP Number of outstanding child object management operations
IPR
EX Number of outstanding exclusive operations
READS Number of outstanding read operations
EM Object's event mask
EV Events raised on this object
F Object flags
S Object work item busy state mask (1:pending 2:running)
VOLUME The volume cookie debug ID (also appears in traces)
REF Number of references on the volume cookie
nCOOK Number of cookies in the volume
ACC Number of accesses pinning the cache
FL Flags on the volume cookie
CACHE Name of the cache or "-"
KEY The indexing key for the volume
======= ===============================================================
and the second set of columns describe the object's cookie, if present:
================ ======================================================
COLUMN DESCRIPTION
================ ======================================================
NETFS_COOKIE_DEF Name of netfs cookie definition
TY Cookie type (IX - index, DT - data, hex - special)
FL Cookie flags
NETFS_DATA Netfs private data stored in the cookie
OBJECT_KEY Object key } 1 column, with separating comma
AUX_DATA Object aux data } presence may be configured
================ ======================================================
The data shown may be filtered by attaching the a key to an appropriate keyring
before viewing the file. Something like::
keyctl add user fscache:objlist <restrictions> @s
where <restrictions> are a selection of the following letters:
== =========================================================
K Show hexdump of object key (don't show if not given)
A Show hexdump of object aux data (don't show if not given)
== =========================================================
Cookie List
===========
and the following paired letters:
FS-Cache provides a list of cookies:
== =========================================================
C Show objects that have a cookie
c Show objects that don't have a cookie
B Show objects that are busy
b Show objects that aren't busy
W Show objects that have pending writes
w Show objects that don't have pending writes
R Show objects that have outstanding reads
r Show objects that don't have outstanding reads
S Show objects that have work queued
s Show objects that don't have work queued
== =========================================================
/proc/fs/fscache/cookies
If neither side of a letter pair is given, then both are implied. For example:
This will look something like::
keyctl add user fscache:objlist KB @s
# head /proc/fs/fscache/cookies
COOKIE VOLUME REF ACT ACC S FL DEF
======== ======== === === === = == ================
00000435 00000001 1 0 -1 - 08 0000000201d080070000000000000000, 0000000000000000
00000436 00000001 1 0 -1 - 00 0000005601d080080000000000000000, 0000000000000051
00000437 00000001 1 0 -1 - 08 00023b3001d0823f0000000000000000, 0000000000000000
00000438 00000001 1 0 -1 - 08 0000005801d0807b0000000000000000, 0000000000000000
00000439 00000001 1 0 -1 - 08 00023b3201d080a10000000000000000, 0000000000000000
0000043a 00000001 1 0 -1 - 08 00023b3401d080a30000000000000000, 0000000000000000
0000043b 00000001 1 0 -1 - 08 00023b3601d080b30000000000000000, 0000000000000000
0000043c 00000001 1 0 -1 - 08 00023b3801d080b40000000000000000, 0000000000000000
shows objects that are busy, and lists their object keys, but does not dump
their auxiliary data. It also implies "CcWwRrSs", but as 'B' is given, 'b' is
not implied.
where the columns are:
By default all objects and all fields will be shown.
======= ===============================================================
COLUMN DESCRIPTION
======= ===============================================================
COOKIE The cookie debug ID (also appears in traces)
VOLUME The parent volume cookie debug ID
REF Number of references on the volume cookie
ACT Number of times the cookie is marked for in use
ACC Number of access pins in the cookie
S State of the cookie
FL Flags on the cookie
DEF Key, auxiliary data
======= ===============================================================
Debugging
......@@ -549,10 +334,8 @@ This is a bitmask of debugging streams to enable:
3 8 Cookie management Function entry trace
4 16 Function exit trace
5 32 General
6 64 Page handling Function entry trace
7 128 Function exit trace
8 256 General
9 512 Operation management Function entry trace
6-8 (Not used)
9 512 I/O operation management Function entry trace
10 1024 Function exit trace
11 2048 General
======= ======= =============================== =======================
......@@ -560,6 +343,6 @@ This is a bitmask of debugging streams to enable:
The appropriate set of values should be OR'd together and the result written to
the control file. For example::
echo $((1|8|64)) >/sys/module/fscache/parameters/debug
echo $((1|8|512)) >/sys/module/fscache/parameters/debug
will turn on all function entry debugging.
......@@ -7,8 +7,6 @@ Filesystem Caching
:maxdepth: 2
fscache
object
netfs-api
backend-api
cachefiles
netfs-api
operations
.. SPDX-License-Identifier: GPL-2.0
===============================
FS-Cache Network Filesystem API
===============================
==============================
Network Filesystem Caching API
==============================
There's an API by which a network filesystem can make use of the FS-Cache
facilities. This is based around a number of principles:
Fscache provides an API by which a network filesystem can make use of local
caching facilities. The API is arranged around a number of principles:
(1) Caches can store a number of different object types. There are two main
object types: indices and files. The first is a special type used by
FS-Cache to make finding objects faster and to make retiring of groups of
objects easier.
(1) A cache is logically organised into volumes and data storage objects
within those volumes.
(2) Every index, file or other object is represented by a cookie. This cookie
may or may not have anything associated with it, but the netfs doesn't
need to care.
(2) Volumes and data storage objects are represented by various types of
cookie.
(3) Barring the top-level index (one entry per cached netfs), the index
hierarchy for each netfs is structured according the whim of the netfs.
(3) Cookies have keys that distinguish them from their peers.
This API is declared in <linux/fscache.h>.
(4) Cookies have coherency data that allows a cache to determine if the
cached data is still valid.
.. This document contains the following sections:
(1) Network filesystem definition
(2) Index definition
(3) Object definition
(4) Network filesystem (un)registration
(5) Cache tag lookup
(6) Index registration
(7) Data file registration
(8) Miscellaneous object registration
(9) Setting the data file size
(10) Page alloc/read/write
(11) Page uncaching
(12) Index and data file consistency
(13) Cookie enablement
(14) Miscellaneous cookie operations
(15) Cookie unregistration
(16) Index invalidation
(17) Data file invalidation
(18) FS-Cache specific page flags.
Network Filesystem Definition
=============================
FS-Cache needs a description of the network filesystem. This is specified
using a record of the following structure::
struct fscache_netfs {
uint32_t version;
const char *name;
struct fscache_cookie *primary_index;
...
};
This first two fields should be filled in before registration, and the third
will be filled in by the registration function; any other fields should just be
ignored and are for internal use only.
The fields are:
(1) The name of the netfs (used as the key in the toplevel index).
(2) The version of the netfs (if the name matches but the version doesn't, the
entire in-cache hierarchy for this netfs will be scrapped and begun
afresh).
(3) The cookie representing the primary index will be allocated according to
another parameter passed into the registration function.
For example, kAFS (linux/fs/afs/) uses the following definitions to describe
itself::
struct fscache_netfs afs_cache_netfs = {
.version = 0,
.name = "afs",
};
Index Definition
================
Indices are used for two purposes:
(1) To aid the finding of a file based on a series of keys (such as AFS's
"cell", "volume ID", "vnode ID").
(2) To make it easier to discard a subset of all the files cached based around
a particular key - for instance to mirror the removal of an AFS volume.
However, since it's unlikely that any two netfs's are going to want to define
their index hierarchies in quite the same way, FS-Cache tries to impose as few
restraints as possible on how an index is structured and where it is placed in
the tree. The netfs can even mix indices and data files at the same level, but
it's not recommended.
Each index entry consists of a key of indeterminate length plus some auxiliary
data, also of indeterminate length.
There are some limits on indices:
(1) Any index containing non-index objects should be restricted to a single
cache. Any such objects created within an index will be created in the
first cache only. The cache in which an index is created can be
controlled by cache tags (see below).
(2) The entry data must be atomically journallable, so it is limited to about
400 bytes at present. At least 400 bytes will be available.
(3) The depth of the index tree should be judged with care as the search
function is recursive. Too many layers will run the kernel out of stack.
Object Definition
=================
To define an object, a structure of the following type should be filled out::
struct fscache_cookie_def
{
uint8_t name[16];
uint8_t type;
struct fscache_cache_tag *(*select_cache)(
const void *parent_netfs_data,
const void *cookie_netfs_data);
enum fscache_checkaux (*check_aux)(void *cookie_netfs_data,
const void *data,
uint16_t datalen,
loff_t object_size);
void (*get_context)(void *cookie_netfs_data, void *context);
void (*put_context)(void *cookie_netfs_data, void *context);
void (*mark_pages_cached)(void *cookie_netfs_data,
struct address_space *mapping,
struct pagevec *cached_pvec);
};
This has the following fields:
(1) The type of the object [mandatory].
This is one of the following values:
FSCACHE_COOKIE_TYPE_INDEX
This defines an index, which is a special FS-Cache type.
FSCACHE_COOKIE_TYPE_DATAFILE
This defines an ordinary data file.
Any other value between 2 and 255
This defines an extraordinary object such as an XATTR.
(2) The name of the object type (NUL terminated unless all 16 chars are used)
[optional].
(3) A function to select the cache in which to store an index [optional].
This function is invoked when an index needs to be instantiated in a cache
during the instantiation of a non-index object. Only the immediate index
parent for the non-index object will be queried. Any indices above that
in the hierarchy may be stored in multiple caches. This function does not
need to be supplied for any non-index object or any index that will only
have index children.
If this function is not supplied or if it returns NULL then the first
cache in the parent's list will be chosen, or failing that, the first
cache in the master list.
(4) A function to check the auxiliary data [optional].
This function will be called to check that a match found in the cache for
this object is valid. For instance with AFS it could check the auxiliary
data against the data version number returned by the server to determine
whether the index entry in a cache is still valid.
If this function is absent, it will be assumed that matching objects in a
cache are always valid.
The function is also passed the cache's idea of the object size and may
use this to manage coherency also.
If present, the function should return one of the following values:
FSCACHE_CHECKAUX_OKAY
- the entry is okay as is
FSCACHE_CHECKAUX_NEEDS_UPDATE
- the entry requires update
FSCACHE_CHECKAUX_OBSOLETE
- the entry should be deleted
(5) I/O is done asynchronously where possible.
This function can also be used to extract data from the auxiliary data in
the cache and copy it into the netfs's structures.
This API is used by::
(5) A pair of functions to manage contexts for the completion callback
[optional].
#include <linux/fscache.h>.
The cache read/write functions are passed a context which is then passed
to the I/O completion callback function. To ensure this context remains
valid until after the I/O completion is called, two functions may be
provided: one to get an extra reference on the context, and one to drop a
reference to it.
If the context is not used or is a type of object that won't go out of
scope, then these functions are not required. These functions are not
required for indices as indices may not contain data. These functions may
be called in interrupt context and so may not sleep.
(6) A function to mark a page as retaining cache metadata [optional].
This is called by the cache to indicate that it is retaining in-memory
information for this page and that the netfs should uncache the page when
it has finished. This does not indicate whether there's data on the disk
or not. Note that several pages at once may be presented for marking.
The PG_fscache bit is set on the pages before this function would be
called, so the function need not be provided if this is sufficient.
This function is not required for indices as they're not permitted data.
(7) A function to unmark all the pages retaining cache metadata [mandatory].
This is called by FS-Cache to indicate that a backing store is being
unbound from a cookie and that all the marks on the pages should be
cleared to prevent confusion. Note that the cache will have torn down all
its tracking information so that the pages don't need to be explicitly
uncached.
This function is not required for indices as they're not permitted data.
Network Filesystem (Un)registration
===================================
The first step is to declare the network filesystem to the cache. This also
involves specifying the layout of the primary index (for AFS, this would be the
"cell" level).
The registration function is::
int fscache_register_netfs(struct fscache_netfs *netfs);
It just takes a pointer to the netfs definition. It returns 0 or an error as
appropriate.
For kAFS, registration is done as follows::
ret = fscache_register_netfs(&afs_cache_netfs);
The last step is, of course, unregistration::
void fscache_unregister_netfs(struct fscache_netfs *netfs);
Cache Tag Lookup
================
FS-Cache permits the use of more than one cache. To permit particular index
subtrees to be bound to particular caches, the second step is to look up cache
representation tags. This step is optional; it can be left entirely up to
FS-Cache as to which cache should be used. The problem with doing that is that
FS-Cache will always pick the first cache that was registered.
To get the representation for a named tag::
struct fscache_cache_tag *fscache_lookup_cache_tag(const char *name);
This takes a text string as the name and returns a representation of a tag. It
will never return an error. It may return a dummy tag, however, if it runs out
of memory; this will inhibit caching with this tag.
Any representation so obtained must be released by passing it to this function::
void fscache_release_cache_tag(struct fscache_cache_tag *tag);
.. This document contains the following sections:
The tag will be retrieved by FS-Cache when it calls the object definition
operation select_cache().
(1) Overview
(2) Volume registration
(3) Data file registration
(4) Declaring a cookie to be in use
(5) Resizing a data file (truncation)
(6) Data I/O API
(7) Data file coherency
(8) Data file invalidation
(9) Write back resource management
(10) Caching of local modifications
(11) Page release and invalidation
Overview
========
The fscache hierarchy is organised on two levels from a network filesystem's
point of view. The upper level represents "volumes" and the lower level
represents "data storage objects". These are represented by two types of
cookie, hereafter referred to as "volume cookies" and "cookies".
A network filesystem acquires a volume cookie for a volume using a volume key,
which represents all the information that defines that volume (e.g. cell name
or server address, volume ID or share name). This must be rendered as a
printable string that can be used as a directory name (ie. no '/' characters
and shouldn't begin with a '.'). The maximum name length is one less than the
maximum size of a filename component (allowing the cache backend one char for
its own purposes).
A filesystem would typically have a volume cookie for each superblock.
The filesystem then acquires a cookie for each file within that volume using an
object key. Object keys are binary blobs and only need to be unique within
their parent volume. The cache backend is reponsible for rendering the binary
blob into something it can use and may employ hash tables, trees or whatever to
improve its ability to find an object. This is transparent to the network
filesystem.
A filesystem would typically have a cookie for each inode, and would acquire it
in iget and relinquish it when evicting the cookie.
Once it has a cookie, the filesystem needs to mark the cookie as being in use.
This causes fscache to send the cache backend off to look up/create resources
for the cookie in the background, to check its coherency and, if necessary, to
mark the object as being under modification.
A filesystem would typically "use" the cookie in its file open routine and
unuse it in file release and it needs to use the cookie around calls to
truncate the cookie locally. It *also* needs to use the cookie when the
pagecache becomes dirty and unuse it when writeback is complete. This is
slightly tricky, and provision is made for it.
When performing a read, write or resize on a cookie, the filesystem must first
begin an operation. This copies the resources into a holding struct and puts
extra pins into the cache to stop cache withdrawal from tearing down the
structures being used. The actual operation can then be issued and conflicting
invalidations can be detected upon completion.
The filesystem is expected to use netfslib to access the cache, but that's not
actually required and it can use the fscache I/O API directly.
Volume Registration
===================
The first step for a network filsystem is to acquire a volume cookie for the
volume it wants to access::
struct fscache_volume *
fscache_acquire_volume(const char *volume_key,
const char *cache_name,
const void *coherency_data,
size_t coherency_len);
This function creates a volume cookie with the specified volume key as its name
and notes the coherency data.
The volume key must be a printable string with no '/' characters in it. It
should begin with the name of the filesystem and should be no longer than 254
characters. It should uniquely represent the volume and will be matched with
what's stored in the cache.
The caller may also specify the name of the cache to use. If specified,
fscache will look up or create a cache cookie of that name and will use a cache
of that name if it is online or comes online. If no cache name is specified,
it will use the first cache that comes to hand and set the name to that.
The specified coherency data is stored in the cookie and will be matched
against coherency data stored on disk. The data pointer may be NULL if no data
is provided. If the coherency data doesn't match, the entire cache volume will
be invalidated.
This function can return errors such as EBUSY if the volume key is already in
use by an acquired volume or ENOMEM if an allocation failure occured. It may
also return a NULL volume cookie if fscache is not enabled. It is safe to
pass a NULL cookie to any function that takes a volume cookie. This will
cause that function to do nothing.
When the network filesystem has finished with a volume, it should relinquish it
by calling::
void fscache_relinquish_volume(struct fscache_volume *volume,
const void *coherency_data,
bool invalidate);
This will cause the volume to be committed or removed, and if sealed the
coherency data will be set to the value supplied. The amount of coherency data
must match the length specified when the volume was acquired. Note that all
data cookies obtained in this volume must be relinquished before the volume is
relinquished.
Index Registration
==================
Data File Registration
======================
The third step is to inform FS-Cache about part of an index hierarchy that can
be used to locate files. This is done by requesting a cookie for each index in
the path to the file::
Once it has a volume cookie, a network filesystem can use it to acquire a
cookie for data storage::
struct fscache_cookie *
fscache_acquire_cookie(struct fscache_cookie *parent,
const struct fscache_object_def *def,
fscache_acquire_cookie(struct fscache_volume *volume,
u8 advice,
const void *index_key,
size_t index_key_len,
const void *aux_data,
size_t aux_data_len,
void *netfs_data,
loff_t object_size,
bool enable);
loff_t object_size)
This function creates an index entry in the index represented by parent,
filling in the index entry by calling the operations pointed to by def.
This creates the cookie in the volume using the specified index key. The index
key is a binary blob of the given length and must be unique for the volume.
This is saved into the cookie. There are no restrictions on the content, but
its length shouldn't exceed about three quarters of the maximum filename length
to allow for encoding.
A unique key that represents the object within the parent must be pointed to by
index_key and is of length index_key_len.
The caller should also pass in a piece of coherency data in aux_data. A buffer
of size aux_data_len will be allocated and the coherency data copied in. It is
assumed that the size is invariant over time. The coherency data is used to
check the validity of data in the cache. Functions are provided by which the
coherency data can be updated.
An optional blob of auxiliary data that is to be stored within the cache can be
pointed to with aux_data and should be of length aux_data_len. This would
typically be used for storing coherency data.
The file size of the object being cached should also be provided. This may be
used to trim the data and will be stored with the coherency data.
The netfs may pass an arbitrary value in netfs_data and this will be presented
to it in the event of any calling back. This may also be used in tracing or
logging of messages.
This function never returns an error, though it may return a NULL cookie on
allocation failure or if fscache is not enabled. It is safe to pass in a NULL
volume cookie and pass the NULL cookie returned to any function that takes it.
This will cause that function to do nothing.
The cache tracks the size of the data attached to an object and this set to be
object_size. For indices, this should be 0. This value will be passed to the
->check_aux() callback.
Note that this function never returns an error - all errors are handled
internally. It may, however, return NULL to indicate no cookie. It is quite
acceptable to pass this token back to this function as the parent to another
acquisition (or even to the relinquish cookie, read page and write page
functions - see below).
When the network filesystem has finished with a cookie, it should relinquish it
by calling::
Note also that no indices are actually created in a cache until a non-index
object needs to be created somewhere down the hierarchy. Furthermore, an index
may be created in several different caches independently at different times.
This is all handled transparently, and the netfs doesn't see any of it.
void fscache_relinquish_cookie(struct fscache_cookie *cookie,
bool retire);
A cookie will be created in the disabled state if enabled is false. A cookie
must be enabled to do anything with it. A disabled cookie can be enabled by
calling fscache_enable_cookie() (see below).
This will cause fscache to either commit the storage backing the cookie or
delete it.
For example, with AFS, a cell would be added to the primary index. This index
entry would have a dependent inode containing volume mappings within this cell::
cell->cache =
fscache_acquire_cookie(afs_cache_netfs.primary_index,
&afs_cell_cache_index_def,
cell->name, strlen(cell->name),
NULL, 0,
cell, 0, true);
Marking A Cookie In-Use
=======================
And then a particular volume could be added to that index by ID, creating
another index for vnodes (AFS inode equivalents)::
Once a cookie has been acquired by a network filesystem, the filesystem should
tell fscache when it intends to use the cookie (typically done on file open)
and should say when it has finished with it (typically on file close)::
volume->cache =
fscache_acquire_cookie(volume->cell->cache,
&afs_volume_cache_index_def,
&volume->vid, sizeof(volume->vid),
NULL, 0,
volume, 0, true);
void fscache_use_cookie(struct fscache_cookie *cookie,
bool will_modify);
void fscache_unuse_cookie(struct fscache_cookie *cookie,
const void *aux_data,
const loff_t *object_size);
The *use* function tells fscache that it will use the cookie and, additionally,
indicate if the user is intending to modify the contents locally. If not yet
done, this will trigger the cache backend to go and gather the resources it
needs to access/store data in the cache. This is done in the background, and
so may not be complete by the time the function returns.
Data File Registration
======================
The *unuse* function indicates that a filesystem has finished using a cookie.
It optionally updates the stored coherency data and object size and then
decreases the in-use counter. When the last user unuses the cookie, it is
scheduled for garbage collection. If not reused within a short time, the
resources will be released to reduce system resource consumption.
The fourth step is to request a data file be created in the cache. This is
identical to index cookie acquisition. The only difference is that the type in
the object definition should be something other than index type::
A cookie must be marked in-use before it can be accessed for read, write or
resize - and an in-use mark must be kept whilst there is dirty data in the
pagecache in order to avoid an oops due to trying to open a file during process
exit.
vnode->cache =
fscache_acquire_cookie(volume->cache,
&afs_vnode_cache_object_def,
&key, sizeof(key),
&aux, sizeof(aux),
vnode, vnode->status.size, true);
Note that in-use marks are cumulative. For each time a cookie is marked
in-use, it must be unused.
Miscellaneous Object Registration
Resizing A Data File (Truncation)
=================================
An optional step is to request an object of miscellaneous type be created in
the cache. This is almost identical to index cookie acquisition. The only
difference is that the type in the object definition should be something other
than index type. While the parent object could be an index, it's more likely
it would be some other type of object such as a data file::
xattr->cache =
fscache_acquire_cookie(vnode->cache,
&afs_xattr_cache_object_def,
&xattr->name, strlen(xattr->name),
NULL, 0,
xattr, strlen(xattr->val), true);
Miscellaneous objects might be used to store extended attributes or directory
entries for example.
Setting the Data File Size
==========================
If a network filesystem file is resized locally by truncation, the following
should be called to notify the cache::
The fifth step is to set the physical attributes of the file, such as its size.
This doesn't automatically reserve any space in the cache, but permits the
cache to adjust its metadata for data tracking appropriately::
void fscache_resize_cookie(struct fscache_cookie *cookie,
loff_t new_size);
int fscache_attr_changed(struct fscache_cookie *cookie);
The caller must have first marked the cookie in-use. The cookie and the new
size are passed in and the cache is synchronously resized. This is expected to
be called from ``->setattr()`` inode operation under the inode lock.
The cache will return -ENOBUFS if there is no backing cache or if there is no
space to allocate any extra metadata required in the cache.
Note that attempts to read or write data pages in the cache over this size may
be rebuffed with -ENOBUFS.
Data I/O API
============
This operation schedules an attribute adjustment to happen asynchronously at
some point in the future, and as such, it may happen after the function returns
to the caller. The attribute adjustment excludes read and write operations.
To do data I/O operations directly through a cookie, the following functions
are available::
int fscache_begin_read_operation(struct netfs_cache_resources *cres,
struct fscache_cookie *cookie);
int fscache_read(struct netfs_cache_resources *cres,
loff_t start_pos,
struct iov_iter *iter,
enum netfs_read_from_hole read_hole,
netfs_io_terminated_t term_func,
void *term_func_priv);
int fscache_write(struct netfs_cache_resources *cres,
loff_t start_pos,
struct iov_iter *iter,
netfs_io_terminated_t term_func,
void *term_func_priv);
Page alloc/read/write
=====================
The *begin* function sets up an operation, attaching the resources required to
the cache resources block from the cookie. Assuming it doesn't return an error
(for instance, it will return -ENOBUFS if given a NULL cookie, but otherwise do
nothing), then one of the other two functions can be issued.
And the sixth step is to store and retrieve pages in the cache. There are
three functions that are used to do this.
The *read* and *write* functions initiate a direct-IO operation. Both take the
previously set up cache resources block, an indication of the start file
position, and an I/O iterator that describes buffer and indicates the amount of
data.
Note:
The read function also takes a parameter to indicate how it should handle a
partially populated region (a hole) in the disk content. This may be to ignore
it, skip over an initial hole and place zeros in the buffer or give an error.
(1) A page should not be re-read or re-allocated without uncaching it first.
(2) A read or allocated page must be uncached when the netfs page is released
from the pagecache.
(3) A page should only be written to the cache if previous read or allocated.
This permits the cache to maintain its page tracking in proper order.
PAGE READ
---------
Firstly, the netfs should ask FS-Cache to examine the caches and read the
contents cached for a particular page of a particular file if present, or else
allocate space to store the contents if not::
The read and write functions can be given an optional termination function that
will be run on completion::
typedef
void (*fscache_rw_complete_t)(struct page *page,
void *context,
int error);
int fscache_read_or_alloc_page(struct fscache_cookie *cookie,
struct page *page,
fscache_rw_complete_t end_io_func,
void *context,
gfp_t gfp);
The cookie argument must specify a cookie for an object that isn't an index,
the page specified will have the data loaded into it (and is also used to
specify the page number), and the gfp argument is used to control how any
memory allocations made are satisfied.
If the cookie indicates the inode is not cached:
(1) The function will return -ENOBUFS.
Else if there's a copy of the page resident in the cache:
(1) The mark_pages_cached() cookie operation will be called on that page.
void (*netfs_io_terminated_t)(void *priv, ssize_t transferred_or_error,
bool was_async);
(2) The function will submit a request to read the data from the cache's
backing device directly into the page specified.
If a termination function is given, the operation will be run asynchronously
and the termination function will be called upon completion. If not given, the
operation will be run synchronously. Note that in the asynchronous case, it is
possible for the operation to complete before the function returns.
(3) The function will return 0.
Both the read and write functions end the operation when they complete,
detaching any pinned resources.
(4) When the read is complete, end_io_func() will be invoked with:
The read operation will fail with ESTALE if invalidation occurred whilst the
operation was ongoing.
* The netfs data supplied when the cookie was created.
* The page descriptor.
Data File Coherency
===================
* The context argument passed to the above function. This will be
maintained with the get_context/put_context functions mentioned above.
* An argument that's 0 on success or negative for an error code.
If an error occurs, it should be assumed that the page contains no usable
data. fscache_readpages_cancel() may need to be called.
end_io_func() will be called in process context if the read is results in
an error, but it might be called in interrupt context if the read is
successful.
Otherwise, if there's not a copy available in cache, but the cache may be able
to store the page:
(1) The mark_pages_cached() cookie operation will be called on that page.
(2) A block may be reserved in the cache and attached to the object at the
appropriate place.
(3) The function will return -ENODATA.
This function may also return -ENOMEM or -EINTR, in which case it won't have
read any data from the cache.
Page Allocate
-------------
Alternatively, if there's not expected to be any data in the cache for a page
because the file has been extended, a block can simply be allocated instead::
int fscache_alloc_page(struct fscache_cookie *cookie,
struct page *page,
gfp_t gfp);
This is similar to the fscache_read_or_alloc_page() function, except that it
never reads from the cache. It will return 0 if a block has been allocated,
rather than -ENODATA as the other would. One or the other must be performed
before writing to the cache.
The mark_pages_cached() cookie operation will be called on the page if
successful.
Page Write
----------
Secondly, if the netfs changes the contents of the page (either due to an
initial download or if a user performs a write), then the page should be
written back to the cache::
int fscache_write_page(struct fscache_cookie *cookie,
struct page *page,
loff_t object_size,
gfp_t gfp);
The cookie argument must specify a data file cookie, the page specified should
contain the data to be written (and is also used to specify the page number),
object_size is the revised size of the object and the gfp argument is used to
control how any memory allocations made are satisfied.
The page must have first been read or allocated successfully and must not have
been uncached before writing is performed.
If the cookie indicates the inode is not cached then:
(1) The function will return -ENOBUFS.
Else if space can be allocated in the cache to hold this page:
(1) PG_fscache_write will be set on the page.
(2) The function will submit a request to write the data to cache's backing
device directly from the page specified.
(3) The function will return 0.
(4) When the write is complete PG_fscache_write is cleared on the page and
anyone waiting for that bit will be woken up.
Else if there's no space available in the cache, -ENOBUFS will be returned. It
is also possible for the PG_fscache_write bit to be cleared when no write took
place if unforeseen circumstances arose (such as a disk error).
Writing takes place asynchronously.
Multiple Page Read
------------------
A facility is provided to read several pages at once, as requested by the
readpages() address space operation::
int fscache_read_or_alloc_pages(struct fscache_cookie *cookie,
struct address_space *mapping,
struct list_head *pages,
int *nr_pages,
fscache_rw_complete_t end_io_func,
void *context,
gfp_t gfp);
This works in a similar way to fscache_read_or_alloc_page(), except:
(1) Any page it can retrieve data for is removed from pages and nr_pages and
dispatched for reading to the disk. Reads of adjacent pages on disk may
be merged for greater efficiency.
(2) The mark_pages_cached() cookie operation will be called on several pages
at once if they're being read or allocated.
(3) If there was an general error, then that error will be returned.
Else if some pages couldn't be allocated or read, then -ENOBUFS will be
returned.
Else if some pages couldn't be read but were allocated, then -ENODATA will
be returned.
Otherwise, if all pages had reads dispatched, then 0 will be returned, the
list will be empty and ``*nr_pages`` will be 0.
(4) end_io_func will be called once for each page being read as the reads
complete. It will be called in process context if error != 0, but it may
be called in interrupt context if there is no error.
Note that a return of -ENODATA, -ENOBUFS or any other error does not preclude
some of the pages being read and some being allocated. Those pages will have
been marked appropriately and will need uncaching.
Cancellation of Unread Pages
----------------------------
If one or more pages are passed to fscache_read_or_alloc_pages() but not then
read from the cache and also not read from the underlying filesystem then
those pages will need to have any marks and reservations removed. This can be
done by calling::
void fscache_readpages_cancel(struct fscache_cookie *cookie,
struct list_head *pages);
prior to returning to the caller. The cookie argument should be as passed to
fscache_read_or_alloc_pages(). Every page in the pages list will be examined
and any that have PG_fscache set will be uncached.
Page Uncaching
==============
To uncache a page, this function should be called::
void fscache_uncache_page(struct fscache_cookie *cookie,
struct page *page);
This function permits the cache to release any in-memory representation it
might be holding for this netfs page. This function must be called once for
each page on which the read or write page functions above have been called to
make sure the cache's in-memory tracking information gets torn down.
Note that pages can't be explicitly deleted from the a data file. The whole
data file must be retired (see the relinquish cookie function below).
Furthermore, note that this does not cancel the asynchronous read or write
operation started by the read/alloc and write functions, so the page
invalidation functions must use::
bool fscache_check_page_write(struct fscache_cookie *cookie,
struct page *page);
to see if a page is being written to the cache, and::
void fscache_wait_on_page_write(struct fscache_cookie *cookie,
struct page *page);
to wait for it to finish if it is.
When releasepage() is being implemented, a special FS-Cache function exists to
manage the heuristics of coping with vmscan trying to eject pages, which may
conflict with the cache trying to write pages to the cache (which may itself
need to allocate memory)::
bool fscache_maybe_release_page(struct fscache_cookie *cookie,
struct page *page,
gfp_t gfp);
This takes the netfs cookie, and the page and gfp arguments as supplied to
releasepage(). It will return false if the page cannot be released yet for
some reason and if it returns true, the page has been uncached and can now be
released.
To make a page available for release, this function may wait for an outstanding
storage request to complete, or it may attempt to cancel the storage request -
in which case the page will not be stored in the cache this time.
Bulk Image Page Uncache
-----------------------
A convenience routine is provided to perform an uncache on all the pages
attached to an inode. This assumes that the pages on the inode correspond on a
1:1 basis with the pages in the cache::
void fscache_uncache_all_inode_pages(struct fscache_cookie *cookie,
struct inode *inode);
This takes the netfs cookie that the pages were cached with and the inode that
the pages are attached to. This function will wait for pages to finish being
written to the cache and for the cache to finish with the page generally. No
error is returned.
Index and Data File consistency
===============================
To find out whether auxiliary data for an object is up to data within the
cache, the following function can be called::
int fscache_check_consistency(struct fscache_cookie *cookie,
const void *aux_data);
This will call back to the netfs to check whether the auxiliary data associated
with a cookie is correct; if aux_data is non-NULL, it will update the auxiliary
data buffer first. It returns 0 if it is and -ESTALE if it isn't; it may also
return -ENOMEM and -ERESTARTSYS.
To request an update of the index data for an index or other object, the
following function should be called::
To request an update of the coherency data and file size on a cookie, the
following should be called::
void fscache_update_cookie(struct fscache_cookie *cookie,
const void *aux_data);
This function will update the cookie's auxiliary data buffer from aux_data if
that is non-NULL and then schedule this to be stored on disk. The update
method in the parent index definition will be called to transfer the data.
Note that partial updates may happen automatically at other times, such as when
data blocks are added to a data file object.
Cookie Enablement
=================
Cookies exist in one of two states: enabled and disabled. If a cookie is
disabled, it ignores all attempts to acquire child cookies; check, update or
invalidate its state; allocate, read or write backing pages - though it is
still possible to uncache pages and relinquish the cookie.
The initial enablement state is set by fscache_acquire_cookie(), but the cookie
can be enabled or disabled later. To disable a cookie, call::
void fscache_disable_cookie(struct fscache_cookie *cookie,
const void *aux_data,
bool invalidate);
If the cookie is not already disabled, this locks the cookie against other
enable and disable ops, marks the cookie as being disabled, discards or
invalidates any backing objects and waits for cessation of activity on any
associated object before unlocking the cookie.
All possible failures are handled internally. The caller should consider
calling fscache_uncache_all_inode_pages() afterwards to make sure all page
markings are cleared up.
Cookies can be enabled or reenabled with::
void fscache_enable_cookie(struct fscache_cookie *cookie,
const void *aux_data,
loff_t object_size,
bool (*can_enable)(void *data),
void *data)
If the cookie is not already enabled, this locks the cookie against other
enable and disable ops, invokes can_enable() and, if the cookie is not an index
cookie, will begin the procedure of acquiring backing objects.
The optional can_enable() function is passed the data argument and returns a
ruling as to whether or not enablement should actually be permitted to begin.
const loff_t *object_size);
All possible failures are handled internally. The cookie will only be marked
as enabled if provisional backing objects are allocated.
This will update the cookie's coherency data and/or file size.
The object's data size is updated from object_size and is passed to the
->check_aux() function.
In both cases, the cookie's auxiliary data buffer is updated from aux_data if
that is non-NULL inside the enablement lock before proceeding.
Miscellaneous Cookie operations
===============================
Data File Invalidation
======================
There are a number of operations that can be used to control cookies:
Sometimes it will be necessary to invalidate an object that contains data.
Typically this will be necessary when the server informs the network filesystem
of a remote third-party change - at which point the filesystem has to throw
away the state and cached data that it had for an file and reload from the
server.
* Cookie pinning::
To indicate that a cache object should be invalidated, the following should be
called::
int fscache_pin_cookie(struct fscache_cookie *cookie);
void fscache_unpin_cookie(struct fscache_cookie *cookie);
void fscache_invalidate(struct fscache_cookie *cookie,
const void *aux_data,
loff_t size,
unsigned int flags);
These operations permit data cookies to be pinned into the cache and to
have the pinning removed. They are not permitted on index cookies.
This increases the invalidation counter in the cookie to cause outstanding
reads to fail with -ESTALE, sets the coherency data and file size from the
information supplied, blocks new I/O on the cookie and dispatches the cache to
go and get rid of the old data.
The pinning function will return 0 if successful, -ENOBUFS in the cookie
isn't backed by a cache, -EOPNOTSUPP if the cache doesn't support pinning,
-ENOSPC if there isn't enough space to honour the operation, -ENOMEM or
-EIO if there's any other problem.
Invalidation runs asynchronously in a worker thread so that it doesn't block
too much.
* Data space reservation::
int fscache_reserve_space(struct fscache_cookie *cookie, loff_t size);
Write-Back Resource Management
==============================
This permits a netfs to request cache space be reserved to store up to the
given amount of a file. It is permitted to ask for more than the current
size of the file to allow for future file expansion.
To write data to the cache from network filesystem writeback, the cache
resources required need to be pinned at the point the modification is made (for
instance when the page is marked dirty) as it's not possible to open a file in
a thread that's exiting.
If size is given as zero then the reservation will be cancelled.
The following facilities are provided to manage this:
The function will return 0 if successful, -ENOBUFS in the cookie isn't
backed by a cache, -EOPNOTSUPP if the cache doesn't support reservations,
-ENOSPC if there isn't enough space to honour the operation, -ENOMEM or
-EIO if there's any other problem.
* An inode flag, ``I_PINNING_FSCACHE_WB``, is provided to indicate that an
in-use is held on the cookie for this inode. It can only be changed if the
the inode lock is held.
Note that this doesn't pin an object in a cache; it can still be culled to
make space if it's not in use.
* A flag, ``unpinned_fscache_wb`` is placed in the ``writeback_control``
struct that gets set if ``__writeback_single_inode()`` clears
``I_PINNING_FSCACHE_WB`` because all the dirty pages were cleared.
To support this, the following functions are provided::
Cookie Unregistration
=====================
int fscache_set_page_dirty(struct page *page,
struct fscache_cookie *cookie);
void fscache_unpin_writeback(struct writeback_control *wbc,
struct fscache_cookie *cookie);
void fscache_clear_inode_writeback(struct fscache_cookie *cookie,
struct inode *inode,
const void *aux);
To get rid of a cookie, this function should be called::
The *set* function is intended to be called from the filesystem's
``set_page_dirty`` address space operation. If ``I_PINNING_FSCACHE_WB`` is not
set, it sets that flag and increments the use count on the cookie (the caller
must already have called ``fscache_use_cookie()``).
void fscache_relinquish_cookie(struct fscache_cookie *cookie,
const void *aux_data,
bool retire);
The *unpin* function is intended to be called from the filesystem's
``write_inode`` superblock operation. It cleans up after writing by unusing
the cookie if unpinned_fscache_wb is set in the writeback_control struct.
If retire is non-zero, then the object will be marked for recycling, and all
copies of it will be removed from all active caches in which it is present.
Not only that but all child objects will also be retired.
The *clear* function is intended to be called from the netfs's ``evict_inode``
superblock operation. It must be called *after*
``truncate_inode_pages_final()``, but *before* ``clear_inode()``. This cleans
up any hanging ``I_PINNING_FSCACHE_WB``. It also allows the coherency data to
be updated.
If retire is zero, then the object may be available again when next the
acquisition function is called. Retirement here will overrule the pinning on a
cookie.
The cookie's auxiliary data will be updated from aux_data if that is non-NULL
so that the cache can lazily update it on disk.
Caching of Local Modifications
==============================
One very important note - relinquish must NOT be called for a cookie unless all
the cookies for "child" indices, objects and pages have been relinquished
first.
If a network filesystem has locally modified data that it wants to write to the
cache, it needs to mark the pages to indicate that a write is in progress, and
if the mark is already present, it needs to wait for it to be removed first
(presumably due to an already in-progress operation). This prevents multiple
competing DIO writes to the same storage in the cache.
Firstly, the netfs should determine if caching is available by doing something
like::
Index Invalidation
==================
bool caching = fscache_cookie_enabled(cookie);
There is no direct way to invalidate an index subtree. To do this, the caller
should relinquish and retire the cookie they have, and then acquire a new one.
If caching is to be attempted, pages should be waited for and then marked using
the following functions provided by the netfs helper library::
void set_page_fscache(struct page *page);
void wait_on_page_fscache(struct page *page);
int wait_on_page_fscache_killable(struct page *page);
Data File Invalidation
======================
Once all the pages in the span are marked, the netfs can ask fscache to
schedule a write of that region::
Sometimes it will be necessary to invalidate an object that contains data.
Typically this will be necessary when the server tells the netfs of a foreign
change - at which point the netfs has to throw away all the state it had for an
inode and reload from the server.
void fscache_write_to_cache(struct fscache_cookie *cookie,
struct address_space *mapping,
loff_t start, size_t len, loff_t i_size,
netfs_io_terminated_t term_func,
void *term_func_priv,
bool caching)
To indicate that a cache object should be invalidated, the following function
can be called::
And if an error occurs before that point is reached, the marks can be removed
by calling::
void fscache_invalidate(struct fscache_cookie *cookie);
void fscache_clear_page_bits(struct fscache_cookie *cookie,
struct address_space *mapping,
loff_t start, size_t len,
bool caching)
This can be called with spinlocks held as it defers the work to a thread pool.
All extant storage, retrieval and attribute change ops at this point are
cancelled and discarded. Some future operations will be rejected until the
cache has had a chance to insert a barrier in the operations queue. After
that, operations will be queued again behind the invalidation operation.
In both of these functions, the cookie representing the cache object to be
written to and a pointer to the mapping to which the source pages are attached
are passed in; start and len indicate the size of the region that's going to be
written (it doesn't have to align to page boundaries necessarily, but it does
have to align to DIO boundaries on the backing filesystem). The caching
parameter indicates if caching should be skipped, and if false, the functions
do nothing.
The invalidation operation will perform an attribute change operation and an
auxiliary data update operation as it is very likely these will have changed.
The write function takes some additional parameters: i_size indicates the size
of the netfs file and term_func indicates an optional completion function, to
which term_func_priv will be passed, along with the error or amount written.
Using the following function, the netfs can wait for the invalidation operation
to have reached a point at which it can start submitting ordinary operations
once again::
Note that the write function will always run asynchronously and will unmark all
the pages upon completion before calling term_func.
void fscache_wait_on_invalidate(struct fscache_cookie *cookie);
Page Release and Invalidation
=============================
FS-cache Specific Page Flag
===========================
Fscache keeps track of whether we have any data in the cache yet for a cache
object we've just created. It knows it doesn't have to do any reading until it
has done a write and then the page it wrote from has been released by the VM,
after which it *has* to look in the cache.
FS-Cache makes use of a page flag, PG_private_2, for its own purpose. This is
given the alternative name PG_fscache.
To inform fscache that a page might now be in the cache, the following function
should be called from the ``releasepage`` address space op::
PG_fscache is used to indicate that the page is known by the cache, and that
the cache must be informed if the page is going to go away. It's an indication
to the netfs that the cache has an interest in this page, where an interest may
be a pointer to it, resources allocated or reserved for it, or I/O in progress
upon it.
void fscache_note_page_release(struct fscache_cookie *cookie);
The netfs can use this information in methods such as releasepage() to
determine whether it needs to uncache a page or update it.
if the page has been released (ie. releasepage returned true).
Furthermore, if this bit is set, releasepage() and invalidatepage() operations
will be called on a page to get rid of it, even if PG_private is not set. This
allows caching to attempted on a page before read_cache_pages() to be called
after fscache_read_or_alloc_pages() as the former will try and release pages it
was given under certain circumstances.
Page release and page invalidation should also wait for any mark left on the
page to say that a DIO write is underway from that page::
This bit does not overlap with such as PG_private. This means that FS-Cache
can be used with a filesystem that uses the block buffering code.
void wait_on_page_fscache(struct page *page);
int wait_on_page_fscache_killable(struct page *page);
There are a number of operations defined on this flag::
int PageFsCache(struct page *page);
void SetPageFsCache(struct page *page)
void ClearPageFsCache(struct page *page)
int TestSetPageFsCache(struct page *page)
int TestClearPageFsCache(struct page *page)
API Function Reference
======================
These functions are bit test, bit set, bit clear, bit test and set and bit
test and clear operations on PG_fscache.
.. kernel-doc:: include/linux/fscache.h
.. SPDX-License-Identifier: GPL-2.0
====================================================
In-Kernel Cache Object Representation and Management
====================================================
By: David Howells <dhowells@redhat.com>
.. Contents:
(*) Representation
(*) Object management state machine.
- Provision of cpu time.
- Locking simplification.
(*) The set of states.
(*) The set of events.
Representation
==============
FS-Cache maintains an in-kernel representation of each object that a netfs is
currently interested in. Such objects are represented by the fscache_cookie
struct and are referred to as cookies.
FS-Cache also maintains a separate in-kernel representation of the objects that
a cache backend is currently actively caching. Such objects are represented by
the fscache_object struct. The cache backends allocate these upon request, and
are expected to embed them in their own representations. These are referred to
as objects.
There is a 1:N relationship between cookies and objects. A cookie may be
represented by multiple objects - an index may exist in more than one cache -
or even by no objects (it may not be cached).
Furthermore, both cookies and objects are hierarchical. The two hierarchies
correspond, but the cookies tree is a superset of the union of the object trees
of multiple caches::
NETFS INDEX TREE : CACHE 1 : CACHE 2
: :
: +-----------+ :
+----------->| IObject | :
+-----------+ | : +-----------+ :
| ICookie |-------+ : | :
+-----------+ | : | : +-----------+
| +------------------------------>| IObject |
| : | : +-----------+
| : V : |
| : +-----------+ : |
V +----------->| IObject | : |
+-----------+ | : +-----------+ : |
| ICookie |-------+ : | : V
+-----------+ | : | : +-----------+
| +------------------------------>| IObject |
+-----+-----+ : | : +-----------+
| | : | : |
V | : V : |
+-----------+ | : +-----------+ : |
| ICookie |------------------------->| IObject | : |
+-----------+ | : +-----------+ : |
| V : | : V
| +-----------+ : | : +-----------+
| | ICookie |-------------------------------->| IObject |
| +-----------+ : | : +-----------+
V | : V : |
+-----------+ | : +-----------+ : |
| DCookie |------------------------->| DObject | : |
+-----------+ | : +-----------+ : |
| : : |
+-------+-------+ : : |
| | : : |
V V : : V
+-----------+ +-----------+ : : +-----------+
| DCookie | | DCookie |------------------------>| DObject |
+-----------+ +-----------+ : : +-----------+
: :
In the above illustration, ICookie and IObject represent indices and DCookie
and DObject represent data storage objects. Indices may have representation in
multiple caches, but currently, non-index objects may not. Objects of any type
may also be entirely unrepresented.
As far as the netfs API goes, the netfs is only actually permitted to see
pointers to the cookies. The cookies themselves and any objects attached to
those cookies are hidden from it.
Object Management State Machine
===============================
Within FS-Cache, each active object is managed by its own individual state
machine. The state for an object is kept in the fscache_object struct, in
object->state. A cookie may point to a set of objects that are in different
states.
Each state has an action associated with it that is invoked when the machine
wakes up in that state. There are four logical sets of states:
(1) Preparation: states that wait for the parent objects to become ready. The
representations are hierarchical, and it is expected that an object must
be created or accessed with respect to its parent object.
(2) Initialisation: states that perform lookups in the cache and validate
what's found and that create on disk any missing metadata.
(3) Normal running: states that allow netfs operations on objects to proceed
and that update the state of objects.
(4) Termination: states that detach objects from their netfs cookies, that
delete objects from disk, that handle disk and system errors and that free
up in-memory resources.
In most cases, transitioning between states is in response to signalled events.
When a state has finished processing, it will usually set the mask of events in
which it is interested (object->event_mask) and relinquish the worker thread.
Then when an event is raised (by calling fscache_raise_event()), if the event
is not masked, the object will be queued for processing (by calling
fscache_enqueue_object()).
Provision of CPU Time
---------------------
The work to be done by the various states was given CPU time by the threads of
the slow work facility. This was used in preference to the workqueue facility
because:
(1) Threads may be completely occupied for very long periods of time by a
particular work item. These state actions may be doing sequences of
synchronous, journalled disk accesses (lookup, mkdir, create, setxattr,
getxattr, truncate, unlink, rmdir, rename).
(2) Threads may do little actual work, but may rather spend a lot of time
sleeping on I/O. This means that single-threaded and 1-per-CPU-threaded
workqueues don't necessarily have the right numbers of threads.
Locking Simplification
----------------------
Because only one worker thread may be operating on any particular object's
state machine at once, this simplifies the locking, particularly with respect
to disconnecting the netfs's representation of a cache object (fscache_cookie)
from the cache backend's representation (fscache_object) - which may be
requested from either end.
The Set of States
=================
The object state machine has a set of states that it can be in. There are
preparation states in which the object sets itself up and waits for its parent
object to transit to a state that allows access to its children:
(1) State FSCACHE_OBJECT_INIT.
Initialise the object and wait for the parent object to become active. In
the cache, it is expected that it will not be possible to look an object
up from the parent object, until that parent object itself has been looked
up.
There are initialisation states in which the object sets itself up and accesses
disk for the object metadata:
(2) State FSCACHE_OBJECT_LOOKING_UP.
Look up the object on disk, using the parent as a starting point.
FS-Cache expects the cache backend to probe the cache to see whether this
object is represented there, and if it is, to see if it's valid (coherency
management).
The cache should call fscache_object_lookup_negative() to indicate lookup
failure for whatever reason, and should call fscache_obtained_object() to
indicate success.
At the completion of lookup, FS-Cache will let the netfs go ahead with
read operations, no matter whether the file is yet cached. If not yet
cached, read operations will be immediately rejected with ENODATA until
the first known page is uncached - as to that point there can be no data
to be read out of the cache for that file that isn't currently also held
in the pagecache.
(3) State FSCACHE_OBJECT_CREATING.
Create an object on disk, using the parent as a starting point. This
happens if the lookup failed to find the object, or if the object's
coherency data indicated what's on disk is out of date. In this state,
FS-Cache expects the cache to create
The cache should call fscache_obtained_object() if creation completes
successfully, fscache_object_lookup_negative() otherwise.
At the completion of creation, FS-Cache will start processing write
operations the netfs has queued for an object. If creation failed, the
write ops will be transparently discarded, and nothing recorded in the
cache.
There are some normal running states in which the object spends its time
servicing netfs requests:
(4) State FSCACHE_OBJECT_AVAILABLE.
A transient state in which pending operations are started, child objects
are permitted to advance from FSCACHE_OBJECT_INIT state, and temporary
lookup data is freed.
(5) State FSCACHE_OBJECT_ACTIVE.
The normal running state. In this state, requests the netfs makes will be
passed on to the cache.
(6) State FSCACHE_OBJECT_INVALIDATING.
The object is undergoing invalidation. When the state comes here, it
discards all pending read, write and attribute change operations as it is
going to clear out the cache entirely and reinitialise it. It will then
continue to the FSCACHE_OBJECT_UPDATING state.
(7) State FSCACHE_OBJECT_UPDATING.
The state machine comes here to update the object in the cache from the
netfs's records. This involves updating the auxiliary data that is used
to maintain coherency.
And there are terminal states in which an object cleans itself up, deallocates
memory and potentially deletes stuff from disk:
(8) State FSCACHE_OBJECT_LC_DYING.
The object comes here if it is dying because of a lookup or creation
error. This would be due to a disk error or system error of some sort.
Temporary data is cleaned up, and the parent is released.
(9) State FSCACHE_OBJECT_DYING.
The object comes here if it is dying due to an error, because its parent
cookie has been relinquished by the netfs or because the cache is being
withdrawn.
Any child objects waiting on this one are given CPU time so that they too
can destroy themselves. This object waits for all its children to go away
before advancing to the next state.
(10) State FSCACHE_OBJECT_ABORT_INIT.
The object comes to this state if it was waiting on its parent in
FSCACHE_OBJECT_INIT, but its parent died. The object will destroy itself
so that the parent may proceed from the FSCACHE_OBJECT_DYING state.
(11) State FSCACHE_OBJECT_RELEASING.
(12) State FSCACHE_OBJECT_RECYCLING.
The object comes to one of these two states when dying once it is rid of
all its children, if it is dying because the netfs relinquished its
cookie. In the first state, the cached data is expected to persist, and
in the second it will be deleted.
(13) State FSCACHE_OBJECT_WITHDRAWING.
The object transits to this state if the cache decides it wants to
withdraw the object from service, perhaps to make space, but also due to
error or just because the whole cache is being withdrawn.
(14) State FSCACHE_OBJECT_DEAD.
The object transits to this state when the in-memory object record is
ready to be deleted. The object processor shouldn't ever see an object in
this state.
The Set of Events
-----------------
There are a number of events that can be raised to an object state machine:
FSCACHE_OBJECT_EV_UPDATE
The netfs requested that an object be updated. The state machine will ask
the cache backend to update the object, and the cache backend will ask the
netfs for details of the change through its cookie definition ops.
FSCACHE_OBJECT_EV_CLEARED
This is signalled in two circumstances:
(a) when an object's last child object is dropped and
(b) when the last operation outstanding on an object is completed.
This is used to proceed from the dying state.
FSCACHE_OBJECT_EV_ERROR
This is signalled when an I/O error occurs during the processing of some
object.
FSCACHE_OBJECT_EV_RELEASE, FSCACHE_OBJECT_EV_RETIRE
These are signalled when the netfs relinquishes a cookie it was using.
The event selected depends on whether the netfs asks for the backing
object to be retired (deleted) or retained.
FSCACHE_OBJECT_EV_WITHDRAW
This is signalled when the cache backend wants to withdraw an object.
This means that the object will have to be detached from the netfs's
cookie.
Because the withdrawing releasing/retiring events are all handled by the object
state machine, it doesn't matter if there's a collision with both ends trying
to sever the connection at the same time. The state machine can just pick
which one it wants to honour, and that effects the other.
.. SPDX-License-Identifier: GPL-2.0
================================
Asynchronous Operations Handling
================================
By: David Howells <dhowells@redhat.com>
.. Contents:
(*) Overview.
(*) Operation record initialisation.
(*) Parameters.
(*) Procedure.
(*) Asynchronous callback.
Overview
========
FS-Cache has an asynchronous operations handling facility that it uses for its
data storage and retrieval routines. Its operations are represented by
fscache_operation structs, though these are usually embedded into some other
structure.
This facility is available to and expected to be used by the cache backends,
and FS-Cache will create operations and pass them off to the appropriate cache
backend for completion.
To make use of this facility, <linux/fscache-cache.h> should be #included.
Operation Record Initialisation
===============================
An operation is recorded in an fscache_operation struct::
struct fscache_operation {
union {
struct work_struct fast_work;
struct slow_work slow_work;
};
unsigned long flags;
fscache_operation_processor_t processor;
...
};
Someone wanting to issue an operation should allocate something with this
struct embedded in it. They should initialise it by calling::
void fscache_operation_init(struct fscache_operation *op,
fscache_operation_release_t release);
with the operation to be initialised and the release function to use.
The op->flags parameter should be set to indicate the CPU time provision and
the exclusivity (see the Parameters section).
The op->fast_work, op->slow_work and op->processor flags should be set as
appropriate for the CPU time provision (see the Parameters section).
FSCACHE_OP_WAITING may be set in op->flags prior to each submission of the
operation and waited for afterwards.
Parameters
==========
There are a number of parameters that can be set in the operation record's flag
parameter. There are three options for the provision of CPU time in these
operations:
(1) The operation may be done synchronously (FSCACHE_OP_MYTHREAD). A thread
may decide it wants to handle an operation itself without deferring it to
another thread.
This is, for example, used in read operations for calling readpages() on
the backing filesystem in CacheFiles. Although readpages() does an
asynchronous data fetch, the determination of whether pages exist is done
synchronously - and the netfs does not proceed until this has been
determined.
If this option is to be used, FSCACHE_OP_WAITING must be set in op->flags
before submitting the operation, and the operating thread must wait for it
to be cleared before proceeding::
wait_on_bit(&op->flags, FSCACHE_OP_WAITING,
TASK_UNINTERRUPTIBLE);
(2) The operation may be fast asynchronous (FSCACHE_OP_FAST), in which case it
will be given to keventd to process. Such an operation is not permitted
to sleep on I/O.
This is, for example, used by CacheFiles to copy data from a backing fs
page to a netfs page after the backing fs has read the page in.
If this option is used, op->fast_work and op->processor must be
initialised before submitting the operation::
INIT_WORK(&op->fast_work, do_some_work);
(3) The operation may be slow asynchronous (FSCACHE_OP_SLOW), in which case it
will be given to the slow work facility to process. Such an operation is
permitted to sleep on I/O.
This is, for example, used by FS-Cache to handle background writes of
pages that have just been fetched from a remote server.
If this option is used, op->slow_work and op->processor must be
initialised before submitting the operation::
fscache_operation_init_slow(op, processor)
Furthermore, operations may be one of two types:
(1) Exclusive (FSCACHE_OP_EXCLUSIVE). Operations of this type may not run in
conjunction with any other operation on the object being operated upon.
An example of this is the attribute change operation, in which the file
being written to may need truncation.
(2) Shareable. Operations of this type may be running simultaneously. It's
up to the operation implementation to prevent interference between other
operations running at the same time.
Procedure
=========
Operations are used through the following procedure:
(1) The submitting thread must allocate the operation and initialise it
itself. Normally this would be part of a more specific structure with the
generic op embedded within.
(2) The submitting thread must then submit the operation for processing using
one of the following two functions::
int fscache_submit_op(struct fscache_object *object,
struct fscache_operation *op);
int fscache_submit_exclusive_op(struct fscache_object *object,
struct fscache_operation *op);
The first function should be used to submit non-exclusive ops and the
second to submit exclusive ones. The caller must still set the
FSCACHE_OP_EXCLUSIVE flag.
If successful, both functions will assign the operation to the specified
object and return 0. -ENOBUFS will be returned if the object specified is
permanently unavailable.
The operation manager will defer operations on an object that is still
undergoing lookup or creation. The operation will also be deferred if an
operation of conflicting exclusivity is in progress on the object.
If the operation is asynchronous, the manager will retain a reference to
it, so the caller should put their reference to it by passing it to::
void fscache_put_operation(struct fscache_operation *op);
(3) If the submitting thread wants to do the work itself, and has marked the
operation with FSCACHE_OP_MYTHREAD, then it should monitor
FSCACHE_OP_WAITING as described above and check the state of the object if
necessary (the object might have died while the thread was waiting).
When it has finished doing its processing, it should call
fscache_op_complete() and fscache_put_operation() on it.
(4) The operation holds an effective lock upon the object, preventing other
exclusive ops conflicting until it is released. The operation can be
enqueued for further immediate asynchronous processing by adjusting the
CPU time provisioning option if necessary, eg::
op->flags &= ~FSCACHE_OP_TYPE;
op->flags |= ~FSCACHE_OP_FAST;
and calling::
void fscache_enqueue_operation(struct fscache_operation *op)
This can be used to allow other things to have use of the worker thread
pools.
Asynchronous Callback
=====================
When used in asynchronous mode, the worker thread pool will invoke the
processor method with a pointer to the operation. This should then get at the
container struct by using container_of()::
static void fscache_write_op(struct fscache_operation *_op)
{
struct fscache_storage *op =
container_of(_op, struct fscache_storage, op);
...
}
The caller holds a reference on the operation, and will invoke
fscache_put_operation() when the processor function returns. The processor
function is at liberty to call fscache_enqueue_operation() or to take extra
references.
......@@ -454,7 +454,8 @@ operation table looks like the following::
void *term_func_priv);
int (*prepare_write)(struct netfs_cache_resources *cres,
loff_t *_start, size_t *_len, loff_t i_size);
loff_t *_start, size_t *_len, loff_t i_size,
bool no_space_allocated_yet);
int (*write)(struct netfs_cache_resources *cres,
loff_t start_pos,
......@@ -515,11 +516,14 @@ The methods defined in the table are:
* ``prepare_write()``
[Required] Called to adjust a write to the cache and check that there is
sufficient space in the cache. The start and length values indicate the
size of the write that netfslib is proposing, and this can be adjusted by
the cache to respect DIO boundaries. The file size is passed for
information.
[Required] Called to prepare a write to the cache to take place. This
involves checking to see whether the cache has sufficient space to honour
the write. ``*_start`` and ``*_len`` indicate the region to be written; the
region can be shrunk or it can be expanded to a page boundary either way as
necessary to align for direct I/O. i_size holds the size of the object and
is provided for reference. no_space_allocated_yet is set to true if the
caller is certain that no data has been written to that region - for example
if it tried to do a read from there already.
* ``write()``
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment