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属性的bitmapserror = hibernation_snapshot(hibernation_mode == HIBERNATION_PLATFORM); //分配image所需的pageif (in_suspend) {pm_pr_dbg("Writing hibernation image.\n");error = swsusp_write(flags); /*将image的page写入swap分区*/swsusp_free(); // 释放image的pageif (!error) {if (hibernation_mode == HIBERNATION_TEST_RESUME) //如果Kernel S4有问题,可以测试是Software的问题还是Hardware和BIOS Firmware的问题snapshot_test = true; elsepower_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所需的pageerror = freeze_kernel_threads(); //冻结内核线程error = dpm_prepare(PMSG_FREEZE); //执行所有device的prepare电源管理回调suspend_console(); //挂起consoleerror = dpm_suspend(PMSG_FREEZE); //执行所有device的freeze电源管理回调if (error || hibernation_test(TEST_DEVICES))platform_recover(platform_mode);elseerror = create_image(platform_mode); //创建imagemsg = 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 Codestruct 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 pagesaveable = 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; //所有应该保存的pagehighmem = 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 pageelsecount += 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) { //并且有这么多空间保存imagepages = 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;elseavail_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;elsealloc = 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,只留一个CPUsave_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,可以参考一下