Driver的S3睡眠时间太慢了,原因找到了,但是S4休眠时间也比友商慢,S4要比S3复杂一点,中间涉及到kernel image的生成和写入disk,这部分花的时间挺多,所以想办法看能不能S4优化一下。
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()
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里。
这个是最恶心的函数,它这代码一写的看起来很绕。
在看之前先补充一点知识:
/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也需要保存。
这里就是创建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_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(©_bm, nr_pages, nr_highmem)) {
pr_err("Memory allocation failed\n");
return -ENOMEM;
}
drain_local_pages(NULL);
copy_data_pages(©_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中就行了。
这个函数不会还要再讲把,作用就是把上面的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,可以参考一下