电源管理入门:PM QoS框架介绍

电子说

1.3w人已加入

描述

QoS(Quality Of Service,服务质量),一般在网络报文中,某个报文的优先级比较高优先传输,例如我们觉得微信聊天比看网页更重要,我们就可以提高微信报文的等级即服务质量QoS,来提供好的网络服务解决延迟、网络阻塞等问题。

在电源管理里面的策略就各种governor,例如什么时候进入cpuidle、什么时候DevFreq等,这些策略是很单一的,算法也都是很单调的,未考虑到消费者的实际需求,那怎么样把用户的需求也在电源管理里面生效呢?

答案就是在governor中引入QoS,在governor中会去查QoS的策略,综合起来进行决策

1. 系统框架介绍

1.1 功耗控制可能影响用户体验的一些痛点

而且功耗管理会引入对性能的缺点,主要两方面:

延时(latency)增加:时间的开销,尤其是在恢复的过程中需要时间。比如,系统唤醒需要经过各驱动的恢复,power domain的上电过程也有时间开销。

吞吐量(Throughput)减少:低功耗也会带来算力的影响,会降低算力及网路的吞吐量。比如,cpu dvfs、cpu hotplug、cpu idle等会影响到cpu算力。

比如在usb传输的时候把dma给限制了,导致传输速率下降,这些是用户不希望看到的。

又比如延时和性能开销,影响到用户的体验,比如界面操作不流畅、卡顿,响应时间过长。

这就像苹果手机很流畅优先响应用户的需求,安卓可能更高效但是有点卡,用户就觉得屏幕划不动了。但是很明显苹果手机更有市场,用户体验才是王道

在面对用户场景的情况下,我们需要在策略中考虑到用户使用感受。在用户眼里更多的看中应用服务,而且不是一味的强调功耗低,为了核心业务和用户体验是可以适当的牺牲功耗的,产品做出来最终还是要用户用的,技术再好,功耗再低,不满足用户习惯就是0.

如果把Linux PM当做一种服务,那么他对其他模块的影响就类比为服务的质量,要满足其他指标不受到影响的情况下最大化的省电,这才是最终目标。那么这里PM QoS的作用就是定义一套框架,以满足系统(如设备驱动等)对QoS的期望为终极目标,通俗的讲:根据实际场景,这些期望可以描述为:xxx不大于某个值等等。

1.2 QoS框架

内核

PM QOS使用constraint(约束)作为指标,用于各模块对PM的诉求及限制。当前系统的指标主要有两类,分别对应两个PM QOS framework。

系统级constraint:包括cpu&dma latency(5.4内核),它的实际意义是,当产生一个事件之后(如一个中断),CPU或DMA的响应延迟。例如有些CPU的串口控制器,只有几个byte的FIFO,当接收数据时,CPU或DMA必须在FIFO填满前,将数据读走,否则就可能丢失数据或者降低数据的传输速率。由PM QoS classes framework管理,定义在kernel/power/qos.c中。

设备级constraint:包括从低功耗状态resume的latency、active状态的latency和一些QoS flag(如是否允许power off)。由per-device PM QoS framework管理,定义在drivers/base/power/qos.c。

内核

整个PM QOS框架分为三部分:

需求方:各service、各driver。他们根据自己的功能需求,提出系统或某些功能的QOS约束,比如cpu&dma latency。

框架层:PM QOS framework,包含PM QOS classes、per device PM QOS。向需求方提供request的add、modify、remove等接口,用于管理QoS requests。对需求方的约束进行分类,计算出极值,比如cpu&dma latency不小于某个值。向执行方提供request value的查询接口。PM QoS classes framework位于kernel/power/qos.c中,负责系统级别的PM QoS管理,通过misc设备(/dev/cpu_dma_latency),向用户空间程序提供PM QoS的request、modify、remove功能,以便满足各service对PM QoS的需求。per-device PM QoS framework位于drivers/base/power/qos.c中,负责per-device的PM QoS管理。

执行方:power management的机制,比如cpuidle、cpu dvfs等。需要满足由框架层根据需求方提供的约束计算的极值,才能执行相应的低功耗机制。

2. 用户空间操作流程

内核

2.2 用户空间数据结构和API

 

struct pm_qos_object {
struct pm_qos_constraints *constraints;
struct miscdevice pm_qos_power_miscdev;
char *name;
};

 

struct pm_qos_object,在给每个class定义pm_qos_constraints结构体的同时也为每个class定义了miscdev变量,用于给用户空间提供接口。

这些接口主要实现各类PM QoS需求的汇总和计算极值的工作:add/update/remove等,并且提供接口给到用户空间process,用于用户空间的QoS需求,另外还提供了一些notifier API,用于跟踪指定的PM QoS的变化。

主要API:

void pm_qos_add_request(struct pm_qos_request *req,int pm_qos_class, s32 value)

1)用于向PM QoS framework添加一个QoS请求,主要是根据指定的pm_qos_class,向pm_qos_class链表中插入一个新的pm_qos_request节点,并且更新target value。

内核

void pm_qos_update_request(struct pm_qos_request *req,s32 new_value)

内核

void pm_qos_update_request_timeout(struct pm_qos_request *req, s32 new_value, unsigned long timeout_us)//在update的基础上多出来一个定时器,用于特定需求的延迟更新

2) pm_qos_update_request/pm_qos_update_request_timeout,如果应用场景变化需要满足不同的要求(比如串口波特率变大,相应的响应延迟需要变小),则需要调用该接口来更新相应的qos请求。函数体的主要部分pm_qos_update_target和Add相似,这里就不再介绍。

3) pm_qos_remove_request,如果对该class没有需求,则可以调用该接口将请求移除。

4) 借助misc设备向用户空间提供的接口(open/read/write等),调用的接口和上面提到的add/remove等类似,这里就不再赘述。

5) pm_qos_add_notifier/ pm_qos_remove_notifier,有部分实体(如cpuidle,比较关注cpu_dma_latency的指标)会比较关注某一个pm qos class的target value的变化,kernel提供了这样一个notifier的机制,该实体可以通过pm_qos_add_notifier接口添加一个notifier,这样当value变化时,framework便会通过notifier的回调函数,通知该实体。

需求方:

内核

执行方:

内核

2.1 struct pm_qos_constraints

 

struct pm_qos_constraints {
struct plist_head list;
s32 target_value;/* Do not change to 64 bit */
s32 default_value;
s32 no_constraint_value;
enum pm_qos_type type;
struct blocking_notifier_head *notifiers;
};

struct pm_qos_request {
struct plist_node node;
int pm_qos_class;
struct delayed_work work; /* for pm_qos_update_request_timeout */
};

 

struct pm_qos_request用于request的add/update/remove等操作。

struct pm_qos_constraints,pm qos约束,用于抽象某一个特定的PM QoS class。

target_value、default_value,分别是该指标的目标值(满足所有需求的value,可以是极大值或者极小值等,某一个指标关注的是极大值还是极小值在初始化的时候已经确定),默认值(该指标的默认值,通常是0,表示没有限制)。

3. 初始化流程

内核

 

static int __init pm_qos_power_init(void)
{
for (i = PM_QOS_CPU_DMA_LATENCY; i < PM_QOS_NUM_CLASSES; i++) {
ret = register_pm_qos_misc(pm_qos_array[i], d);
if (ret < 0) {
printk(KERN_ERR "pm_qos_param: %s setup failed
",
       pm_qos_array[i]->name);
return ret;
}
}

 

系统支持的QOS类型:

 

enum {
PM_QOS_RESERVED = 0,
PM_QOS_CPU_DMA_LATENCY,
PM_QOS_NETWORK_LATENCY,
PM_QOS_NETWORK_THROUGHPUT,
PM_QOS_MEMORY_BANDWIDTH,

/* insert new class ID */
PM_QOS_NUM_CLASSES,
};

 

debugfs_create_file()会创建sysfs供用户空间调用。

4. DMA举例

例如启动摄像头的时候,我们系统即便在省电的情况下,也需要cpu_dma允许的延迟时间不能超过50us,否则影响画面质量。

drivers/media/platform/via-camera.c中

内核

pm_qos_add_request:在启动camera的时候,这里请求了一个cpu_dma_latency的指标,为50us,即camera driver申请的cpu_dma允许的延迟时间不能超过50us

cpuidle初始化的时候会调用:

 

static inline void latency_notifier_init(struct notifier_block *n)
{
pm_qos_add_notifier(PM_QOS_CPU_DMA_LATENCY, n);
}

 

PM_QOS_CPU_DMA_LATENCY变化的时候会通知cpuidle

在进行cpuidle决策的时候,例如ladder governor中,

 

static int ladder_select_state(struct cpuidle_driver *drv,
struct cpuidle_device *dev)
{
struct ladder_device *ldev = this_cpu_ptr(&ladder_devices);
struct ladder_device_state *last_state;
int last_residency, last_idx = ldev->last_state_idx;
int latency_req = pm_qos_request(PM_QOS_CPU_DMA_LATENCY);

 

pm_qos_request()函数会获取target_value,根据这个值来决定进行什么级别的idle。

对于cpuidle idle来说,一般有C1~C3几个等级,在C3等级的退出延迟时间是57us(不同平台会有差别),那么这里camera driver需求的50us容忍延迟就可以让cpuidle退到C2 idle等级(即前面章节提到的执行方需要确保自身的行为满足这些pm qos的需求),就不会导致上面说的DMA transfer gets corrupted的问题了。

后记

内核版本有时候差异也挺大的,一个机制,特别是小众的可能会有更新,我们在找资料的时候,就需要注意这点,找到合适的学习资料。不过主要的思想是不变的,变的就是结构体定义,api函数的调用流程等。

审核编辑:汤梓红

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分