zoukankan      html  css  js  c++  java
  • 电源管理

    极力推荐Android 开发大总结文章:欢迎收藏
    程序员Android 力荐 ,Android 开发者需要的必备技能

    本篇文章主要介绍 Android 开发中的电源管理部分知识点,本篇文章转载网络,通过阅读本篇文章,您将收获以下内容:

    1. Sleep/Suspend
    2. SPM
    3. wakeup 唤醒源

    原文地址:http://www.robinheztto.com/2017/04/20/android-power-basic/

    1. Sleep/Suspend

    系统休眠Sleep,Linux Kernel中称作Suspend。系统进入Suspend状态确切来说时CPU进入了Suspend模式,因为对整个系统来说CPU休眠是整个系统休眠的先决条件。CPU Suspend即CPU进入Wait for interrupt状态(WFI),SW完全不跑了,停在suspend workqueue里面。
    Android系统从灭屏到系统进入Suspend的大体流程框架如下:
    灭屏到系统进入Suspend的大体流程
    相关代码如下:

    /frameworks/base/services/core/java/com/android/server/power/PowerManagerService.java
    /frameworks/base/services/core/jni/com_android_server_power_PowerManagerService.cpp
    /system/core/libsuspend/
    /kernel-x.x/kernel/power/
    

    2.SPM

    SPM即System Power Manager,管理着包括AP,Modem,Connectivity等子系统。在CPU进入WFI状态后,整个系统就依靠SPM监控各个子系统的状态来控制睡眠/唤醒的流程。
    SPM控制Cpu Suspend之后系统是否能掉到最小电流,当系统的关键资源(memory、clock)没有任何使用的时候,它就会让系统进入一个真正的深睡状态(最小电流),但只要检测到有任何资源请求还没释放,系统就无法降到底电流。在底电流的debug流程中,不仅仅要看CPU有没有Suspend成功,还要看SPM的状态是否正确。

    MTK平台:
    CPU在进入WFI状态前会把SPM的firmware写入到SPM里的可编程控制器PCM中(Programmable Command Master),然后PCM就依据firmware的逻辑来控制SPM的工作。系统中存在32k,26M二个时钟,系统工作在最小电流的时候,SPM只依靠32K时钟工作,因此要判断系统是不是已经到深休状态,就要看26M有没有关闭。

    26M 时钟逻辑

    如上图所示,26M时钟有没有关,只需要看SCLKENA信号有没有关闭,而SPM对这个信号的输出以及子系统的信号输入,都记录在SPM的寄存器里面,这个就是我们通过log排查的依据。
    相关代码如下:

    /kernel-x.x/drivers/misc/mediatek/base/power/spm_vx/
    

    3. wakeup 唤醒源

    主要涉及以下代码

    kernel/msm-4.4/drivers/base/power/wakeup.c
    kernel/msm-4.4/include/linux/pm_wakeup.h
    kernel/msm-4.4/include/linux/pm.h
    kernel/msm-4.4/include/linux/device.h
    

    Android以Linux kernel为系统内核,其电源管理也是基于Linux电源管理子系统的,但是由于传统的Linux系统主要针对PC而非移动设备,对于PC系统,Linux默认是在用户不再使用时才再触发系统进入休眠(STR、Standby、Hibernate),但是对于移动设备的使用特点而言,并没有明确的不再使用设备的时候,用户随时随地都可能需要使用设备,于是在Android上就有提出了“Opportunistic suspend”的概念,即逮到机会就睡直到下次被唤醒,以适应移动设备的使用特点。

    早期,Android为解决该问题,在标准Linux的基础上增加了Early Suspend和Late Resume机制,Early suspend是在熄屏后,提前将一些不会用到的设备(比如背光、重力感应器和触摸屏等设备)关闭,但此时系统仍能持有wake lock后台运行。该方案将Linux原来suspend的流程改变,并增加了Android自己的处理函数,与kernel的流程与机制相冲突,另外一个问题就是存在suspend和wakeup events之间的同步问题,比如当系统进入suspend流程中,会进行freeze process,device prepared,device suspend,disabled irq等操作,如果在suspend流程中有wakeup events产生,而此时系统无法从suspend过程中唤醒。

    后来Linux加入wakeup events framework,包括wake lock、wakeup count、autosleep等机制。用来解决system suspend和system wakeup events之间的同步问题。同时在Android4.4中,Android中也去掉了之前的”wakelocks”机制,利用wakeup events framework重新设计了wakelocks,并且维持上层API不变。

    system suspend和system wakeup events之间的同步问题:

    • 内核空间的同步:
      wakeup events产生后,通常是以中断的形式通知device driver。driver会处理events,处理的过程中,系统不能suspend。
    • 用户空间的同步:
      一般情况下,driver对wakeup events处理后,会交给用户空间程序继续处理,处理的过程,也不允许suspend。这又可以分为两种情况:
      进行后续处理的用户进程,根本没有机会被调度,即该wakeup events无法上报到用户空间。
      进行后续处理的用户进程被调度,处理的过程中(以及处理结束后,决定终止suspend操作),系统不能suspend。(wake lock功能)
      wakeup events framework主要解决上述三个同步问题,内核空间的同步(framework的核心功能),用户空间的同步情形1(wakeup count功能),用户空间的同步情形2(wake lock功能)。

    代码分析
    下面是wakeup events framework的architecture图(来自蜗窝科技)

    wakeup events framework sysfs将设备的wakeup信息,以sysfs的形式提供到用户空间,供用户空间访问(在drivers/base/power/sysfs.c中实现)。

    wakeup source

    在kernel中,只有设备才能唤醒系统,但并不是所有设备都具备唤醒系统的能力,具备唤醒能力的设备即“wakeup source”,会在设备结构体struce device中标志该设备具有唤醒能力。
    kernel/msm-4.4/include/linux/device.h

    struct device {  
        ...  
        struct dev_pm_info  power;   
        struct dev_pm_domain    *pm_domain;  
        ...  
    }
    

    kernel/msm-4.4/include/linux/pm_wakeup.h

    static inline bool device_can_wakeup(struct device *dev)
    {
    	return dev->power.can_wakeup;
    }
    
    static inline bool device_may_wakeup(struct device *dev)
    {
    	return dev->power.can_wakeup && !!dev->power.wakeup;
    }
    

    由device_can_wakeup()函数可知,通过dev->power.can_wakeup来判断该设备是否能唤醒系统,struct dev_pm_info的定义如下:

    kernel/msm-4.4/include/linux/pm.h

    .....
    	unsigned int		can_wakeup:1;
      ......
    #ifdef CONFIG_PM_SLEEP
      ......
    	struct wakeup_source	*wakeup;
      ......
    #else
    	unsigned int		should_wakeup:1;
    #endif
      ......
    };
    

    struct dev_pm_info中的can_wakeup标识该设备是否具有唤醒能力。具备唤醒能力的设备在sys/devices/xxx/下存在power相关目录,用于提供所有的wakeup信息,这些信息是以struct wakeup_source的结构组织,即struct dev_pm_info中的wakeup指针。

    kernel/msm-4.4/include/linux/pm_wakeup.h

    /**
     * struct wakeup_source - Representation of wakeup sources
     *
     * @name: 唤醒源的名字
     * @entry: 用来将唤醒源挂到链表上,用于管理
     * @lock: 同步机制,用于访问链表时使用
     * @wakeirq:Optional device specific wakeirq
     * @timer: 定时器,用于设置该唤醒源的超时时间
     * @timer_expires:  定时器的超时时间
     * @total_time:  wakeup source处于active状态的总时间,可指示该wakeup source对应的设备的繁忙程度、耗电等级
     * @max_time: wakeup source处于active状态的最长时间(越长越不合理)
     * @last_time: wakeup source处于active状态的上次时间
     * @prevent_sleep_time: wakeup source阻止系统自动休眠的总时间
     * @event_count:  wakeup source上报wakeup event的个数
     * @active_count: wakeup source处于active状态的次数
     * @relax_count: wakeup source处于deactive状态的次数
     * @expire_count: wakeup source timeout次数
     * @wakeup_count: wakeup source abort睡眠的次数
     * @active: wakeup source的状态
     * @has_timeout: The wakeup source has been activated with a timeout.
     */
    struct wakeup_source {
    	const char 		*name;
    	struct list_head	entry;
    	spinlock_t		lock;
    	struct wake_irq		*wakeirq;
    	struct timer_list	timer;
    	unsigned long		timer_expires;
    	ktime_t total_time;
    	ktime_t max_time;
    	ktime_t last_time;
    	ktime_t start_prevent_time;
    	ktime_t prevent_sleep_time;
    	unsigned long		event_count;
    	unsigned long		active_count;
    	unsigned long		relax_count;
    	unsigned long		expire_count;
    	unsigned long		wakeup_count;
    	bool			active:1;
    	bool			autosleep_enabled:1;
    };
    

    wakeup source代表一个具有唤醒能力的设备,该设备产生的可以唤醒系统的事件,就称作wakeup event。当wakeup source产生wakeup event时,需要将wakeup source切换为activate状态;当wakeup event处理完毕后,要切换为deactivate状态。当wakeup source产生wakeup event时,需要切换到activate状态,但并不是每次都需要切换,因此有可能已经处于activate状态了。因此active_count可能小于event_count,换句话说,很有可能在前一个wakeup event没被处理完时,又产生了一个,这从一定程度上反映了wakeup source所代表的设备的繁忙程度。wakeup source在suspend过程中产生wakeup event的话,就会终止suspend过程,wakeup_count记录了wakeup source终止suspend过程的次数(如果发现系统总是suspend失败,检查一下各个wakeup source的该变量,就可以知道问题出在谁身上了)。
    为了方便查看系统的wakeup sources的信息,linux系统在/sys/kernel/debug下创建了一个”wakeup_sources”文件,此文件记录了系统的唤醒源的详细信息。

    kernel/msm-4.4/drivers/base/power/wakeup.c

    static int wakeup_sources_stats_show(struct seq_file *m, void *unused)
    {
    	struct wakeup_source *ws;
    
    	seq_puts(m, "name		active_count	event_count	wakeup_count	"
    		"expire_count	active_since	total_time	max_time	"
    		"last_change	prevent_suspend_time
    ");
    
    	rcu_read_lock();
    	list_for_each_entry_rcu(ws, &wakeup_sources, entry)
    		print_wakeup_source_stats(m, ws);
    	rcu_read_unlock();
    
    	print_wakeup_source_stats(m, &deleted_ws);
    
    	return 0;
    }
    
    static int wakeup_sources_stats_open(struct inode *inode, struct file *file)
    {
    	return single_open(file, wakeup_sources_stats_show, NULL);
    }
    
    static const struct file_operations wakeup_sources_stats_fops = {
    	.owner = THIS_MODULE,
    	.open = wakeup_sources_stats_open,
    	.read = seq_read,
    	.llseek = seq_lseek,
    	.release = single_release,
    };
    
    static int __init wakeup_sources_debugfs_init(void)
    {
    	wakeup_sources_stats_dentry = debugfs_create_file("wakeup_sources",
    			S_IRUGO, NULL, NULL, &wakeup_sources_stats_fops);
    	return 0;
    }
    
    postcore_initcall(wakeup_sources_debugfs_init);
    

    如下图示,通过cat /sys/kernel/debug/wakeup_sources获取wakeup_sources信息:


    机制
    wakeup events framework中抽象了wakeup source和wakeup event的概念,向各个device driver提供wakeup source的注册、使能等及wakeup event的上报、停止等接口,同时也向上层提供wakeup event的查询接口,以判断是否可以suspend或者是否需要终止正在进行的suspend。

    pm_stay_awake()

    当设备有wakeup event正在处理时,需要调用该接口通知PM core,该接口的实现如下:

    kernel/msm-4.4/drivers/base/power/wakeup.c

    void pm_stay_awake(struct device *dev)
    {
    	unsigned long flags;
    
    	if (!dev)
    		return;
    
    	spin_lock_irqsave(&dev->power.lock, flags);
    	__pm_stay_awake(dev->power.wakeup);
    	spin_unlock_irqrestore(&dev->power.lock, flags);
    }
    
    void __pm_stay_awake(struct wakeup_source *ws)
    {
    	unsigned long flags;
    
    	if (!ws)
    		return;
    
    	spin_lock_irqsave(&ws->lock, flags);
    
    	wakeup_source_report_event(ws);
    	del_timer(&ws->timer);
    	ws->timer_expires = 0;
    
    	spin_unlock_irqrestore(&ws->lock, flags);
    }
    
    static void wakeup_source_report_event(struct wakeup_source *ws)
    {
    	ws->event_count++;
    	/* This is racy, but the counter is approximate anyway. */
    	if (events_check_enabled)
    		ws->wakeup_count++;
    
    	if (!ws->active)
    		wakeup_source_activate(ws);
    }
    

    pm_stay_awake中直接调用pm_stay_awake,pm_stay_awake直接调用wakeup_source_report_event。
    kernel/msm-4.4/drivers/base/power/wakeup.c

    static void wakeup_source_report_event(struct wakeup_source *ws)
    {
    	ws->event_count++;
    	/* This is racy, but the counter is approximate anyway. */
    	if (events_check_enabled)
    		ws->wakeup_count++;
    
    	if (!ws->active)
    		wakeup_source_activate(ws);
    }
    

    wakeup_source_report_event中增加wakeup source的event_count次数,即表示该source又产生了一个event。然后根据events_check_enabled变量的状态,增加wakeup_count。如果wakeup source没有active,则调用wakeup_source_activate进行activate操作。

    kernel/msm-4.4/drivers/base/power/wakeup.c

    static void wakeup_source_activate(struct wakeup_source *ws)
    {
    	unsigned int cec;
    
    	/*
    	 * active wakeup source should bring the system
    	 * out of PM_SUSPEND_FREEZE state
    	 */
    	freeze_wake();
    
    	ws->active = true;
    	ws->active_count++;
    	ws->last_time = ktime_get();
    	if (ws->autosleep_enabled)
    		ws->start_prevent_time = ws->last_time;
    
    	/* Increment the counter of events in progress. */
    	cec = atomic_inc_return(&combined_event_count);
    
    	trace_wakeup_source_activate(ws->name, cec);
    }
    

    wakeup_source_activate中首先调用freeze_wake,将系统从suspend to freeze状态下唤醒,然后设置active标志,增加active_count,更新last_time。如果使能了autosleep,更新start_prevent_time,此刻该wakeup source会开始阻止系统auto sleep。增加“wakeup events in progress”计数,增加该计数意味着系统正在处理的wakeup event数目不为零,即系统不能suspend。

    pm_relax()
    pm_relaxpm_stay_awake成对出现,用于在wakeup event处理结束后通知PM core,其实现如下

    kernel/msm-4.4/drivers/base/power/wakeup.c

    void pm_relax(struct device *dev)
    {
    	unsigned long flags;
    
    	if (!dev)
    		return;
    
    	spin_lock_irqsave(&dev->power.lock, flags);
    	__pm_relax(dev->power.wakeup);
    	spin_unlock_irqrestore(&dev->power.lock, flags);
    }
    
    void __pm_relax(struct wakeup_source *ws)
    {
    	unsigned long flags;
    
    	if (!ws)
    		return;
    
    	spin_lock_irqsave(&ws->lock, flags);
    	if (ws->active)
    		wakeup_source_deactivate(ws);
    	spin_unlock_irqrestore(&ws->lock, flags);
    }
    

    pm_relax中直接调用pm_relax,pm_relax判断wakeup source如果处于active状态,则调用wakeup_source_deactivate接口,deactivate该wakeup source

    kernel/msm-4.4/drivers/base/power/wakeup.c

    static void wakeup_source_deactivate(struct wakeup_source *ws)
    {
    	unsigned int cnt, inpr, cec;
    	ktime_t duration;
    	ktime_t now;
    
    	ws->relax_count++;
    	if (ws->relax_count != ws->active_count) {
    		ws->relax_count--;
    		return;
    	}
    
    	ws->active = false;
    
    	now = ktime_get();
    	duration = ktime_sub(now, ws->last_time);
    	ws->total_time = ktime_add(ws->total_time, duration);
    	if (ktime_to_ns(duration) > ktime_to_ns(ws->max_time))
    		ws->max_time = duration;
    
    	ws->last_time = now;
    	del_timer(&ws->timer);
    	ws->timer_expires = 0;
    
    	if (ws->autosleep_enabled)
    		update_prevent_sleep_time(ws, now);
    
    	/*
    	 * Increment the counter of registered wakeup events and decrement the
    	 * couter of wakeup events in progress simultaneously.
    	 */
    	cec = atomic_add_return(MAX_IN_PROGRESS, &combined_event_count);
    	trace_wakeup_source_deactivate(ws->name, cec);
    
    	split_counters(&cnt, &inpr);
    	if (!inpr && waitqueue_active(&wakeup_count_wait_queue))
    		wake_up(&wakeup_count_wait_queue);
    }
    

    至此,本篇已结束,如有不对的地方,欢迎您的建议与指正。同时期待您的关注,感谢您的阅读,谢谢!

    微信关注公众号:  程序员Android,领福利

  • 相关阅读:
    让SiteMapDataSource能选择特定的SiteMap文件
    程序员之路──如何学习C语言(转载)
    Sql Server 2005 Express使用命令工具无法连接问题
    移远公司 NBIoT模块AT指令详细解释
    Excel自动化技术
    解析HTTP报文头
    SOAP报文下发
    双缓冲队列,生产者消费者模式
    夸孩子少用“你真棒”,教你如何夸孩子
    C++面试题相关
  • 原文地址:https://www.cnblogs.com/wangjie1990/p/11327020.html
Copyright © 2011-2022 走看看