213 lines
7.2 KiB
Text
213 lines
7.2 KiB
Text
================================
|
|
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 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 whilst 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.
|