Linux源码 | PM Qos源码剖析
本文是基于linux kernel 5.15.41
QoS(Quality Of Service),最大限度地满足多个子系统的需求。具体的数据结构和函数声明在/include/linux/pm_qos.h,主要分成两类,系统级(PM QoS classes framework,/kernel/power/qos.c)和设备级(per-device PM QoS framework,/drivers/base/power/qos.c),这两者有共同的实现流程,都是将需求进行分类计算极值,并向执行者提供需求查询接口,但是作用的场合不同。
- 系统级主要是限制cpu&dma latency、cpu frequecy;
- 设备级主要是限制resume_latency、active状态的latency_tolerance和QoS flag(如:no power off(不允许设备断电)/remote wakeup(设备应具备唤醒功能));
PM QoS classes framework
PM Qos( /kernel/power/qos.c)底层原理是维护一个双向的优先级链表plist,优先级从大到小排序,即prio值从小到大,prio值相同的节点按先插入在前的顺序。
需求方:提出需求,如进程、driver等。
框架层:汇总所有需求方的需求,计算出极值。
执行方:需要满足所有的需求方的需求,如cpuidle、cpufreq。
Freq Qos
多个进程或者driver需要设置max frequency,如果是直接设置scaling_max_freq(/sys/devices/system/cpu/cpufreq/policy*/),会存在覆盖的行为,无法满足所有需求,因此采用qos的方式。
在freq qos中,需求方的frequency需求作为prio值,把需求做为链表节点插入进去。
- 获取最大max frequency,执行方为满足所有的需求方,需要取极大值(PM_QOS_MAX),直接取链表尾即可。
- 获取最大min frequency,执行方为满足所有的需求方,需要取极小值(PM_QOS_MIN),直接取链表头即可。
这个plist是由prio_list和node_list组成
- prio_list:只链接优先级不同的node,相同优先级的node先进先出。
- node_list:链接所有的node。
freq qos公开API函数:
int freq_qos_add_request(struct freq_constraints *qos,
struct freq_qos_request *req,
enum freq_qos_req_type type, s32 value);
int freq_qos_update_request(struct freq_qos_request *req, s32 new_value);
int freq_qos_remove_request(struct freq_qos_request *req)
int freq_qos_add_notifier(struct freq_constraints *qos,
enum freq_qos_req_type type,
struct notifier_block *notifier);
int freq_qos_remove_notifier(struct freq_constraints *qos,
enum freq_qos_req_type type,
struct notifier_block *notifier);
1. freq_constraints_init
// 初始化freq_constraints
void freq_constraints_init(struct freq_constraints *qos)
{
struct pm_qos_constraints *c;
c = &qos->min_freq;
plist_head_init(&c->list);
c->target_value = FREQ_QOS_MIN_DEFAULT_VALUE;
c->default_value = FREQ_QOS_MIN_DEFAULT_VALUE;
c->no_constraint_value = FREQ_QOS_MIN_DEFAULT_VALUE;
//min_freq的初始化类型是PM_QOS_MAX
c->type = PM_QOS_MAX;
c->notifiers = &qos->min_freq_notifiers;
BLOCKING_INIT_NOTIFIER_HEAD(c->notifiers);
c = &qos->max_freq;
plist_head_init(&c->list);
c->target_value = FREQ_QOS_MAX_DEFAULT_VALUE;
c->default_value = FREQ_QOS_MAX_DEFAULT_VALUE;
c->no_constraint_value = FREQ_QOS_MAX_DEFAULT_VALUE;
//max_freq的初始化类型是PM_QOS_MIN
c->type = PM_QOS_MIN;
c->notifiers = &qos->max_freq_notifiers;
BLOCKING_INIT_NOTIFIER_HEAD(c->notifiers);
}
2. freq_qos_add_request
// 添加frequency的需求节点,插入优先级双向链表
freq_qos_add_request(struct freq_constraints *qos,struct freq_qos_request *req,enum freq_qos_req_type type, s32 value)
|-freq_qos_apply(req, PM_QOS_ADD_REQ, value);
|-case FREQ_QOS_MIN://如果req->type是FREQ_QOS_MIN,省略FREQ_QOS_MAX
|-pm_qos_update_target(&req->qos->min_freq, &req->pnode, action, value);
|-case PM_QOS_ADD_REQ:
|-plist_node_init(node, new_value);//创建初始化node,prio值为new_value
|-plist_add(node, &c->list);//插入优先级双向链表,优先级从大到小排序,高优先级(prio值小)在前面
|-curr_value = pm_qos_get_value(c);//获取当前值
|-pm_qos_set_value(c, curr_value);//设置c->target_value值
// 添加frequency的需求节点,插入优先级双向链表
int freq_qos_add_request(struct freq_constraints *qos,
struct freq_qos_request *req,
enum freq_qos_req_type type, s32 value)
{
int ret;
if (IS_ERR_OR_NULL(qos) || !req)
return -EINVAL;
if (WARN(freq_qos_request_active(req),
"%s() called for active request\n", __func__))
return -EINVAL;
req->qos = qos;
req->type = type;
ret = freq_qos_apply(req, PM_QOS_ADD_REQ, value);
if (ret < 0) {
req->qos = NULL;
req->type = 0;
}
return ret;
}
EXPORT_SYMBOL_GPL(freq_qos_add_request);
// 添加/修改/删除频率 QoS 请求
int freq_qos_apply(struct freq_qos_request *req,
enum pm_qos_req_action action, s32 value)
{
int ret;
switch(req->type) {
case FREQ_QOS_MIN:
ret = pm_qos_update_target(&req->qos->min_freq, &req->pnode,
action, value);
break;
case FREQ_QOS_MAX:
ret = pm_qos_update_target(&req->qos->max_freq, &req->pnode,
action, value);
break;
default:
ret = -EINVAL;
}
return ret;
}
//更新PM QoS 约束请求链表
int pm_qos_update_target(struct pm_qos_constraints *c, struct plist_node *node,
enum pm_qos_req_action action, int value)
{
int prev_value, curr_value, new_value;
unsigned long flags;
spin_lock_irqsave(&pm_qos_lock, flags);
//获取上一个value
prev_value = pm_qos_get_value(c);
if (value == PM_QOS_DEFAULT_VALUE)
new_value = c->default_value;
else
new_value = value;
switch (action) {
case PM_QOS_REMOVE_REQ:
plist_del(node, &c->list);//在链表中删除node节点
break;
case PM_QOS_UPDATE_REQ:
/*
* To change the list, atomically remove, reinit with new value
* and add, then see if the aggregate has changed.
*/
plist_del(node, &c->list);//在链表中删除node节点
fallthrough;//在C17中,没有break,会warning,消除warning
case PM_QOS_ADD_REQ:
plist_node_init(node, new_value);//创建初始化node,prio值为new_value
plist_add(node, &c->list);//插入优先级双向链表,优先级从大到小排序,优先级越高,prio值小。
break;
default:
/* no action */
;
}
//获取当前值
curr_value = pm_qos_get_value(c);
//设置c->target_value值
pm_qos_set_value(c, curr_value);
spin_unlock_irqrestore(&pm_qos_lock, flags);
trace_pm_qos_update_target(action, prev_value, curr_value);
if (prev_value == curr_value)
return 0;
//当value发生改变时,将当前的值通知所有节点
if (c->notifiers)
blocking_notifier_call_chain(c->notifiers, curr_value, NULL);
return 1;
}
static int pm_qos_get_value(struct pm_qos_constraints *c)
{
if (plist_head_empty(&c->list))
return c->no_constraint_value;
switch (c->type) {
case PM_QOS_MIN:
return plist_first(&c->list)->prio;//获取链表头,即prio最小
case PM_QOS_MAX:
return plist_last(&c->list)->prio;//获取链表尾,即prio最大
default:
WARN(1, "Unknown PM QoS type in %s\n", __func__);
return PM_QOS_DEFAULT_VALUE;
}
}
3. freq_qos_update_request
//更新现有freq QoS需求
int freq_qos_update_request(struct freq_qos_request *req, s32 new_value)
{
if (!req)
return -EINVAL;
if (WARN(!freq_qos_request_active(req),
"%s() called for unknown object\n", __func__))
return -EINVAL;
// 更新prio值
if (req->pnode.prio == new_value)
return 0;
//先删除plist_node,再添加新的node
return freq_qos_apply(req, PM_QOS_UPDATE_REQ, new_value);
}
EXPORT_SYMBOL_GPL(freq_qos_update_request);
4. freq_qos_remove_request
//从plist中删除freq QoS请求
int freq_qos_remove_request(struct freq_qos_request *req)
{
int ret;
if (!req)
return -EINVAL;
if (WARN(!freq_qos_request_active(req),
"%s() called for unknown object\n", __func__))
return -EINVAL;
ret = freq_qos_apply(req, PM_QOS_REMOVE_REQ, PM_QOS_DEFAULT_VALUE);
req->qos = NULL;
req->type = 0;
return ret;
}
EXPORT_SYMBOL_GPL(freq_qos_remove_request);
5. freq_qos_add_notifier
//注册freq QoS更改通知
int freq_qos_add_notifier(struct freq_constraints *qos,
enum freq_qos_req_type type,
struct notifier_block *notifier)
{
int ret;
if (IS_ERR_OR_NULL(qos) || !notifier)
return -EINVAL;
switch (type) {
case FREQ_QOS_MIN:
// 内核通知链机制,订阅/发布
ret = blocking_notifier_chain_register(qos->min_freq.notifiers,
notifier);
break;
case FREQ_QOS_MAX:
ret = blocking_notifier_chain_register(qos->max_freq.notifiers,
notifier);
break;
default:
WARN_ON(1);
ret = -EINVAL;
}
return ret;
}
EXPORT_SYMBOL_GPL(freq_qos_add_notifier);
6. freq_qos_remove_notifier
//删除freq QoS更改通知
int freq_qos_remove_notifier(struct freq_constraints *qos,
enum freq_qos_req_type type,
struct notifier_block *notifier)
{
int ret;
if (IS_ERR_OR_NULL(qos) || !notifier)
return -EINVAL;
switch (type) {
case FREQ_QOS_MIN:
ret = blocking_notifier_chain_unregister(qos->min_freq.notifiers,
notifier);
break;
case FREQ_QOS_MAX:
ret = blocking_notifier_chain_unregister(qos->max_freq.notifiers,
notifier);
break;
default:
WARN_ON(1);
ret = -EINVAL;
}
return ret;
}
EXPORT_SYMBOL_GPL(freq_qos_remove_notifier);