| |
| JFFS2 LOCKING DOCUMENTATION |
| --------------------------- |
| |
| At least theoretically, JFFS2 does not require the Big Kernel Lock |
| (BKL), which was always helpfully obtained for it by Linux 2.4 VFS |
| code. It has its own locking, as described below. |
| |
| This document attempts to describe the existing locking rules for |
| JFFS2. It is not expected to remain perfectly up to date, but ought to |
| be fairly close. |
| |
| |
| alloc_sem |
| --------- |
| |
| The alloc_sem is a per-filesystem semaphore, used primarily to ensure |
| contiguous allocation of space on the medium. It is automatically |
| obtained during space allocations (jffs2_reserve_space()) and freed |
| upon write completion (jffs2_complete_reservation()). Note that |
| the garbage collector will obtain this right at the beginning of |
| jffs2_garbage_collect_pass() and release it at the end, thereby |
| preventing any other write activity on the file system during a |
| garbage collect pass. |
| |
| When writing new nodes, the alloc_sem must be held until the new nodes |
| have been properly linked into the data structures for the inode to |
| which they belong. This is for the benefit of NAND flash - adding new |
| nodes to an inode may obsolete old ones, and by holding the alloc_sem |
| until this happens we ensure that any data in the write-buffer at the |
| time this happens are part of the new node, not just something that |
| was written afterwards. Hence, we can ensure the newly-obsoleted nodes |
| don't actually get erased until the write-buffer has been flushed to |
| the medium. |
| |
| With the introduction of NAND flash support and the write-buffer, |
| the alloc_sem is also used to protect the wbuf-related members of the |
| jffs2_sb_info structure. Atomically reading the wbuf_len member to see |
| if the wbuf is currently holding any data is permitted, though. |
| |
| Ordering constraints: See f->sem. |
| |
| |
| File Semaphore f->sem |
| --------------------- |
| |
| This is the JFFS2-internal equivalent of the inode semaphore i->i_sem. |
| It protects the contents of the jffs2_inode_info private inode data, |
| including the linked list of node fragments (but see the notes below on |
| erase_completion_lock), etc. |
| |
| The reason that the i_sem itself isn't used for this purpose is to |
| avoid deadlocks with garbage collection -- the VFS will lock the i_sem |
| before calling a function which may need to allocate space. The |
| allocation may trigger garbage-collection, which may need to move a |
| node belonging to the inode which was locked in the first place by the |
| VFS. If the garbage collection code were to attempt to lock the i_sem |
| of the inode from which it's garbage-collecting a physical node, this |
| lead to deadlock, unless we played games with unlocking the i_sem |
| before calling the space allocation functions. |
| |
| Instead of playing such games, we just have an extra internal |
| semaphore, which is obtained by the garbage collection code and also |
| by the normal file system code _after_ allocation of space. |
| |
| Ordering constraints: |
| |
| 1. Never attempt to allocate space or lock alloc_sem with |
| any f->sem held. |
| 2. Never attempt to lock two file semaphores in one thread. |
| No ordering rules have been made for doing so. |
| |
| |
| erase_completion_lock spinlock |
| ------------------------------ |
| |
| This is used to serialise access to the eraseblock lists, to the |
| per-eraseblock lists of physical jffs2_raw_node_ref structures, and |
| (NB) the per-inode list of physical nodes. The latter is a special |
| case - see below. |
| |
| As the MTD API no longer permits erase-completion callback functions |
| to be called from bottom-half (timer) context (on the basis that nobody |
| ever actually implemented such a thing), it's now sufficient to use |
| a simple spin_lock() rather than spin_lock_bh(). |
| |
| Note that the per-inode list of physical nodes (f->nodes) is a special |
| case. Any changes to _valid_ nodes (i.e. ->flash_offset & 1 == 0) in |
| the list are protected by the file semaphore f->sem. But the erase |
| code may remove _obsolete_ nodes from the list while holding only the |
| erase_completion_lock. So you can walk the list only while holding the |
| erase_completion_lock, and can drop the lock temporarily mid-walk as |
| long as the pointer you're holding is to a _valid_ node, not an |
| obsolete one. |
| |
| The erase_completion_lock is also used to protect the c->gc_task |
| pointer when the garbage collection thread exits. The code to kill the |
| GC thread locks it, sends the signal, then unlocks it - while the GC |
| thread itself locks it, zeroes c->gc_task, then unlocks on the exit path. |
| |
| |
| inocache_lock spinlock |
| ---------------------- |
| |
| This spinlock protects the hashed list (c->inocache_list) of the |
| in-core jffs2_inode_cache objects (each inode in JFFS2 has the |
| correspondent jffs2_inode_cache object). So, the inocache_lock |
| has to be locked while walking the c->inocache_list hash buckets. |
| |
| This spinlock also covers allocation of new inode numbers, which is |
| currently just '++->highest_ino++', but might one day get more complicated |
| if we need to deal with wrapping after 4 milliard inode numbers are used. |
| |
| Note, the f->sem guarantees that the correspondent jffs2_inode_cache |
| will not be removed. So, it is allowed to access it without locking |
| the inocache_lock spinlock. |
| |
| Ordering constraints: |
| |
| If both erase_completion_lock and inocache_lock are needed, the |
| c->erase_completion has to be acquired first. |
| |
| |
| erase_free_sem |
| -------------- |
| |
| This semaphore is only used by the erase code which frees obsolete |
| node references and the jffs2_garbage_collect_deletion_dirent() |
| function. The latter function on NAND flash must read _obsolete_ nodes |
| to determine whether the 'deletion dirent' under consideration can be |
| discarded or whether it is still required to show that an inode has |
| been unlinked. Because reading from the flash may sleep, the |
| erase_completion_lock cannot be held, so an alternative, more |
| heavyweight lock was required to prevent the erase code from freeing |
| the jffs2_raw_node_ref structures in question while the garbage |
| collection code is looking at them. |
| |
| Suggestions for alternative solutions to this problem would be welcomed. |
| |
| |
| wbuf_sem |
| -------- |
| |
| This read/write semaphore protects against concurrent access to the |
| write-behind buffer ('wbuf') used for flash chips where we must write |
| in blocks. It protects both the contents of the wbuf and the metadata |
| which indicates which flash region (if any) is currently covered by |
| the buffer. |
| |
| Ordering constraints: |
| Lock wbuf_sem last, after the alloc_sem or and f->sem. |
| |
| |
| c->xattr_sem |
| ------------ |
| |
| This read/write semaphore protects against concurrent access to the |
| xattr related objects which include stuff in superblock and ic->xref. |
| In read-only path, write-semaphore is too much exclusion. It's enough |
| by read-semaphore. But you must hold write-semaphore when updating, |
| creating or deleting any xattr related object. |
| |
| Once xattr_sem released, there would be no assurance for the existence |
| of those objects. Thus, a series of processes is often required to retry, |
| when updating such a object is necessary under holding read semaphore. |
| For example, do_jffs2_getxattr() holds read-semaphore to scan xref and |
| xdatum at first. But it retries this process with holding write-semaphore |
| after release read-semaphore, if it's necessary to load name/value pair |
| from medium. |
| |
| Ordering constraints: |
| Lock xattr_sem last, after the alloc_sem. |