214 lines
		
	
	
		
			7.2 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
			
		
		
	
	
			214 lines
		
	
	
		
			7.2 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
		       ================================
 | 
						|
		       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,
 | 
						|
			    fscache_wait_bit, 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_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.
 |