mm: Add balance_dirty_pages_ratelimited_flags() function

This adds the helper function balance_dirty_pages_ratelimited_flags().
It adds the parameter flags to balance_dirty_pages_ratelimited().
The flags parameter is passed to balance_dirty_pages(). For async
buffered writes the flag value will be BDP_ASYNC.

If balance_dirty_pages() gets called for async buffered write, we don't
want to wait. Instead we need to indicate to the caller that throttling
is needed so that it can stop writing and offload the rest of the write
to a context that can block.

The new helper function is also used by balance_dirty_pages_ratelimited().

Signed-off-by: Jan Kara <jack@suse.cz>
Signed-off-by: Stefan Roesch <shr@fb.com>
Reviewed-by: Christoph Hellwig <hch@lst.de>
Link: https://lore.kernel.org/r/20220623175157.1715274-4-shr@fb.com
[axboe: fix kerneltest bot 'ret' issue]
Signed-off-by: Jens Axboe <axboe@kernel.dk>
This commit is contained in:
Jan Kara 2022-06-23 10:51:46 -07:00 committed by Jens Axboe
parent e92eebbb09
commit fe6c9c6e3e
2 changed files with 48 additions and 10 deletions

View File

@ -364,7 +364,14 @@ void global_dirty_limits(unsigned long *pbackground, unsigned long *pdirty);
unsigned long wb_calc_thresh(struct bdi_writeback *wb, unsigned long thresh); unsigned long wb_calc_thresh(struct bdi_writeback *wb, unsigned long thresh);
void wb_update_bandwidth(struct bdi_writeback *wb); void wb_update_bandwidth(struct bdi_writeback *wb);
/* Invoke balance dirty pages in async mode. */
#define BDP_ASYNC 0x0001
void balance_dirty_pages_ratelimited(struct address_space *mapping); void balance_dirty_pages_ratelimited(struct address_space *mapping);
int balance_dirty_pages_ratelimited_flags(struct address_space *mapping,
unsigned int flags);
bool wb_over_bg_thresh(struct bdi_writeback *wb); bool wb_over_bg_thresh(struct bdi_writeback *wb);
typedef int (*writepage_t)(struct page *page, struct writeback_control *wbc, typedef int (*writepage_t)(struct page *page, struct writeback_control *wbc,

View File

@ -1554,8 +1554,8 @@ static inline void wb_dirty_limits(struct dirty_throttle_control *dtc)
* If we're over `background_thresh' then the writeback threads are woken to * If we're over `background_thresh' then the writeback threads are woken to
* perform some writeout. * perform some writeout.
*/ */
static void balance_dirty_pages(struct bdi_writeback *wb, static int balance_dirty_pages(struct bdi_writeback *wb,
unsigned long pages_dirtied) unsigned long pages_dirtied, unsigned int flags)
{ {
struct dirty_throttle_control gdtc_stor = { GDTC_INIT(wb) }; struct dirty_throttle_control gdtc_stor = { GDTC_INIT(wb) };
struct dirty_throttle_control mdtc_stor = { MDTC_INIT(wb, &gdtc_stor) }; struct dirty_throttle_control mdtc_stor = { MDTC_INIT(wb, &gdtc_stor) };
@ -1575,6 +1575,7 @@ static void balance_dirty_pages(struct bdi_writeback *wb,
struct backing_dev_info *bdi = wb->bdi; struct backing_dev_info *bdi = wb->bdi;
bool strictlimit = bdi->capabilities & BDI_CAP_STRICTLIMIT; bool strictlimit = bdi->capabilities & BDI_CAP_STRICTLIMIT;
unsigned long start_time = jiffies; unsigned long start_time = jiffies;
int ret = 0;
for (;;) { for (;;) {
unsigned long now = jiffies; unsigned long now = jiffies;
@ -1803,6 +1804,10 @@ pause:
period, period,
pause, pause,
start_time); start_time);
if (flags & BDP_ASYNC) {
ret = -EAGAIN;
break;
}
__set_current_state(TASK_KILLABLE); __set_current_state(TASK_KILLABLE);
wb->dirty_sleep = now; wb->dirty_sleep = now;
io_schedule_timeout(pause); io_schedule_timeout(pause);
@ -1834,6 +1839,7 @@ pause:
if (fatal_signal_pending(current)) if (fatal_signal_pending(current))
break; break;
} }
return ret;
} }
static DEFINE_PER_CPU(int, bdp_ratelimits); static DEFINE_PER_CPU(int, bdp_ratelimits);
@ -1855,27 +1861,34 @@ static DEFINE_PER_CPU(int, bdp_ratelimits);
DEFINE_PER_CPU(int, dirty_throttle_leaks) = 0; DEFINE_PER_CPU(int, dirty_throttle_leaks) = 0;
/** /**
* balance_dirty_pages_ratelimited - balance dirty memory state * balance_dirty_pages_ratelimited_flags - Balance dirty memory state.
* @mapping: address_space which was dirtied * @mapping: address_space which was dirtied.
* @flags: BDP flags.
* *
* Processes which are dirtying memory should call in here once for each page * Processes which are dirtying memory should call in here once for each page
* which was newly dirtied. The function will periodically check the system's * which was newly dirtied. The function will periodically check the system's
* dirty state and will initiate writeback if needed. * dirty state and will initiate writeback if needed.
* *
* Once we're over the dirty memory limit we decrease the ratelimiting * See balance_dirty_pages_ratelimited() for details.
* by a lot, to prevent individual processes from overshooting the limit *
* by (ratelimit_pages) each. * Return: If @flags contains BDP_ASYNC, it may return -EAGAIN to
* indicate that memory is out of balance and the caller must wait
* for I/O to complete. Otherwise, it will return 0 to indicate
* that either memory was already in balance, or it was able to sleep
* until the amount of dirty memory returned to balance.
*/ */
void balance_dirty_pages_ratelimited(struct address_space *mapping) int balance_dirty_pages_ratelimited_flags(struct address_space *mapping,
unsigned int flags)
{ {
struct inode *inode = mapping->host; struct inode *inode = mapping->host;
struct backing_dev_info *bdi = inode_to_bdi(inode); struct backing_dev_info *bdi = inode_to_bdi(inode);
struct bdi_writeback *wb = NULL; struct bdi_writeback *wb = NULL;
int ratelimit; int ratelimit;
int ret = 0;
int *p; int *p;
if (!(bdi->capabilities & BDI_CAP_WRITEBACK)) if (!(bdi->capabilities & BDI_CAP_WRITEBACK))
return; return ret;
if (inode_cgwb_enabled(inode)) if (inode_cgwb_enabled(inode))
wb = wb_get_create_current(bdi, GFP_KERNEL); wb = wb_get_create_current(bdi, GFP_KERNEL);
@ -1915,9 +1928,27 @@ void balance_dirty_pages_ratelimited(struct address_space *mapping)
preempt_enable(); preempt_enable();
if (unlikely(current->nr_dirtied >= ratelimit)) if (unlikely(current->nr_dirtied >= ratelimit))
balance_dirty_pages(wb, current->nr_dirtied); ret = balance_dirty_pages(wb, current->nr_dirtied, flags);
wb_put(wb); wb_put(wb);
return ret;
}
/**
* balance_dirty_pages_ratelimited - balance dirty memory state.
* @mapping: address_space which was dirtied.
*
* Processes which are dirtying memory should call in here once for each page
* which was newly dirtied. The function will periodically check the system's
* dirty state and will initiate writeback if needed.
*
* Once we're over the dirty memory limit we decrease the ratelimiting
* by a lot, to prevent individual processes from overshooting the limit
* by (ratelimit_pages) each.
*/
void balance_dirty_pages_ratelimited(struct address_space *mapping)
{
balance_dirty_pages_ratelimited_flags(mapping, 0);
} }
EXPORT_SYMBOL(balance_dirty_pages_ratelimited); EXPORT_SYMBOL(balance_dirty_pages_ratelimited);