1222 lines · 29.6 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-only #include <linux/blkdev.h>#include <linux/wait.h>#include <linux/rbtree.h>#include <linux/kthread.h>#include <linux/backing-dev.h>#include <linux/blk-cgroup.h>#include <linux/freezer.h>#include <linux/fs.h>#include <linux/pagemap.h>#include <linux/mm.h>#include <linux/sched/mm.h>#include <linux/sched.h>#include <linux/module.h>#include <linux/writeback.h>#include <linux/device.h>#include <trace/events/writeback.h>#include "internal.h" struct backing_dev_info noop_backing_dev_info;EXPORT_SYMBOL_GPL(noop_backing_dev_info); static const char *bdi_unknown_name = "(unknown)"; /* * bdi_lock protects bdi_tree and updates to bdi_list. bdi_list has RCU * reader side locking. */DEFINE_SPINLOCK(bdi_lock);static u64 bdi_id_cursor;static struct rb_root bdi_tree = RB_ROOT;LIST_HEAD(bdi_list); /* bdi_wq serves all asynchronous writeback tasks */struct workqueue_struct *bdi_wq; #ifdef CONFIG_DEBUG_FS#include <linux/debugfs.h>#include <linux/seq_file.h> struct wb_stats {	unsigned long nr_dirty;	unsigned long nr_io;	unsigned long nr_more_io;	unsigned long nr_dirty_time;	unsigned long nr_writeback;	unsigned long nr_reclaimable;	unsigned long nr_dirtied;	unsigned long nr_written;	unsigned long dirty_thresh;	unsigned long wb_thresh;}; static struct dentry *bdi_debug_root; static void bdi_debug_init(void){	bdi_debug_root = debugfs_create_dir("bdi", NULL);} static void collect_wb_stats(struct wb_stats *stats,			     struct bdi_writeback *wb){	struct inode *inode; 	spin_lock(&wb->list_lock);	list_for_each_entry(inode, &wb->b_dirty, i_io_list)		stats->nr_dirty++;	list_for_each_entry(inode, &wb->b_io, i_io_list)		stats->nr_io++;	list_for_each_entry(inode, &wb->b_more_io, i_io_list)		stats->nr_more_io++;	list_for_each_entry(inode, &wb->b_dirty_time, i_io_list)		if (inode_state_read_once(inode) & I_DIRTY_TIME)			stats->nr_dirty_time++;	spin_unlock(&wb->list_lock); 	stats->nr_writeback += wb_stat(wb, WB_WRITEBACK);	stats->nr_reclaimable += wb_stat(wb, WB_RECLAIMABLE);	stats->nr_dirtied += wb_stat(wb, WB_DIRTIED);	stats->nr_written += wb_stat(wb, WB_WRITTEN);	stats->wb_thresh += wb_calc_thresh(wb, stats->dirty_thresh);} #ifdef CONFIG_CGROUP_WRITEBACKstatic void bdi_collect_stats(struct backing_dev_info *bdi,			      struct wb_stats *stats){	struct bdi_writeback *wb; 	rcu_read_lock();	list_for_each_entry_rcu(wb, &bdi->wb_list, bdi_node) {		if (!wb_tryget(wb))			continue; 		collect_wb_stats(stats, wb);		wb_put(wb);	}	rcu_read_unlock();}#elsestatic void bdi_collect_stats(struct backing_dev_info *bdi,			      struct wb_stats *stats){	collect_wb_stats(stats, &bdi->wb);}#endif static int bdi_debug_stats_show(struct seq_file *m, void *v){	struct backing_dev_info *bdi = m->private;	unsigned long background_thresh;	unsigned long dirty_thresh;	struct wb_stats stats;	unsigned long tot_bw; 	global_dirty_limits(&background_thresh, &dirty_thresh); 	memset(&stats, 0, sizeof(stats));	stats.dirty_thresh = dirty_thresh;	bdi_collect_stats(bdi, &stats);	tot_bw = atomic_long_read(&bdi->tot_write_bandwidth); 	seq_printf(m,		   "BdiWriteback:       %10lu kB\n"		   "BdiReclaimable:     %10lu kB\n"		   "BdiDirtyThresh:     %10lu kB\n"		   "DirtyThresh:        %10lu kB\n"		   "BackgroundThresh:   %10lu kB\n"		   "BdiDirtied:         %10lu kB\n"		   "BdiWritten:         %10lu kB\n"		   "BdiWriteBandwidth:  %10lu kBps\n"		   "b_dirty:            %10lu\n"		   "b_io:               %10lu\n"		   "b_more_io:          %10lu\n"		   "b_dirty_time:       %10lu\n"		   "bdi_list:           %10u\n"		   "state:              %10lx\n",		   K(stats.nr_writeback),		   K(stats.nr_reclaimable),		   K(stats.wb_thresh),		   K(dirty_thresh),		   K(background_thresh),		   K(stats.nr_dirtied),		   K(stats.nr_written),		   K(tot_bw),		   stats.nr_dirty,		   stats.nr_io,		   stats.nr_more_io,		   stats.nr_dirty_time,		   !list_empty(&bdi->bdi_list), bdi->wb.state); 	return 0;}DEFINE_SHOW_ATTRIBUTE(bdi_debug_stats); static void wb_stats_show(struct seq_file *m, struct bdi_writeback *wb,			  struct wb_stats *stats){ 	seq_printf(m,		   "WbCgIno:           %10lu\n"		   "WbWriteback:       %10lu kB\n"		   "WbReclaimable:     %10lu kB\n"		   "WbDirtyThresh:     %10lu kB\n"		   "WbDirtied:         %10lu kB\n"		   "WbWritten:         %10lu kB\n"		   "WbWriteBandwidth:  %10lu kBps\n"		   "b_dirty:           %10lu\n"		   "b_io:              %10lu\n"		   "b_more_io:         %10lu\n"		   "b_dirty_time:      %10lu\n"		   "state:             %10lx\n\n",#ifdef CONFIG_CGROUP_WRITEBACK		   cgroup_ino(wb->memcg_css->cgroup),#else		   1ul,#endif		   K(stats->nr_writeback),		   K(stats->nr_reclaimable),		   K(stats->wb_thresh),		   K(stats->nr_dirtied),		   K(stats->nr_written),		   K(wb->avg_write_bandwidth),		   stats->nr_dirty,		   stats->nr_io,		   stats->nr_more_io,		   stats->nr_dirty_time,		   wb->state);} static int cgwb_debug_stats_show(struct seq_file *m, void *v){	struct backing_dev_info *bdi = m->private;	unsigned long background_thresh;	unsigned long dirty_thresh;	struct bdi_writeback *wb; 	global_dirty_limits(&background_thresh, &dirty_thresh); 	rcu_read_lock();	list_for_each_entry_rcu(wb, &bdi->wb_list, bdi_node) {		struct wb_stats stats = { .dirty_thresh = dirty_thresh }; 		if (!wb_tryget(wb))			continue; 		collect_wb_stats(&stats, wb); 		/*		 * Calculate thresh of wb in writeback cgroup which is min of		 * thresh in global domain and thresh in cgroup domain. Drop		 * rcu lock because cgwb_calc_thresh may sleep in		 * cgroup_rstat_flush. We can do so here because we have a ref.		 */		if (mem_cgroup_wb_domain(wb)) {			rcu_read_unlock();			stats.wb_thresh = min(stats.wb_thresh, cgwb_calc_thresh(wb));			rcu_read_lock();		} 		wb_stats_show(m, wb, &stats); 		wb_put(wb);	}	rcu_read_unlock(); 	return 0;}DEFINE_SHOW_ATTRIBUTE(cgwb_debug_stats); static void bdi_debug_register(struct backing_dev_info *bdi, const char *name){	bdi->debug_dir = debugfs_create_dir(name, bdi_debug_root); 	debugfs_create_file("stats", 0444, bdi->debug_dir, bdi,			    &bdi_debug_stats_fops);	debugfs_create_file("wb_stats", 0444, bdi->debug_dir, bdi,			    &cgwb_debug_stats_fops);} static void bdi_debug_unregister(struct backing_dev_info *bdi){	debugfs_remove_recursive(bdi->debug_dir);}#else /* CONFIG_DEBUG_FS */static inline void bdi_debug_init(void){}static inline void bdi_debug_register(struct backing_dev_info *bdi,				      const char *name){}static inline void bdi_debug_unregister(struct backing_dev_info *bdi){}#endif /* CONFIG_DEBUG_FS */ static ssize_t read_ahead_kb_store(struct device *dev,				  struct device_attribute *attr,				  const char *buf, size_t count){	struct backing_dev_info *bdi = dev_get_drvdata(dev);	unsigned long read_ahead_kb;	ssize_t ret; 	ret = kstrtoul(buf, 10, &read_ahead_kb);	if (ret < 0)		return ret; 	bdi->ra_pages = read_ahead_kb >> (PAGE_SHIFT - 10); 	return count;} #define BDI_SHOW(name, expr)						\static ssize_t name##_show(struct device *dev,				\			   struct device_attribute *attr, char *buf)	\{									\	struct backing_dev_info *bdi = dev_get_drvdata(dev);		\									\	return sysfs_emit(buf, "%lld\n", (long long)expr);		\}									\static DEVICE_ATTR_RW(name); BDI_SHOW(read_ahead_kb, K(bdi->ra_pages)) static ssize_t min_ratio_store(struct device *dev,		struct device_attribute *attr, const char *buf, size_t count){	struct backing_dev_info *bdi = dev_get_drvdata(dev);	unsigned int ratio;	ssize_t ret; 	ret = kstrtouint(buf, 10, &ratio);	if (ret < 0)		return ret; 	ret = bdi_set_min_ratio(bdi, ratio);	if (!ret)		ret = count; 	return ret;}BDI_SHOW(min_ratio, bdi->min_ratio / BDI_RATIO_SCALE) static ssize_t min_ratio_fine_store(struct device *dev,		struct device_attribute *attr, const char *buf, size_t count){	struct backing_dev_info *bdi = dev_get_drvdata(dev);	unsigned int ratio;	ssize_t ret; 	ret = kstrtouint(buf, 10, &ratio);	if (ret < 0)		return ret; 	ret = bdi_set_min_ratio_no_scale(bdi, ratio);	if (!ret)		ret = count; 	return ret;}BDI_SHOW(min_ratio_fine, bdi->min_ratio) static ssize_t max_ratio_store(struct device *dev,		struct device_attribute *attr, const char *buf, size_t count){	struct backing_dev_info *bdi = dev_get_drvdata(dev);	unsigned int ratio;	ssize_t ret; 	ret = kstrtouint(buf, 10, &ratio);	if (ret < 0)		return ret; 	ret = bdi_set_max_ratio(bdi, ratio);	if (!ret)		ret = count; 	return ret;}BDI_SHOW(max_ratio, bdi->max_ratio / BDI_RATIO_SCALE) static ssize_t max_ratio_fine_store(struct device *dev,		struct device_attribute *attr, const char *buf, size_t count){	struct backing_dev_info *bdi = dev_get_drvdata(dev);	unsigned int ratio;	ssize_t ret; 	ret = kstrtouint(buf, 10, &ratio);	if (ret < 0)		return ret; 	ret = bdi_set_max_ratio_no_scale(bdi, ratio);	if (!ret)		ret = count; 	return ret;}BDI_SHOW(max_ratio_fine, bdi->max_ratio) static ssize_t min_bytes_show(struct device *dev,			      struct device_attribute *attr,			      char *buf){	struct backing_dev_info *bdi = dev_get_drvdata(dev); 	return sysfs_emit(buf, "%llu\n", bdi_get_min_bytes(bdi));} static ssize_t min_bytes_store(struct device *dev,		struct device_attribute *attr, const char *buf, size_t count){	struct backing_dev_info *bdi = dev_get_drvdata(dev);	u64 bytes;	ssize_t ret; 	ret = kstrtoull(buf, 10, &bytes);	if (ret < 0)		return ret; 	ret = bdi_set_min_bytes(bdi, bytes);	if (!ret)		ret = count; 	return ret;}static DEVICE_ATTR_RW(min_bytes); static ssize_t max_bytes_show(struct device *dev,			      struct device_attribute *attr,			      char *buf){	struct backing_dev_info *bdi = dev_get_drvdata(dev); 	return sysfs_emit(buf, "%llu\n", bdi_get_max_bytes(bdi));} static ssize_t max_bytes_store(struct device *dev,		struct device_attribute *attr, const char *buf, size_t count){	struct backing_dev_info *bdi = dev_get_drvdata(dev);	u64 bytes;	ssize_t ret; 	ret = kstrtoull(buf, 10, &bytes);	if (ret < 0)		return ret; 	ret = bdi_set_max_bytes(bdi, bytes);	if (!ret)		ret = count; 	return ret;}static DEVICE_ATTR_RW(max_bytes); static ssize_t stable_pages_required_show(struct device *dev,					  struct device_attribute *attr,					  char *buf){	dev_warn_once(dev,		"the stable_pages_required attribute has been removed. Use the stable_writes queue attribute instead.\n");	return sysfs_emit(buf, "%d\n", 0);}static DEVICE_ATTR_RO(stable_pages_required); static ssize_t strict_limit_store(struct device *dev,		struct device_attribute *attr, const char *buf, size_t count){	struct backing_dev_info *bdi = dev_get_drvdata(dev);	unsigned int strict_limit;	ssize_t ret; 	ret = kstrtouint(buf, 10, &strict_limit);	if (ret < 0)		return ret; 	ret = bdi_set_strict_limit(bdi, strict_limit);	if (!ret)		ret = count; 	return ret;} static ssize_t strict_limit_show(struct device *dev,		struct device_attribute *attr, char *buf){	struct backing_dev_info *bdi = dev_get_drvdata(dev); 	return sysfs_emit(buf, "%d\n",			!!(bdi->capabilities & BDI_CAP_STRICTLIMIT));}static DEVICE_ATTR_RW(strict_limit); static struct attribute *bdi_dev_attrs[] = {	&dev_attr_read_ahead_kb.attr,	&dev_attr_min_ratio.attr,	&dev_attr_min_ratio_fine.attr,	&dev_attr_max_ratio.attr,	&dev_attr_max_ratio_fine.attr,	&dev_attr_min_bytes.attr,	&dev_attr_max_bytes.attr,	&dev_attr_stable_pages_required.attr,	&dev_attr_strict_limit.attr,	NULL,};ATTRIBUTE_GROUPS(bdi_dev); static const struct class bdi_class = {	.name		= "bdi",	.dev_groups	= bdi_dev_groups,}; static __init int bdi_class_init(void){	int ret; 	ret = class_register(&bdi_class);	if (ret)		return ret; 	bdi_debug_init(); 	return 0;}postcore_initcall(bdi_class_init); static int __init default_bdi_init(void){	bdi_wq = alloc_workqueue("writeback", WQ_MEM_RECLAIM | WQ_UNBOUND |				 WQ_SYSFS, 0);	if (!bdi_wq)		return -ENOMEM;	return 0;}subsys_initcall(default_bdi_init);

Lines 1–500 of 1222.