一、 PM_QoS引入的背景
QoS(Quality Of Service,服务质量),大多数时候都是网络领域的名词,指网络能够利用各种基础技术,为指定的网络通信提供更好的服务,解决延迟、网络阻塞等问题的技术。它属于一种安全机制,用于保证整体的质量。广义地讲,它可以简化为对一个服务的满意程度。在Linux的电源管理领域,不管是suspend、Runtime PM还是idle等等,它的一个主要功能就是省功耗,在没必要的时候器件该休眠的休眠,该下电的下电,clk该关的关,但是有一点,需要省到什么程度呢,不可能一味地偏向功耗,比如在usb传输的时候把dma给限制了,导致传输速率下降,这些是不希望看到的,如果把Linux PM当做一种服务,那么他对其他模块的影响就类比为服务的质量,要满足其他指标不受到影响的情况下最大化的省电,这才是最终目标。那么这里PM QoS的作用就是定义一套框架,以满足系统(如设备驱动等)对QoS的期望为终极目标,通俗的讲:根据实际场景,这些期望可以描述为:xxx不大于某个值等等。
二、PM QoS的概述和框架
上面大致介绍了下PM QoS的一个概念,那么问题来了,目前有哪些质量指标呢,对这些指标是如何去满足的呢。
问题一:PM QoS有哪些指标
A:针对这个指标PM QoS有一个专门的名词:constraint(约束),其实也比较好理解,可以看做其他模块对PM的诉求和限制,目前PM QoS针对这些指标分为两类:一类是系统级的,包括:cpu&dma latency、network latency、network throughput和memory bandwidth,定义在kernel/power/qos.c中(在最新的kernel-5.4版本中,这一类constraint从4个缩减到只有cpu&dma latency这一个,同时增加了一类cpufreq的constraint,具体链接见文末),本文是以kernel-4.19为例展开介绍;另外一类是device层级的,针对各个device在具体场合的latency(主要是从低功耗状态resume的latency和active 状态的latency),定义在drivers/base/power/qos.c。
问题二:这些指标该如何去满足呢
A:这里就要涉及到后面提到的PM QoS框架,简单来说就是三方协作,
需求方:如进程、drivers等,它们根据自身的特性提出一系列需求(如cpu&dma latency等),
框架层:PM QoS framework对这些需求进行汇总,根据实际情况,计算出来极值(比如cpu&dma的latency不能小于xx等),
执行方:执行方需要确保自身的行为可以满足这些需求,这里的执行方很容易想到都是电源管理相关的,比如cpuidle,需要确保cpuidle等级满足cpu&dma latency的最低要求等等,可以参考下第三章的实例。
1. 整体框架
根据上面提到的PM QoS指标的类别,kernel提供了两个不同的QoS framework:一个是系统级的(称作PM QoS classes framework),一个是device级的(称作per-device PM QoS framework),两者的作用的场合不同,但是有着共同的实现流程:向一系列的需求方提供需求的add/update /remove等接口,将需求进行分类计算极值;然后向执行方提供这些需求的查询接口等,两种实现方式类似,本篇仅介绍其中的PM QoS classes framework,另外系统级的Qos framework通过misc设备,向用户空间提供了一套接口,以方便用户空间对PM QoS的需求,具体在:
/dev/cpu_dma_latency
/dev/network_latency
/dev/network_throughput
/dev/memory_bandwidth
整体的框架如下:
2. 主体数据结构
上面提到三方协作,这里重点介绍其中PM QoS framework的部分。
PM QoS framework主要工作是汇总需求方诉求,将这些需求分类汇总计算出极值,汇总的时候就自然涉及到为各需求方封装add/update/remove等接口,在介绍这些关键接口之前先说下PM QoS framework涉及到的一些主要数据结构:
1) struct pm_qos_request,pm qos request句柄,用于request的add/update/remove等操作
这个结构体主要看下后面三个变量:
node,这里面是用的struct plist_node类型来定义的,这里比较巧妙,plist_node类型除了双向链表的通用功能外还定义了一个prio变量,这个变量刚好可以保存request的value值,而plist_node链表在添加一个新的node的时候是会按照prio的数值按照进行升序排序,即prio最小的优先级越高在链表越靠前位置,这样在取指标的极小或者极大值的时候就更便捷了。
pm_qos_class,就是上面这些枚举的值,定义了系统级的一些PM QoS指标。
delay work,用于实现pm_qos_update_request_timeout接口。
2) struct pm_qos_constraints,pm qos约束,用于抽象某一个特定的PM QoS class。
list,所有该class的需求都会挂在这个链表。
target_value、default_value,分别是该指标的目标值(满足所有需求的value,可以是极大值或者极小值等,某一个指标关注的是极大值还是极小值在初始化的时候已经确定),默认值(该指标的默认值,通常是0,表示没有限制)。
type,主要是表示这几个指标极值的类型,极大/极小/求和,比如cpu_dma_latency的极值就是取极小值,这部分在各个contraints初始化的时候已经确定好。
notifiers,用于constraint value改变时通知其它driver。
3) struct pm_qos_object,在给每个class定义pm_qos_constraints结构体的同时也为每个class定义了miscdev变量,用于给用户空间提供接口。
3. 主体API:
主要的数据结构介绍完了,下面介绍下PM QoS framework用到的几个主要函数接口,像上面提到的一样,这些接口主要实现各类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)
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的基础上多出来一个定时器,用于特定需求的延迟更新
void pm_qos_remove_request(struct pm_qos_request *req)
1) pm_qos_add_request,该接口用于向PM QoS framework添加一个QoS请求,主要是根据指定的pm_qos_class,向pm_qos_class链表中插入一个新的pm_qos_request节点,并且更新target value。
pm_qos_add_request
将里面pm_qos_update_target函数单独拿出来如下:
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的回调函数,通知该实体。
三、PM_QoS的用法
上面两个章节主要介绍了PM QoS的一些背景、框架和实现原理,那么在实际工作中该怎么使用呢,下面以一个实例来介绍下。
pm qos framework对外的主要接口有
pm_qos_add_request/pm_qos_update_request/pm_qos_remove_request----kernel drivers等
pm_qos_add_notifier/ pm_qos_remove_notifier/pm_qos_request----PM service等
/dev/cpu_dma_latency等----user space
以pm qos class其中一个指标CPU_DMA_LATENCY为例(路径:drivers/media/platform/via-camera.c)
在启动camera的时候,这里请求了一个cpu_dma_latency的指标,为50us,即camera driver申请的cpu_dma允许的延迟时间不能超过50us,对于cpuidle idle来说,一般有C1~C3几个等级,在C3等级的退出延迟时间是57us(不同平台会有差别),那么这里camera driver需求的50us容忍延迟就可以让cpuidle退到C2 idle等级(即前面章节提到的执行方需要确保自身的行为满足这些pm qos的需求),就不会导致上面说的DMA transfer gets corrupted的问题了。
参考文献:
1、http://www.wowotech.net/tag/pm_qos
2、kernel-5.4源码:
https://android.googlesource.com/kernel/common/+/refs/heads/android11-5.4
3、kernel-4.19源码:
https://android.googlesource.com/kernel/common/+/refs/heads/android-4.19-stable
长按关注
内核工匠微信
Linux 内核黑科技 | 技术文章 | 精选教程