针对低端机KSM的优化


简介: KSM的基本概念和思想可参考如下的分析

一, 编译设置,初始化
1. @kernel-3.18/arch/arm/configs/FRT_debug_defconfig & FRT_defconfig
CONFIG_KSM=y
write /sys/kernel/mm/ ksm /pages_to_scan 100
write /sys/kernel/mm/ ksm /sleep_millisecs 20
write /sys/kernel/mm/ ksm /run 1  <0,1,2>
循环kernel thread: ksmd

二,代码分析
1. @Ksm.c (mm)
static int __init ksm_init(void)
{
struct task_struct *ksm_thread;
int err;
err = ksm_slab_init();
if (err)
goto out;
ksm_thread = kthread_run( ksm_scan_thread, NULL, " ksmd");
if (IS_ERR(ksm_thread)) {
pr_err("ksm: creating kthread failed\n");
err = PTR_ERR(ksm_thread);
goto out_free;
}
static int ksm_scan_thread(void *nothing)
{
set_freezable();
/* M: set KSMD's priority to the lowest value */
set_user_nice(current, 19);
/* set_user_nice(current, 5); */
while (! kthread_should_stop()) {
mutex_lock(&ksm_thread_mutex);
wait_while_offlining();
if ( ksmd_should_run()) {
#ifdef KSM_KCTL_INTERFACE
ksm_tuning_pressure();
#endif
ksm_do_scan(ksm_thread_pages_to_scan);
}
mutex_unlock(&ksm_thread_mutex);
try_to_freeze(); //是否当前进程可以被frezze
if ( ksmd_should_run()) {
schedule_timeout_interruptible(
msecs_to_jiffies(ksm_thread_sleep_millisecs));
} else {
wait_event_freezable( ksm_thread_wait,
ksmd_should_run() || kthread_should_stop());
}
}
return 0;
}
/**
* kthread_should_stop - should this kthread return now?
*
* When someone calls kthread_stop() on your kthread, it will be woken
* and this will return true.  You should then return, and your return
* value will be passed through to kthread_stop().
*/
bool kthread_should_stop(void)
{
return test_bit( KTHREAD_SHOULD_STOP, &to_kthread(current)->flags);
}
static int ksmd_should_run(void)
{
return (ksm_run & KSM_RUN_MERGE) && !list_empty(&ksm_mm_head.mm_list); //KSM_RUN_MERGE 1
}
static void ksm_tuning_pressure(void)
{
#if NR_CPUS > 1
if (bat_is_charger_exist() == KAL_TRUE) {
if (ksm_thread_sleep_millisecs == 20 &&
ksm_thread_pages_to_scan == 100)
return;
/*set to default value */
ksm_thread_sleep_millisecs = 20;
ksm_thread_pages_to_scan = 100;
} else {
int num_cpus = num_online_cpus();
int three_quater_cpus = ((3 * num_possible_cpus() * 10)/4 + 5)/10;
int one_half_cpus = num_possible_cpus() >> 1;
if (num_cpus >= three_quater_cpus) {
ksm_thread_sleep_millisecs = 20;
ksm_thread_pages_to_scan = 100;
} else if (num_cpus >= one_half_cpus) {
ksm_thread_sleep_millisecs = 3000;
ksm_thread_pages_to_scan = 200;
} else {
ksm_thread_sleep_millisecs = 10000;
ksm_thread_pages_to_scan = 200;
}
}
#endif
}
/**
* ksm_do_scan  - the ksm scanner main worker function.
* @scan_npages - number of pages we want to scan before we return.
*/
static void ksm_do_scan(unsigned int scan_npages)
{
struct rmap_item * rmap_item;
struct page *uninitialized_var(page);
while (scan_npages-- && likely(!freezing(current))) {
cond_resched();
rmap_item = scan_get_next_rmap_item(&page);
if (!rmap_item)
return;
cmp_and_merge_page(page, rmap_item);
put_page(page);
}
}
struct rmap_item { 该条目保存有物理地址到虚拟地址的反向映射,同时用来组织稳定树的红黑树结构,对于非稳定树保存有校验和
struct rmap_item *rmap_list;
union {
struct anon_vma *anon_vma; /* when stable */
};
struct mm_struct *mm;
unsigned long address; /* + low bits used for flags below */
unsigned int oldchecksum; /* when unstable */
union {
struct rb_node node; /* when node of unstable tree */
struct { /* when listed from stable tree */
struct stable_node *head;
struct hlist_node hlist;
};
};
};
三, 主要流程图
1. ksmd线程扫描
  • ksmd线程优先级设置为最低
    • F S   UID   PID  PPID C PRI  NI BIT    SZ WCHAN  TTY          TIME CMD
    • 1 R     0     26    2       0   0    19   -     0        ?        00:00:43 ksmd
  • ksmd线程可以通过ksm_run控制是否一直扫描和暂停等
  • ksmd的sleep时长和每次扫描的page页数,是通过cpu的负载来调整的,负载越高sleep越短,扫描页数越少
    • 调整规则: (xx_millisecs, xx_scan)
    • 1.如果充电则(20, 100)
    • 2. online/possible_cpu > ¾, (20,100)
    • 3. online/possible_cpu > ½, (3000, 200)
    • 4. 其他(10000, 200)
  • ksmd线程会挂住,如果设置ksm_run=0(停止)或者扫描完所有页,这时就需要唤醒该线程,比如设置ksm_run=1
#define KSM_RUN_STOP 0
#define KSM_RUN_MERGE 1
#define KSM_RUN_UNMERGE 2
#define KSM_RUN_OFFLINE 4
  • ksmd线程目前是亮屏会启动,灭屏会挂起
  • 如果ksmd没有被中止, 则它会一直扫描完每个进程MM中所有的VMA匿名页后

           

  • 有新的用户进程被创建,则把该进程的mm添加到KSM的扫描队列中;如果有用户进程被杀,则把该进程的mm从KSM扫描队列中删除。
  • 分析KSM的scan_get_next_rmap_item()函数,可以看到KSM尽力为每一个页面分配一个rmap_item结构体,如果该页面不能合并,那么不仅没有减少内存使用,相反还增加了一个rmap_item结构体大小的内存开销。要解决这个问题,需要采样另外一种rmap_item的生成算法,对于合并不成功的rmap_item,能够回收该rmap_item
  • 每次扫描ksm_thread_pages_to_scan个页面后,下次扫描则接着上次的页面继续扫描,每次开始扫描页面的起始地址保存在全局变量ksm_scan.address中
  • KSM用struct rmap_item链表保存相同页合并的信息,比如匿名页虚拟地址和物理地址之间的映射关系。举例:进程A中mm的虚拟地址V1指向物理页地址P1,进程B中mm的虚拟地址V2指向物理页地址P2,KSM通过扫描发现物理页P1和P2完全相同,则建立一个rmap_item反映射关系(虚拟地址--物理页),然后删除其中一个物理页P2, 如果进程A只读访问P1, 进程B只读访问P2, 则KSM直接返回唯一的共同页,如果进程A需要写入P1则产生缺页中断,重新分配物理页P3,同时也给进程B分配物理页P4。KSM就释放以前的相同页
  • KSM管理合并页面,有稳定树和不稳定树两个重要数据结构。稳定树:存储那些已经发现是稳定的且通过 KSM 合并的页面;不稳定树:用于存储还不能理解为稳定的新页面
  • 当扫描完成(通过 ksm.c/ksm_do_scan() 执行)时,稳定树被保存下来,但不稳定树则被删除并在下一次扫描时重新构建。
  • 由于稳定树中的所有页面都是写保护的,因此当一个页面试图被写入时将生成一个页面故障,从而允许 CoW 进程为写入程序取消页面合并(请参见 ksm.c/break_cow())。稳定树中的孤立页面将在稍后被删除(除非该页面的两个或更多用户存在,表明该页面还在被共享)。
  • KSM扫描完,只是表示把链表ksm_scan中的每个进程mm的VMA匿名页处理完。如果新创建的进程少,则很快能处理完一次;如果进程创建和死掉反反复复,则容易造成系统负担
四,如何使用
1. @Ksm.txt (documentation\vm)
The KSM daemon is controlled by sysfs files in /sys/kernel/mm/ksm/, readable by all but writable only by root:
pages_to_scan    - how many present pages to scan before ksmd goes to sleep e.g. "echo 100 > /sys/kernel/mm/ksm/pages_to_scan"
                   Default: 100 (chosen for demonstration purposes)
sleep_millisecs  - how many milliseconds ksmd should sleep before next scan e.g. "echo 20 > /sys/kernel/mm/ksm/sleep_millisecs"
                   Default: 20 (chosen for demonstration purposes)
merge_across_nodes - specifies if pages from different numa nodes can be merged. When set to 0, ksm merges only pages which physically
                   reside in the memory area of same NUMA node. That brings lower latency to access of shared pages. Systems with more
                   nodes, at significant NUMA distances, are likely to benefit from the lower latency of setting 0. Smaller systems, which
                   need to minimize memory usage, are likely to benefit from the greater sharing of setting 1 (default). You may wish to
                   compare how your system performs under each setting, before deciding on which to use. merge_across_nodes setting can be
                   changed only when there are no ksm shared pages in system: set run 2 to unmerge pages first, then to 1 after changing merge_across_nodes, to remerge according to the new setting.
                   Default: 1 (merging across nodes as in earlier releases)
run              - set 0 to stop ksmd from running but keep merged pages, set 1 to run ksmd e.g. "echo 1 > /sys/kernel/mm/ksm/run",
                   set 2 to stop ksmd and unmerge all pages currently merged, but leave mergeable areas registered for next run Default: 0 (must be changed to 1 to activate KSM, except if CONFIG_SYSFS is disabled)
The effectiveness of KSM and MADV_MERGEABLE is shown in /sys/kernel/mm/ksm/:
pages_shared     - how many shared pages are being used
pages_sharing    - how many more sites are sharing them i.e. how much saved
pages_unshared   - how many pages unique but repeatedly checked for merging
pages_volatile   - how many pages changing too fast to be placed in a tree
full_scans       - how many times all mergeable areas have been scanned
A high ratio of pages_sharing to pages_shared indicates good sharing, but a high ratio of pages_unshared to pages_sharing indicates wasted effort. pages_volatile embraces several different kinds of activity, but a high proportion there would also indicate poor use of madvise MADV_MERGEABLE.
2. KSM 配置和监控
     KSM 的管理和监控通过 sysfs(位于根 /sys/kernel/mm/ksm)执行。在这个 sysfs 子目录中,您将发现一些文件,有些用于控制,其他的用于监控。
     第一个文件 run 用于启用和禁用 KSM 的页面合并。默认情况下,KSM 被禁用(0),但 可以通过将一个 1 写入这个文件来启用 KSM 守护进程 (例如,echo 1 > sys/kernel/mm/ksm/run)。 通过写入一个 0,可以从运行状态禁用这个守护进程(但是保留合并页面的当前集合) 。另外, 通过写入一个 2,可以从运行状态(1)停止 KSM 并请求取消合并所有合并页面。
     KSM 运行时,可以通过 3 个参数(sysfs 中的文件)来控制它。 sleep_millisecs 定义执行另一次页面扫描前 ksmd 休眠的毫秒数。 max_kernel_pages 文件定义 ksmd 可以使用的最大页面数(默认值是可用内存的 25%,但可以写入一个 0 来指定为无限)。最后, pages_to_scan 文件定义一次给定扫描中可以扫描的页面数。任何用户都可以查看这些文件,但是用户必须拥有根权限才能修改它们。
     还有 5 个通过 sysfs 导出的可监控文件(均为只读),它们表明 ksmd 的运行情况和效果。full_scans 文件表明已经执行的全区域扫描的次数。剩下的 4 个文件表明 KSM 的页面级统计数据:
full_scans :表明已经执行的全区域扫描的次数
pages_shared: stable稳定树的节点数(共享后的物理页面数)。
pages_sharing:表示被共享的物理页面数。(例如将3个相同的页面合并成1个页面,则pages_shared=1,pages_sharing=2,两者比例体现了页面共享效率)
pages_unshared:ksm的暂未共享页面数,即unstable不稳定树的节点数。
pages_volatile:频繁改变的页面的数量。
pages_to_scan: 每次扫描的页面数
sleep_millisecs:每次扫描需要休息的间隔时间,不能一直run,这样会消耗CPU
     KSM 作者定义: 较高的 pages_sharing/pages_shared 比率表明高效的页面共享 (反之则表明资源浪费)。
3. 关键点
1). 合并的都是VMA中的匿名页
2). 每次处理固定的 页数(pages_to_scan)
3). 根据CPU online个数调整pages_to_scan & sleep_millisecs, online cpu个数越多,sleep_millisecs越短
4). 亮屏就开始合并页的扫描和操作,灭屏就停止
五,调试测试
1. 读取 KSM信息 /sys/kernel/mm/ksm/*
full_scans
pages_shared
pages_sharing
pages_to_scan
pages_unshared
pages_volatile
run
sleep_millisecs
2. 控制CPU online/offline: sys/devices/proc/hps/system/cpu
echo 0  > /proc/hps/enabled      该命令屏蔽系统自己的调核策略
echo 0 > /sys/devices/system/cpu/cpu3/online  该命令关cpu3
固定GPU频率
echo 0 > gpufreq_state
echo 549250 > gpufreq_opp_freq
3. 控制是否充电
cat /proc/mtk_battery_cmd/current_cmd  //就可以disable 充电
echo 1500 0 > /proc/mtk_battery_cmd/current_cmd  //就可以enable充电
4. 打印输出
pr_info("peter_xu ksm:  %s enter pid = %d, uid = %d, state = %ld\n",
current->comm, current->pid, current_uid().val, current->state);
UID对应的名称
6. pmap pid
六,优化手段
1.  ksm_thread_sleep_millisecs 和 ksm_thread_pages_to_scan 重新合理设置;  bat_is_charger_exist
2. 只对系统常驻进程采取KSM, 容易被杀掉的用户进程则不做KSM处理
@Ksm.c (mm)
#ifdef CONFIG_KSM_GO
/* Merge page or not after once scan */
static bool ksm_find_same_page = true;
#endif

static void stable_tree_append(struct rmap_item *rmap_item,
			       struct stable_node *stable_node)
{
	rmap_item->head = stable_node;
	rmap_item->address |= STABLE_FLAG;
	hlist_add_head(&rmap_item->hlist, &stable_node->hlist);

	if (rmap_item->hlist.next)
		ksm_pages_sharing++;
	else
		ksm_pages_shared++;
#ifdef CONFIG_KSM_GO
	ksm_find_same_page = true;
#endif
}

static void ksm_tuning_pressure(void)
{
#ifdef CONFIG_KSM_GO
	if (ksm_find_same_page) {
		ksm_thread_sleep_millisecs = 20;
		ksm_thread_pages_to_scan = 100;
	} else {
		ksm_thread_sleep_millisecs += 50;
		if (ksm_thread_sleep_millisecs > 1000) {
			ksm_thread_sleep_millisecs = 1000;
		}
		ksm_thread_pages_to_scan += 100;
		if (ksm_thread_pages_to_scan > 500) {
			ksm_thread_pages_to_scan = 500;
		}
	}

	ksm_find_same_page = false;
#else
#if NR_CPUS > 1
	if (bat_is_charger_exist() == KAL_TRUE) {
		if (ksm_thread_sleep_millisecs == 20 &&
			ksm_thread_pages_to_scan == 100)
			return;
		/*set to default value */
		ksm_thread_sleep_millisecs = 20;
		ksm_thread_pages_to_scan = 100;
	} else {
		int num_cpus = num_online_cpus();
		int three_quater_cpus = ((3 * num_possible_cpus() * 10)/4 + 5)/10;
		int one_half_cpus = num_possible_cpus() >> 1;

		if (num_cpus >= three_quater_cpus) {
			ksm_thread_sleep_millisecs = 20;
			ksm_thread_pages_to_scan = 100;
		} else if (num_cpus >= one_half_cpus) {
			ksm_thread_sleep_millisecs = 3000;
			ksm_thread_pages_to_scan = 200;
		} else {
			ksm_thread_sleep_millisecs = 10000;
			ksm_thread_pages_to_scan = 200;
		}
	}
#endif
#endif
}

static int ksm_fb_notifier_callback(struct notifier_block *p,
				unsigned long event, void *data)
{
	int blank;

	if (event != FB_EVENT_BLANK)
		return 0;

	blank = *(int *)((struct fb_event *)data)->data;

	if (blank == FB_BLANK_UNBLANK) { /*LCD ON*/
#ifdef CONFIG_KSM_GO
		ksm_run_change(KSM_RUN_STOP);
#else
		ksm_run_change(KSM_RUN_MERGE);
#endif
	} else if (blank == FB_BLANK_POWERDOWN) { /*LCD OFF*/
#ifdef CONFIG_KSM_GO
		ksm_find_same_page = true;
		ksm_run_change(KSM_RUN_MERGE);
#else
		ksm_run_change(KSM_RUN_STOP);
#endif
	}

	return 0;
}






©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页