Linux Driver优化S4 hibernate休眠速度

前言

  Driver的S3睡眠时间太慢了,原因找到了,但是S4休眠时间也比友商慢,S4要比S3复杂一点,中间涉及到kernel image的生成和写入disk,这部分花的时间挺多,所以想办法看能不能S4优化一下。

正文

  

hibernate

Kernel version V5.18
int hibernate(void)
{
	//只给出重要代码
	pm_prepare_console(); //切换成虚拟console
	/* 通知其他子系统要准备休眠了*/
	error = pm_notifier_call_chain_robust(PM_HIBERNATION_PREPARE, PM_POST_HIBERNATION);
	ksys_sync_helper(); //同步文件系统
	
	error = freeze_processes(); //冻结用户层进程
	error = create_basic_memory_bitmaps(); //创建包含page属性的bitmaps
	error = hibernation_snapshot(hibernation_mode == HIBERNATION_PLATFORM); //分配image所需的page

	if (in_suspend) {
		pm_pr_dbg("Writing hibernation image.\n");
		error = swsusp_write(flags); /*将image的page写入swap分区*/
		swsusp_free(); // 释放image的page
		
		if (!error) {
			if (hibernation_mode == HIBERNATION_TEST_RESUME) //如果Kernel S4有问题,可以测试是Software的问题还是Hardware和BIOS Firmware的问题
				snapshot_test = true; 
			else
				power_down(); //下电咯,device close
		}

  pm_prepare_console:VT switch,将当前console切换到一个虚拟console并重定向内核的kmsg,这部分代码比较复杂,就不展开看源码了。

  create_basic_memory_bitmaps() 计算和预留page给image。

  swsusp_write() 即swap suspend write,将休眠用的image写入swap分区所在的disk。

  顺便提一下,Kernel本身提供测试选项用于 测试S4的功能是否正常,因为S4不仅仅是Kernel负责,BIOS,硬件的上电时序也参与了这一个过程,如果这些有问题,那S4也会有问题,所以预留了测试选项,使得hibernate()也可以不下电,而是马上进行resume,可以测试S4的问题是软件还是硬件的锅,

我们继续看hibernation_snapshot()

hibernation_snapshot

Kernel version V5.18
int hibernation_snapshot(int platform_mode)
{
	//只给出重要代码
	hibernate_preallocate_memory(); //计算和预分配image所需的page
	
	error = freeze_kernel_threads(); //冻结内核线程
	
	error = dpm_prepare(PMSG_FREEZE); //执行所有device的prepare电源管理回调
	
	suspend_console(); //挂起console
	
	error = dpm_suspend(PMSG_FREEZE); //执行所有device的freeze电源管理回调
	
	if (error || hibernation_test(TEST_DEVICES))
		platform_recover(platform_mode);
	else
		error = create_image(platform_mode); //创建image
	
	msg = in_suspend ? (error ? PMSG_RECOVER : PMSG_THAW) : PMSG_RESTORE;
	
	dpm_resume(msg); //执行thaw电源管理回调,相当于撤销之前的freeze电源回调,活还没干完 起来干
	
	resume_console(); //恢复控制台
}

  主要就是调用hibernate_preallocate_memory计算大概需要多少page,是否满足用户的需求,接着创建休眠的image。
  接着看一下hibernate_preallocate_memory是如何让计算哪些page需要保存进image里。

hibernate_preallocate_memory

这个是最恶心的函数,它这代码一写的看起来很绕。

在看之前先补充一点知识:
/sys/power/image_size 是休眠image的最大大小
Kernel创建的image不得大于这个值,用户可以更改这个值。

哪些page需要保存? 当然是当然伙伴系统已经被用的page需要保存
image放在哪? 先放在内存里,所以我们需要申请page去存储image
如果伙伴系统里用掉的page比剩余的多,或者说使用量大于50%,这个时候还能生成image吗? 可能就不能了,这个问题后面解释。

Kernel version V5.18
int hibernate_preallocate_memory(void)
{
	//Fucking Source Code
	struct zone *zone;
	unsigned long saveable, size, max_size, count, highmem, pages = 0;
	unsigned long alloc, save_highmem, pages_highmem, avail_normal;
	ktime_t start, stop;
	int error;

	alloc_normal = 0;
	alloc_highmem = 0;
	
	/* Count the number of saveable data pages. */
	save_highmem = count_highmem_pages(); //计算需要保存的high-mem page
	saveable = count_data_pages();	//计算需要保存的normal-mem page

	/*
	 * Compute the total number of page frames we can use (count) and the
	 * number of pages needed for image metadata (size).
	 */
	count = saveable;
	saveable += save_highmem;  //所有应该保存的page
	highmem = save_highmem;
	size = 0;

	for_each_populated_zone(zone) {
		size += snapshot_additional_pages(zone);
		if (is_highmem(zone))
			highmem += zone_page_state(zone, NR_FREE_PAGES);  //加上high-mem free page
		else
			count += zone_page_state(zone, NR_FREE_PAGES); //加上normal-mem free page
	}
	
	avail_normal = count;  
	count += highmem;
	count -= totalreserve_pages;
	
	/* Compute the maximum number of saveable pages to leave in memory. */
	max_size = (count - (size + PAGES_FOR_IO)) / 2
			- 2 * DIV_ROUND_UP(reserved_size, PAGE_SIZE); //计算image可能达到的最大大小
	size = DIV_ROUND_UP(image_size, PAGE_SIZE); //image_size就是/sys/power/image_size 用户可以设定的值
	
	if (size > max_size) //如果用户允许我们保存这么大(这是理想情况)
		size = max_size;
		
	if (size >= saveable) { //并且有这么多空间保存image
		pages = preallocate_image_highmem(save_highmem);
		pages += preallocate_image_memory(saveable - pages, avail_normal);
		goto out; //计算完所需的page,退出
	}
		
	/* 如果走到这来,非常抱歉,可能我们要抛弃一些page*/
	/* Estimate the minimum size of the image. */
	pages = minimum_image_size(saveable);  //计算image的最小值

	if (avail_normal > pages)
		avail_normal -= pages;
	else
		avail_normal = 0;

	if (size < pages) //如果计算出来的image比minimum_image_size还小,自求多福吧,取一个最小值
		size = min_t(unsigned long, pages, max_size);

	shrink_all_memory(saveable - size); //尝试清理内存

	pages_highmem = preallocate_image_highmem(highmem / 2);
	alloc = count - max_size;
	if (alloc > pages_highmem)
		alloc -= pages_highmem;
	else
		alloc = 0;
	pages = preallocate_image_memory(alloc, avail_normal);

	if (pages < alloc) { //如果分配的不够,尝试从highmem再次分配一些出来
		/* We have exhausted non-highmem pages, try highmem. */
		alloc -= pages;
		pages += pages_highmem;
		pages_highmem = preallocate_image_highmem(alloc);
		if (pages_highmem < alloc) {
			pr_err("Image allocation is %lu pages short\n",
				alloc - pages_highmem);
			goto err_out;
		}
		pages += pages_highmem;
		/*
		 * size is the desired number of saveable pages to leave in
		 * memory, so try to preallocate (all memory - size) pages.
		 */
		alloc = (count - pages) - size;
		pages += preallocate_image_highmem(alloc);
	} else {
		/*
		 * There are approximately max_size saveable pages at this point
		 * and we want to reduce this number down to size.
		 */
		alloc = max_size - size;
		size = preallocate_highmem_fraction(alloc, highmem, count);
		pages_highmem += size;
		alloc -= size;
		size = preallocate_image_memory(alloc, avail_normal);
		pages_highmem += preallocate_image_highmem(alloc - size);
		pages += pages_highmem + size;
	}

	/*
	 * We only need as many page frames for the image as there are saveable
	 * pages in memory, but we have allocated more.  Release the excessive
	 * ones now.
	 */
	pages -= free_unnecessary_pages();

 out:
	stop = ktime_get();
	pr_info("Allocated %lu pages for snapshot\n", pages);
	swsusp_show_speed(start, stop, pages, "Allocated");



}

count_highmem_pages()和count_data_pages()
  都是统计不是nosave区域或者在forbidden,free的page数量(看是否在对于bitmaps中)
preallocate_image_memory()和preallocate_image_highmem()
会分配page同时累加alloc_normal,alloc_highmem,记录已分配的page数量。

  这个函数的作用就是记录image需要多少page,预先分配这些page
当然还不是最终的,因为我们在hibernate的时候Driver可能会申请page,这些page也需要保存。

create_image

这里就是创建image的地方,因为我们以及预先分配了page,我们需要将需要保存的page拷贝到分配好的page中。

Kernel version V5.18
static int create_image(int platform_mode)
{
	error = pm_sleep_disable_secondary_cpus(); //关SMP,只留一个CPU
	
	save_processor_state(); //架构相关,保存CPU的当前的状态
	error = swsusp_arch_suspend(); //架构相关,生成image

}

swsusp_save


swsusp_arch_suspend()是架构相关的,最终会调用到swsusp_save()
Kernel version V5.18
int swsusp_save(unsigned int flags)
{
	nr_pages = count_data_pages();
	nr_highmem = count_highmem_pages();

	if (!enough_free_mem(nr_pages, nr_highmem)) {
		pr_err("Not enough free memory\n");
		return -ENOMEM;
	}

	if (swsusp_alloc(&copy_bm, nr_pages, nr_highmem)) {
		pr_err("Memory allocation failed\n");
		return -ENOMEM;
	}

	drain_local_pages(NULL);
	copy_data_pages(&copy_bm, &orig_bm);

	/*
	 * End of critical section. From now on, we can write to memory,
	 * but we should not touch disk. This specially means we must _not_
	 * touch swap space! Except we must write out our image of course.
	 */

	nr_pages += nr_highmem;
	nr_copy_pages = nr_pages;
	nr_meta_pages = DIV_ROUND_UP(nr_pages * sizeof(long), PAGE_SIZE);

	pr_info("Image created (%d pages copied)\n", nr_pages);

}

  这个函数也很简单,因为Driver hibernate的可能会申请一些page,所以需要重新计算需要保存的page。
  然后orig_bm的bitmap中对需要保存page置位,copy_bm在分配page的时候会也被置位过,所以现在只要把orig_bm置位过的page拷贝到copy_bm置位的page中就行了。

swsusp_write

  这个函数不会还要再讲把,作用就是把上面的image写入swap分区里~,很简单的。

总结

Driver可优化的地方
  1.S4 hibernate会冻结用户层和内核层的线程,内核层冻结如果用时超过0.001S是需要优化的。
  2.预分配之后Driver额外使用大量的page,造成image大小增加,增加image写入disk的时间。这个其实也蛮难优化,可以考虑free一些cache page,以及减少内存的使用,或者考虑使用shmem?
  3.Driver电源管理回调耗时,计算一下各回调的时间,找一下哪些耗时。

Driver不可优化/难优化的:
  1.预分配阶段分配大量的page会耗时间
  2.文件系统同步

其他的有空再完善吧~

引用

http://www.wowotech.net/pm_subsystem/hibernation.html
里面有两个hibernate和resume流程的pdf,可以参考一下

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值