电子说
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函数的调用流程等。
审核编辑:汤梓红
全部0条评论
快来发表一下你的评论吧 !