| David Howells | 952efe7 | 2009-04-03 16:42:39 +0100 | [diff] [blame] | 1 | ================================ | 
|  | 2 | ASYNCHRONOUS OPERATIONS HANDLING | 
|  | 3 | ================================ | 
|  | 4 |  | 
|  | 5 | By: David Howells <dhowells@redhat.com> | 
|  | 6 |  | 
|  | 7 | Contents: | 
|  | 8 |  | 
|  | 9 | (*) Overview. | 
|  | 10 |  | 
|  | 11 | (*) Operation record initialisation. | 
|  | 12 |  | 
|  | 13 | (*) Parameters. | 
|  | 14 |  | 
|  | 15 | (*) Procedure. | 
|  | 16 |  | 
|  | 17 | (*) Asynchronous callback. | 
|  | 18 |  | 
|  | 19 |  | 
|  | 20 | ======== | 
|  | 21 | OVERVIEW | 
|  | 22 | ======== | 
|  | 23 |  | 
|  | 24 | FS-Cache has an asynchronous operations handling facility that it uses for its | 
|  | 25 | data storage and retrieval routines.  Its operations are represented by | 
|  | 26 | fscache_operation structs, though these are usually embedded into some other | 
|  | 27 | structure. | 
|  | 28 |  | 
|  | 29 | This facility is available to and expected to be be used by the cache backends, | 
|  | 30 | and FS-Cache will create operations and pass them off to the appropriate cache | 
|  | 31 | backend for completion. | 
|  | 32 |  | 
|  | 33 | To make use of this facility, <linux/fscache-cache.h> should be #included. | 
|  | 34 |  | 
|  | 35 |  | 
|  | 36 | =============================== | 
|  | 37 | OPERATION RECORD INITIALISATION | 
|  | 38 | =============================== | 
|  | 39 |  | 
|  | 40 | An operation is recorded in an fscache_operation struct: | 
|  | 41 |  | 
|  | 42 | struct fscache_operation { | 
|  | 43 | union { | 
|  | 44 | struct work_struct fast_work; | 
|  | 45 | struct slow_work slow_work; | 
|  | 46 | }; | 
|  | 47 | unsigned long		flags; | 
|  | 48 | fscache_operation_processor_t processor; | 
|  | 49 | ... | 
|  | 50 | }; | 
|  | 51 |  | 
|  | 52 | Someone wanting to issue an operation should allocate something with this | 
|  | 53 | struct embedded in it.  They should initialise it by calling: | 
|  | 54 |  | 
|  | 55 | void fscache_operation_init(struct fscache_operation *op, | 
|  | 56 | fscache_operation_release_t release); | 
|  | 57 |  | 
|  | 58 | with the operation to be initialised and the release function to use. | 
|  | 59 |  | 
|  | 60 | The op->flags parameter should be set to indicate the CPU time provision and | 
|  | 61 | the exclusivity (see the Parameters section). | 
|  | 62 |  | 
|  | 63 | The op->fast_work, op->slow_work and op->processor flags should be set as | 
|  | 64 | appropriate for the CPU time provision (see the Parameters section). | 
|  | 65 |  | 
|  | 66 | FSCACHE_OP_WAITING may be set in op->flags prior to each submission of the | 
|  | 67 | operation and waited for afterwards. | 
|  | 68 |  | 
|  | 69 |  | 
|  | 70 | ========== | 
|  | 71 | PARAMETERS | 
|  | 72 | ========== | 
|  | 73 |  | 
|  | 74 | There are a number of parameters that can be set in the operation record's flag | 
|  | 75 | parameter.  There are three options for the provision of CPU time in these | 
|  | 76 | operations: | 
|  | 77 |  | 
|  | 78 | (1) The operation may be done synchronously (FSCACHE_OP_MYTHREAD).  A thread | 
|  | 79 | may decide it wants to handle an operation itself without deferring it to | 
|  | 80 | another thread. | 
|  | 81 |  | 
|  | 82 | This is, for example, used in read operations for calling readpages() on | 
|  | 83 | the backing filesystem in CacheFiles.  Although readpages() does an | 
|  | 84 | asynchronous data fetch, the determination of whether pages exist is done | 
|  | 85 | synchronously - and the netfs does not proceed until this has been | 
|  | 86 | determined. | 
|  | 87 |  | 
|  | 88 | If this option is to be used, FSCACHE_OP_WAITING must be set in op->flags | 
|  | 89 | before submitting the operation, and the operating thread must wait for it | 
|  | 90 | to be cleared before proceeding: | 
|  | 91 |  | 
|  | 92 | wait_on_bit(&op->flags, FSCACHE_OP_WAITING, | 
|  | 93 | fscache_wait_bit, TASK_UNINTERRUPTIBLE); | 
|  | 94 |  | 
|  | 95 |  | 
|  | 96 | (2) The operation may be fast asynchronous (FSCACHE_OP_FAST), in which case it | 
|  | 97 | will be given to keventd to process.  Such an operation is not permitted | 
|  | 98 | to sleep on I/O. | 
|  | 99 |  | 
|  | 100 | This is, for example, used by CacheFiles to copy data from a backing fs | 
|  | 101 | page to a netfs page after the backing fs has read the page in. | 
|  | 102 |  | 
|  | 103 | If this option is used, op->fast_work and op->processor must be | 
|  | 104 | initialised before submitting the operation: | 
|  | 105 |  | 
|  | 106 | INIT_WORK(&op->fast_work, do_some_work); | 
|  | 107 |  | 
|  | 108 |  | 
|  | 109 | (3) The operation may be slow asynchronous (FSCACHE_OP_SLOW), in which case it | 
|  | 110 | will be given to the slow work facility to process.  Such an operation is | 
|  | 111 | permitted to sleep on I/O. | 
|  | 112 |  | 
|  | 113 | This is, for example, used by FS-Cache to handle background writes of | 
|  | 114 | pages that have just been fetched from a remote server. | 
|  | 115 |  | 
|  | 116 | If this option is used, op->slow_work and op->processor must be | 
|  | 117 | initialised before submitting the operation: | 
|  | 118 |  | 
|  | 119 | fscache_operation_init_slow(op, processor) | 
|  | 120 |  | 
|  | 121 |  | 
|  | 122 | Furthermore, operations may be one of two types: | 
|  | 123 |  | 
|  | 124 | (1) Exclusive (FSCACHE_OP_EXCLUSIVE).  Operations of this type may not run in | 
|  | 125 | conjunction with any other operation on the object being operated upon. | 
|  | 126 |  | 
|  | 127 | An example of this is the attribute change operation, in which the file | 
|  | 128 | being written to may need truncation. | 
|  | 129 |  | 
|  | 130 | (2) Shareable.  Operations of this type may be running simultaneously.  It's | 
|  | 131 | up to the operation implementation to prevent interference between other | 
|  | 132 | operations running at the same time. | 
|  | 133 |  | 
|  | 134 |  | 
|  | 135 | ========= | 
|  | 136 | PROCEDURE | 
|  | 137 | ========= | 
|  | 138 |  | 
|  | 139 | Operations are used through the following procedure: | 
|  | 140 |  | 
|  | 141 | (1) The submitting thread must allocate the operation and initialise it | 
|  | 142 | itself.  Normally this would be part of a more specific structure with the | 
|  | 143 | generic op embedded within. | 
|  | 144 |  | 
|  | 145 | (2) The submitting thread must then submit the operation for processing using | 
|  | 146 | one of the following two functions: | 
|  | 147 |  | 
|  | 148 | int fscache_submit_op(struct fscache_object *object, | 
|  | 149 | struct fscache_operation *op); | 
|  | 150 |  | 
|  | 151 | int fscache_submit_exclusive_op(struct fscache_object *object, | 
|  | 152 | struct fscache_operation *op); | 
|  | 153 |  | 
|  | 154 | The first function should be used to submit non-exclusive ops and the | 
|  | 155 | second to submit exclusive ones.  The caller must still set the | 
|  | 156 | FSCACHE_OP_EXCLUSIVE flag. | 
|  | 157 |  | 
|  | 158 | If successful, both functions will assign the operation to the specified | 
|  | 159 | object and return 0.  -ENOBUFS will be returned if the object specified is | 
|  | 160 | permanently unavailable. | 
|  | 161 |  | 
|  | 162 | The operation manager will defer operations on an object that is still | 
|  | 163 | undergoing lookup or creation.  The operation will also be deferred if an | 
|  | 164 | operation of conflicting exclusivity is in progress on the object. | 
|  | 165 |  | 
|  | 166 | If the operation is asynchronous, the manager will retain a reference to | 
|  | 167 | it, so the caller should put their reference to it by passing it to: | 
|  | 168 |  | 
|  | 169 | void fscache_put_operation(struct fscache_operation *op); | 
|  | 170 |  | 
|  | 171 | (3) If the submitting thread wants to do the work itself, and has marked the | 
|  | 172 | operation with FSCACHE_OP_MYTHREAD, then it should monitor | 
|  | 173 | FSCACHE_OP_WAITING as described above and check the state of the object if | 
|  | 174 | necessary (the object might have died whilst the thread was waiting). | 
|  | 175 |  | 
|  | 176 | When it has finished doing its processing, it should call | 
|  | 177 | fscache_put_operation() on it. | 
|  | 178 |  | 
|  | 179 | (4) The operation holds an effective lock upon the object, preventing other | 
|  | 180 | exclusive ops conflicting until it is released.  The operation can be | 
|  | 181 | enqueued for further immediate asynchronous processing by adjusting the | 
|  | 182 | CPU time provisioning option if necessary, eg: | 
|  | 183 |  | 
|  | 184 | op->flags &= ~FSCACHE_OP_TYPE; | 
|  | 185 | op->flags |= ~FSCACHE_OP_FAST; | 
|  | 186 |  | 
|  | 187 | and calling: | 
|  | 188 |  | 
|  | 189 | void fscache_enqueue_operation(struct fscache_operation *op) | 
|  | 190 |  | 
|  | 191 | This can be used to allow other things to have use of the worker thread | 
|  | 192 | pools. | 
|  | 193 |  | 
|  | 194 |  | 
|  | 195 | ===================== | 
|  | 196 | ASYNCHRONOUS CALLBACK | 
|  | 197 | ===================== | 
|  | 198 |  | 
|  | 199 | When used in asynchronous mode, the worker thread pool will invoke the | 
|  | 200 | processor method with a pointer to the operation.  This should then get at the | 
|  | 201 | container struct by using container_of(): | 
|  | 202 |  | 
|  | 203 | static void fscache_write_op(struct fscache_operation *_op) | 
|  | 204 | { | 
|  | 205 | struct fscache_storage *op = | 
|  | 206 | container_of(_op, struct fscache_storage, op); | 
|  | 207 | ... | 
|  | 208 | } | 
|  | 209 |  | 
|  | 210 | The caller holds a reference on the operation, and will invoke | 
|  | 211 | fscache_put_operation() when the processor function returns.  The processor | 
|  | 212 | function is at liberty to call fscache_enqueue_operation() or to take extra | 
|  | 213 | references. |