2009-04-03 23:42:37 +08:00
|
|
|
/* FS-Cache statistics
|
|
|
|
*
|
|
|
|
* Copyright (C) 2007 Red Hat, Inc. All Rights Reserved.
|
|
|
|
* Written by David Howells (dhowells@redhat.com)
|
|
|
|
*
|
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#define FSCACHE_DEBUG_LEVEL THREAD
|
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/proc_fs.h>
|
|
|
|
#include <linux/seq_file.h>
|
|
|
|
#include "internal.h"
|
|
|
|
|
|
|
|
/*
|
|
|
|
* operation counters
|
|
|
|
*/
|
|
|
|
atomic_t fscache_n_op_pend;
|
|
|
|
atomic_t fscache_n_op_run;
|
|
|
|
atomic_t fscache_n_op_enqueue;
|
|
|
|
atomic_t fscache_n_op_requeue;
|
|
|
|
atomic_t fscache_n_op_deferred_release;
|
|
|
|
atomic_t fscache_n_op_release;
|
|
|
|
atomic_t fscache_n_op_gc;
|
2009-11-20 02:11:19 +08:00
|
|
|
atomic_t fscache_n_op_cancelled;
|
2009-11-20 02:11:32 +08:00
|
|
|
atomic_t fscache_n_op_rejected;
|
2009-04-03 23:42:37 +08:00
|
|
|
|
|
|
|
atomic_t fscache_n_attr_changed;
|
|
|
|
atomic_t fscache_n_attr_changed_ok;
|
|
|
|
atomic_t fscache_n_attr_changed_nobufs;
|
|
|
|
atomic_t fscache_n_attr_changed_nomem;
|
|
|
|
atomic_t fscache_n_attr_changed_calls;
|
|
|
|
|
|
|
|
atomic_t fscache_n_allocs;
|
|
|
|
atomic_t fscache_n_allocs_ok;
|
|
|
|
atomic_t fscache_n_allocs_wait;
|
|
|
|
atomic_t fscache_n_allocs_nobufs;
|
2009-11-20 02:11:19 +08:00
|
|
|
atomic_t fscache_n_allocs_intr;
|
2009-11-20 02:11:45 +08:00
|
|
|
atomic_t fscache_n_allocs_object_dead;
|
2009-04-03 23:42:37 +08:00
|
|
|
atomic_t fscache_n_alloc_ops;
|
|
|
|
atomic_t fscache_n_alloc_op_waits;
|
|
|
|
|
|
|
|
atomic_t fscache_n_retrievals;
|
|
|
|
atomic_t fscache_n_retrievals_ok;
|
|
|
|
atomic_t fscache_n_retrievals_wait;
|
|
|
|
atomic_t fscache_n_retrievals_nodata;
|
|
|
|
atomic_t fscache_n_retrievals_nobufs;
|
|
|
|
atomic_t fscache_n_retrievals_intr;
|
|
|
|
atomic_t fscache_n_retrievals_nomem;
|
2009-11-20 02:11:45 +08:00
|
|
|
atomic_t fscache_n_retrievals_object_dead;
|
2009-04-03 23:42:37 +08:00
|
|
|
atomic_t fscache_n_retrieval_ops;
|
|
|
|
atomic_t fscache_n_retrieval_op_waits;
|
|
|
|
|
|
|
|
atomic_t fscache_n_stores;
|
|
|
|
atomic_t fscache_n_stores_ok;
|
|
|
|
atomic_t fscache_n_stores_again;
|
|
|
|
atomic_t fscache_n_stores_nobufs;
|
|
|
|
atomic_t fscache_n_stores_oom;
|
|
|
|
atomic_t fscache_n_store_ops;
|
|
|
|
atomic_t fscache_n_store_calls;
|
FS-Cache: Fix lock misorder in fscache_write_op()
FS-Cache has two structs internally for keeping track of the internal state of
a cached file: the fscache_cookie struct, which represents the netfs's state,
and fscache_object struct, which represents the cache's state. Each has a
pointer that points to the other (when both are in existence), and each has a
spinlock for pointer maintenance.
Since netfs operations approach these structures from the cookie side, they get
the cookie lock first, then the object lock. Cache operations, on the other
hand, approach from the object side, and get the object lock first. It is not
then permitted for a cache operation to get the cookie lock whilst it is
holding the object lock lest deadlock occur; instead, it must do one of two
things:
(1) increment the cookie usage counter, drop the object lock and then get both
locks in order, or
(2) simply hold the object lock as certain parts of the cookie may not be
altered whilst the object lock is held.
It is also not permitted to follow either pointer without holding the lock at
the end you start with. To break the pointers between the cookie and the
object, both locks must be held.
fscache_write_op(), however, violates the locking rules: It attempts to get the
cookie lock without (a) checking that the cookie pointer is a valid pointer,
and (b) holding the object lock to protect the cookie pointer whilst it follows
it. This is so that it can access the pending page store tree without
interference from __fscache_write_page().
This is fixed by splitting the cookie lock, such that the page store tracking
tree is protected by its own lock, and checking that the cookie pointer is
non-NULL before we attempt to follow it whilst holding the object lock.
The new lock is subordinate to both the cookie lock and the object lock, and so
should be taken after those.
Signed-off-by: David Howells <dhowells@redhat.com>
2009-11-20 02:11:25 +08:00
|
|
|
atomic_t fscache_n_store_pages;
|
|
|
|
atomic_t fscache_n_store_radix_deletes;
|
|
|
|
atomic_t fscache_n_store_pages_over_limit;
|
2009-04-03 23:42:37 +08:00
|
|
|
|
FS-Cache: Handle pages pending storage that get evicted under OOM conditions
Handle netfs pages that the vmscan algorithm wants to evict from the pagecache
under OOM conditions, but that are waiting for write to the cache. Under these
conditions, vmscan calls the releasepage() function of the netfs, asking if a
page can be discarded.
The problem is typified by the following trace of a stuck process:
kslowd005 D 0000000000000000 0 4253 2 0x00000080
ffff88001b14f370 0000000000000046 ffff880020d0d000 0000000000000007
0000000000000006 0000000000000001 ffff88001b14ffd8 ffff880020d0d2a8
000000000000ddf0 00000000000118c0 00000000000118c0 ffff880020d0d2a8
Call Trace:
[<ffffffffa00782d8>] __fscache_wait_on_page_write+0x8b/0xa7 [fscache]
[<ffffffff8104c0f1>] ? autoremove_wake_function+0x0/0x34
[<ffffffffa0078240>] ? __fscache_check_page_write+0x63/0x70 [fscache]
[<ffffffffa00b671d>] nfs_fscache_release_page+0x4e/0xc4 [nfs]
[<ffffffffa00927f0>] nfs_release_page+0x3c/0x41 [nfs]
[<ffffffff810885d3>] try_to_release_page+0x32/0x3b
[<ffffffff81093203>] shrink_page_list+0x316/0x4ac
[<ffffffff8109372b>] shrink_inactive_list+0x392/0x67c
[<ffffffff813532fa>] ? __mutex_unlock_slowpath+0x100/0x10b
[<ffffffff81058df0>] ? trace_hardirqs_on_caller+0x10c/0x130
[<ffffffff8135330e>] ? mutex_unlock+0x9/0xb
[<ffffffff81093aa2>] shrink_list+0x8d/0x8f
[<ffffffff81093d1c>] shrink_zone+0x278/0x33c
[<ffffffff81052d6c>] ? ktime_get_ts+0xad/0xba
[<ffffffff81094b13>] try_to_free_pages+0x22e/0x392
[<ffffffff81091e24>] ? isolate_pages_global+0x0/0x212
[<ffffffff8108e743>] __alloc_pages_nodemask+0x3dc/0x5cf
[<ffffffff81089529>] grab_cache_page_write_begin+0x65/0xaa
[<ffffffff8110f8c0>] ext3_write_begin+0x78/0x1eb
[<ffffffff81089ec5>] generic_file_buffered_write+0x109/0x28c
[<ffffffff8103cb69>] ? current_fs_time+0x22/0x29
[<ffffffff8108a509>] __generic_file_aio_write+0x350/0x385
[<ffffffff8108a588>] ? generic_file_aio_write+0x4a/0xae
[<ffffffff8108a59e>] generic_file_aio_write+0x60/0xae
[<ffffffff810b2e82>] do_sync_write+0xe3/0x120
[<ffffffff8104c0f1>] ? autoremove_wake_function+0x0/0x34
[<ffffffff810b18e1>] ? __dentry_open+0x1a5/0x2b8
[<ffffffff810b1a76>] ? dentry_open+0x82/0x89
[<ffffffffa00e693c>] cachefiles_write_page+0x298/0x335 [cachefiles]
[<ffffffffa0077147>] fscache_write_op+0x178/0x2c2 [fscache]
[<ffffffffa0075656>] fscache_op_execute+0x7a/0xd1 [fscache]
[<ffffffff81082093>] slow_work_execute+0x18f/0x2d1
[<ffffffff8108239a>] slow_work_thread+0x1c5/0x308
[<ffffffff8104c0f1>] ? autoremove_wake_function+0x0/0x34
[<ffffffff810821d5>] ? slow_work_thread+0x0/0x308
[<ffffffff8104be91>] kthread+0x7a/0x82
[<ffffffff8100beda>] child_rip+0xa/0x20
[<ffffffff8100b87c>] ? restore_args+0x0/0x30
[<ffffffff8102ef83>] ? tg_shares_up+0x171/0x227
[<ffffffff8104be17>] ? kthread+0x0/0x82
[<ffffffff8100bed0>] ? child_rip+0x0/0x20
In the above backtrace, the following is happening:
(1) A page storage operation is being executed by a slow-work thread
(fscache_write_op()).
(2) FS-Cache farms the operation out to the cache to perform
(cachefiles_write_page()).
(3) CacheFiles is then calling Ext3 to perform the actual write, using Ext3's
standard write (do_sync_write()) under KERNEL_DS directly from the netfs
page.
(4) However, for Ext3 to perform the write, it must allocate some memory, in
particular, it must allocate at least one page cache page into which it
can copy the data from the netfs page.
(5) Under OOM conditions, the memory allocator can't immediately come up with
a page, so it uses vmscan to find something to discard
(try_to_free_pages()).
(6) vmscan finds a clean netfs page it might be able to discard (possibly the
one it's trying to write out).
(7) The netfs is called to throw the page away (nfs_release_page()) - but it's
called with __GFP_WAIT, so the netfs decides to wait for the store to
complete (__fscache_wait_on_page_write()).
(8) This blocks a slow-work processing thread - possibly against itself.
The system ends up stuck because it can't write out any netfs pages to the
cache without allocating more memory.
To avoid this, we make FS-Cache cancel some writes that aren't in the middle of
actually being performed. This means that some data won't make it into the
cache this time. To support this, a new FS-Cache function is added
fscache_maybe_release_page() that replaces what the netfs releasepage()
functions used to do with respect to the cache.
The decisions fscache_maybe_release_page() makes are counted and displayed
through /proc/fs/fscache/stats on a line labelled "VmScan". There are four
counters provided: "nos=N" - pages that weren't pending storage; "gon=N" -
pages that were pending storage when we first looked, but weren't by the time
we got the object lock; "bsy=N" - pages that we ignored as they were actively
being written when we looked; and "can=N" - pages that we cancelled the storage
of.
What I'd really like to do is alter the behaviour of the cancellation
heuristics, depending on how necessary it is to expel pages. If there are
plenty of other pages that aren't waiting to be written to the cache that
could be ejected first, then it would be nice to hold up on immediate
cancellation of cache writes - but I don't see a way of doing that.
Signed-off-by: David Howells <dhowells@redhat.com>
2009-11-20 02:11:35 +08:00
|
|
|
atomic_t fscache_n_store_vmscan_not_storing;
|
|
|
|
atomic_t fscache_n_store_vmscan_gone;
|
|
|
|
atomic_t fscache_n_store_vmscan_busy;
|
|
|
|
atomic_t fscache_n_store_vmscan_cancelled;
|
|
|
|
|
2009-04-03 23:42:37 +08:00
|
|
|
atomic_t fscache_n_marks;
|
|
|
|
atomic_t fscache_n_uncaches;
|
|
|
|
|
|
|
|
atomic_t fscache_n_acquires;
|
|
|
|
atomic_t fscache_n_acquires_null;
|
|
|
|
atomic_t fscache_n_acquires_no_cache;
|
|
|
|
atomic_t fscache_n_acquires_ok;
|
|
|
|
atomic_t fscache_n_acquires_nobufs;
|
|
|
|
atomic_t fscache_n_acquires_oom;
|
|
|
|
|
|
|
|
atomic_t fscache_n_updates;
|
|
|
|
atomic_t fscache_n_updates_null;
|
|
|
|
atomic_t fscache_n_updates_run;
|
|
|
|
|
|
|
|
atomic_t fscache_n_relinquishes;
|
|
|
|
atomic_t fscache_n_relinquishes_null;
|
|
|
|
atomic_t fscache_n_relinquishes_waitcrt;
|
2009-11-20 02:11:38 +08:00
|
|
|
atomic_t fscache_n_relinquishes_retire;
|
2009-04-03 23:42:37 +08:00
|
|
|
|
|
|
|
atomic_t fscache_n_cookie_index;
|
|
|
|
atomic_t fscache_n_cookie_data;
|
|
|
|
atomic_t fscache_n_cookie_special;
|
|
|
|
|
|
|
|
atomic_t fscache_n_object_alloc;
|
|
|
|
atomic_t fscache_n_object_no_alloc;
|
|
|
|
atomic_t fscache_n_object_lookups;
|
|
|
|
atomic_t fscache_n_object_lookups_negative;
|
|
|
|
atomic_t fscache_n_object_lookups_positive;
|
2009-11-20 02:12:05 +08:00
|
|
|
atomic_t fscache_n_object_lookups_timed_out;
|
2009-04-03 23:42:37 +08:00
|
|
|
atomic_t fscache_n_object_created;
|
|
|
|
atomic_t fscache_n_object_avail;
|
|
|
|
atomic_t fscache_n_object_dead;
|
|
|
|
|
|
|
|
atomic_t fscache_n_checkaux_none;
|
|
|
|
atomic_t fscache_n_checkaux_okay;
|
|
|
|
atomic_t fscache_n_checkaux_update;
|
|
|
|
atomic_t fscache_n_checkaux_obsolete;
|
|
|
|
|
2009-11-20 02:11:08 +08:00
|
|
|
atomic_t fscache_n_cop_alloc_object;
|
|
|
|
atomic_t fscache_n_cop_lookup_object;
|
|
|
|
atomic_t fscache_n_cop_lookup_complete;
|
|
|
|
atomic_t fscache_n_cop_grab_object;
|
|
|
|
atomic_t fscache_n_cop_update_object;
|
|
|
|
atomic_t fscache_n_cop_drop_object;
|
|
|
|
atomic_t fscache_n_cop_put_object;
|
|
|
|
atomic_t fscache_n_cop_sync_cache;
|
|
|
|
atomic_t fscache_n_cop_attr_changed;
|
|
|
|
atomic_t fscache_n_cop_read_or_alloc_page;
|
|
|
|
atomic_t fscache_n_cop_read_or_alloc_pages;
|
|
|
|
atomic_t fscache_n_cop_allocate_page;
|
|
|
|
atomic_t fscache_n_cop_allocate_pages;
|
|
|
|
atomic_t fscache_n_cop_write_page;
|
|
|
|
atomic_t fscache_n_cop_uncache_page;
|
|
|
|
atomic_t fscache_n_cop_dissociate_pages;
|
|
|
|
|
2009-04-03 23:42:37 +08:00
|
|
|
/*
|
|
|
|
* display the general statistics
|
|
|
|
*/
|
|
|
|
static int fscache_stats_show(struct seq_file *m, void *v)
|
|
|
|
{
|
|
|
|
seq_puts(m, "FS-Cache statistics\n");
|
|
|
|
|
|
|
|
seq_printf(m, "Cookies: idx=%u dat=%u spc=%u\n",
|
|
|
|
atomic_read(&fscache_n_cookie_index),
|
|
|
|
atomic_read(&fscache_n_cookie_data),
|
|
|
|
atomic_read(&fscache_n_cookie_special));
|
|
|
|
|
|
|
|
seq_printf(m, "Objects: alc=%u nal=%u avl=%u ded=%u\n",
|
|
|
|
atomic_read(&fscache_n_object_alloc),
|
|
|
|
atomic_read(&fscache_n_object_no_alloc),
|
|
|
|
atomic_read(&fscache_n_object_avail),
|
|
|
|
atomic_read(&fscache_n_object_dead));
|
|
|
|
seq_printf(m, "ChkAux : non=%u ok=%u upd=%u obs=%u\n",
|
|
|
|
atomic_read(&fscache_n_checkaux_none),
|
|
|
|
atomic_read(&fscache_n_checkaux_okay),
|
|
|
|
atomic_read(&fscache_n_checkaux_update),
|
|
|
|
atomic_read(&fscache_n_checkaux_obsolete));
|
|
|
|
|
|
|
|
seq_printf(m, "Pages : mrk=%u unc=%u\n",
|
|
|
|
atomic_read(&fscache_n_marks),
|
|
|
|
atomic_read(&fscache_n_uncaches));
|
|
|
|
|
|
|
|
seq_printf(m, "Acquire: n=%u nul=%u noc=%u ok=%u nbf=%u"
|
|
|
|
" oom=%u\n",
|
|
|
|
atomic_read(&fscache_n_acquires),
|
|
|
|
atomic_read(&fscache_n_acquires_null),
|
|
|
|
atomic_read(&fscache_n_acquires_no_cache),
|
|
|
|
atomic_read(&fscache_n_acquires_ok),
|
|
|
|
atomic_read(&fscache_n_acquires_nobufs),
|
|
|
|
atomic_read(&fscache_n_acquires_oom));
|
|
|
|
|
2009-11-20 02:12:05 +08:00
|
|
|
seq_printf(m, "Lookups: n=%u neg=%u pos=%u crt=%u tmo=%u\n",
|
2009-04-03 23:42:37 +08:00
|
|
|
atomic_read(&fscache_n_object_lookups),
|
|
|
|
atomic_read(&fscache_n_object_lookups_negative),
|
|
|
|
atomic_read(&fscache_n_object_lookups_positive),
|
2010-04-07 05:35:09 +08:00
|
|
|
atomic_read(&fscache_n_object_created),
|
|
|
|
atomic_read(&fscache_n_object_lookups_timed_out));
|
2009-04-03 23:42:37 +08:00
|
|
|
|
|
|
|
seq_printf(m, "Updates: n=%u nul=%u run=%u\n",
|
|
|
|
atomic_read(&fscache_n_updates),
|
|
|
|
atomic_read(&fscache_n_updates_null),
|
|
|
|
atomic_read(&fscache_n_updates_run));
|
|
|
|
|
2009-11-20 02:11:38 +08:00
|
|
|
seq_printf(m, "Relinqs: n=%u nul=%u wcr=%u rtr=%u\n",
|
2009-04-03 23:42:37 +08:00
|
|
|
atomic_read(&fscache_n_relinquishes),
|
|
|
|
atomic_read(&fscache_n_relinquishes_null),
|
2009-11-20 02:11:38 +08:00
|
|
|
atomic_read(&fscache_n_relinquishes_waitcrt),
|
|
|
|
atomic_read(&fscache_n_relinquishes_retire));
|
2009-04-03 23:42:37 +08:00
|
|
|
|
|
|
|
seq_printf(m, "AttrChg: n=%u ok=%u nbf=%u oom=%u run=%u\n",
|
|
|
|
atomic_read(&fscache_n_attr_changed),
|
|
|
|
atomic_read(&fscache_n_attr_changed_ok),
|
|
|
|
atomic_read(&fscache_n_attr_changed_nobufs),
|
|
|
|
atomic_read(&fscache_n_attr_changed_nomem),
|
|
|
|
atomic_read(&fscache_n_attr_changed_calls));
|
|
|
|
|
2009-11-20 02:11:19 +08:00
|
|
|
seq_printf(m, "Allocs : n=%u ok=%u wt=%u nbf=%u int=%u\n",
|
2009-04-03 23:42:37 +08:00
|
|
|
atomic_read(&fscache_n_allocs),
|
|
|
|
atomic_read(&fscache_n_allocs_ok),
|
|
|
|
atomic_read(&fscache_n_allocs_wait),
|
2009-11-20 02:11:19 +08:00
|
|
|
atomic_read(&fscache_n_allocs_nobufs),
|
|
|
|
atomic_read(&fscache_n_allocs_intr));
|
2009-11-20 02:11:45 +08:00
|
|
|
seq_printf(m, "Allocs : ops=%u owt=%u abt=%u\n",
|
2009-04-03 23:42:37 +08:00
|
|
|
atomic_read(&fscache_n_alloc_ops),
|
2009-11-20 02:11:45 +08:00
|
|
|
atomic_read(&fscache_n_alloc_op_waits),
|
|
|
|
atomic_read(&fscache_n_allocs_object_dead));
|
2009-04-03 23:42:37 +08:00
|
|
|
|
|
|
|
seq_printf(m, "Retrvls: n=%u ok=%u wt=%u nod=%u nbf=%u"
|
|
|
|
" int=%u oom=%u\n",
|
|
|
|
atomic_read(&fscache_n_retrievals),
|
|
|
|
atomic_read(&fscache_n_retrievals_ok),
|
|
|
|
atomic_read(&fscache_n_retrievals_wait),
|
|
|
|
atomic_read(&fscache_n_retrievals_nodata),
|
|
|
|
atomic_read(&fscache_n_retrievals_nobufs),
|
|
|
|
atomic_read(&fscache_n_retrievals_intr),
|
|
|
|
atomic_read(&fscache_n_retrievals_nomem));
|
2009-11-20 02:11:45 +08:00
|
|
|
seq_printf(m, "Retrvls: ops=%u owt=%u abt=%u\n",
|
2009-04-03 23:42:37 +08:00
|
|
|
atomic_read(&fscache_n_retrieval_ops),
|
2009-11-20 02:11:45 +08:00
|
|
|
atomic_read(&fscache_n_retrieval_op_waits),
|
|
|
|
atomic_read(&fscache_n_retrievals_object_dead));
|
2009-04-03 23:42:37 +08:00
|
|
|
|
|
|
|
seq_printf(m, "Stores : n=%u ok=%u agn=%u nbf=%u oom=%u\n",
|
|
|
|
atomic_read(&fscache_n_stores),
|
|
|
|
atomic_read(&fscache_n_stores_ok),
|
|
|
|
atomic_read(&fscache_n_stores_again),
|
|
|
|
atomic_read(&fscache_n_stores_nobufs),
|
|
|
|
atomic_read(&fscache_n_stores_oom));
|
FS-Cache: Fix lock misorder in fscache_write_op()
FS-Cache has two structs internally for keeping track of the internal state of
a cached file: the fscache_cookie struct, which represents the netfs's state,
and fscache_object struct, which represents the cache's state. Each has a
pointer that points to the other (when both are in existence), and each has a
spinlock for pointer maintenance.
Since netfs operations approach these structures from the cookie side, they get
the cookie lock first, then the object lock. Cache operations, on the other
hand, approach from the object side, and get the object lock first. It is not
then permitted for a cache operation to get the cookie lock whilst it is
holding the object lock lest deadlock occur; instead, it must do one of two
things:
(1) increment the cookie usage counter, drop the object lock and then get both
locks in order, or
(2) simply hold the object lock as certain parts of the cookie may not be
altered whilst the object lock is held.
It is also not permitted to follow either pointer without holding the lock at
the end you start with. To break the pointers between the cookie and the
object, both locks must be held.
fscache_write_op(), however, violates the locking rules: It attempts to get the
cookie lock without (a) checking that the cookie pointer is a valid pointer,
and (b) holding the object lock to protect the cookie pointer whilst it follows
it. This is so that it can access the pending page store tree without
interference from __fscache_write_page().
This is fixed by splitting the cookie lock, such that the page store tracking
tree is protected by its own lock, and checking that the cookie pointer is
non-NULL before we attempt to follow it whilst holding the object lock.
The new lock is subordinate to both the cookie lock and the object lock, and so
should be taken after those.
Signed-off-by: David Howells <dhowells@redhat.com>
2009-11-20 02:11:25 +08:00
|
|
|
seq_printf(m, "Stores : ops=%u run=%u pgs=%u rxd=%u olm=%u\n",
|
2009-04-03 23:42:37 +08:00
|
|
|
atomic_read(&fscache_n_store_ops),
|
FS-Cache: Fix lock misorder in fscache_write_op()
FS-Cache has two structs internally for keeping track of the internal state of
a cached file: the fscache_cookie struct, which represents the netfs's state,
and fscache_object struct, which represents the cache's state. Each has a
pointer that points to the other (when both are in existence), and each has a
spinlock for pointer maintenance.
Since netfs operations approach these structures from the cookie side, they get
the cookie lock first, then the object lock. Cache operations, on the other
hand, approach from the object side, and get the object lock first. It is not
then permitted for a cache operation to get the cookie lock whilst it is
holding the object lock lest deadlock occur; instead, it must do one of two
things:
(1) increment the cookie usage counter, drop the object lock and then get both
locks in order, or
(2) simply hold the object lock as certain parts of the cookie may not be
altered whilst the object lock is held.
It is also not permitted to follow either pointer without holding the lock at
the end you start with. To break the pointers between the cookie and the
object, both locks must be held.
fscache_write_op(), however, violates the locking rules: It attempts to get the
cookie lock without (a) checking that the cookie pointer is a valid pointer,
and (b) holding the object lock to protect the cookie pointer whilst it follows
it. This is so that it can access the pending page store tree without
interference from __fscache_write_page().
This is fixed by splitting the cookie lock, such that the page store tracking
tree is protected by its own lock, and checking that the cookie pointer is
non-NULL before we attempt to follow it whilst holding the object lock.
The new lock is subordinate to both the cookie lock and the object lock, and so
should be taken after those.
Signed-off-by: David Howells <dhowells@redhat.com>
2009-11-20 02:11:25 +08:00
|
|
|
atomic_read(&fscache_n_store_calls),
|
|
|
|
atomic_read(&fscache_n_store_pages),
|
|
|
|
atomic_read(&fscache_n_store_radix_deletes),
|
|
|
|
atomic_read(&fscache_n_store_pages_over_limit));
|
2009-04-03 23:42:37 +08:00
|
|
|
|
FS-Cache: Handle pages pending storage that get evicted under OOM conditions
Handle netfs pages that the vmscan algorithm wants to evict from the pagecache
under OOM conditions, but that are waiting for write to the cache. Under these
conditions, vmscan calls the releasepage() function of the netfs, asking if a
page can be discarded.
The problem is typified by the following trace of a stuck process:
kslowd005 D 0000000000000000 0 4253 2 0x00000080
ffff88001b14f370 0000000000000046 ffff880020d0d000 0000000000000007
0000000000000006 0000000000000001 ffff88001b14ffd8 ffff880020d0d2a8
000000000000ddf0 00000000000118c0 00000000000118c0 ffff880020d0d2a8
Call Trace:
[<ffffffffa00782d8>] __fscache_wait_on_page_write+0x8b/0xa7 [fscache]
[<ffffffff8104c0f1>] ? autoremove_wake_function+0x0/0x34
[<ffffffffa0078240>] ? __fscache_check_page_write+0x63/0x70 [fscache]
[<ffffffffa00b671d>] nfs_fscache_release_page+0x4e/0xc4 [nfs]
[<ffffffffa00927f0>] nfs_release_page+0x3c/0x41 [nfs]
[<ffffffff810885d3>] try_to_release_page+0x32/0x3b
[<ffffffff81093203>] shrink_page_list+0x316/0x4ac
[<ffffffff8109372b>] shrink_inactive_list+0x392/0x67c
[<ffffffff813532fa>] ? __mutex_unlock_slowpath+0x100/0x10b
[<ffffffff81058df0>] ? trace_hardirqs_on_caller+0x10c/0x130
[<ffffffff8135330e>] ? mutex_unlock+0x9/0xb
[<ffffffff81093aa2>] shrink_list+0x8d/0x8f
[<ffffffff81093d1c>] shrink_zone+0x278/0x33c
[<ffffffff81052d6c>] ? ktime_get_ts+0xad/0xba
[<ffffffff81094b13>] try_to_free_pages+0x22e/0x392
[<ffffffff81091e24>] ? isolate_pages_global+0x0/0x212
[<ffffffff8108e743>] __alloc_pages_nodemask+0x3dc/0x5cf
[<ffffffff81089529>] grab_cache_page_write_begin+0x65/0xaa
[<ffffffff8110f8c0>] ext3_write_begin+0x78/0x1eb
[<ffffffff81089ec5>] generic_file_buffered_write+0x109/0x28c
[<ffffffff8103cb69>] ? current_fs_time+0x22/0x29
[<ffffffff8108a509>] __generic_file_aio_write+0x350/0x385
[<ffffffff8108a588>] ? generic_file_aio_write+0x4a/0xae
[<ffffffff8108a59e>] generic_file_aio_write+0x60/0xae
[<ffffffff810b2e82>] do_sync_write+0xe3/0x120
[<ffffffff8104c0f1>] ? autoremove_wake_function+0x0/0x34
[<ffffffff810b18e1>] ? __dentry_open+0x1a5/0x2b8
[<ffffffff810b1a76>] ? dentry_open+0x82/0x89
[<ffffffffa00e693c>] cachefiles_write_page+0x298/0x335 [cachefiles]
[<ffffffffa0077147>] fscache_write_op+0x178/0x2c2 [fscache]
[<ffffffffa0075656>] fscache_op_execute+0x7a/0xd1 [fscache]
[<ffffffff81082093>] slow_work_execute+0x18f/0x2d1
[<ffffffff8108239a>] slow_work_thread+0x1c5/0x308
[<ffffffff8104c0f1>] ? autoremove_wake_function+0x0/0x34
[<ffffffff810821d5>] ? slow_work_thread+0x0/0x308
[<ffffffff8104be91>] kthread+0x7a/0x82
[<ffffffff8100beda>] child_rip+0xa/0x20
[<ffffffff8100b87c>] ? restore_args+0x0/0x30
[<ffffffff8102ef83>] ? tg_shares_up+0x171/0x227
[<ffffffff8104be17>] ? kthread+0x0/0x82
[<ffffffff8100bed0>] ? child_rip+0x0/0x20
In the above backtrace, the following is happening:
(1) A page storage operation is being executed by a slow-work thread
(fscache_write_op()).
(2) FS-Cache farms the operation out to the cache to perform
(cachefiles_write_page()).
(3) CacheFiles is then calling Ext3 to perform the actual write, using Ext3's
standard write (do_sync_write()) under KERNEL_DS directly from the netfs
page.
(4) However, for Ext3 to perform the write, it must allocate some memory, in
particular, it must allocate at least one page cache page into which it
can copy the data from the netfs page.
(5) Under OOM conditions, the memory allocator can't immediately come up with
a page, so it uses vmscan to find something to discard
(try_to_free_pages()).
(6) vmscan finds a clean netfs page it might be able to discard (possibly the
one it's trying to write out).
(7) The netfs is called to throw the page away (nfs_release_page()) - but it's
called with __GFP_WAIT, so the netfs decides to wait for the store to
complete (__fscache_wait_on_page_write()).
(8) This blocks a slow-work processing thread - possibly against itself.
The system ends up stuck because it can't write out any netfs pages to the
cache without allocating more memory.
To avoid this, we make FS-Cache cancel some writes that aren't in the middle of
actually being performed. This means that some data won't make it into the
cache this time. To support this, a new FS-Cache function is added
fscache_maybe_release_page() that replaces what the netfs releasepage()
functions used to do with respect to the cache.
The decisions fscache_maybe_release_page() makes are counted and displayed
through /proc/fs/fscache/stats on a line labelled "VmScan". There are four
counters provided: "nos=N" - pages that weren't pending storage; "gon=N" -
pages that were pending storage when we first looked, but weren't by the time
we got the object lock; "bsy=N" - pages that we ignored as they were actively
being written when we looked; and "can=N" - pages that we cancelled the storage
of.
What I'd really like to do is alter the behaviour of the cancellation
heuristics, depending on how necessary it is to expel pages. If there are
plenty of other pages that aren't waiting to be written to the cache that
could be ejected first, then it would be nice to hold up on immediate
cancellation of cache writes - but I don't see a way of doing that.
Signed-off-by: David Howells <dhowells@redhat.com>
2009-11-20 02:11:35 +08:00
|
|
|
seq_printf(m, "VmScan : nos=%u gon=%u bsy=%u can=%u\n",
|
|
|
|
atomic_read(&fscache_n_store_vmscan_not_storing),
|
|
|
|
atomic_read(&fscache_n_store_vmscan_gone),
|
|
|
|
atomic_read(&fscache_n_store_vmscan_busy),
|
|
|
|
atomic_read(&fscache_n_store_vmscan_cancelled));
|
|
|
|
|
2009-11-20 02:11:32 +08:00
|
|
|
seq_printf(m, "Ops : pend=%u run=%u enq=%u can=%u rej=%u\n",
|
2009-04-03 23:42:37 +08:00
|
|
|
atomic_read(&fscache_n_op_pend),
|
|
|
|
atomic_read(&fscache_n_op_run),
|
2009-11-20 02:11:19 +08:00
|
|
|
atomic_read(&fscache_n_op_enqueue),
|
2009-11-20 02:11:32 +08:00
|
|
|
atomic_read(&fscache_n_op_cancelled),
|
|
|
|
atomic_read(&fscache_n_op_rejected));
|
2009-04-03 23:42:37 +08:00
|
|
|
seq_printf(m, "Ops : dfr=%u rel=%u gc=%u\n",
|
|
|
|
atomic_read(&fscache_n_op_deferred_release),
|
|
|
|
atomic_read(&fscache_n_op_release),
|
|
|
|
atomic_read(&fscache_n_op_gc));
|
2009-11-20 02:11:08 +08:00
|
|
|
|
|
|
|
seq_printf(m, "CacheOp: alo=%d luo=%d luc=%d gro=%d\n",
|
|
|
|
atomic_read(&fscache_n_cop_alloc_object),
|
|
|
|
atomic_read(&fscache_n_cop_lookup_object),
|
|
|
|
atomic_read(&fscache_n_cop_lookup_complete),
|
|
|
|
atomic_read(&fscache_n_cop_grab_object));
|
|
|
|
seq_printf(m, "CacheOp: upo=%d dro=%d pto=%d atc=%d syn=%d\n",
|
|
|
|
atomic_read(&fscache_n_cop_update_object),
|
|
|
|
atomic_read(&fscache_n_cop_drop_object),
|
|
|
|
atomic_read(&fscache_n_cop_put_object),
|
|
|
|
atomic_read(&fscache_n_cop_attr_changed),
|
|
|
|
atomic_read(&fscache_n_cop_sync_cache));
|
|
|
|
seq_printf(m, "CacheOp: rap=%d ras=%d alp=%d als=%d wrp=%d ucp=%d dsp=%d\n",
|
|
|
|
atomic_read(&fscache_n_cop_read_or_alloc_page),
|
|
|
|
atomic_read(&fscache_n_cop_read_or_alloc_pages),
|
|
|
|
atomic_read(&fscache_n_cop_allocate_page),
|
|
|
|
atomic_read(&fscache_n_cop_allocate_pages),
|
|
|
|
atomic_read(&fscache_n_cop_write_page),
|
|
|
|
atomic_read(&fscache_n_cop_uncache_page),
|
|
|
|
atomic_read(&fscache_n_cop_dissociate_pages));
|
2009-04-03 23:42:37 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* open "/proc/fs/fscache/stats" allowing provision of a statistical summary
|
|
|
|
*/
|
|
|
|
static int fscache_stats_open(struct inode *inode, struct file *file)
|
|
|
|
{
|
|
|
|
return single_open(file, fscache_stats_show, NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
const struct file_operations fscache_stats_fops = {
|
|
|
|
.owner = THIS_MODULE,
|
|
|
|
.open = fscache_stats_open,
|
|
|
|
.read = seq_read,
|
|
|
|
.llseek = seq_lseek,
|
|
|
|
.release = seq_release,
|
|
|
|
};
|