• 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
    		}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

      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(); //恢复控制台
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

      主要就是调用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");
    
    
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129

    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
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    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);
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

      这个函数也很简单,因为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,可以参考一下

  • 相关阅读:
    独有且优质,这些Mac软件绝了
    hive工具-zeppelin部署
    ElasticSearch讲解(开源的、高拓展的分布式全文检索引擎)
    【最接地气的KMP算法】带着问题思考,带你彻底搞懂KMP算法中难以理解的点!
    Linux的常用指令用法
    JavaGUI——Java图形用户界面
    10_springAOP基础_JDK动态代理
    目前看过最全的一线大厂面试题(题 + 详解),你所不知道的都在这
    Kafka源码简要分析
    golang后端开发非常成熟好用的总结的几个高频使用库快速实现开发
  • 原文地址:https://blog.csdn.net/qq_40937426/article/details/128119109