1. 前言
回顾上一篇文章(Linux PM QoS framework(1)_概述和软件架构),PM QoS framework抽象出4个系统级别的QoS constraint(统称为PM QoS class),分别是cpu&dma latency、network latency、network throughput和memory bandwidth。并提供一系列的接口,动态的搜集、整理系统对这些constraint的需求情况。
2. API汇整
PM QoS class framework提供的API有2类:一类是以函数调用的形式,为kernel space的driver、service等提供的;另一类是以misc设备的形式,为用户空间进程提供的。
2.1 向kernel其它driver提供的,用于提出PM QoS需求的API
EXPORT_SYMBOL_GPL(cpu_latency_qos_request_active);
EXPORT_SYMBOL_GPL(cpu_latency_qos_add_request);
EXPORT_SYMBOL_GPL(cpu_latency_qos_update_request);
EXPORT_SYMBOL_GPL(cpu_latency_qos_remove_request);
EXPORT_SYMBOL_GPL(freq_qos_add_request);
EXPORT_SYMBOL_GPL(freq_qos_update_request);
EXPORT_SYMBOL_GPL(freq_qos_remove_request);
EXPORT_SYMBOL_GPL(freq_qos_add_notifier);
- cpu_latency_qos_add_request
/**
* cpu_latency_qos_add_request - 添加新的CPU延迟QoS请求。
* @req: 指向预分配的句柄的指针。
* @value: 请求的约束值。
*
* 使用@value来初始化由@req指向的请求句柄,将其作为新条目插入到CPU延迟QoS列表中,
* 并重新计算该列表的有效QoS约束。
*
* 调用者需要保存该句柄,以便以后在更新和移除其表示的QoS请求时使用。
*/
void cpu_latency_qos_add_request(struct pm_qos_request *req, s32 value)
{
// 检查请求句柄是否为空
if (!req)
return;
// 检查是否已经添加过该请求句柄
if (cpu_latency_qos_request_active(req)) {
// 如果已经添加过,发出警告并直接返回
WARN(1, KERN_ERR "%s called for already added request\n", __func__);
return;
}
// 记录添加QoS请求的追踪信息
trace_pm_qos_add_request(value);
// 将请求句柄与CPU延迟约束列表关联
req->qos = &cpu_latency_constraints;
// 调用函数,将请求添加到QoS列表并重新计算约束
cpu_latency_qos_apply(req, PM_QOS_ADD_REQ, value);
}
EXPORT_SYMBOL_GPL(cpu_latency_qos_add_request);
总结:这个函数用于添加新的CPU延迟QoS请求。它会检查是否已经添加过该请求,如果没有则将其添加到QoS列表,并与相关约束关联。这允许应用程序在运行时指定其对CPU响应时间的要求。
举例子:一个需要实时数据处理的应用程序,如音频处理应用,可能会在启动时调用此函数,通过请求较低的CPU延迟,以确保它能够在实时性要求下处理数据。如果在后续运行中需要更改这一要求,可以使用保存的请求句柄更新约束。例如,实时音频应用可能在处理音频流时需要更低的CPU延迟,因此可以在每次处理音频前调用此函数来更新延迟约束。
/* the ipw2100 hardware really doesn't want power management delays
* longer than 175usec
*/
cpu_latency_qos_update_request(&ipw2100_pm_qos_req, 175);
另外,为了便于对已添加请求的维护(修改、移除等),framework会将该请求保存在一个句柄中,就是第一个参数–struct pm_qos_request指针。调用者不需要知道该结构的具体内容,只要定义一个变量,并把指针传给pm_qos_add_request接口,以后使用该指针进行修改、移除等操作。
- cpu_latency_qos_update_request
如果应用场景改变(如串口波特率变大,相应的响应延迟就要变小),可以通过该接口更新QoS请求。req为句柄指针,new_value为新的value。
3)cpu_latency_qos_remove_request
如果对该pm qos class不再有要求,则可以调用该接口将请求移除。
- cpu_latency_qos_request_active
该接口可以获取某一个QoS请求的active状态。
EXPORT_SYMBOL_GPL(freq_qos_add_request);
EXPORT_SYMBOL_GPL(freq_qos_update_request);
EXPORT_SYMBOL_GPL(freq_qos_remove_request);
EXPORT_SYMBOL_GPL(freq_qos_add_notifier);
EXPORT_SYMBOL_GPL(freq_qos_remove_notifier);
/**
* freq_qos_add_request - 将新的频率 QoS 请求插入给定的列表中。
* @qos: 要更新的约束条件。
* @req: 预分配的请求对象。
* @type: 请求类型。
* @value: 请求的值。
*
* 在 @qos 请求列表中插入新的条目,重新计算该列表的有效 QoS 约束值,并初始化 @req 对象。
* 调用者需要保存该对象以便在以后进行更新和删除操作。
*
* 如果有效约束值已更改,返回1;如果有效约束值未更改,返回0;如果发生错误,返回负错误代码。
*/
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 || value < 0)
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;
}
// 记录请求的添加操作的跟踪信息
trace_android_vh_freq_qos_add_request(qos, req, type, value, ret);
// 返回操作结果
return ret;
}
EXPORT_SYMBOL_GPL(freq_qos_add_request);
这个函数用于添加新的频率 QoS 请求到约束条件列表中,并根据情况更新约束条件的有效值。
假设系统中有一个 CPU 频率管理模块,可以通过此函数向该模块添加频率约束请求。例如,一个需要较低频率的任务可以调用此函数将一个请求添加到约束条件列表中,以确保系统在任务运行期间维持较低的 CPU 频率,以节省能源。
drivers/thermal/cpufreq_cooling.c
ret = freq_qos_add_request(&policy->constraints,
&cpufreq_cdev->qos_req, FREQ_QOS_MAX,
get_state_freq(cpufreq_cdev, 0));
ret = freq_qos_update_request(&cpufreq_cdev->qos_req, frequency);
/**
* freq_qos_add_notifier - 添加频率 QoS 变化通知器。
* @qos: 要添加通知器的请求列表。
* @type: 请求类型。
* @notifier: 要添加的通知器块。
*/
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);
这个函数用于向指定类型的频率 QoS 请求添加一个通知器,以便在频率变化时得到通知。
freq_qos_add_notifier() 函数用于向频率 QoS 子系统添加新的通知器。它需要一个 struct freq_constraints 结构体作为参数,该结构体包含了请求的类型、值、目标设备等信息。该函数还需要一个 enum freq_qos_req_type 类型的参数 type,用于指定请求的类型。最后,它需要一个指向 struct notifier_block 结构体的指针 notifier,该结构体包含了通知器的回调函数和优先级等信息。
当 PM QoS 子系统中的最小(最大)频率请求发生变化时,该回调函数将被调用。具体来说,当系统资源紧张时,PM QoS 子系统可能会降低 CPU 的最小频率以节省功耗。在这种情况下,PM QoS 子系统将通知所有已注册的通知器,并调用它们的回调函数。因此,如果在示例中添加了一个名为 my_device 的新频率请求,并将其绑定到 my_notifier 通知器上,则当系统降低 CPU 的最小频率时,将调用 my_callback() 函数。
static struct notifier_block my_notifier = {
.notifier_call = my_callback,
};
int my_driver_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct cpufreq_policy *policy;
struct freq_constraints *constraints;
int ret;
policy = cpufreq_cpu_get(dev->id);
if (IS_ERR(policy)) {
dev_err(dev, "Failed to get CPUFreq policy\n");
return PTR_ERR(policy);
}
constraints = kzalloc(sizeof(*constraints), GFP_KERNEL);
if (!constraints) {
dev_err(dev, "Failed to allocate memory\n");
ret = -ENOMEM;
goto out_put_policy;
}
constraints->name = "my_device";
constraints->policy = policy;
ret = freq_qos_add_request(constraints, FREQ_QOS_MIN, NULL);
if (ret) {
dev_err(dev, "Failed to add frequency request\n");
goto out_free_constraints;
}
ret = freq_qos_add_notifier(constraints, FREQ_QOS_MIN, &my_notifier);
if (ret) {
dev_err(dev, "Failed to add notifier\n");
goto out_remove_request;
}
return 0;
out_remove_request:
freq_qos_remove_request(constraints, FREQ_QOS_MIN);
out_free_constraints:
kfree(constraints);
out_put_policy:
cpufreq_cpu_put(policy);
return ret;
}
假设在一个移动设备上,有一个需要在 CPU 频率发生变化时进行某些操作的模块。例如,当 CPU 频率降低到节能模式时,可能需要通知某个模块降低性能要求以节省电量。通过调用此函数,该模块可以向最小频率约束条件请求添加一个通知器,以便在最小频率发生变化时得到通知。这样,模块可以根据通知调整自身的性能要求。
2.3 向per-device PM QoS framework提供,low level的PM QoS操作API
int pm_qos_update_target(struct pm_qos_constraints *c, struct plist_node *node,
enum pm_qos_req_action action, int value);
bool pm_qos_update_flags(struct pm_qos_flags *pqf,
struct pm_qos_flags_request *req,
enum pm_qos_req_action action, s32 val);
s32 pm_qos_read_value(struct pm_qos_constraints *c);
2.4 向用户空间process提供的,用于提出QoS需求的API
根据不同的PM QoS class,包括(cpu&dma latency)
/dev/cpu_dma_latency
打开文件,将会使用默认值向PM QoS framework添加一个QoS请求;关闭文件,会移除相应的请求;写入value,更改请求的值;读取文件,将会获取QoS的极值。
具体和2.1中的各个接口类似,不再详细说明了。
3. 实现思路和内部逻辑
3.1 主要数据结构
1)struct pm_qos_request,pm qos request句柄,用于request的add、update、remove等操作
struct pm_qos_request {
struct plist_node node;
struct pm_qos_constraints *qos;
};
2)struct pm_qos_constraints,pm qos的内部抽象,用于抽象某一特定的PM QoS class
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;
};
list,链表头,所有该class的request,都会挂到该list上;
target_value,该constraint的目标值,即可以满足所有该class的request那个value。通常情况下,根据request的类型(enum pm_qos_type),可以是所有request中的最大值,所有request中的最小值,或者所有request的和;
default_value,该constraint的默认值,通常为0,表示没有限制(或没有要求);
no_constraint_value,当该class的qos不存在请求时,pm_qos_get_value返回的值,通常为默认值,表示没有限制(或没有要求);
type,该constraint的类型,具体请参考下面的描述;
notifiers,用于constraint value改变时通知其它driver。
enum pm_qos_type {
PM_QOS_UNITIALIZED,
PM_QOS_MAX, /* return the largest value */
PM_QOS_MIN, /* return the smallest value */
};
enum pm_qos_type包括PM_QOS_MAX、PM_QOS_MIN。PM_QOS_MAX表示在所有的request中取最大值,即可满足所有的request,如network_throughput;PM_QOS_MIN表示在所有的request中取最小值,即可满足所有的request,如cpu_dma_latency;
当调用pm_qos_get_value接口时,framework会更具qos type,从list head中,取最小值、最大值。
3.2 实现逻辑
QoS class framework为每个class定义了一个全局的struct pm_qos_constraints变量,用于保存所有该class的request。同时为每个class定义一个misc device变量,用于向用户空间提供接口。最终,将这些信息组织在一个内部的数据结构中(struct pm_qos_object),如下(具体内容可参考kernel/power/qos.c,这里不再详细介绍):
misc设备注册
当kernel会在QoS class framework的初始化接口(cpu_latency_qos_init)中,调用misc_register,将各个class的miscdevice变量,注册到kernel中。misc设备提供了open、release、read、write等接口(pm_qos_power_fops,具体可参考源文件),用于qos的request、update和remove。