Merge 6b611388b6 ("memcg-v1: remove charge move code") into android-mainline
Steps on the way to 6.13-rc1 Change-Id: I53e428ee5334506d515e1d1a7ae3a87361da4694 Signed-off-by: Greg Kroah-Hartman <gregkh@google.com>
This commit is contained in:
commit
7c1bde562e
19 files changed
+173
-1104
No files matched your search
@@ -90,9 +90,7 @@ Brief summary of control files.
|
||||
used.
|
||||
memory.swappiness set/show swappiness parameter of vmscan
|
||||
(See sysctl's vm.swappiness)
|
||||
memory.move_charge_at_immigrate set/show controls of moving charges
|
||||
This knob is deprecated and shouldn't be
|
||||
used.
|
||||
memory.move_charge_at_immigrate This knob is deprecated.
|
||||
memory.oom_control set/show oom controls.
|
||||
This knob is deprecated and shouldn't be
|
||||
used.
|
||||
@@ -243,10 +241,6 @@ behind this approach is that a cgroup that aggressively uses a shared
|
||||
page will eventually get charged for it (once it is uncharged from
|
||||
the cgroup that brought it in -- this will happen on memory pressure).
|
||||
|
||||
But see :ref:`section 8.2 <cgroup-v1-memory-movable-charges>` when moving a
|
||||
task to another cgroup, its pages may be recharged to the new cgroup, if
|
||||
move_charge_at_immigrate has been chosen.
|
||||
|
||||
2.4 Swap Extension
|
||||
--------------------------------------
|
||||
|
||||
@@ -756,78 +750,8 @@ If we want to change this to 1G, we can at any time use::
|
||||
|
||||
THIS IS DEPRECATED!
|
||||
|
||||
It's expensive and unreliable! It's better practice to launch workload
|
||||
tasks directly from inside their target cgroup. Use dedicated workload
|
||||
cgroups to allow fine-grained policy adjustments without having to
|
||||
move physical pages between control domains.
|
||||
|
||||
Users can move charges associated with a task along with task migration, that
|
||||
is, uncharge task's pages from the old cgroup and charge them to the new cgroup.
|
||||
This feature is not supported in !CONFIG_MMU environments because of lack of
|
||||
page tables.
|
||||
|
||||
8.1 Interface
|
||||
-------------
|
||||
|
||||
This feature is disabled by default. It can be enabled (and disabled again) by
|
||||
writing to memory.move_charge_at_immigrate of the destination cgroup.
|
||||
|
||||
If you want to enable it::
|
||||
|
||||
# echo (some positive value) > memory.move_charge_at_immigrate
|
||||
|
||||
.. note::
|
||||
Each bits of move_charge_at_immigrate has its own meaning about what type
|
||||
of charges should be moved. See :ref:`section 8.2
|
||||
<cgroup-v1-memory-movable-charges>` for details.
|
||||
|
||||
.. note::
|
||||
Charges are moved only when you move mm->owner, in other words,
|
||||
a leader of a thread group.
|
||||
|
||||
.. note::
|
||||
If we cannot find enough space for the task in the destination cgroup, we
|
||||
try to make space by reclaiming memory. Task migration may fail if we
|
||||
cannot make enough space.
|
||||
|
||||
.. note::
|
||||
It can take several seconds if you move charges much.
|
||||
|
||||
And if you want disable it again::
|
||||
|
||||
# echo 0 > memory.move_charge_at_immigrate
|
||||
|
||||
.. _cgroup-v1-memory-movable-charges:
|
||||
|
||||
8.2 Type of charges which can be moved
|
||||
--------------------------------------
|
||||
|
||||
Each bit in move_charge_at_immigrate has its own meaning about what type of
|
||||
charges should be moved. But in any case, it must be noted that an account of
|
||||
a page or a swap can be moved only when it is charged to the task's current
|
||||
(old) memory cgroup.
|
||||
|
||||
+---+--------------------------------------------------------------------------+
|
||||
|bit| what type of charges would be moved ? |
|
||||
+===+==========================================================================+
|
||||
| 0 | A charge of an anonymous page (or swap of it) used by the target task. |
|
||||
| | You must enable Swap Extension (see 2.4) to enable move of swap charges. |
|
||||
+---+--------------------------------------------------------------------------+
|
||||
| 1 | A charge of file pages (normal file, tmpfs file (e.g. ipc shared memory) |
|
||||
| | and swaps of tmpfs file) mmapped by the target task. Unlike the case of |
|
||||
| | anonymous pages, file pages (and swaps) in the range mmapped by the task |
|
||||
| | will be moved even if the task hasn't done page fault, i.e. they might |
|
||||
| | not be the task's "RSS", but other task's "RSS" that maps the same file. |
|
||||
| | The mapcount of the page is ignored (the page can be moved independent |
|
||||
| | of the mapcount). You must enable Swap Extension (see 2.4) to |
|
||||
| | enable move of swap charges. |
|
||||
+---+--------------------------------------------------------------------------+
|
||||
|
||||
8.3 TODO
|
||||
--------
|
||||
|
||||
- All of moving charge operations are done under cgroup_mutex. It's not good
|
||||
behavior to hold the mutex too long, so we may need some trick.
|
||||
Reading memory.move_charge_at_immigrate will always return 0 and writing
|
||||
to it will always return -EINVAL.
|
||||
|
||||
9. Memory thresholds
|
||||
====================
|
||||
|
||||
@@ -224,7 +224,7 @@ typedef struct { /* nothing */ } lockdep_map_p;
|
||||
* (set at tree creation time) and dynamic information set under the spinlock.
|
||||
*
|
||||
* Another use of flags are to indicate global states of the tree. This is the
|
||||
* case with the MAPLE_USE_RCU flag, which indicates the tree is currently in
|
||||
* case with the MT_FLAGS_USE_RCU flag, which indicates the tree is currently in
|
||||
* RCU mode. This mode was added to allow the tree to reuse nodes instead of
|
||||
* re-allocating and RCU freeing nodes when there is a single user.
|
||||
*/
|
||||
|
||||
@@ -299,11 +299,6 @@ struct mem_cgroup {
|
||||
/* For oom notifier event fd */
|
||||
struct list_head oom_notify;
|
||||
|
||||
/*
|
||||
* Should we move charges of a task when a task is moved into this
|
||||
* mem_cgroup ? And what type of charges should we move ?
|
||||
*/
|
||||
unsigned long move_charge_at_immigrate;
|
||||
/* taken only while moving_account > 0 */
|
||||
spinlock_t move_lock;
|
||||
unsigned long move_lock_flags;
|
||||
|
||||
+1
-1
@@ -4220,7 +4220,7 @@ static inline void pgalloc_tag_copy(struct folio *new, struct folio *old)
|
||||
/* Clear the old ref to the original allocation tag. */
|
||||
clear_page_tag_ref(&old->page);
|
||||
/* Decrement the counters of the tag on get_new_folio. */
|
||||
alloc_tag_sub(ref, folio_nr_pages(new));
|
||||
alloc_tag_sub(ref, folio_size(new));
|
||||
|
||||
__alloc_tag_ref_set(ref, tag);
|
||||
|
||||
|
||||
@@ -606,6 +606,13 @@ static inline int mmu_notifier_clear_flush_young(struct mm_struct *mm,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int mmu_notifier_clear_young(struct mm_struct *mm,
|
||||
unsigned long start,
|
||||
unsigned long end)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int mmu_notifier_test_young(struct mm_struct *mm,
|
||||
unsigned long address)
|
||||
{
|
||||
|
||||
@@ -3,10 +3,6 @@
|
||||
#define __LINUX_PAGEISOLATION_H
|
||||
|
||||
#ifdef CONFIG_MEMORY_ISOLATION
|
||||
static inline bool has_isolate_pageblock(struct zone *zone)
|
||||
{
|
||||
return zone->nr_isolate_pageblock;
|
||||
}
|
||||
static inline bool is_migrate_isolate_page(struct page *page)
|
||||
{
|
||||
return get_pageblock_migratetype(page) == MIGRATE_ISOLATE;
|
||||
@@ -16,10 +12,6 @@ static inline bool is_migrate_isolate(int migratetype)
|
||||
return migratetype == MIGRATE_ISOLATE;
|
||||
}
|
||||
#else
|
||||
static inline bool has_isolate_pageblock(struct zone *zone)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
static inline bool is_migrate_isolate_page(struct page *page)
|
||||
{
|
||||
return false;
|
||||
|
||||
+3
-4
@@ -3897,7 +3897,8 @@ static inline void mas_wr_slot_store(struct ma_wr_state *wr_mas)
|
||||
wr_mas->pivots[offset] = mas->index - 1;
|
||||
mas->offset++; /* Keep mas accurate. */
|
||||
}
|
||||
} else if (!mt_in_rcu(mas->tree)) {
|
||||
} else {
|
||||
WARN_ON_ONCE(mt_in_rcu(mas->tree));
|
||||
/*
|
||||
* Expand the range, only partially overwriting the previous and
|
||||
* next ranges
|
||||
@@ -3907,8 +3908,6 @@ static inline void mas_wr_slot_store(struct ma_wr_state *wr_mas)
|
||||
wr_mas->pivots[offset] = mas->index - 1;
|
||||
wr_mas->pivots[offset + 1] = mas->last;
|
||||
mas->offset++; /* Keep mas accurate. */
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
trace_ma_write(__func__, mas, 0, wr_mas->entry);
|
||||
@@ -4211,13 +4210,13 @@ static inline enum store_type mas_wr_store_type(struct ma_wr_state *wr_mas)
|
||||
if (!wr_mas->entry)
|
||||
mas_wr_extend_null(wr_mas);
|
||||
|
||||
new_end = mas_wr_new_end(wr_mas);
|
||||
if ((wr_mas->r_min == mas->index) && (wr_mas->r_max == mas->last))
|
||||
return wr_exact_fit;
|
||||
|
||||
if (unlikely(!mas->index && mas->last == ULONG_MAX))
|
||||
return wr_new_root;
|
||||
|
||||
new_end = mas_wr_new_end(wr_mas);
|
||||
/* Potential spanning rebalance collapsing a node */
|
||||
if (new_end < mt_min_slots[wr_mas->type]) {
|
||||
if (!mte_is_root(mas->node) && !(mas->mas_flags & MA_STATE_BULK))
|
||||
|
||||
@@ -300,6 +300,8 @@ static void damon_test_split_evenly(struct kunit *test)
|
||||
damon_test_split_evenly_fail(test, 0, 100, 0);
|
||||
damon_test_split_evenly_succ(test, 0, 100, 10);
|
||||
damon_test_split_evenly_succ(test, 5, 59, 5);
|
||||
damon_test_split_evenly_succ(test, 4, 6, 1);
|
||||
damon_test_split_evenly_succ(test, 0, 3, 2);
|
||||
damon_test_split_evenly_fail(test, 5, 6, 2);
|
||||
}
|
||||
|
||||
|
||||
+5
-4
@@ -67,10 +67,14 @@ static int damon_va_evenly_split_region(struct damon_target *t,
|
||||
unsigned long sz_orig, sz_piece, orig_end;
|
||||
struct damon_region *n = NULL, *next;
|
||||
unsigned long start;
|
||||
unsigned int i;
|
||||
|
||||
if (!r || !nr_pieces)
|
||||
return -EINVAL;
|
||||
|
||||
if (nr_pieces == 1)
|
||||
return 0;
|
||||
|
||||
orig_end = r->ar.end;
|
||||
sz_orig = damon_sz_region(r);
|
||||
sz_piece = ALIGN_DOWN(sz_orig / nr_pieces, DAMON_MIN_REGION);
|
||||
@@ -80,8 +84,7 @@ static int damon_va_evenly_split_region(struct damon_target *t,
|
||||
|
||||
r->ar.end = r->ar.start + sz_piece;
|
||||
next = damon_next_region(r);
|
||||
for (start = r->ar.end; start + sz_piece <= orig_end;
|
||||
start += sz_piece) {
|
||||
for (start = r->ar.end, i = 1; i < nr_pieces; start += sz_piece, i++) {
|
||||
n = damon_new_region(start, start + sz_piece);
|
||||
if (!n)
|
||||
return -ENOMEM;
|
||||
@@ -353,11 +356,9 @@ static void damon_hugetlb_mkold(pte_t *pte, struct mm_struct *mm,
|
||||
set_huge_pte_at(mm, addr, pte, entry, psize);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_MMU_NOTIFIER
|
||||
if (mmu_notifier_clear_young(mm, addr,
|
||||
addr + huge_page_size(hstate_vma(vma))))
|
||||
referenced = true;
|
||||
#endif /* CONFIG_MMU_NOTIFIER */
|
||||
|
||||
if (referenced)
|
||||
folio_set_young(folio);
|
||||
|
||||
+2
-899
@@ -40,31 +40,6 @@ static struct mem_cgroup_tree soft_limit_tree __read_mostly;
|
||||
#define MEM_CGROUP_MAX_RECLAIM_LOOPS 100
|
||||
#define MEM_CGROUP_MAX_SOFT_LIMIT_RECLAIM_LOOPS 2
|
||||
|
||||
/* Stuffs for move charges at task migration. */
|
||||
/*
|
||||
* Types of charges to be moved.
|
||||
*/
|
||||
#define MOVE_ANON 0x1ULL
|
||||
#define MOVE_FILE 0x2ULL
|
||||
#define MOVE_MASK (MOVE_ANON | MOVE_FILE)
|
||||
|
||||
/* "mc" and its members are protected by cgroup_mutex */
|
||||
static struct move_charge_struct {
|
||||
spinlock_t lock; /* for from, to */
|
||||
struct mm_struct *mm;
|
||||
struct mem_cgroup *from;
|
||||
struct mem_cgroup *to;
|
||||
unsigned long flags;
|
||||
unsigned long precharge;
|
||||
unsigned long moved_charge;
|
||||
unsigned long moved_swap;
|
||||
struct task_struct *moving_task; /* a task moving charges */
|
||||
wait_queue_head_t waitq; /* a waitq for other context */
|
||||
} mc = {
|
||||
.lock = __SPIN_LOCK_UNLOCKED(mc.lock),
|
||||
.waitq = __WAIT_QUEUE_HEAD_INITIALIZER(mc.waitq),
|
||||
};
|
||||
|
||||
/* for OOM */
|
||||
struct mem_cgroup_eventfd_list {
|
||||
struct list_head list;
|
||||
@@ -426,51 +401,6 @@ unsigned long memcg1_soft_limit_reclaim(pg_data_t *pgdat, int order,
|
||||
return nr_reclaimed;
|
||||
}
|
||||
|
||||
/*
|
||||
* A routine for checking "mem" is under move_account() or not.
|
||||
*
|
||||
* Checking a cgroup is mc.from or mc.to or under hierarchy of
|
||||
* moving cgroups. This is for waiting at high-memory pressure
|
||||
* caused by "move".
|
||||
*/
|
||||
static bool mem_cgroup_under_move(struct mem_cgroup *memcg)
|
||||
{
|
||||
struct mem_cgroup *from;
|
||||
struct mem_cgroup *to;
|
||||
bool ret = false;
|
||||
/*
|
||||
* Unlike task_move routines, we access mc.to, mc.from not under
|
||||
* mutual exclusion by cgroup_mutex. Here, we take spinlock instead.
|
||||
*/
|
||||
spin_lock(&mc.lock);
|
||||
from = mc.from;
|
||||
to = mc.to;
|
||||
if (!from)
|
||||
goto unlock;
|
||||
|
||||
ret = mem_cgroup_is_descendant(from, memcg) ||
|
||||
mem_cgroup_is_descendant(to, memcg);
|
||||
unlock:
|
||||
spin_unlock(&mc.lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool memcg1_wait_acct_move(struct mem_cgroup *memcg)
|
||||
{
|
||||
if (mc.moving_task && current != mc.moving_task) {
|
||||
if (mem_cgroup_under_move(memcg)) {
|
||||
DEFINE_WAIT(wait);
|
||||
prepare_to_wait(&mc.waitq, &wait, TASK_INTERRUPTIBLE);
|
||||
/* moving charge context might have finished. */
|
||||
if (mc.moving_task)
|
||||
schedule();
|
||||
finish_wait(&mc.waitq, &wait);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* folio_memcg_lock - Bind a folio to its memcg.
|
||||
* @folio: The folio.
|
||||
@@ -552,70 +482,22 @@ void folio_memcg_unlock(struct folio *folio)
|
||||
__folio_memcg_unlock(folio_memcg(folio));
|
||||
}
|
||||
|
||||
#ifdef CONFIG_SWAP
|
||||
/**
|
||||
* mem_cgroup_move_swap_account - move swap charge and swap_cgroup's record.
|
||||
* @entry: swap entry to be moved
|
||||
* @from: mem_cgroup which the entry is moved from
|
||||
* @to: mem_cgroup which the entry is moved to
|
||||
*
|
||||
* It succeeds only when the swap_cgroup's record for this entry is the same
|
||||
* as the mem_cgroup's id of @from.
|
||||
*
|
||||
* Returns 0 on success, -EINVAL on failure.
|
||||
*
|
||||
* The caller must have charged to @to, IOW, called page_counter_charge() about
|
||||
* both res and memsw, and called css_get().
|
||||
*/
|
||||
static int mem_cgroup_move_swap_account(swp_entry_t entry,
|
||||
struct mem_cgroup *from, struct mem_cgroup *to)
|
||||
{
|
||||
unsigned short old_id, new_id;
|
||||
|
||||
old_id = mem_cgroup_id(from);
|
||||
new_id = mem_cgroup_id(to);
|
||||
|
||||
if (swap_cgroup_cmpxchg(entry, old_id, new_id) == old_id) {
|
||||
mod_memcg_state(from, MEMCG_SWAP, -1);
|
||||
mod_memcg_state(to, MEMCG_SWAP, 1);
|
||||
return 0;
|
||||
}
|
||||
return -EINVAL;
|
||||
}
|
||||
#else
|
||||
static inline int mem_cgroup_move_swap_account(swp_entry_t entry,
|
||||
struct mem_cgroup *from, struct mem_cgroup *to)
|
||||
{
|
||||
return -EINVAL;
|
||||
}
|
||||
#endif
|
||||
|
||||
static u64 mem_cgroup_move_charge_read(struct cgroup_subsys_state *css,
|
||||
struct cftype *cft)
|
||||
{
|
||||
return mem_cgroup_from_css(css)->move_charge_at_immigrate;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_MMU
|
||||
static int mem_cgroup_move_charge_write(struct cgroup_subsys_state *css,
|
||||
struct cftype *cft, u64 val)
|
||||
{
|
||||
struct mem_cgroup *memcg = mem_cgroup_from_css(css);
|
||||
|
||||
pr_warn_once("Cgroup memory moving (move_charge_at_immigrate) is deprecated. "
|
||||
"Please report your usecase to linux-mm@kvack.org if you "
|
||||
"depend on this functionality.\n");
|
||||
|
||||
if (val & ~MOVE_MASK)
|
||||
if (val != 0)
|
||||
return -EINVAL;
|
||||
|
||||
/*
|
||||
* No kind of locking is needed in here, because ->can_attach() will
|
||||
* check this value once in the beginning of the process, and then carry
|
||||
* on with stale data. This means that changes to this value will only
|
||||
* affect task migrations starting after the change.
|
||||
*/
|
||||
memcg->move_charge_at_immigrate = val;
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
@@ -626,785 +508,6 @@ static int mem_cgroup_move_charge_write(struct cgroup_subsys_state *css,
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_MMU
|
||||
/* Handlers for move charge at task migration. */
|
||||
static int mem_cgroup_do_precharge(unsigned long count)
|
||||
{
|
||||
int ret;
|
||||
|
||||
/* Try a single bulk charge without reclaim first, kswapd may wake */
|
||||
ret = try_charge(mc.to, GFP_KERNEL & ~__GFP_DIRECT_RECLAIM, count);
|
||||
if (!ret) {
|
||||
mc.precharge += count;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Try charges one by one with reclaim, but do not retry */
|
||||
while (count--) {
|
||||
ret = try_charge(mc.to, GFP_KERNEL | __GFP_NORETRY, 1);
|
||||
if (ret)
|
||||
return ret;
|
||||
mc.precharge++;
|
||||
cond_resched();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
union mc_target {
|
||||
struct folio *folio;
|
||||
swp_entry_t ent;
|
||||
};
|
||||
|
||||
enum mc_target_type {
|
||||
MC_TARGET_NONE = 0,
|
||||
MC_TARGET_PAGE,
|
||||
MC_TARGET_SWAP,
|
||||
MC_TARGET_DEVICE,
|
||||
};
|
||||
|
||||
static struct page *mc_handle_present_pte(struct vm_area_struct *vma,
|
||||
unsigned long addr, pte_t ptent)
|
||||
{
|
||||
struct page *page = vm_normal_page(vma, addr, ptent);
|
||||
|
||||
if (!page)
|
||||
return NULL;
|
||||
if (PageAnon(page)) {
|
||||
if (!(mc.flags & MOVE_ANON))
|
||||
return NULL;
|
||||
} else {
|
||||
if (!(mc.flags & MOVE_FILE))
|
||||
return NULL;
|
||||
}
|
||||
get_page(page);
|
||||
|
||||
return page;
|
||||
}
|
||||
|
||||
#if defined(CONFIG_SWAP) || defined(CONFIG_DEVICE_PRIVATE)
|
||||
static struct page *mc_handle_swap_pte(struct vm_area_struct *vma,
|
||||
pte_t ptent, swp_entry_t *entry)
|
||||
{
|
||||
struct page *page = NULL;
|
||||
swp_entry_t ent = pte_to_swp_entry(ptent);
|
||||
|
||||
if (!(mc.flags & MOVE_ANON))
|
||||
return NULL;
|
||||
|
||||
/*
|
||||
* Handle device private pages that are not accessible by the CPU, but
|
||||
* stored as special swap entries in the page table.
|
||||
*/
|
||||
if (is_device_private_entry(ent)) {
|
||||
page = pfn_swap_entry_to_page(ent);
|
||||
if (!get_page_unless_zero(page))
|
||||
return NULL;
|
||||
return page;
|
||||
}
|
||||
|
||||
if (non_swap_entry(ent))
|
||||
return NULL;
|
||||
|
||||
/*
|
||||
* Because swap_cache_get_folio() updates some statistics counter,
|
||||
* we call find_get_page() with swapper_space directly.
|
||||
*/
|
||||
page = find_get_page(swap_address_space(ent), swap_cache_index(ent));
|
||||
entry->val = ent.val;
|
||||
|
||||
return page;
|
||||
}
|
||||
#else
|
||||
static struct page *mc_handle_swap_pte(struct vm_area_struct *vma,
|
||||
pte_t ptent, swp_entry_t *entry)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
static struct page *mc_handle_file_pte(struct vm_area_struct *vma,
|
||||
unsigned long addr, pte_t ptent)
|
||||
{
|
||||
unsigned long index;
|
||||
struct folio *folio;
|
||||
|
||||
if (!vma->vm_file) /* anonymous vma */
|
||||
return NULL;
|
||||
if (!(mc.flags & MOVE_FILE))
|
||||
return NULL;
|
||||
|
||||
/* folio is moved even if it's not RSS of this task(page-faulted). */
|
||||
/* shmem/tmpfs may report page out on swap: account for that too. */
|
||||
index = linear_page_index(vma, addr);
|
||||
folio = filemap_get_incore_folio(vma->vm_file->f_mapping, index);
|
||||
if (IS_ERR(folio))
|
||||
return NULL;
|
||||
return folio_file_page(folio, index);
|
||||
}
|
||||
|
||||
static void memcg1_check_events(struct mem_cgroup *memcg, int nid);
|
||||
static void memcg1_charge_statistics(struct mem_cgroup *memcg, int nr_pages);
|
||||
|
||||
/**
|
||||
* mem_cgroup_move_account - move account of the folio
|
||||
* @folio: The folio.
|
||||
* @compound: charge the page as compound or small page
|
||||
* @from: mem_cgroup which the folio is moved from.
|
||||
* @to: mem_cgroup which the folio is moved to. @from != @to.
|
||||
*
|
||||
* The folio must be locked and not on the LRU.
|
||||
*
|
||||
* This function doesn't do "charge" to new cgroup and doesn't do "uncharge"
|
||||
* from old cgroup.
|
||||
*/
|
||||
static int mem_cgroup_move_account(struct folio *folio,
|
||||
bool compound,
|
||||
struct mem_cgroup *from,
|
||||
struct mem_cgroup *to)
|
||||
{
|
||||
struct lruvec *from_vec, *to_vec;
|
||||
struct pglist_data *pgdat;
|
||||
unsigned int nr_pages = compound ? folio_nr_pages(folio) : 1;
|
||||
int nid, ret;
|
||||
|
||||
VM_BUG_ON(from == to);
|
||||
VM_BUG_ON_FOLIO(!folio_test_locked(folio), folio);
|
||||
VM_BUG_ON_FOLIO(folio_test_lru(folio), folio);
|
||||
VM_BUG_ON(compound && !folio_test_large(folio));
|
||||
|
||||
ret = -EINVAL;
|
||||
if (folio_memcg(folio) != from)
|
||||
goto out;
|
||||
|
||||
pgdat = folio_pgdat(folio);
|
||||
from_vec = mem_cgroup_lruvec(from, pgdat);
|
||||
to_vec = mem_cgroup_lruvec(to, pgdat);
|
||||
|
||||
folio_memcg_lock(folio);
|
||||
|
||||
if (folio_test_anon(folio)) {
|
||||
if (folio_mapped(folio)) {
|
||||
__mod_lruvec_state(from_vec, NR_ANON_MAPPED, -nr_pages);
|
||||
__mod_lruvec_state(to_vec, NR_ANON_MAPPED, nr_pages);
|
||||
if (folio_test_pmd_mappable(folio)) {
|
||||
__mod_lruvec_state(from_vec, NR_ANON_THPS,
|
||||
-nr_pages);
|
||||
__mod_lruvec_state(to_vec, NR_ANON_THPS,
|
||||
nr_pages);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
__mod_lruvec_state(from_vec, NR_FILE_PAGES, -nr_pages);
|
||||
__mod_lruvec_state(to_vec, NR_FILE_PAGES, nr_pages);
|
||||
|
||||
if (folio_test_swapbacked(folio)) {
|
||||
__mod_lruvec_state(from_vec, NR_SHMEM, -nr_pages);
|
||||
__mod_lruvec_state(to_vec, NR_SHMEM, nr_pages);
|
||||
}
|
||||
|
||||
if (folio_mapped(folio)) {
|
||||
__mod_lruvec_state(from_vec, NR_FILE_MAPPED, -nr_pages);
|
||||
__mod_lruvec_state(to_vec, NR_FILE_MAPPED, nr_pages);
|
||||
}
|
||||
|
||||
if (folio_test_dirty(folio)) {
|
||||
struct address_space *mapping = folio_mapping(folio);
|
||||
|
||||
if (mapping_can_writeback(mapping)) {
|
||||
__mod_lruvec_state(from_vec, NR_FILE_DIRTY,
|
||||
-nr_pages);
|
||||
__mod_lruvec_state(to_vec, NR_FILE_DIRTY,
|
||||
nr_pages);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef CONFIG_SWAP
|
||||
if (folio_test_swapcache(folio)) {
|
||||
__mod_lruvec_state(from_vec, NR_SWAPCACHE, -nr_pages);
|
||||
__mod_lruvec_state(to_vec, NR_SWAPCACHE, nr_pages);
|
||||
}
|
||||
#endif
|
||||
if (folio_test_writeback(folio)) {
|
||||
__mod_lruvec_state(from_vec, NR_WRITEBACK, -nr_pages);
|
||||
__mod_lruvec_state(to_vec, NR_WRITEBACK, nr_pages);
|
||||
}
|
||||
|
||||
/*
|
||||
* All state has been migrated, let's switch to the new memcg.
|
||||
*
|
||||
* It is safe to change page's memcg here because the page
|
||||
* is referenced, charged, isolated, and locked: we can't race
|
||||
* with (un)charging, migration, LRU putback, or anything else
|
||||
* that would rely on a stable page's memory cgroup.
|
||||
*
|
||||
* Note that folio_memcg_lock is a memcg lock, not a page lock,
|
||||
* to save space. As soon as we switch page's memory cgroup to a
|
||||
* new memcg that isn't locked, the above state can change
|
||||
* concurrently again. Make sure we're truly done with it.
|
||||
*/
|
||||
smp_mb();
|
||||
|
||||
css_get(&to->css);
|
||||
css_put(&from->css);
|
||||
|
||||
/* Warning should never happen, so don't worry about refcount non-0 */
|
||||
WARN_ON_ONCE(folio_unqueue_deferred_split(folio));
|
||||
folio->memcg_data = (unsigned long)to;
|
||||
|
||||
__folio_memcg_unlock(from);
|
||||
|
||||
ret = 0;
|
||||
nid = folio_nid(folio);
|
||||
|
||||
local_irq_disable();
|
||||
memcg1_charge_statistics(to, nr_pages);
|
||||
memcg1_check_events(to, nid);
|
||||
memcg1_charge_statistics(from, -nr_pages);
|
||||
memcg1_check_events(from, nid);
|
||||
local_irq_enable();
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* get_mctgt_type - get target type of moving charge
|
||||
* @vma: the vma the pte to be checked belongs
|
||||
* @addr: the address corresponding to the pte to be checked
|
||||
* @ptent: the pte to be checked
|
||||
* @target: the pointer the target page or swap ent will be stored(can be NULL)
|
||||
*
|
||||
* Context: Called with pte lock held.
|
||||
* Return:
|
||||
* * MC_TARGET_NONE - If the pte is not a target for move charge.
|
||||
* * MC_TARGET_PAGE - If the page corresponding to this pte is a target for
|
||||
* move charge. If @target is not NULL, the folio is stored in target->folio
|
||||
* with extra refcnt taken (Caller should release it).
|
||||
* * MC_TARGET_SWAP - If the swap entry corresponding to this pte is a
|
||||
* target for charge migration. If @target is not NULL, the entry is
|
||||
* stored in target->ent.
|
||||
* * MC_TARGET_DEVICE - Like MC_TARGET_PAGE but page is device memory and
|
||||
* thus not on the lru. For now such page is charged like a regular page
|
||||
* would be as it is just special memory taking the place of a regular page.
|
||||
* See Documentations/vm/hmm.txt and include/linux/hmm.h
|
||||
*/
|
||||
static enum mc_target_type get_mctgt_type(struct vm_area_struct *vma,
|
||||
unsigned long addr, pte_t ptent, union mc_target *target)
|
||||
{
|
||||
struct page *page = NULL;
|
||||
struct folio *folio;
|
||||
enum mc_target_type ret = MC_TARGET_NONE;
|
||||
swp_entry_t ent = { .val = 0 };
|
||||
|
||||
if (pte_present(ptent))
|
||||
page = mc_handle_present_pte(vma, addr, ptent);
|
||||
else if (pte_none_mostly(ptent))
|
||||
/*
|
||||
* PTE markers should be treated as a none pte here, separated
|
||||
* from other swap handling below.
|
||||
*/
|
||||
page = mc_handle_file_pte(vma, addr, ptent);
|
||||
else if (is_swap_pte(ptent))
|
||||
page = mc_handle_swap_pte(vma, ptent, &ent);
|
||||
|
||||
if (page)
|
||||
folio = page_folio(page);
|
||||
if (target && page) {
|
||||
if (!folio_trylock(folio)) {
|
||||
folio_put(folio);
|
||||
return ret;
|
||||
}
|
||||
/*
|
||||
* page_mapped() must be stable during the move. This
|
||||
* pte is locked, so if it's present, the page cannot
|
||||
* become unmapped. If it isn't, we have only partial
|
||||
* control over the mapped state: the page lock will
|
||||
* prevent new faults against pagecache and swapcache,
|
||||
* so an unmapped page cannot become mapped. However,
|
||||
* if the page is already mapped elsewhere, it can
|
||||
* unmap, and there is nothing we can do about it.
|
||||
* Alas, skip moving the page in this case.
|
||||
*/
|
||||
if (!pte_present(ptent) && page_mapped(page)) {
|
||||
folio_unlock(folio);
|
||||
folio_put(folio);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
if (!page && !ent.val)
|
||||
return ret;
|
||||
if (page) {
|
||||
/*
|
||||
* Do only loose check w/o serialization.
|
||||
* mem_cgroup_move_account() checks the page is valid or
|
||||
* not under LRU exclusion.
|
||||
*/
|
||||
if (folio_memcg(folio) == mc.from) {
|
||||
ret = MC_TARGET_PAGE;
|
||||
if (folio_is_device_private(folio) ||
|
||||
folio_is_device_coherent(folio))
|
||||
ret = MC_TARGET_DEVICE;
|
||||
if (target)
|
||||
target->folio = folio;
|
||||
}
|
||||
if (!ret || !target) {
|
||||
if (target)
|
||||
folio_unlock(folio);
|
||||
folio_put(folio);
|
||||
}
|
||||
}
|
||||
/*
|
||||
* There is a swap entry and a page doesn't exist or isn't charged.
|
||||
* But we cannot move a tail-page in a THP.
|
||||
*/
|
||||
if (ent.val && !ret && (!page || !PageTransCompound(page)) &&
|
||||
mem_cgroup_id(mc.from) == lookup_swap_cgroup_id(ent)) {
|
||||
ret = MC_TARGET_SWAP;
|
||||
if (target)
|
||||
target->ent = ent;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_TRANSPARENT_HUGEPAGE
|
||||
/*
|
||||
* We don't consider PMD mapped swapping or file mapped pages because THP does
|
||||
* not support them for now.
|
||||
* Caller should make sure that pmd_trans_huge(pmd) is true.
|
||||
*/
|
||||
static enum mc_target_type get_mctgt_type_thp(struct vm_area_struct *vma,
|
||||
unsigned long addr, pmd_t pmd, union mc_target *target)
|
||||
{
|
||||
struct page *page = NULL;
|
||||
struct folio *folio;
|
||||
enum mc_target_type ret = MC_TARGET_NONE;
|
||||
|
||||
if (unlikely(is_swap_pmd(pmd))) {
|
||||
VM_BUG_ON(thp_migration_supported() &&
|
||||
!is_pmd_migration_entry(pmd));
|
||||
return ret;
|
||||
}
|
||||
page = pmd_page(pmd);
|
||||
VM_BUG_ON_PAGE(!page || !PageHead(page), page);
|
||||
folio = page_folio(page);
|
||||
if (!(mc.flags & MOVE_ANON))
|
||||
return ret;
|
||||
if (folio_memcg(folio) == mc.from) {
|
||||
ret = MC_TARGET_PAGE;
|
||||
if (target) {
|
||||
folio_get(folio);
|
||||
if (!folio_trylock(folio)) {
|
||||
folio_put(folio);
|
||||
return MC_TARGET_NONE;
|
||||
}
|
||||
target->folio = folio;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
#else
|
||||
static inline enum mc_target_type get_mctgt_type_thp(struct vm_area_struct *vma,
|
||||
unsigned long addr, pmd_t pmd, union mc_target *target)
|
||||
{
|
||||
return MC_TARGET_NONE;
|
||||
}
|
||||
#endif
|
||||
|
||||
static int mem_cgroup_count_precharge_pte_range(pmd_t *pmd,
|
||||
unsigned long addr, unsigned long end,
|
||||
struct mm_walk *walk)
|
||||
{
|
||||
struct vm_area_struct *vma = walk->vma;
|
||||
pte_t *pte;
|
||||
spinlock_t *ptl;
|
||||
|
||||
ptl = pmd_trans_huge_lock(pmd, vma);
|
||||
if (ptl) {
|
||||
/*
|
||||
* Note their can not be MC_TARGET_DEVICE for now as we do not
|
||||
* support transparent huge page with MEMORY_DEVICE_PRIVATE but
|
||||
* this might change.
|
||||
*/
|
||||
if (get_mctgt_type_thp(vma, addr, *pmd, NULL) == MC_TARGET_PAGE)
|
||||
mc.precharge += HPAGE_PMD_NR;
|
||||
spin_unlock(ptl);
|
||||
return 0;
|
||||
}
|
||||
|
||||
pte = pte_offset_map_lock(vma->vm_mm, pmd, addr, &ptl);
|
||||
if (!pte)
|
||||
return 0;
|
||||
for (; addr != end; pte++, addr += PAGE_SIZE)
|
||||
if (get_mctgt_type(vma, addr, ptep_get(pte), NULL))
|
||||
mc.precharge++; /* increment precharge temporarily */
|
||||
pte_unmap_unlock(pte - 1, ptl);
|
||||
cond_resched();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct mm_walk_ops precharge_walk_ops = {
|
||||
.pmd_entry = mem_cgroup_count_precharge_pte_range,
|
||||
.walk_lock = PGWALK_RDLOCK,
|
||||
};
|
||||
|
||||
static unsigned long mem_cgroup_count_precharge(struct mm_struct *mm)
|
||||
{
|
||||
unsigned long precharge;
|
||||
|
||||
mmap_read_lock(mm);
|
||||
walk_page_range(mm, 0, ULONG_MAX, &precharge_walk_ops, NULL);
|
||||
mmap_read_unlock(mm);
|
||||
|
||||
precharge = mc.precharge;
|
||||
mc.precharge = 0;
|
||||
|
||||
return precharge;
|
||||
}
|
||||
|
||||
static int mem_cgroup_precharge_mc(struct mm_struct *mm)
|
||||
{
|
||||
unsigned long precharge = mem_cgroup_count_precharge(mm);
|
||||
|
||||
VM_BUG_ON(mc.moving_task);
|
||||
mc.moving_task = current;
|
||||
return mem_cgroup_do_precharge(precharge);
|
||||
}
|
||||
|
||||
/* cancels all extra charges on mc.from and mc.to, and wakes up all waiters. */
|
||||
static void __mem_cgroup_clear_mc(void)
|
||||
{
|
||||
struct mem_cgroup *from = mc.from;
|
||||
struct mem_cgroup *to = mc.to;
|
||||
|
||||
/* we must uncharge all the leftover precharges from mc.to */
|
||||
if (mc.precharge) {
|
||||
mem_cgroup_cancel_charge(mc.to, mc.precharge);
|
||||
mc.precharge = 0;
|
||||
}
|
||||
/*
|
||||
* we didn't uncharge from mc.from at mem_cgroup_move_account(), so
|
||||
* we must uncharge here.
|
||||
*/
|
||||
if (mc.moved_charge) {
|
||||
mem_cgroup_cancel_charge(mc.from, mc.moved_charge);
|
||||
mc.moved_charge = 0;
|
||||
}
|
||||
/* we must fixup refcnts and charges */
|
||||
if (mc.moved_swap) {
|
||||
/* uncharge swap account from the old cgroup */
|
||||
if (!mem_cgroup_is_root(mc.from))
|
||||
page_counter_uncharge(&mc.from->memsw, mc.moved_swap);
|
||||
|
||||
mem_cgroup_id_put_many(mc.from, mc.moved_swap);
|
||||
|
||||
/*
|
||||
* we charged both to->memory and to->memsw, so we
|
||||
* should uncharge to->memory.
|
||||
*/
|
||||
if (!mem_cgroup_is_root(mc.to))
|
||||
page_counter_uncharge(&mc.to->memory, mc.moved_swap);
|
||||
|
||||
mc.moved_swap = 0;
|
||||
}
|
||||
memcg1_oom_recover(from);
|
||||
memcg1_oom_recover(to);
|
||||
wake_up_all(&mc.waitq);
|
||||
}
|
||||
|
||||
static void mem_cgroup_clear_mc(void)
|
||||
{
|
||||
struct mm_struct *mm = mc.mm;
|
||||
|
||||
/*
|
||||
* we must clear moving_task before waking up waiters at the end of
|
||||
* task migration.
|
||||
*/
|
||||
mc.moving_task = NULL;
|
||||
__mem_cgroup_clear_mc();
|
||||
spin_lock(&mc.lock);
|
||||
mc.from = NULL;
|
||||
mc.to = NULL;
|
||||
mc.mm = NULL;
|
||||
spin_unlock(&mc.lock);
|
||||
|
||||
mmput(mm);
|
||||
}
|
||||
|
||||
int memcg1_can_attach(struct cgroup_taskset *tset)
|
||||
{
|
||||
struct cgroup_subsys_state *css;
|
||||
struct mem_cgroup *memcg = NULL; /* unneeded init to make gcc happy */
|
||||
struct mem_cgroup *from;
|
||||
struct task_struct *leader, *p;
|
||||
struct mm_struct *mm;
|
||||
unsigned long move_flags;
|
||||
int ret = 0;
|
||||
|
||||
/* charge immigration isn't supported on the default hierarchy */
|
||||
if (cgroup_subsys_on_dfl(memory_cgrp_subsys))
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* Multi-process migrations only happen on the default hierarchy
|
||||
* where charge immigration is not used. Perform charge
|
||||
* immigration if @tset contains a leader and whine if there are
|
||||
* multiple.
|
||||
*/
|
||||
p = NULL;
|
||||
cgroup_taskset_for_each_leader(leader, css, tset) {
|
||||
WARN_ON_ONCE(p);
|
||||
p = leader;
|
||||
memcg = mem_cgroup_from_css(css);
|
||||
}
|
||||
if (!p)
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* We are now committed to this value whatever it is. Changes in this
|
||||
* tunable will only affect upcoming migrations, not the current one.
|
||||
* So we need to save it, and keep it going.
|
||||
*/
|
||||
move_flags = READ_ONCE(memcg->move_charge_at_immigrate);
|
||||
if (!move_flags)
|
||||
return 0;
|
||||
|
||||
from = mem_cgroup_from_task(p);
|
||||
|
||||
VM_BUG_ON(from == memcg);
|
||||
|
||||
mm = get_task_mm(p);
|
||||
if (!mm)
|
||||
return 0;
|
||||
/* We move charges only when we move a owner of the mm */
|
||||
if (mm->owner == p) {
|
||||
VM_BUG_ON(mc.from);
|
||||
VM_BUG_ON(mc.to);
|
||||
VM_BUG_ON(mc.precharge);
|
||||
VM_BUG_ON(mc.moved_charge);
|
||||
VM_BUG_ON(mc.moved_swap);
|
||||
|
||||
spin_lock(&mc.lock);
|
||||
mc.mm = mm;
|
||||
mc.from = from;
|
||||
mc.to = memcg;
|
||||
mc.flags = move_flags;
|
||||
spin_unlock(&mc.lock);
|
||||
/* We set mc.moving_task later */
|
||||
|
||||
ret = mem_cgroup_precharge_mc(mm);
|
||||
if (ret)
|
||||
mem_cgroup_clear_mc();
|
||||
} else {
|
||||
mmput(mm);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void memcg1_cancel_attach(struct cgroup_taskset *tset)
|
||||
{
|
||||
if (mc.to)
|
||||
mem_cgroup_clear_mc();
|
||||
}
|
||||
|
||||
static int mem_cgroup_move_charge_pte_range(pmd_t *pmd,
|
||||
unsigned long addr, unsigned long end,
|
||||
struct mm_walk *walk)
|
||||
{
|
||||
int ret = 0;
|
||||
struct vm_area_struct *vma = walk->vma;
|
||||
pte_t *pte;
|
||||
spinlock_t *ptl;
|
||||
enum mc_target_type target_type;
|
||||
union mc_target target;
|
||||
struct folio *folio;
|
||||
bool tried_split_before = false;
|
||||
|
||||
retry_pmd:
|
||||
ptl = pmd_trans_huge_lock(pmd, vma);
|
||||
if (ptl) {
|
||||
if (mc.precharge < HPAGE_PMD_NR) {
|
||||
spin_unlock(ptl);
|
||||
return 0;
|
||||
}
|
||||
target_type = get_mctgt_type_thp(vma, addr, *pmd, &target);
|
||||
if (target_type == MC_TARGET_PAGE) {
|
||||
folio = target.folio;
|
||||
/*
|
||||
* Deferred split queue locking depends on memcg,
|
||||
* and unqueue is unsafe unless folio refcount is 0:
|
||||
* split or skip if on the queue? first try to split.
|
||||
*/
|
||||
if (!list_empty(&folio->_deferred_list)) {
|
||||
spin_unlock(ptl);
|
||||
if (!tried_split_before)
|
||||
split_folio(folio);
|
||||
folio_unlock(folio);
|
||||
folio_put(folio);
|
||||
if (tried_split_before)
|
||||
return 0;
|
||||
tried_split_before = true;
|
||||
goto retry_pmd;
|
||||
}
|
||||
/*
|
||||
* So long as that pmd lock is held, the folio cannot
|
||||
* be racily added to the _deferred_list, because
|
||||
* __folio_remove_rmap() will find !partially_mapped.
|
||||
*/
|
||||
if (folio_isolate_lru(folio)) {
|
||||
if (!mem_cgroup_move_account(folio, true,
|
||||
mc.from, mc.to)) {
|
||||
mc.precharge -= HPAGE_PMD_NR;
|
||||
mc.moved_charge += HPAGE_PMD_NR;
|
||||
}
|
||||
folio_putback_lru(folio);
|
||||
}
|
||||
folio_unlock(folio);
|
||||
folio_put(folio);
|
||||
} else if (target_type == MC_TARGET_DEVICE) {
|
||||
folio = target.folio;
|
||||
if (!mem_cgroup_move_account(folio, true,
|
||||
mc.from, mc.to)) {
|
||||
mc.precharge -= HPAGE_PMD_NR;
|
||||
mc.moved_charge += HPAGE_PMD_NR;
|
||||
}
|
||||
folio_unlock(folio);
|
||||
folio_put(folio);
|
||||
}
|
||||
spin_unlock(ptl);
|
||||
return 0;
|
||||
}
|
||||
|
||||
retry:
|
||||
pte = pte_offset_map_lock(vma->vm_mm, pmd, addr, &ptl);
|
||||
if (!pte)
|
||||
return 0;
|
||||
for (; addr != end; addr += PAGE_SIZE) {
|
||||
pte_t ptent = ptep_get(pte++);
|
||||
bool device = false;
|
||||
swp_entry_t ent;
|
||||
|
||||
if (!mc.precharge)
|
||||
break;
|
||||
|
||||
switch (get_mctgt_type(vma, addr, ptent, &target)) {
|
||||
case MC_TARGET_DEVICE:
|
||||
device = true;
|
||||
fallthrough;
|
||||
case MC_TARGET_PAGE:
|
||||
folio = target.folio;
|
||||
/*
|
||||
* We can have a part of the split pmd here. Moving it
|
||||
* can be done but it would be too convoluted so simply
|
||||
* ignore such a partial THP and keep it in original
|
||||
* memcg. There should be somebody mapping the head.
|
||||
*/
|
||||
if (folio_test_large(folio))
|
||||
goto put;
|
||||
if (!device && !folio_isolate_lru(folio))
|
||||
goto put;
|
||||
if (!mem_cgroup_move_account(folio, false,
|
||||
mc.from, mc.to)) {
|
||||
mc.precharge--;
|
||||
/* we uncharge from mc.from later. */
|
||||
mc.moved_charge++;
|
||||
}
|
||||
if (!device)
|
||||
folio_putback_lru(folio);
|
||||
put: /* get_mctgt_type() gets & locks the page */
|
||||
folio_unlock(folio);
|
||||
folio_put(folio);
|
||||
break;
|
||||
case MC_TARGET_SWAP:
|
||||
ent = target.ent;
|
||||
if (!mem_cgroup_move_swap_account(ent, mc.from, mc.to)) {
|
||||
mc.precharge--;
|
||||
mem_cgroup_id_get_many(mc.to, 1);
|
||||
/* we fixup other refcnts and charges later. */
|
||||
mc.moved_swap++;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
pte_unmap_unlock(pte - 1, ptl);
|
||||
cond_resched();
|
||||
|
||||
if (addr != end) {
|
||||
/*
|
||||
* We have consumed all precharges we got in can_attach().
|
||||
* We try charge one by one, but don't do any additional
|
||||
* charges to mc.to if we have failed in charge once in attach()
|
||||
* phase.
|
||||
*/
|
||||
ret = mem_cgroup_do_precharge(1);
|
||||
if (!ret)
|
||||
goto retry;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct mm_walk_ops charge_walk_ops = {
|
||||
.pmd_entry = mem_cgroup_move_charge_pte_range,
|
||||
.walk_lock = PGWALK_RDLOCK,
|
||||
};
|
||||
|
||||
static void mem_cgroup_move_charge(void)
|
||||
{
|
||||
lru_add_drain_all();
|
||||
/*
|
||||
* Signal folio_memcg_lock() to take the memcg's move_lock
|
||||
* while we're moving its pages to another memcg. Then wait
|
||||
* for already started RCU-only updates to finish.
|
||||
*/
|
||||
atomic_inc(&mc.from->moving_account);
|
||||
synchronize_rcu();
|
||||
retry:
|
||||
if (unlikely(!mmap_read_trylock(mc.mm))) {
|
||||
/*
|
||||
* Someone who are holding the mmap_lock might be waiting in
|
||||
* waitq. So we cancel all extra charges, wake up all waiters,
|
||||
* and retry. Because we cancel precharges, we might not be able
|
||||
* to move enough charges, but moving charge is a best-effort
|
||||
* feature anyway, so it wouldn't be a big problem.
|
||||
*/
|
||||
__mem_cgroup_clear_mc();
|
||||
cond_resched();
|
||||
goto retry;
|
||||
}
|
||||
/*
|
||||
* When we have consumed all precharges and failed in doing
|
||||
* additional charge, the page walk just aborts.
|
||||
*/
|
||||
walk_page_range(mc.mm, 0, ULONG_MAX, &charge_walk_ops, NULL);
|
||||
mmap_read_unlock(mc.mm);
|
||||
atomic_dec(&mc.from->moving_account);
|
||||
}
|
||||
|
||||
void memcg1_move_task(void)
|
||||
{
|
||||
if (mc.to) {
|
||||
mem_cgroup_move_charge();
|
||||
mem_cgroup_clear_mc();
|
||||
}
|
||||
}
|
||||
|
||||
#else /* !CONFIG_MMU */
|
||||
int memcg1_can_attach(struct cgroup_taskset *tset)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
void memcg1_cancel_attach(struct cgroup_taskset *tset)
|
||||
{
|
||||
}
|
||||
void memcg1_move_task(void)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
static void __mem_cgroup_threshold(struct mem_cgroup *memcg, bool swap)
|
||||
{
|
||||
struct mem_cgroup_threshold_ary *t;
|
||||
|
||||
@@ -80,12 +80,7 @@ static inline void memcg1_soft_limit_reset(struct mem_cgroup *memcg)
|
||||
WRITE_ONCE(memcg->soft_limit, PAGE_COUNTER_MAX);
|
||||
}
|
||||
|
||||
bool memcg1_wait_acct_move(struct mem_cgroup *memcg);
|
||||
|
||||
struct cgroup_taskset;
|
||||
int memcg1_can_attach(struct cgroup_taskset *tset);
|
||||
void memcg1_cancel_attach(struct cgroup_taskset *tset);
|
||||
void memcg1_move_task(void);
|
||||
void memcg1_css_offline(struct mem_cgroup *memcg);
|
||||
|
||||
/* for encoding cft->private value on file */
|
||||
@@ -130,7 +125,6 @@ static inline void memcg1_free_events(struct mem_cgroup *memcg) {}
|
||||
static inline void memcg1_memcg_init(struct mem_cgroup *memcg) {}
|
||||
static inline void memcg1_remove_from_trees(struct mem_cgroup *memcg) {}
|
||||
static inline void memcg1_soft_limit_reset(struct mem_cgroup *memcg) {}
|
||||
static inline bool memcg1_wait_acct_move(struct mem_cgroup *memcg) { return false; }
|
||||
static inline void memcg1_css_offline(struct mem_cgroup *memcg) {}
|
||||
|
||||
static inline bool memcg1_oom_prepare(struct mem_cgroup *memcg, bool *locked) { return true; }
|
||||
|
||||
+5
-13
@@ -118,6 +118,7 @@ struct mem_cgroup *vmpressure_to_memcg(struct vmpressure *vmpr)
|
||||
return container_of(vmpr, struct mem_cgroup, vmpressure);
|
||||
}
|
||||
|
||||
#define SEQ_BUF_SIZE SZ_4K
|
||||
#define CURRENT_OBJCG_UPDATE_BIT 0
|
||||
#define CURRENT_OBJCG_UPDATE_FLAG (1UL << CURRENT_OBJCG_UPDATE_BIT)
|
||||
|
||||
@@ -1531,7 +1532,7 @@ void mem_cgroup_print_oom_context(struct mem_cgroup *memcg, struct task_struct *
|
||||
void mem_cgroup_print_oom_meminfo(struct mem_cgroup *memcg)
|
||||
{
|
||||
/* Use static buffer, for the caller is holding oom_lock. */
|
||||
static char buf[PAGE_SIZE];
|
||||
static char buf[SEQ_BUF_SIZE];
|
||||
struct seq_buf s;
|
||||
|
||||
lockdep_assert_held(&oom_lock);
|
||||
@@ -1557,7 +1558,7 @@ void mem_cgroup_print_oom_meminfo(struct mem_cgroup *memcg)
|
||||
pr_info("Memory cgroup stats for ");
|
||||
pr_cont_cgroup_path(memcg->css.cgroup);
|
||||
pr_cont(":");
|
||||
seq_buf_init(&s, buf, sizeof(buf));
|
||||
seq_buf_init(&s, buf, SEQ_BUF_SIZE);
|
||||
memory_stat_format(memcg, &s);
|
||||
seq_buf_do_printk(&s, KERN_INFO);
|
||||
}
|
||||
@@ -2245,12 +2246,6 @@ retry:
|
||||
*/
|
||||
if (nr_reclaimed && nr_pages <= (1 << PAGE_ALLOC_COSTLY_ORDER))
|
||||
goto retry;
|
||||
/*
|
||||
* At task move, charge accounts can be doubly counted. So, it's
|
||||
* better to wait until the end of task_move if something is going on.
|
||||
*/
|
||||
if (memcg1_wait_acct_move(mem_over_limit))
|
||||
goto retry;
|
||||
|
||||
if (nr_retries--)
|
||||
goto retry;
|
||||
@@ -4200,12 +4195,12 @@ static int memory_events_local_show(struct seq_file *m, void *v)
|
||||
int memory_stat_show(struct seq_file *m, void *v)
|
||||
{
|
||||
struct mem_cgroup *memcg = mem_cgroup_from_seq(m);
|
||||
char *buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
|
||||
char *buf = kmalloc(SEQ_BUF_SIZE, GFP_KERNEL);
|
||||
struct seq_buf s;
|
||||
|
||||
if (!buf)
|
||||
return -ENOMEM;
|
||||
seq_buf_init(&s, buf, PAGE_SIZE);
|
||||
seq_buf_init(&s, buf, SEQ_BUF_SIZE);
|
||||
memory_stat_format(memcg, &s);
|
||||
seq_puts(m, buf);
|
||||
kfree(buf);
|
||||
@@ -4444,9 +4439,6 @@ struct cgroup_subsys memory_cgrp_subsys = {
|
||||
.exit = mem_cgroup_exit,
|
||||
.dfl_cftypes = memory_files,
|
||||
#ifdef CONFIG_MEMCG_V1
|
||||
.can_attach = memcg1_can_attach,
|
||||
.cancel_attach = memcg1_cancel_attach,
|
||||
.post_attach = memcg1_move_task,
|
||||
.legacy_cftypes = mem_cgroup_legacy_files,
|
||||
#endif
|
||||
.early_init = 0,
|
||||
|
||||
+1
-1
@@ -6389,7 +6389,7 @@ static inline void pfnmap_lockdep_assert(struct vm_area_struct *vma)
|
||||
struct address_space *mapping = file ? file->f_mapping : NULL;
|
||||
|
||||
if (mapping)
|
||||
lockdep_assert(lockdep_is_held(&vma->vm_file->f_mapping->i_mmap_rwsem) ||
|
||||
lockdep_assert(lockdep_is_held(&mapping->i_mmap_rwsem) ||
|
||||
lockdep_is_held(&vma->vm_mm->mmap_lock));
|
||||
else
|
||||
lockdep_assert(lockdep_is_held(&vma->vm_mm->mmap_lock));
|
||||
|
||||
+1
-1
@@ -1733,7 +1733,7 @@ static int migrate_pages_batch(struct list_head *from,
|
||||
|
||||
list_for_each_entry_safe(folio, folio2, from, lru) {
|
||||
is_large = folio_test_large(folio);
|
||||
is_thp = is_large && folio_test_pmd_mappable(folio);
|
||||
is_thp = folio_test_pmd_mappable(folio);
|
||||
nr_pages = folio_nr_pages(folio);
|
||||
|
||||
cond_resched();
|
||||
|
||||
+55
-38
@@ -826,17 +826,24 @@ static unsigned long move_vma(struct vm_area_struct *vma,
|
||||
return new_addr;
|
||||
}
|
||||
|
||||
static struct vm_area_struct *vma_to_resize(unsigned long addr,
|
||||
/*
|
||||
* resize_is_valid() - Ensure the vma can be resized to the new length at the give
|
||||
* address.
|
||||
*
|
||||
* @vma: The vma to resize
|
||||
* @addr: The old address
|
||||
* @old_len: The current size
|
||||
* @new_len: The desired size
|
||||
* @flags: The vma flags
|
||||
*
|
||||
* Return 0 on success, error otherwise.
|
||||
*/
|
||||
static int resize_is_valid(struct vm_area_struct *vma, unsigned long addr,
|
||||
unsigned long old_len, unsigned long new_len, unsigned long flags)
|
||||
{
|
||||
struct mm_struct *mm = current->mm;
|
||||
struct vm_area_struct *vma;
|
||||
unsigned long pgoff;
|
||||
|
||||
vma = vma_lookup(mm, addr);
|
||||
if (!vma)
|
||||
return ERR_PTR(-EFAULT);
|
||||
|
||||
/*
|
||||
* !old_len is a special case where an attempt is made to 'duplicate'
|
||||
* a mapping. This makes no sense for private mappings as it will
|
||||
@@ -847,39 +854,53 @@ static struct vm_area_struct *vma_to_resize(unsigned long addr,
|
||||
*/
|
||||
if (!old_len && !(vma->vm_flags & (VM_SHARED | VM_MAYSHARE))) {
|
||||
pr_warn_once("%s (%d): attempted to duplicate a private mapping with mremap. This is not supported.\n", current->comm, current->pid);
|
||||
return ERR_PTR(-EINVAL);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if ((flags & MREMAP_DONTUNMAP) &&
|
||||
(vma->vm_flags & (VM_DONTEXPAND | VM_PFNMAP)))
|
||||
return ERR_PTR(-EINVAL);
|
||||
return -EINVAL;
|
||||
|
||||
/* We can't remap across vm area boundaries */
|
||||
if (old_len > vma->vm_end - addr)
|
||||
return ERR_PTR(-EFAULT);
|
||||
return -EFAULT;
|
||||
|
||||
if (new_len == old_len)
|
||||
return vma;
|
||||
return 0;
|
||||
|
||||
/* Need to be careful about a growing mapping */
|
||||
pgoff = (addr - vma->vm_start) >> PAGE_SHIFT;
|
||||
pgoff += vma->vm_pgoff;
|
||||
if (pgoff + (new_len >> PAGE_SHIFT) < pgoff)
|
||||
return ERR_PTR(-EINVAL);
|
||||
return -EINVAL;
|
||||
|
||||
if (vma->vm_flags & (VM_DONTEXPAND | VM_PFNMAP))
|
||||
return ERR_PTR(-EFAULT);
|
||||
return -EFAULT;
|
||||
|
||||
if (!mlock_future_ok(mm, vma->vm_flags, new_len - old_len))
|
||||
return ERR_PTR(-EAGAIN);
|
||||
return -EAGAIN;
|
||||
|
||||
if (!may_expand_vm(mm, vma->vm_flags,
|
||||
(new_len - old_len) >> PAGE_SHIFT))
|
||||
return ERR_PTR(-ENOMEM);
|
||||
return -ENOMEM;
|
||||
|
||||
return vma;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* mremap_to() - remap a vma to a new location
|
||||
* @addr: The old address
|
||||
* @old_len: The old size
|
||||
* @new_addr: The target address
|
||||
* @new_len: The new size
|
||||
* @locked: If the returned vma is locked (VM_LOCKED)
|
||||
* @flags: the mremap flags
|
||||
* @uf: The mremap userfaultfd context
|
||||
* @uf_unmap_early: The userfaultfd unmap early context
|
||||
* @uf_unmap: The userfaultfd unmap context
|
||||
*
|
||||
* Returns: The new address of the vma or an error.
|
||||
*/
|
||||
static unsigned long mremap_to(unsigned long addr, unsigned long old_len,
|
||||
unsigned long new_addr, unsigned long new_len, bool *locked,
|
||||
unsigned long flags, struct vm_userfaultfd_ctx *uf,
|
||||
@@ -888,18 +909,18 @@ static unsigned long mremap_to(unsigned long addr, unsigned long old_len,
|
||||
{
|
||||
struct mm_struct *mm = current->mm;
|
||||
struct vm_area_struct *vma;
|
||||
unsigned long ret = -EINVAL;
|
||||
unsigned long ret;
|
||||
unsigned long map_flags = 0;
|
||||
|
||||
if (offset_in_page(new_addr))
|
||||
goto out;
|
||||
return -EINVAL;
|
||||
|
||||
if (new_len > TASK_SIZE || new_addr > TASK_SIZE - new_len)
|
||||
goto out;
|
||||
return -EINVAL;
|
||||
|
||||
/* Ensure the old/new locations do not overlap */
|
||||
if (addr + old_len > new_addr && new_addr + new_len > addr)
|
||||
goto out;
|
||||
return -EINVAL;
|
||||
|
||||
/*
|
||||
* move_vma() need us to stay 4 maps below the threshold, otherwise
|
||||
@@ -926,27 +947,28 @@ static unsigned long mremap_to(unsigned long addr, unsigned long old_len,
|
||||
*/
|
||||
ret = do_munmap(mm, new_addr, new_len, uf_unmap_early);
|
||||
if (ret)
|
||||
goto out;
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (old_len > new_len) {
|
||||
ret = do_munmap(mm, addr+new_len, old_len - new_len, uf_unmap);
|
||||
if (ret)
|
||||
goto out;
|
||||
return ret;
|
||||
old_len = new_len;
|
||||
}
|
||||
|
||||
vma = vma_to_resize(addr, old_len, new_len, flags);
|
||||
if (IS_ERR(vma)) {
|
||||
ret = PTR_ERR(vma);
|
||||
goto out;
|
||||
}
|
||||
vma = vma_lookup(mm, addr);
|
||||
if (!vma)
|
||||
return -EFAULT;
|
||||
|
||||
ret = resize_is_valid(vma, addr, old_len, new_len, flags);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* MREMAP_DONTUNMAP expands by old_len since old_len == new_len */
|
||||
if (flags & MREMAP_DONTUNMAP &&
|
||||
!may_expand_vm(mm, vma->vm_flags, old_len >> PAGE_SHIFT)) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
if (flags & MREMAP_FIXED)
|
||||
@@ -959,17 +981,14 @@ static unsigned long mremap_to(unsigned long addr, unsigned long old_len,
|
||||
((addr - vma->vm_start) >> PAGE_SHIFT),
|
||||
map_flags);
|
||||
if (IS_ERR_VALUE(ret))
|
||||
goto out;
|
||||
return ret;
|
||||
|
||||
/* We got a new mapping */
|
||||
if (!(flags & MREMAP_FIXED))
|
||||
new_addr = ret;
|
||||
|
||||
ret = move_vma(vma, addr, old_len, new_len, new_addr, locked, flags, uf,
|
||||
uf_unmap);
|
||||
|
||||
out:
|
||||
return ret;
|
||||
return move_vma(vma, addr, old_len, new_len, new_addr, locked, flags,
|
||||
uf, uf_unmap);
|
||||
}
|
||||
|
||||
static int vma_expandable(struct vm_area_struct *vma, unsigned long delta)
|
||||
@@ -1114,11 +1133,9 @@ SYSCALL_DEFINE5(mremap, unsigned long, addr, unsigned long, old_len,
|
||||
/*
|
||||
* Ok, we need to grow..
|
||||
*/
|
||||
vma = vma_to_resize(addr, old_len, new_len, flags);
|
||||
if (IS_ERR(vma)) {
|
||||
ret = PTR_ERR(vma);
|
||||
ret = resize_is_valid(vma, addr, old_len, new_len, flags);
|
||||
if (ret)
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* old_len exactly to the end of the area..
|
||||
*/
|
||||
|
||||
+22
-3
@@ -917,7 +917,9 @@ static unsigned long __wb_calc_thresh(struct dirty_throttle_control *dtc,
|
||||
unsigned long thresh)
|
||||
{
|
||||
struct wb_domain *dom = dtc_dom(dtc);
|
||||
struct bdi_writeback *wb = dtc->wb;
|
||||
u64 wb_thresh;
|
||||
u64 wb_max_thresh;
|
||||
unsigned long numerator, denominator;
|
||||
unsigned long wb_min_ratio, wb_max_ratio;
|
||||
|
||||
@@ -931,11 +933,28 @@ static unsigned long __wb_calc_thresh(struct dirty_throttle_control *dtc,
|
||||
wb_thresh *= numerator;
|
||||
wb_thresh = div64_ul(wb_thresh, denominator);
|
||||
|
||||
wb_min_max_ratio(dtc->wb, &wb_min_ratio, &wb_max_ratio);
|
||||
wb_min_max_ratio(wb, &wb_min_ratio, &wb_max_ratio);
|
||||
|
||||
wb_thresh += (thresh * wb_min_ratio) / (100 * BDI_RATIO_SCALE);
|
||||
if (wb_thresh > (thresh * wb_max_ratio) / (100 * BDI_RATIO_SCALE))
|
||||
wb_thresh = thresh * wb_max_ratio / (100 * BDI_RATIO_SCALE);
|
||||
wb_max_thresh = thresh * wb_max_ratio / (100 * BDI_RATIO_SCALE);
|
||||
if (wb_thresh > wb_max_thresh)
|
||||
wb_thresh = wb_max_thresh;
|
||||
|
||||
/*
|
||||
* With strictlimit flag, the wb_thresh is treated as
|
||||
* a hard limit in balance_dirty_pages() and wb_position_ratio().
|
||||
* It's possible that wb_thresh is close to zero, not because
|
||||
* the device is slow, but because it has been inactive.
|
||||
* To prevent occasional writes from being blocked, we raise wb_thresh.
|
||||
*/
|
||||
if (unlikely(wb->bdi->capabilities & BDI_CAP_STRICTLIMIT)) {
|
||||
unsigned long limit = hard_dirty_limit(dom, dtc->thresh);
|
||||
u64 wb_scale_thresh = 0;
|
||||
|
||||
if (limit > dtc->dirty)
|
||||
wb_scale_thresh = (limit - dtc->dirty) / 100;
|
||||
wb_thresh = max(wb_thresh, min(wb_scale_thresh, wb_max_thresh / 4));
|
||||
}
|
||||
|
||||
return wb_thresh;
|
||||
}
|
||||
|
||||
+1
-1
@@ -5377,7 +5377,7 @@ void __ref build_all_zonelists(pg_data_t *pgdat)
|
||||
|
||||
pr_info("Built %u zonelists, mobility grouping %s. Total pages: %ld\n",
|
||||
nr_online_nodes,
|
||||
page_group_by_mobility_disabled ? "off" : "on",
|
||||
str_off_on(page_group_by_mobility_disabled),
|
||||
vm_total_pages);
|
||||
#ifdef CONFIG_NUMA
|
||||
pr_info("Policy zone: %s\n", zone_names[policy_zone]);
|
||||
|
||||
+63
-40
@@ -3147,27 +3147,19 @@ static ssize_t shmem_file_read_iter(struct kiocb *iocb, struct iov_iter *to)
|
||||
unsigned long offset;
|
||||
int error = 0;
|
||||
ssize_t retval = 0;
|
||||
loff_t *ppos = &iocb->ki_pos;
|
||||
|
||||
index = *ppos >> PAGE_SHIFT;
|
||||
offset = *ppos & ~PAGE_MASK;
|
||||
|
||||
for (;;) {
|
||||
struct folio *folio = NULL;
|
||||
struct page *page = NULL;
|
||||
pgoff_t end_index;
|
||||
unsigned long nr, ret;
|
||||
loff_t i_size = i_size_read(inode);
|
||||
loff_t end_offset, i_size = i_size_read(inode);
|
||||
bool fallback_page_copy = false;
|
||||
size_t fsize;
|
||||
|
||||
end_index = i_size >> PAGE_SHIFT;
|
||||
if (index > end_index)
|
||||
if (unlikely(iocb->ki_pos >= i_size))
|
||||
break;
|
||||
if (index == end_index) {
|
||||
nr = i_size & ~PAGE_MASK;
|
||||
if (nr <= offset)
|
||||
break;
|
||||
}
|
||||
|
||||
index = iocb->ki_pos >> PAGE_SHIFT;
|
||||
error = shmem_get_folio(inode, index, 0, &folio, SGP_READ);
|
||||
if (error) {
|
||||
if (error == -EINVAL)
|
||||
@@ -3183,24 +3175,29 @@ static ssize_t shmem_file_read_iter(struct kiocb *iocb, struct iov_iter *to)
|
||||
error = -EIO;
|
||||
break;
|
||||
}
|
||||
|
||||
if (folio_test_large(folio) &&
|
||||
folio_test_has_hwpoisoned(folio))
|
||||
fallback_page_copy = true;
|
||||
}
|
||||
|
||||
/*
|
||||
* We must evaluate after, since reads (unlike writes)
|
||||
* are called without i_rwsem protection against truncate
|
||||
*/
|
||||
nr = PAGE_SIZE;
|
||||
i_size = i_size_read(inode);
|
||||
end_index = i_size >> PAGE_SHIFT;
|
||||
if (index == end_index) {
|
||||
nr = i_size & ~PAGE_MASK;
|
||||
if (nr <= offset) {
|
||||
if (folio)
|
||||
folio_put(folio);
|
||||
break;
|
||||
}
|
||||
if (unlikely(iocb->ki_pos >= i_size)) {
|
||||
if (folio)
|
||||
folio_put(folio);
|
||||
break;
|
||||
}
|
||||
nr -= offset;
|
||||
end_offset = min_t(loff_t, i_size, iocb->ki_pos + to->count);
|
||||
if (folio && likely(!fallback_page_copy))
|
||||
fsize = folio_size(folio);
|
||||
else
|
||||
fsize = PAGE_SIZE;
|
||||
offset = iocb->ki_pos & (fsize - 1);
|
||||
nr = min_t(loff_t, end_offset - iocb->ki_pos, fsize - offset);
|
||||
|
||||
if (folio) {
|
||||
/*
|
||||
@@ -3208,10 +3205,15 @@ static ssize_t shmem_file_read_iter(struct kiocb *iocb, struct iov_iter *to)
|
||||
* virtual addresses, take care about potential aliasing
|
||||
* before reading the page on the kernel side.
|
||||
*/
|
||||
if (mapping_writably_mapped(mapping))
|
||||
flush_dcache_page(page);
|
||||
if (mapping_writably_mapped(mapping)) {
|
||||
if (likely(!fallback_page_copy))
|
||||
flush_dcache_folio(folio);
|
||||
else
|
||||
flush_dcache_page(page);
|
||||
}
|
||||
|
||||
/*
|
||||
* Mark the page accessed if we read the beginning.
|
||||
* Mark the folio accessed if we read the beginning.
|
||||
*/
|
||||
if (!offset)
|
||||
folio_mark_accessed(folio);
|
||||
@@ -3219,9 +3221,11 @@ static ssize_t shmem_file_read_iter(struct kiocb *iocb, struct iov_iter *to)
|
||||
* Ok, we have the page, and it's up-to-date, so
|
||||
* now we can copy it to user space...
|
||||
*/
|
||||
ret = copy_page_to_iter(page, offset, nr, to);
|
||||
if (likely(!fallback_page_copy))
|
||||
ret = copy_folio_to_iter(folio, offset, nr, to);
|
||||
else
|
||||
ret = copy_page_to_iter(page, offset, nr, to);
|
||||
folio_put(folio);
|
||||
|
||||
} else if (user_backed_iter(to)) {
|
||||
/*
|
||||
* Copy to user tends to be so well optimized, but
|
||||
@@ -3239,9 +3243,7 @@ static ssize_t shmem_file_read_iter(struct kiocb *iocb, struct iov_iter *to)
|
||||
}
|
||||
|
||||
retval += ret;
|
||||
offset += ret;
|
||||
index += offset >> PAGE_SHIFT;
|
||||
offset &= ~PAGE_MASK;
|
||||
iocb->ki_pos += ret;
|
||||
|
||||
if (!iov_iter_count(to))
|
||||
break;
|
||||
@@ -3252,7 +3254,6 @@ static ssize_t shmem_file_read_iter(struct kiocb *iocb, struct iov_iter *to)
|
||||
cond_resched();
|
||||
}
|
||||
|
||||
*ppos = ((loff_t) index << PAGE_SHIFT) + offset;
|
||||
file_accessed(file);
|
||||
return retval ? retval : error;
|
||||
}
|
||||
@@ -3341,11 +3342,16 @@ static ssize_t shmem_file_splice_read(struct file *in, loff_t *ppos,
|
||||
len = min_t(size_t, len, npages * PAGE_SIZE);
|
||||
|
||||
do {
|
||||
bool fallback_page_splice = false;
|
||||
struct page *page = NULL;
|
||||
pgoff_t index;
|
||||
size_t size;
|
||||
|
||||
if (*ppos >= i_size_read(inode))
|
||||
break;
|
||||
|
||||
error = shmem_get_folio(inode, *ppos / PAGE_SIZE, 0, &folio,
|
||||
SGP_READ);
|
||||
index = *ppos >> PAGE_SHIFT;
|
||||
error = shmem_get_folio(inode, index, 0, &folio, SGP_READ);
|
||||
if (error) {
|
||||
if (error == -EINVAL)
|
||||
error = 0;
|
||||
@@ -3354,12 +3360,15 @@ static ssize_t shmem_file_splice_read(struct file *in, loff_t *ppos,
|
||||
if (folio) {
|
||||
folio_unlock(folio);
|
||||
|
||||
if (folio_test_hwpoison(folio) ||
|
||||
(folio_test_large(folio) &&
|
||||
folio_test_has_hwpoisoned(folio))) {
|
||||
page = folio_file_page(folio, index);
|
||||
if (PageHWPoison(page)) {
|
||||
error = -EIO;
|
||||
break;
|
||||
}
|
||||
|
||||
if (folio_test_large(folio) &&
|
||||
folio_test_has_hwpoisoned(folio))
|
||||
fallback_page_splice = true;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -3373,7 +3382,17 @@ static ssize_t shmem_file_splice_read(struct file *in, loff_t *ppos,
|
||||
isize = i_size_read(inode);
|
||||
if (unlikely(*ppos >= isize))
|
||||
break;
|
||||
part = min_t(loff_t, isize - *ppos, len);
|
||||
/*
|
||||
* Fallback to PAGE_SIZE splice if the large folio has hwpoisoned
|
||||
* pages.
|
||||
*/
|
||||
size = len;
|
||||
if (unlikely(fallback_page_splice)) {
|
||||
size_t offset = *ppos & ~PAGE_MASK;
|
||||
|
||||
size = umin(size, PAGE_SIZE - offset);
|
||||
}
|
||||
part = min_t(loff_t, isize - *ppos, size);
|
||||
|
||||
if (folio) {
|
||||
/*
|
||||
@@ -3381,8 +3400,12 @@ static ssize_t shmem_file_splice_read(struct file *in, loff_t *ppos,
|
||||
* virtual addresses, take care about potential aliasing
|
||||
* before reading the page on the kernel side.
|
||||
*/
|
||||
if (mapping_writably_mapped(mapping))
|
||||
flush_dcache_folio(folio);
|
||||
if (mapping_writably_mapped(mapping)) {
|
||||
if (likely(!fallback_page_splice))
|
||||
flush_dcache_folio(folio);
|
||||
else
|
||||
flush_dcache_page(page);
|
||||
}
|
||||
folio_mark_accessed(folio);
|
||||
/*
|
||||
* Ok, we have the page, and it's up-to-date, so we can
|
||||
|
||||
@@ -377,6 +377,7 @@ static char *get_comm(char *buf)
|
||||
if (errno != 0) {
|
||||
if (debug_on)
|
||||
fprintf(stderr, "wrong comm in follow buf:\n%s\n", buf);
|
||||
free(comm_str);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user