| ================================ | |
| 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_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. |