214 lines
		
	
	
	
		
			7.2 KiB
			
		
	
	
	
		
			Text
		
	
	
	
	
	
		
		
			
		
	
	
			214 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, | ||
|  | 			    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. |