diff options
Diffstat (limited to 'fs/jfs/jfs_metapage.c')
-rw-r--r-- | fs/jfs/jfs_metapage.c | 580 |
1 files changed, 580 insertions, 0 deletions
diff --git a/fs/jfs/jfs_metapage.c b/fs/jfs/jfs_metapage.c new file mode 100644 index 000000000000..4c0a3ac75c08 --- /dev/null +++ b/fs/jfs/jfs_metapage.c @@ -0,0 +1,580 @@ +/* + * Copyright (C) International Business Machines Corp., 2000-2003 + * Portions Copyright (C) Christoph Hellwig, 2001-2002 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See + * the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/fs.h> +#include <linux/init.h> +#include <linux/buffer_head.h> +#include <linux/mempool.h> +#include <linux/delay.h> +#include "jfs_incore.h" +#include "jfs_superblock.h" +#include "jfs_filsys.h" +#include "jfs_metapage.h" +#include "jfs_txnmgr.h" +#include "jfs_debug.h" + +static DEFINE_SPINLOCK(meta_lock); + +#ifdef CONFIG_JFS_STATISTICS +static struct { + uint pagealloc; /* # of page allocations */ + uint pagefree; /* # of page frees */ + uint lockwait; /* # of sleeping lock_metapage() calls */ +} mpStat; +#endif + + +#define HASH_BITS 10 /* This makes hash_table 1 4K page */ +#define HASH_SIZE (1 << HASH_BITS) +static struct metapage **hash_table = NULL; +static unsigned long hash_order; + + +static inline int metapage_locked(struct metapage *mp) +{ + return test_bit(META_locked, &mp->flag); +} + +static inline int trylock_metapage(struct metapage *mp) +{ + return test_and_set_bit(META_locked, &mp->flag); +} + +static inline void unlock_metapage(struct metapage *mp) +{ + clear_bit(META_locked, &mp->flag); + wake_up(&mp->wait); +} + +static void __lock_metapage(struct metapage *mp) +{ + DECLARE_WAITQUEUE(wait, current); + + INCREMENT(mpStat.lockwait); + + add_wait_queue_exclusive(&mp->wait, &wait); + do { + set_current_state(TASK_UNINTERRUPTIBLE); + if (metapage_locked(mp)) { + spin_unlock(&meta_lock); + schedule(); + spin_lock(&meta_lock); + } + } while (trylock_metapage(mp)); + __set_current_state(TASK_RUNNING); + remove_wait_queue(&mp->wait, &wait); +} + +/* needs meta_lock */ +static inline void lock_metapage(struct metapage *mp) +{ + if (trylock_metapage(mp)) + __lock_metapage(mp); +} + +#define METAPOOL_MIN_PAGES 32 +static kmem_cache_t *metapage_cache; +static mempool_t *metapage_mempool; + +static void init_once(void *foo, kmem_cache_t *cachep, unsigned long flags) +{ + struct metapage *mp = (struct metapage *)foo; + + if ((flags & (SLAB_CTOR_VERIFY|SLAB_CTOR_CONSTRUCTOR)) == + SLAB_CTOR_CONSTRUCTOR) { + mp->lid = 0; + mp->lsn = 0; + mp->flag = 0; + mp->data = NULL; + mp->clsn = 0; + mp->log = NULL; + set_bit(META_free, &mp->flag); + init_waitqueue_head(&mp->wait); + } +} + +static inline struct metapage *alloc_metapage(int gfp_mask) +{ + return mempool_alloc(metapage_mempool, gfp_mask); +} + +static inline void free_metapage(struct metapage *mp) +{ + mp->flag = 0; + set_bit(META_free, &mp->flag); + + mempool_free(mp, metapage_mempool); +} + +int __init metapage_init(void) +{ + /* + * Allocate the metapage structures + */ + metapage_cache = kmem_cache_create("jfs_mp", sizeof(struct metapage), + 0, 0, init_once, NULL); + if (metapage_cache == NULL) + return -ENOMEM; + + metapage_mempool = mempool_create(METAPOOL_MIN_PAGES, mempool_alloc_slab, + mempool_free_slab, metapage_cache); + + if (metapage_mempool == NULL) { + kmem_cache_destroy(metapage_cache); + return -ENOMEM; + } + /* + * Now the hash list + */ + for (hash_order = 0; + ((PAGE_SIZE << hash_order) / sizeof(void *)) < HASH_SIZE; + hash_order++); + hash_table = + (struct metapage **) __get_free_pages(GFP_KERNEL, hash_order); + assert(hash_table); + memset(hash_table, 0, PAGE_SIZE << hash_order); + + return 0; +} + +void metapage_exit(void) +{ + mempool_destroy(metapage_mempool); + kmem_cache_destroy(metapage_cache); +} + +/* + * Basically same hash as in pagemap.h, but using our hash table + */ +static struct metapage **meta_hash(struct address_space *mapping, + unsigned long index) +{ +#define i (((unsigned long)mapping)/ \ + (sizeof(struct inode) & ~(sizeof(struct inode) -1 ))) +#define s(x) ((x) + ((x) >> HASH_BITS)) + return hash_table + (s(i + index) & (HASH_SIZE - 1)); +#undef i +#undef s +} + +static struct metapage *search_hash(struct metapage ** hash_ptr, + struct address_space *mapping, + unsigned long index) +{ + struct metapage *ptr; + + for (ptr = *hash_ptr; ptr; ptr = ptr->hash_next) { + if ((ptr->mapping == mapping) && (ptr->index == index)) + return ptr; + } + + return NULL; +} + +static void add_to_hash(struct metapage * mp, struct metapage ** hash_ptr) +{ + if (*hash_ptr) + (*hash_ptr)->hash_prev = mp; + + mp->hash_prev = NULL; + mp->hash_next = *hash_ptr; + *hash_ptr = mp; +} + +static void remove_from_hash(struct metapage * mp, struct metapage ** hash_ptr) +{ + if (mp->hash_prev) + mp->hash_prev->hash_next = mp->hash_next; + else { + assert(*hash_ptr == mp); + *hash_ptr = mp->hash_next; + } + + if (mp->hash_next) + mp->hash_next->hash_prev = mp->hash_prev; +} + +struct metapage *__get_metapage(struct inode *inode, unsigned long lblock, + unsigned int size, int absolute, + unsigned long new) +{ + struct metapage **hash_ptr; + int l2BlocksPerPage; + int l2bsize; + struct address_space *mapping; + struct metapage *mp; + unsigned long page_index; + unsigned long page_offset; + + jfs_info("__get_metapage: inode = 0x%p, lblock = 0x%lx", inode, lblock); + + if (absolute) + mapping = inode->i_sb->s_bdev->bd_inode->i_mapping; + else { + /* + * If an nfs client tries to read an inode that is larger + * than any existing inodes, we may try to read past the + * end of the inode map + */ + if ((lblock << inode->i_blkbits) >= inode->i_size) + return NULL; + mapping = inode->i_mapping; + } + + hash_ptr = meta_hash(mapping, lblock); +again: + spin_lock(&meta_lock); + mp = search_hash(hash_ptr, mapping, lblock); + if (mp) { + page_found: + if (test_bit(META_stale, &mp->flag)) { + spin_unlock(&meta_lock); + msleep(1); + goto again; + } + mp->count++; + lock_metapage(mp); + spin_unlock(&meta_lock); + if (test_bit(META_discard, &mp->flag)) { + if (!new) { + jfs_error(inode->i_sb, + "__get_metapage: using a " + "discarded metapage"); + release_metapage(mp); + return NULL; + } + clear_bit(META_discard, &mp->flag); + } + jfs_info("__get_metapage: found 0x%p, in hash", mp); + if (mp->logical_size != size) { + jfs_error(inode->i_sb, + "__get_metapage: mp->logical_size != size"); + release_metapage(mp); + return NULL; + } + } else { + l2bsize = inode->i_blkbits; + l2BlocksPerPage = PAGE_CACHE_SHIFT - l2bsize; + page_index = lblock >> l2BlocksPerPage; + page_offset = (lblock - (page_index << l2BlocksPerPage)) << + l2bsize; + if ((page_offset + size) > PAGE_CACHE_SIZE) { + spin_unlock(&meta_lock); + jfs_err("MetaData crosses page boundary!!"); + return NULL; + } + + /* + * Locks held on aggregate inode pages are usually + * not held long, and they are taken in critical code + * paths (committing dirty inodes, txCommit thread) + * + * Attempt to get metapage without blocking, tapping into + * reserves if necessary. + */ + mp = NULL; + if (JFS_IP(inode)->fileset == AGGREGATE_I) { + mp = alloc_metapage(GFP_ATOMIC); + if (!mp) { + /* + * mempool is supposed to protect us from + * failing here. We will try a blocking + * call, but a deadlock is possible here + */ + printk(KERN_WARNING + "__get_metapage: atomic call to mempool_alloc failed.\n"); + printk(KERN_WARNING + "Will attempt blocking call\n"); + } + } + if (!mp) { + struct metapage *mp2; + + spin_unlock(&meta_lock); + mp = alloc_metapage(GFP_NOFS); + spin_lock(&meta_lock); + + /* we dropped the meta_lock, we need to search the + * hash again. + */ + mp2 = search_hash(hash_ptr, mapping, lblock); + if (mp2) { + free_metapage(mp); + mp = mp2; + goto page_found; + } + } + mp->flag = 0; + lock_metapage(mp); + if (absolute) + set_bit(META_absolute, &mp->flag); + mp->xflag = COMMIT_PAGE; + mp->count = 1; + atomic_set(&mp->nohomeok,0); + mp->mapping = mapping; + mp->index = lblock; + mp->page = NULL; + mp->logical_size = size; + add_to_hash(mp, hash_ptr); + spin_unlock(&meta_lock); + + if (new) { + jfs_info("__get_metapage: Calling grab_cache_page"); + mp->page = grab_cache_page(mapping, page_index); + if (!mp->page) { + jfs_err("grab_cache_page failed!"); + goto freeit; + } else { + INCREMENT(mpStat.pagealloc); + unlock_page(mp->page); + } + } else { + jfs_info("__get_metapage: Calling read_cache_page"); + mp->page = read_cache_page(mapping, lblock, + (filler_t *)mapping->a_ops->readpage, NULL); + if (IS_ERR(mp->page)) { + jfs_err("read_cache_page failed!"); + goto freeit; + } else + INCREMENT(mpStat.pagealloc); + } + mp->data = kmap(mp->page) + page_offset; + } + + if (new) + memset(mp->data, 0, PSIZE); + + jfs_info("__get_metapage: returning = 0x%p", mp); + return mp; + +freeit: + spin_lock(&meta_lock); + remove_from_hash(mp, hash_ptr); + free_metapage(mp); + spin_unlock(&meta_lock); + return NULL; +} + +void hold_metapage(struct metapage * mp, int force) +{ + spin_lock(&meta_lock); + + mp->count++; + + if (force) { + ASSERT (!(test_bit(META_forced, &mp->flag))); + if (trylock_metapage(mp)) + set_bit(META_forced, &mp->flag); + } else + lock_metapage(mp); + + spin_unlock(&meta_lock); +} + +static void __write_metapage(struct metapage * mp) +{ + int l2bsize = mp->mapping->host->i_blkbits; + int l2BlocksPerPage = PAGE_CACHE_SHIFT - l2bsize; + unsigned long page_index; + unsigned long page_offset; + int rc; + + jfs_info("__write_metapage: mp = 0x%p", mp); + + page_index = mp->page->index; + page_offset = + (mp->index - (page_index << l2BlocksPerPage)) << l2bsize; + + lock_page(mp->page); + rc = mp->mapping->a_ops->prepare_write(NULL, mp->page, page_offset, + page_offset + + mp->logical_size); + if (rc) { + jfs_err("prepare_write return %d!", rc); + ClearPageUptodate(mp->page); + unlock_page(mp->page); + clear_bit(META_dirty, &mp->flag); + return; + } + rc = mp->mapping->a_ops->commit_write(NULL, mp->page, page_offset, + page_offset + + mp->logical_size); + if (rc) { + jfs_err("commit_write returned %d", rc); + } + + unlock_page(mp->page); + clear_bit(META_dirty, &mp->flag); + + jfs_info("__write_metapage done"); +} + +static inline void sync_metapage(struct metapage *mp) +{ + struct page *page = mp->page; + + page_cache_get(page); + lock_page(page); + + /* we're done with this page - no need to check for errors */ + if (page_has_buffers(page)) + write_one_page(page, 1); + else + unlock_page(page); + page_cache_release(page); +} + +void release_metapage(struct metapage * mp) +{ + struct jfs_log *log; + + jfs_info("release_metapage: mp = 0x%p, flag = 0x%lx", mp, mp->flag); + + spin_lock(&meta_lock); + if (test_bit(META_forced, &mp->flag)) { + clear_bit(META_forced, &mp->flag); + mp->count--; + spin_unlock(&meta_lock); + return; + } + + assert(mp->count); + if (--mp->count || atomic_read(&mp->nohomeok)) { + unlock_metapage(mp); + spin_unlock(&meta_lock); + return; + } + + if (mp->page) { + set_bit(META_stale, &mp->flag); + spin_unlock(&meta_lock); + kunmap(mp->page); + mp->data = NULL; + if (test_bit(META_dirty, &mp->flag)) + __write_metapage(mp); + if (test_bit(META_sync, &mp->flag)) { + sync_metapage(mp); + clear_bit(META_sync, &mp->flag); + } + + if (test_bit(META_discard, &mp->flag)) { + lock_page(mp->page); + block_invalidatepage(mp->page, 0); + unlock_page(mp->page); + } + + page_cache_release(mp->page); + mp->page = NULL; + INCREMENT(mpStat.pagefree); + spin_lock(&meta_lock); + } + + if (mp->lsn) { + /* + * Remove metapage from logsynclist. + */ + log = mp->log; + LOGSYNC_LOCK(log); + mp->log = NULL; + mp->lsn = 0; + mp->clsn = 0; + log->count--; + list_del(&mp->synclist); + LOGSYNC_UNLOCK(log); + } + remove_from_hash(mp, meta_hash(mp->mapping, mp->index)); + spin_unlock(&meta_lock); + + free_metapage(mp); +} + +void __invalidate_metapages(struct inode *ip, s64 addr, int len) +{ + struct metapage **hash_ptr; + unsigned long lblock; + int l2BlocksPerPage = PAGE_CACHE_SHIFT - ip->i_blkbits; + /* All callers are interested in block device's mapping */ + struct address_space *mapping = ip->i_sb->s_bdev->bd_inode->i_mapping; + struct metapage *mp; + struct page *page; + + /* + * First, mark metapages to discard. They will eventually be + * released, but should not be written. + */ + for (lblock = addr; lblock < addr + len; + lblock += 1 << l2BlocksPerPage) { + hash_ptr = meta_hash(mapping, lblock); +again: + spin_lock(&meta_lock); + mp = search_hash(hash_ptr, mapping, lblock); + if (mp) { + if (test_bit(META_stale, &mp->flag)) { + spin_unlock(&meta_lock); + msleep(1); + goto again; + } + + clear_bit(META_dirty, &mp->flag); + set_bit(META_discard, &mp->flag); + spin_unlock(&meta_lock); + } else { + spin_unlock(&meta_lock); + page = find_lock_page(mapping, lblock>>l2BlocksPerPage); + if (page) { + block_invalidatepage(page, 0); + unlock_page(page); + page_cache_release(page); + } + } + } +} + +#ifdef CONFIG_JFS_STATISTICS +int jfs_mpstat_read(char *buffer, char **start, off_t offset, int length, + int *eof, void *data) +{ + int len = 0; + off_t begin; + + len += sprintf(buffer, + "JFS Metapage statistics\n" + "=======================\n" + "page allocations = %d\n" + "page frees = %d\n" + "lock waits = %d\n", + mpStat.pagealloc, + mpStat.pagefree, + mpStat.lockwait); + + begin = offset; + *start = buffer + begin; + len -= begin; + + if (len > length) + len = length; + else + *eof = 1; + + if (len < 0) + len = 0; + + return len; +} +#endif |