Charge loop device i/o to issuing cgroup
Subsystems (6)
v14 cover Charge loop device i/o to issuing cgroup
Rebased on top of mm-tree
As per the suggestion last time [1], could this go through the -mm tree to deal with memcg conflicts?
[1] https://lore.kernel.org/lkml/CALvZod6FMQQC17Zsu9xoKs=dFWaJdMC2Qk3YiDPUUQHx8teLYg@mail.gmail.com/
Changes since V13:
* Small change to get_mem_cgroup_from_mm to return early when the return is root memcg
* Avoid allocating loop worker if the issuing blkcg is root_blkcg
Changes since V12:
* Small change to get_mem_cgroup_from_mm to avoid needing get_active_memcg
Changes since V11:
* Removed WQ_MEM_RECLAIM flag from loop workqueue. Technically, this can be driven by writeback, but this was causing a warning in xfs and likely other filesystems aren't equipped to be driven by reclaim at the VFS layer.
* Included a small fix from Colin Ian King.
* reworked get_mem_cgroup_from_mm to institute the necessary charge priority.
Changes since V10:
* Added page-cache charging to mm: Charge active memcg when no mm is set
Changes since V9:
* Rebased against linus's branch which now includes Roman Gushchin's patch this series is based off of
Changes since V8:
* Rebased on top of Roman Gushchin's patch
(https://lkml.org/lkml/2020/8/21/1464) which provides the nesting support for setting active memcg. Dropped the patch from this series that did the same thing.
Changes since V7:
* Rebased against linus's branch
Changes since V6:
* Added separate spinlock for worker synchronization
* Minor style changes
Changes since V5:
* Fixed a missing css_put when failing to allocate a worker
* Minor style changes
Changes since V4:
Only patches 1 and 2 have changed.
* Fixed irq lock ordering bug
* Simplified loop detach
* Added support for nesting memalloc_use_memcg
Changes since V3:
* Fix race on loop device destruction and deferred worker cleanup
* Ensure charge on shmem_swapin_page works just like getpage
* Minor style changes
Changes since V2:
* Deferred destruction of workqueue items so in the common case there is no allocation needed
Changes since V1:
* Split out and reordered patches so cgroup charging changes are separate from kworker -> workqueue change
* Add mem_css to struct loop_cmd to simplify logic
The loop device runs all i/o to the backing file on a separate kworker thread which results in all i/o being charged to the root cgroup. This allows a loop device to be used to trivially bypass resource limits and other policy. This patch series fixes this gap in accounting.
A simple script to demonstrate this behavior on cgroupv2 machine:
'''
#!/bin/bash
set -e
CGROUP=/sys/fs/cgroup/test.slice
LOOP_DEV=/dev/loop0
if [[ ! -d $CGROUP ]]
then
sudo mkdir $CGROUP
fi
grep oom_kill $CGROUP/memory.events
# Set a memory limit, write more than that limit to tmpfs -> OOM kill sudo unshare -m bash -c "
echo \$\$ > $CGROUP/cgroup.procs;
echo 0 > $CGROUP/memory.swap.max;
echo 64M > $CGROUP/memory.max;
mount -t tmpfs -o size=512m tmpfs /tmp;
dd if=/dev/zero of=/tmp/file bs=1M count=256" || true
grep oom_kill $CGROUP/memory.events
# Set a memory limit, write more than that limit through loopback
# device -> no OOM kill sudo unshare -m bash -c " echo \$\$ > $CGROUP/cgroup.procs; echo 0 > $CGROUP/memory.swap.max; echo 64M > $CGROUP/memory.max; mount -t tmpfs -o size=512m tmpfs /tmp; truncate -s 512m /tmp/backing_file losetup $LOOP_DEV /tmp/backing_file dd if=/dev/zero of=$LOOP_DEV bs=1M count=256; losetup -D $LOOP_DEV" || true
grep oom_kill $CGROUP/memory.events
'''
Naively charging cgroups could result in priority inversions through the single kworker thread in the case where multiple cgroups are reading/writing to the same loop device. This patch series does some minor modification to the loop driver so that each cgroup can make forward progress independently to avoid this inversion.
With this patch series applied, the above script triggers OOM kills when writing through the loop device as expected.
Dan Schatzberg (3):
loop: Use worker per cgroup instead of kworker
mm: Charge active memcg when no mm is set
loop: Charge i/o to mem and blk cg
drivers/block/loop.c | 250 +++++++++++++++++++++++++++++++------ drivers/block/loop.h | 15 ++- include/linux/memcontrol.h | 6 + kernel/cgroup/cgroup.c | 1 + mm/filemap.c | 2 +- mm/memcontrol.c | 42 ++++--- mm/shmem.c | 4 +- 7 files changed, 258 insertions(+), 62 deletions(-)
Signature · 1 line
2.30.2
PATCH v14 1/3 loop: Use worker per cgroup instead of kworker
Existing uses of loop device may have multiple cgroups reading/writing to the same device. Simply charging resources for I/O to the backing file could result in priority inversion where one cgroup gets synchronously blocked, holding up all other I/O to the loop device.
In order to avoid this priority inversion, we use a single workqueue where each work item is a "struct loop_worker" which contains a queue of struct loop_cmds to issue. The loop device maintains a tree mapping blk css_id -> loop_worker. This allows each cgroup to independently make forward progress issuing I/O to the backing file.
There is also a single queue for I/O associated with the rootcg which can be used in cases of extreme memory shortage where we cannot allocate a loop_worker.
The locking for the tree and queues is fairly heavy handed - we acquire a per-loop-device spinlock any time either is accessed. The existing implementation serializes all I/O through a single thread anyways, so I don't believe this is any worse.
Fixes-from: Colin Ian King <[email protected]>
--- drivers/block/loop.c | 209 ++++++++++++++++++++++++++++++++++++------- drivers/block/loop.h | 12 ++- 2 files changed, 187 insertions(+), 34 deletions(-)
drivers/block/loop.c +179 -30
@@ -71,7 +71,6 @@#include <linux/writeback.h> #include <linux/completion.h> #include <linux/highmem.h> -#include <linux/kthread.h> #include <linux/splice.h> #include <linux/sysfs.h> #include <linux/miscdevice.h>
@@ -84,6 +83,8 @@#include <linux/uaccess.h> +#define LOOP_IDLE_WORKER_TIMEOUT (60 * HZ) +static DEFINE_IDR (loop_index_idr);static DEFINE_MUTEX (loop_ctl_mutex);
@@ -921,27 +922,95 @@ static void loop_config_discard(struct loop_device *lo) q->limits.discard_alignment =0 ; } -static void loop_unprepare_queue (struct loop_device *lo) +struct loop_worker { +struct rb_node rb_node; +struct work_struct work; +struct list_head cmd_list; +struct list_head idle_list; +struct loop_device *lo; +struct cgroup_subsys_state *css; +unsigned long last_ran_at; +}; + +static void loop_workfn (struct work_struct *work); +static void loop_rootcg_workfn (struct work_struct *work); +static void loop_free_idle_workers (struct timer_list *timer); + +#ifdef CONFIG_BLK_CGROUP +static inline int queue_on_root_worker (struct cgroup_subsys_state *css) { -kthread_flush_worker (&lo->worker); -kthread_stop (lo->worker_task); +return !css || css == blkcg_root_css; } - -static int loop_kthread_worker_fn (void *worker_ptr) +#else +static inline int queue_on_root_worker (struct cgroup_subsys_state *css) { - current->flags |=PF_LOCAL_THROTTLE |PF_MEMALLOC_NOIO ; -return kthread_worker_fn (worker_ptr); +return !css; } +#endif -static int loop_prepare_queue (struct loop_device *lo) +static void loop_queue_work (struct loop_device *lo,struct loop_cmd *cmd) { -kthread_init_worker (&lo->worker); - lo->worker_task =kthread_run (loop_kthread_worker_fn, - &lo->worker,"loop%d" , lo->lo_number); -if (IS_ERR (lo->worker_task)) -return -ENOMEM ; -set_user_nice (lo->worker_task,MIN_NICE ); -return 0 ; +struct rb_node **node = &(lo->worker_tree.rb_node), *parent =NULL ; +struct loop_worker *cur_worker, *worker =NULL ; +struct work_struct *work; +struct list_head *cmd_list; + +spin_lock_irq (&lo->lo_work_lock); + +if (queue_on_root_worker (cmd->css)) + goto queue_work; + + node = &lo->worker_tree.rb_node; + +while (*node) { + parent = *node; + cur_worker = container_of(*node, struct loop_worker, rb_node); +if (cur_worker->css == cmd->css) { + worker = cur_worker; +break ; + }else if ((long )cur_worker->css < (long )cmd->css) { + node = &(*node)->rb_left; + }else { + node = &(*node)->rb_right; + } + } +if (worker) + goto queue_work; + + worker =kzalloc (sizeof (struct loop_worker ),GFP_NOWAIT | __GFP_NOWARN); +/* +* In the event we cannot allocate a worker, just queue on the +* rootcg worker +*/ +if (!worker) + goto queue_work; + + worker->css = cmd->css; +css_get (worker->css); +INIT_WORK (&worker->work, loop_workfn); +INIT_LIST_HEAD (&worker->cmd_list); +INIT_LIST_HEAD (&worker->idle_list); + worker->lo = lo; +rb_link_node (&worker->rb_node, parent, node); +rb_insert_color (&worker->rb_node, &lo->worker_tree); +queue_work: +if (worker) { +/* +* We need to remove from the idle list here while +* holding the lock so that the idle timer doesn't +* free the worker +*/ +if (!list_empty (&worker->idle_list)) +list_del_init (&worker->idle_list); + work = &worker->work; + cmd_list = &worker->cmd_list; + }else { + work = &lo->rootcg_work; + cmd_list = &lo->rootcg_cmd_list; + } +list_add_tail (&cmd->list_entry, cmd_list); +queue_work (lo->workqueue, work); +spin_unlock_irq (&lo->lo_work_lock); }static void loop_update_rotational (struct loop_device *lo)
@@ -1127,12 +1196,23 @@ static int loop_configure(struct loop_device *lo, fmode_t mode, !file->f_op->write_iter) lo->lo_flags |=LO_FLAGS_READ_ONLY ; - error =loop_prepare_queue (lo); -if (error) + lo->workqueue =alloc_workqueue ("loop%d" , +WQ_UNBOUND |WQ_FREEZABLE , +0 , + lo->lo_number); +if (!lo->workqueue) { + error = -ENOMEM ; goto out_unlock; + }set_disk_ro (lo->lo_disk, (lo->lo_flags &LO_FLAGS_READ_ONLY ) !=0 ); +INIT_WORK (&lo->rootcg_work, loop_rootcg_workfn); +INIT_LIST_HEAD (&lo->rootcg_cmd_list); +INIT_LIST_HEAD (&lo->idle_worker_list); + lo->worker_tree =RB_ROOT ; +timer_setup (&lo->timer, loop_free_idle_workers, +TIMER_DEFERRABLE ); lo->use_dio = lo->lo_flags &LO_FLAGS_DIRECT_IO ; lo->lo_device = bdev; lo->lo_backing_file = file;
@@ -1200,6 +1280,7 @@ static int __loop_clr_fd(struct loop_device *lo, bool release)int err =0 ;bool partscan = false;int lo_number; +struct loop_worker *pos, *worker;mutex_lock (&lo->lo_mutex);if (WARN_ON_ONCE (lo->lo_state != Lo_rundown)) {
@@ -1219,6 +1300,18 @@ static int __loop_clr_fd(struct loop_device *lo, bool release)/* freeze request queue during the transition */ blk_mq_freeze_queue (lo->lo_queue); +destroy_workqueue (lo->workqueue); +spin_lock_irq (&lo->lo_work_lock); +list_for_each_entry_safe (worker, pos, &lo->idle_worker_list, + idle_list) { +list_del (&worker->idle_list); +rb_erase (&worker->rb_node, &lo->worker_tree); +css_put (worker->css); +kfree (worker); + } +spin_unlock_irq (&lo->lo_work_lock); +del_timer_sync (&lo->timer); +spin_lock_irq (&lo->lo_lock); lo->lo_backing_file =NULL ;spin_unlock_irq (&lo->lo_lock);
@@ -1255,7 +1348,6 @@ static int __loop_clr_fd(struct loop_device *lo, bool release) partscan = lo->lo_flags &LO_FLAGS_PARTSCAN && bdev; lo_number = lo->lo_number; -loop_unprepare_queue (lo); out_unlock:mutex_unlock (&lo->lo_mutex);if (partscan) {
@@ -2026,7 +2118,7 @@ static blk_status_t loop_queue_rq(struct blk_mq_hw_ctx *hctx, } else#endif cmd->css =NULL ; -kthread_queue_work (&lo->worker, &cmd->work); +loop_queue_work (lo, cmd);return BLK_STS_OK ; }
@@ -2056,26 +2148,82 @@ static void loop_handle_cmd(struct loop_cmd *cmd) } } -static void loop_queue_work (struct kthread_work *work) +static void loop_set_timer (struct loop_device *lo) +{ +timer_reduce (&lo->timer, jiffies +LOOP_IDLE_WORKER_TIMEOUT ); +} + +static void loop_process_work (struct loop_worker *worker, +struct list_head *cmd_list,struct loop_device *lo) { -struct loop_cmd *cmd = - container_of(work, struct loop_cmd, work); +int orig_flags = current->flags; +struct loop_cmd *cmd; -loop_handle_cmd (cmd); + current->flags |=PF_LOCAL_THROTTLE |PF_MEMALLOC_NOIO ; +spin_lock_irq (&lo->lo_work_lock); +while (!list_empty (cmd_list)) { + cmd =container_of ( + cmd_list->next, struct loop_cmd, list_entry); +list_del (cmd_list->next); +spin_unlock_irq (&lo->lo_work_lock); + +loop_handle_cmd (cmd); +cond_resched (); + +spin_lock_irq (&lo->lo_work_lock); + } + +/* +* We only add to the idle list if there are no pending cmds +* *and* the worker will not run again which ensures that it +* is safe to free any worker on the idle list +*/ +if (worker && !work_pending (&worker->work)) { + worker->last_ran_at = jiffies; +list_add_tail (&worker->idle_list, &lo->idle_worker_list); +loop_set_timer (lo); + } +spin_unlock_irq (&lo->lo_work_lock); + current->flags = orig_flags; } -static int loop_init_request (struct blk_mq_tag_set *set,struct request *rq, -unsigned int hctx_idx,unsigned int numa_node) +static void loop_workfn (struct work_struct *work) { -struct loop_cmd *cmd =blk_mq_rq_to_pdu (rq); +struct loop_worker *worker = + container_of(work, struct loop_worker, work); +loop_process_work (worker, &worker->cmd_list, worker->lo); +} -kthread_init_work (&cmd->work, loop_queue_work); -return 0 ; +static void loop_rootcg_workfn (struct work_struct *work) +{ +struct loop_device *lo = + container_of(work, struct loop_device, rootcg_work); +loop_process_work (NULL , &lo->rootcg_cmd_list, lo); +} + +static void loop_free_idle_workers (struct timer_list *timer) +{ + struct loop_device *lo = container_of(timer, struct loop_device, timer); +struct loop_worker *pos, *worker; + +spin_lock_irq (&lo->lo_work_lock); +list_for_each_entry_safe (worker, pos, &lo->idle_worker_list, + idle_list) { +if (time_is_after_jiffies (worker->last_ran_at + +LOOP_IDLE_WORKER_TIMEOUT )) +break ; +list_del (&worker->idle_list); +rb_erase (&worker->rb_node, &lo->worker_tree); +css_put (worker->css); +kfree (worker); + } +if (!list_empty (&lo->idle_worker_list)) +loop_set_timer (lo); +spin_unlock_irq (&lo->lo_work_lock); }static const struct blk_mq_ops loop_mq_ops = { .queue_rq = loop_queue_rq, - .init_request = loop_init_request, .complete = lo_complete_rq, };
@@ -2164,6 +2312,7 @@ static int loop_add(struct loop_device **l, int i)mutex_init (&lo->lo_mutex); lo->lo_number = i;spin_lock_init (&lo->lo_lock); +spin_lock_init (&lo->lo_work_lock); disk->major =LOOP_MAJOR ; disk->first_minor = i << part_shift; disk->fops = &lo_fops;
drivers/block/loop.h +8 -4
@@ -14,7 +14,6 @@#include <linux/blk-mq.h> #include <linux/spinlock.h> #include <linux/mutex.h> -#include <linux/kthread.h> #include <uapi/linux/loop.h> /* Possible states of device */
@@ -54,8 +53,13 @@ struct loop_device {spinlock_t lo_lock;int lo_state; -struct kthread_worker worker; -struct task_struct *worker_task; +spinlock_t lo_work_lock; +struct workqueue_struct *workqueue; +struct work_struct rootcg_work; +struct list_head rootcg_cmd_list; +struct list_head idle_worker_list; +struct rb_root worker_tree; +struct timer_list timer;bool use_dio;bool sysfs_inited;
@@ -66,7 +70,7 @@ struct loop_device { };struct loop_cmd { -struct kthread_work work; +struct list_head list_entry;bool use_aio;/* use AIO interface to handle I/O */ atomic_t ref;/* only for aio */ long ret;
Signature · 1 line
2.30.2
PATCH v14 2/3 mm: Charge active memcg when no mm is set
In reply to Dan Schatzbergset_active_memcg() worked for kernel allocations but was silently ignored for user pages.
This patch establishes a precedence order for who gets charged:
1. If there is a memcg associated with the page already, that memcg is charged. This happens during swapin.
2. If an explicit mm is passed, mm->memcg is charged. This happens during page faults, which can be triggered in remote VMs (eg gup).
3. Otherwise consult the current process context. If there is an active_memcg, use that. Otherwise, current->mm->memcg.
Previously, if a NULL mm was passed to mem_cgroup_charge (case 3) it would always charge the root cgroup. Now it looks up the active_memcg first (falling back to charging the root cgroup if not set).
--- mm/filemap.c | 2 +- mm/memcontrol.c | 41 +++++++++++++++++++++++++++-------------- mm/shmem.c | 4 ++-- 3 files changed, 30 insertions(+), 17 deletions(-)
mm/filemap.c +1 -1
@@ -872,7 +872,7 @@ noinline int __add_to_page_cache_locked(struct page *page, page->index = offset;if (!huge) { - error =mem_cgroup_charge (page, current->mm, gfp); + error =mem_cgroup_charge (page,NULL , gfp);if (error) goto error; charged = true;
mm/memcontrol.c +27 -14
69 lines not on this page · raw message
mm/shmem.c +2 -2
@@ -1695,7 +1695,7 @@ static int shmem_swapin_page(struct inode *inode, pgoff_t index, {struct address_space *mapping = inode->i_mapping;struct shmem_inode_info *info =SHMEM_I (inode); -struct mm_struct *charge_mm = vma ? vma->vm_mm : current->mm; +struct mm_struct *charge_mm = vma ? vma->vm_mm :NULL ;struct swap_info_struct *si;struct page *page =NULL ;swp_entry_t swap;
@@ -1828,7 +1828,7 @@ static int shmem_getpage_gfp(struct inode *inode, pgoff_t index, } sbinfo =SHMEM_SB (inode->i_sb); - charge_mm = vma ? vma->vm_mm : current->mm; + charge_mm = vma ? vma->vm_mm :NULL ; page =pagecache_get_page (mapping, index,FGP_ENTRY |FGP_HEAD |FGP_LOCK ,0 );
Signature · 1 line
2.30.2
Re: [PATCH 2/3] mm: Charge active memcg when no mm is set
Quoted 7 lines · Dan Schatzberg
mm/memcontrol.c +1
-2@@ -926,8 +937,17 @@ struct mem_cgroup *get_mem_cgroup_from_mm(struct mm_struct *mm)* counting is disabled on the root level in the * cgroup core. See CSS_NO_REF. */ -if (unlikely (!mm)) -return root_mem_cgroup; +if (unlikely (!mm)) {
mm/memcontrol.c struct mem_cgroup *get_mem_cgroup_from_mm(struct mm_struct *mm) · line 950
Quoted 10 lines
mm/memcontrol.c +10
-0+ memcg =active_memcg (); +if (unlikely (memcg)) { +/* remote memcg must hold a ref */ +css_get (&memcg->css); +return memcg; + } + mm = current->mm; +if (unlikely (!mm)) +return root_mem_cgroup; + }
With the change in __add_to_page_cache_locked() all page cache charges will supply null mm, so the first !mm unlikely hint may not be warranted anymore. Just an interesting point, generally, I'm adding
PATCH v14 3/3 loop: Charge i/o to mem and blk cg
In reply to Dan SchatzbergThe current code only associates with the existing blkcg when aio is used to access the backing file. This patch covers all types of i/o to the backing file and also associates the memcg so if the backing file is on tmpfs, memory is charged appropriately.
This patch also exports cgroup_get_e_css and int_active_memcg so it can be used by the loop module.
--- drivers/block/loop.c | 61 +++++++++++++++++++++++++------------- drivers/block/loop.h | 3 +- include/linux/memcontrol.h | 6 ++++ kernel/cgroup/cgroup.c | 1 + mm/memcontrol.c | 1 + 5 files changed, 51 insertions(+), 21 deletions(-)
drivers/block/loop.c +41 -20
@@ -78,6 +78,7 @@#include <linux/uio.h> #include <linux/ioprio.h> #include <linux/blk-cgroup.h> +#include <linux/sched/mm.h> #include "loop.h"
@@ -516,8 +517,6 @@ static void lo_rw_aio_complete(struct kiocb *iocb, long ret, long ret2) { struct loop_cmd *cmd = container_of(iocb, struct loop_cmd, iocb); -if (cmd->css) -css_put (cmd->css); cmd->ret = ret;lo_rw_aio_do_completion (cmd); }
@@ -578,8 +577,6 @@ static int lo_rw_aio(struct loop_device *lo, struct loop_cmd *cmd, cmd->iocb.ki_complete = lo_rw_aio_complete; cmd->iocb.ki_flags =IOCB_DIRECT ; cmd->iocb.ki_ioprio =IOPRIO_PRIO_VALUE (IOPRIO_CLASS_NONE ,0 ); -if (cmd->css) -kthread_associate_blkcg (cmd->css);if (rw ==WRITE ) ret =call_write_iter (file, &cmd->iocb, &iter);
@@ -587,7 +584,6 @@ static int lo_rw_aio(struct loop_device *lo, struct loop_cmd *cmd, ret =call_read_iter (file, &cmd->iocb, &iter);lo_rw_aio_do_completion (cmd); -kthread_associate_blkcg (NULL );if (ret != -EIOCBQUEUED ) cmd->iocb.ki_complete (&cmd->iocb, ret,0 );
@@ -928,7 +924,7 @@ struct loop_worker {struct list_head cmd_list;struct list_head idle_list;struct loop_device *lo; -struct cgroup_subsys_state *css; +struct cgroup_subsys_state *blkcg_css;unsigned long last_ran_at; };
@@ -957,7 +953,7 @@ static void loop_queue_work(struct loop_device *lo, struct loop_cmd *cmd)spin_lock_irq (&lo->lo_work_lock); -if (queue_on_root_worker (cmd->css)) +if (queue_on_root_worker (cmd->blkcg_css)) goto queue_work; node = &lo->worker_tree.rb_node;
@@ -965,10 +961,10 @@ static void loop_queue_work(struct loop_device *lo, struct loop_cmd *cmd)while (*node) { parent = *node; cur_worker = container_of(*node, struct loop_worker, rb_node); -if (cur_worker->css == cmd->css) { +if (cur_worker->blkcg_css == cmd->blkcg_css) { worker = cur_worker;break ; - }else if ((long )cur_worker->css < (long )cmd->css) { + }else if ((long )cur_worker->blkcg_css < (long )cmd->blkcg_css) { node = &(*node)->rb_left; }else { node = &(*node)->rb_right;
@@ -980,13 +976,18 @@ static void loop_queue_work(struct loop_device *lo, struct loop_cmd *cmd) worker =kzalloc (sizeof (struct loop_worker ),GFP_NOWAIT | __GFP_NOWARN);/* * In the event we cannot allocate a worker, just queue on the -* rootcg worker +* rootcg worker and issue the I/O as the rootcg */ -if (!worker) +if (!worker) { + cmd->blkcg_css =NULL ; +if (cmd->memcg_css) +css_put (cmd->memcg_css); + cmd->memcg_css =NULL ; goto queue_work; + } - worker->css = cmd->css; -css_get (worker->css); + worker->blkcg_css = cmd->blkcg_css; +css_get (worker->blkcg_css);INIT_WORK (&worker->work, loop_workfn);INIT_LIST_HEAD (&worker->cmd_list);INIT_LIST_HEAD (&worker->idle_list);
@@ -1306,7 +1307,7 @@ static int __loop_clr_fd(struct loop_device *lo, bool release) idle_list) {list_del (&worker->idle_list);rb_erase (&worker->rb_node, &lo->worker_tree); -css_put (worker->css); +css_put (worker->blkcg_css);kfree (worker); }spin_unlock_irq (&lo->lo_work_lock);
@@ -2111,13 +2112,18 @@ static blk_status_t loop_queue_rq(struct blk_mq_hw_ctx *hctx, }/* always use the first bio's css */ + cmd->blkcg_css =NULL ; + cmd->memcg_css =NULL ;#ifdef CONFIG_BLK_CGROUP -if (cmd->use_aio && rq->bio && rq->bio->bi_blkg) { - cmd->css = &bio_blkcg (rq->bio)->css; -css_get (cmd->css); - } else +if (rq->bio && rq->bio->bi_blkg) { + cmd->blkcg_css = &bio_blkcg (rq->bio)->css; +#ifdef CONFIG_MEMCG + cmd->memcg_css = +cgroup_get_e_css (cmd->blkcg_css->cgroup, + &memory_cgrp_subsys); +#endif + }#endif - cmd->css =NULL ;loop_queue_work (lo, cmd);return BLK_STS_OK ;
@@ -2129,13 +2135,28 @@ static void loop_handle_cmd(struct loop_cmd *cmd)const bool write =op_is_write (req_op (rq));struct loop_device *lo = rq->q->queuedata;int ret =0 ; +struct mem_cgroup *old_memcg =NULL ;if (write && (lo->lo_flags &LO_FLAGS_READ_ONLY )) { ret = -EIO ; goto failed; } +if (cmd->blkcg_css) +kthread_associate_blkcg (cmd->blkcg_css); +if (cmd->memcg_css) + old_memcg =set_active_memcg ( +mem_cgroup_from_css (cmd->memcg_css)); + ret =do_req_filebacked (lo, rq); + +if (cmd->blkcg_css) +kthread_associate_blkcg (NULL ); + +if (cmd->memcg_css) { +set_active_memcg (old_memcg); +css_put (cmd->memcg_css); + } failed:/* complete non-aio request */ if (!cmd->use_aio || ret) {
@@ -2214,7 +2235,7 @@ static void loop_free_idle_workers(struct timer_list *timer)break ;list_del (&worker->idle_list);rb_erase (&worker->rb_node, &lo->worker_tree); -css_put (worker->css); +css_put (worker->blkcg_css);kfree (worker); }if (!list_empty (&lo->idle_worker_list))
drivers/block/loop.h +2 -1
@@ -76,7 +76,8 @@ struct loop_cmd {long ret;struct kiocb iocb;struct bio_vec *bvec; -struct cgroup_subsys_state *css; +struct cgroup_subsys_state *blkcg_css; +struct cgroup_subsys_state *memcg_css; };/* Support for loadable transfer modules */
include/linux/memcontrol.h +6 -0
@@ -1230,6 +1230,12 @@ static inline struct mem_cgroup *get_mem_cgroup_from_mm(struct mm_struct *mm)return NULL ; } +static inline +struct mem_cgroup *mem_cgroup_from_css (struct cgroup_subsys_state *css) +{ +return NULL ; +} +static inline void mem_cgroup_put (struct mem_cgroup *memcg) { }
kernel/cgroup/cgroup.c +1 -0
@@ -577,6 +577,7 @@ struct cgroup_subsys_state *cgroup_get_e_css(struct cgroup *cgrp,rcu_read_unlock ();return css; } +EXPORT_SYMBOL_GPL (cgroup_get_e_css);static void cgroup_get_live (struct cgroup *cgrp) {
mm/memcontrol.c +1 -0
@@ -78,6 +78,7 @@ struct mem_cgroup *root_mem_cgroup __read_mostly;/* Active memory cgroup to use from an interrupt context */ DEFINE_PER_CPU(struct mem_cgroup *, int_active_memcg); +EXPORT_PER_CPU_SYMBOL_GPL (int_active_memcg);/* Socket memory accounting disabled? */ static bool cgroup_memory_nosocket__ro_after_init ;
Signature · 1 line
2.30.2
Michal Koutný
maintainerRe: [PATCH 3/3] loop: Charge i/o to mem and blk cg
Hi.
Quoted 7 lines · Dan Schatzberg
The current code only associates with the existing blkcg when aio is used to access the backing file. This patch covers all types of i/o to the backing file and also associates the memcg so if the backing file is on tmpfs, memory is charged appropriately.
This patch also exports
cgroup_get_e_cssandint_active_memcgso it can be used by the loop module.
Wouldn't it be clearer to export (not explicitly inlined anymore) set_active_memcg() instead of the int_active_memcg that's rather an implementation detail?
Quoted 12 lines
drivers/block/loop.c +3
-4@@ -2111,13 +2112,18 @@ static blk_status_t loop_queue_rq(struct blk_mq_hw_ctx *hctx, }/* always use the first bio's css */ + cmd->blkcg_css =NULL ; + cmd->memcg_css =NULL ;#ifdef CONFIG_BLK_CGROUP -if (cmd->use_aio && rq->bio && rq->bio->bi_blkg) { - cmd->css = &bio_blkcg (rq->bio)->css; -css_get (cmd->css); - }else +if (rq->bio && rq->bio->bi_blkg) {
drivers/block/loop.c static blk_status_t loop_queue_rq(struct blk_mq_hw_ctx *hctx, · line 2127
Quoted 10 lines
drivers/block/loop.c +7
-1+ cmd->blkcg_css = &bio_blkcg (rq->bio)->css; +#ifdef CONFIG_MEMCG + cmd->memcg_css = +cgroup_get_e_css (cmd->blkcg_css->cgroup, + &memory_cgrp_subsys); +#endif + }#endif - cmd->css =NULL ;loop_queue_work (lo, cmd);
I see you dropped the cmd->blkcg_css reference (while rq is handled). Is it intentional?
Thanks,
Michal
Re: [PATCH 3/3] loop: Charge i/o to mem and blk cg
Hi Michal,
Quoted 8 lines · Michal Koutný
Hi.
Quoted 5 lines · Dan Schatzberg
The current code only associates with the existing blkcg when aio is used to access the backing file. This patch covers all types of i/o to the backing file and also associates the memcg so if the backing file is on tmpfs, memory is charged appropriately.
Quoted 6 lines
Quoted 2 lines
This patch also exports
cgroup_get_e_cssandint_active_memcgso it can be used by the loop module.Wouldn't it be clearer to export (not explicitly inlined anymore)
set_active_memcg()instead of theint_active_memcgthat's rather an implementation detail?
Agreed that exporting int_active_memcg is an implementation detail, but would this prevent set_active_memcg from being inlined? Is that desireable?
Quoted 18 lines
Quoted 18 lines
drivers/block/loop.c +8
-4@@ -2111,13 +2112,18 @@ static blk_status_t loop_queue_rq(struct blk_mq_hw_ctx *hctx, }/* always use the first bio's css */ + cmd->blkcg_css =NULL ; + cmd->memcg_css =NULL ;#ifdef CONFIG_BLK_CGROUP -if (cmd->use_aio && rq->bio && rq->bio->bi_blkg) { - cmd->css = &bio_blkcg (rq->bio)->css; -css_get (cmd->css); - }else +if (rq->bio && rq->bio->bi_blkg) { + cmd->blkcg_css = &bio_blkcg (rq->bio)->css; +#ifdef CONFIG_MEMCG + cmd->memcg_css = +cgroup_get_e_css (cmd->blkcg_css->cgroup, + &memory_cgrp_subsys);
Quoted 8 lines
drivers/block/loop.c static blk_status_t loop_queue_rq(struct blk_mq_hw_ctx *hctx, · line 2127
Quoted 5 lines
drivers/block/loop.c +2
-1+#endif + }#endif - cmd->css =NULL ;loop_queue_work (lo, cmd);I see you dropped the
cmd->blkcg_cssreference (while rq is handled). Is it intentional?
Yes it is intentional. All requests (not just aio) go through the loop worker which grabs the blkcg reference in loop_queue_work() on construction. So I believe grabbing a reference per request is unnecessary.
Michal Koutný
maintainerRe: [PATCH 3/3] loop: Charge i/o to mem and blk cg
Quoted 2 lines · Dan Schatzberg
Agreed that exporting
int_active_memcgis an implementation detail, but would this preventset_active_memcgfrom being inlined?
Non-inlining in the loop module doesn't seem like a big trouble. OTOH, other callers may be more sensitive and would need to rely on inlining. I can't currently think of a nice way to have both the exported and the exlicitly inlined variant at once. It seems it's either API or perf craft in the end but both are uncertain, so I guess the current approach is fine in the end.
Quoted 4 lines
Yes it is intentional. All requests (not just aio) go through the loop worker which grabs the blkcg reference in
loop_queue_work()on construction. So I believe grabbing a reference per request is unnecessary.
Isn't there a window without the reference between loop_queue_rq and loop_queue_work? I don't know, you seem to know better, so I'd suggest dropping a comment line into the code explaining this.
Thanks,
Michal
Re: [PATCH 3/3] loop: Charge i/o to mem and blk cg
Quoted 2 lines
Non-inlining in the loop module doesn't seem like a big trouble. OTOH, other callers may be more sensitive and would need to rely on inlining.
Yes, this is my concern as well.
Quoted 5 lines
I can't currently think of a nice way to have both the exported and the exlicitly inlined variant at once. It seems it's either API or perf craft in the end but both are uncertain, so I guess the current approach is fine in the end.
Quoted 7 lines
Quoted 4 lines
Yes it is intentional. All requests (not just aio) go through the loop worker which grabs the blkcg reference in
loop_queue_work()on construction. So I believe grabbing a reference per request is unnecessary.Isn't there a window without the reference between
loop_queue_rqandloop_queue_work?
Hmm, perhaps I'm not understanding how the reference counting works, but my understanding is that we enter loop_queue_rq with presumably some code earlier holding a reference to the blkcg, we only need to acquire a reference sometime before returning from loop_queue_rq. The "window" between loop_queue_rq and loop_queue_work is all straight-line code so there's no possibility for the earlier code to get control back and drop the reference.
Quoted 2 lines
I don't know, you seem to know better, so I'd suggest dropping a comment line into the code explaining this.
I wouldn't be so sure that I know any better here :D - I'm fairly inexperienced in this domain.
Where would you suggest putting such a comment? The change in question removed a particular case where we explicitly grab a reference to the blkcg because now we do it uniformly in one place. Would you like a comment explaining why we acquire a reference for all loop workers or one explaining specifically why we don't need to acquire one for aio?
Michal Koutný
maintainerRe: [PATCH 3/3] loop: Charge i/o to mem and blk cg
Quoted 7 lines · Dan Schatzberg
Hmm, perhaps I'm not understanding how the reference counting works, but my understanding is that we enter
loop_queue_rqwith presumably some code earlier holding a reference to the blkcg, we only need to acquire a reference sometime before returning fromloop_queue_rq. The "window" betweenloop_queue_rqandloop_queue_workis all straight-line code so there's no possibility for the earlier code to get control back and drop the reference.
I don't say the current implementation is wrong, it just looked suspicious to me when the css address is copied without taking the reference.
The straight path is clear, I'm not sure about later invocations through loop_workfn where the blkcg_css is accessed via the cmd->blkcg_css.
Quoted 1 line
Where would you suggest putting such a comment?
This is how I understand it:
drivers/block/loop.c +3 -0
@@ -996,6 +996,7 @@ static void loop_queue_work(struct loop_device *lo, struct loop_cmd *cmd)rb_insert_color (&worker->rb_node, &lo->worker_tree); queue_work:if (worker) { +WARN_ON_ONCE (worker->blkcg_css != cmd->blkcg_css);/* * We need to remove from the idle list here while * holding the lock so that the idle timer doesn't
@@ -2106,6 +2107,8 @@ static blk_status_t loop_queue_rq(struct blk_mq_hw_ctx *hctx, cmd->memcg_css =NULL ;#ifdef CONFIG_BLK_CGROUP if (rq->bio && rq->bio->bi_blkg) { +/* reference to blkcg_css will be held by loop_worker (outlives +* cmd) or it is the eternal root css */ cmd->blkcg_css = &bio_blkcg (rq->bio)->css;#ifdef CONFIG_MEMCG cmd->memcg_css =
(On further thoughts, maybe the blkcg_css reference isn't needed even in the loop_worker if it can be reasoned that blkcg_css won't go away while there's an outstanding rq.)
HTH,
Michal
Re: [PATCH 3/3] loop: Charge i/o to mem and blk cg
drivers/block/loop.c static void loop_queue_work(struct loop_device *lo, struct loop_cmd *cmd) · line 999
Quoted 9 lines
This is how I understand it:
drivers/block/loop.c +1
-0@@ -996,6 +996,7 @@ static void loop_queue_work(struct loop_device *lo, struct loop_cmd *cmd)rb_insert_color (&worker->rb_node, &lo->worker_tree); queue_work:if (worker) { +WARN_ON_ONCE (worker->blkcg_css != cmd->blkcg_css);
Yes, this is correct. Though the check here seems a bit obvious to me
- it must be correct because we assign worker above:
if (cur_worker->blkcg_css == cmd->blkcg_css) {
worker = cur_worker;
break;or when we construct the worker:
worker->blkcg_css = cmd->blkcg_css;
I think this WARN_ON_ONCE check might be more interesting in loop_process_work which invokes loop_handle_cmd and actually uses cmd->blkcg_css. In any event, your understanding is correct here.
drivers/block/loop.c static blk_status_t loop_queue_rq(struct blk_mq_hw_ctx *hctx, · line 2111
Quoted 9 lines
drivers/block/loop.c +2
-0/* * We need to remove from the idle list here while * holding the lock so that the idle timer doesn't @@ -2106,6 +2107,8 @@ static blk_status_t loop_queue_rq(struct blk_mq_hw_ctx *hctx, cmd->memcg_css =NULL ;#ifdef CONFIG_BLK_CGROUP if (rq->bio && rq->bio->bi_blkg) { +/* reference to blkcg_css will be held by loop_worker (outlives +* cmd) or it is the eternal root css */
Yes, this is correct. Feel free to add my Acked-by to such a patch