Linux 内核学习(4) --- devfreq 动态调频框架

目录

      • [Linux devfreq 简介](#Linux devfreq 简介)
      • 核心数据结构
        • [devfreq_dev_profile 结构体](#devfreq_dev_profile 结构体)
        • [devfreq_governor 结构体](#devfreq_governor 结构体)
        • [devfreq 结构体](#devfreq 结构体)
      • 工作流程
        • [devFreq framework 初始化](#devFreq framework 初始化)
        • [governor 初始化](#governor 初始化)
        • [devfreq Device 注册](#devfreq Device 注册)
        • 动态变频的实现
        • [device_unregister 流程](#device_unregister 流程)
      • 用户空间节点

Linux devfreq 简介

现在的 Soc 由众多的子模块构成,比如CNN,DSP,ISP,CPU等,在不同的场景下,并非所有的模块都要保持最高的性能,因此,SoC 设计的时候会划分一些电压域,这些电压域内的模块,可以根据具体需要调整电压和频率,从而达到既能实现功能,又能降低功耗的目的

不过频率电压并不是随意搭配的,一般情况下,高频对应着高压,低频对应着低压,这是由于晶体管的电器特性决定的

Linux 内核用 OPP(Operation Performance Point) 对这些设备支持的频率和电压进行描述和管理,CPU 的 DVFS 也就是 cpufreq 也是基于 OPP 实现的,但是仅仅支持 CPU 设备的调频和调压,这里主要介绍的是设备的 DVFS,也是基于 OPP 实现的,Linux 内核实现了一个 devfreq framework 用于实现和管理 device 的 dvfs,用于支持非CPU设备的调频,调压,并且设备可以匹配自己的 governor 策略

devFreq framework 规范了设备调频调压的过程,也标准化了用户空间的控制接口,通过设备的需求,利用 governor 策略控制频率,进而根据 opp table 选择对应的电压

核心数据结构

devfreq_dev_profile 结构体

devfreq_dev_profile 结构体, 是OPP device 注册到 devfreq framework 的数据结构 ,主要包含 OPP 设备的频率信息和相关的回调函数,是 devfreq framework 和 OPP device 的交互接口,将 OPP device driver 对 devfreq 的使用,简化为对 devfreq profile 结构体的填充

c 复制代码
// drivers/devfreq/devfreq.c 
// include/linux/devfreq.h
struct devfreq_dev_profile {
	// devfreq 注册的初始化频率
	unsigned long initial_freq;
	// governor polling 的时间间隔,单位 ms
	unsigned int polling_ms;

	// devfreq framework用于设定 OPP device frequency的回调函数
	int (*target)(struct device *dev, unsigned long *freq, u32 flags);
	// devfreq framework 用于获取 OPP device 负载状态的回调函数
	int (*get_dev_status)(struct device *dev, struct devfreq_dev_status *stat);
	// devfreq framework 用于获取当前频率的回调函数
	int (*get_cur_freq)(struct device *dev, unsigned long *freq);

	// 当 devfreq 设备从 devfreq framework 移除的时候调用
	// 调用时机是 error 或者 devfreq_remove_device() call
	void (*exit)(struct device *dev);

	unsigned long *freq_table;
	unsigned int max_state;
};
devfreq_governor 结构体

devfreq_governor 是 governor 注册到devfreq framework 的数据结构 ,主要包含 governor 的相关属性和具体的函数实现,是devfreq framework 和 governor 的交互接口

c 复制代码
struct devfreq_governor {
    // 将 devfreq_governor 作为链表节点进行管理
	struct list_head node;
    // governor name
	const char name[DEVFREQ_NAME_LEN];
    // 是否可以切换为其他 governor 1:不能切换
	const unsigned int immutable;

    // governor 注册到 devfreq framework 的算法实现函数,返回调整后的频率
	int (*get_target_freq)(struct devfreq *this, unsigned long *freq);
	// governor 注册到 devfreq framework 的 event 处理函数,处理 start,stop,suspend,resume 等 event
    int (*event_handler)(struct devfreq *devfreq,
				unsigned int event, void *data);
};

其中的 get_target_freq 一般会调用 devfreq_dev_profile.get_dev_status() 获取当前 OPP device 的负载情况 (load = busy_time / total_time)

devfreq 结构体

devfreq 结构体是 devfreq device 的核心结构 ,每个注册的设备都会新建一个 devfreq 结构体,

作用是将上述 OPP device 的 devfreq_dev_profile 和 governor 的 devfreq_governor 连接到一起,

并通过设备模型的 device 类,为user空间提供接口

c 复制代码
struct devfreq {
	struct list_head node;
	struct mutex lock;
    // device 属于 devfreq_class, 父节点就是使用 devfreq 的 device 比如 GPU,DDR等
	struct device dev;
    // OPP device 注册到 devfreq framework 的配置信息
	struct devfreq_dev_profile *profile;
    // governor注册到devfreq framework的配置信息
	const struct devfreq_governor *governor;
	char governor_name[DEVFREQ_NAME_LEN];
	struct notifier_block nb;

    // 用于监控负载情况的 delayed work
	struct delayed_work work;

	unsigned long previous_freq;
	struct devfreq_dev_status last_status;

	void *data; /* private data for governors */
    
    // 限制用户请求的最小频率
	unsigned long min_freq;
    // 限制用户请求的最大频率
	unsigned long max_freq;
	bool stop_polling;

	/* information for device frequency transition */
    // 记录 devfreq 中 frequency 的转移信息
	unsigned int total_trans;
	unsigned int *trans_table;
	unsigned long *time_in_state;
	unsigned long last_stat_updated;

	struct srcu_notifier_head transition_notifier_list;
};

工作流程

devFreq framework 初始化

逻辑非常简单,主要完成下面的任务:

  • 创建 devfreq_class 设备类
  • 创建 delayed work,是用于监控负载的工作队列
  • 加入到 subsys_initcall,系统启动时进行初始化
c 复制代码
static int __init devfreq_init(void)
{
	devfreq_class = class_create(THIS_MODULE, "devfreq");
	devfreq_wq = create_freezable_workqueue("devfreq_wq");
	devfreq_class->dev_groups = devfreq_groups;
	return 0;
}
subsys_initcall(devfreq_init);
governor 初始化

系统中可以支持多个 governor,在系统启动时就会进行初始化,并注册到 devFreq framework 中,后续的 OPP device 创建 devFreq 设备,

会根据 governor name 从已经初始化的 governor 列表中,查找对应的 governor 实例

c 复制代码
// driver/devfreq 目录下 governor_performance.c  
// driver/devfreq 目录下 governor_simpleondemand.c

static struct devfreq_governor devfreq_performance = {
	.name = "performance",
	.get_target_freq = devfreq_performance_func,
	.event_handler = devfreq_performance_handler,
};

static int __init devfreq_performance_init(void)
{
	return devfreq_add_governor(&devfreq_performance);
}
subsys_initcall(devfreq_performance_init);

主要流程:

  1. 填充 devfreq_governor 结构体,不同类型的结构体,对 get_target_freq 和 event_handler 会有不同的实现
  2. 调用 devfreq_add_governor 加入到 devfreq framework 的 governor 列表中
  3. 加入到 subsys_initcall,系统启动时进行初始化

目前系统支持下面几种 governor

  • Simple_ondemand: 按需调整模式,根据负载动态频率,平衡性能和功耗
  • Performance: 性能优先模式,调整到最大频率
  • Powersave: 功耗优先模式,调整到最小频率
  • Userspace: 用户指定模式,根据用于 sysfs 节点写入进行调整
  • Passive: 被动模式,使用设备指定方法做调整或者跟随father devfreq 设备的governor
devfreq Device 注册

这里以开源的 mali gpu midgard 内核为例

  1. 配置正常的OPP table,确定正常的 OPP 电压和频率
  2. 获取 OPP device 的频率信息,完善正确的回调函数,最后填充 devfreq profile 结构体
  3. 调用 devfreq_add_device 函数,将 devfreq profile 结构,governor name 添加到 devfreq 框架
c 复制代码
int kbase_devfreq_init(struct kbase_device *kbdev)
{
	struct devfreq_dev_profile *dp;
	dp->initial_freq = kbdev->current_freqs[0];
	dp->polling_ms = 100;
	dp->target = kbase_devfreq_target;
	dp->get_dev_status = kbase_devfreq_status;
	dp->get_cur_freq = kbase_devfreq_cur_freq;
	dp->exit = kbase_devfreq_exit;
	
	kbdev->devfreq = devfreq_add_device(kbdev->dev, dp, "simple_ondemand", NULL);
}
动态变频的实现

devfreq framework 负责监控程序的运行,governor 提供算法,OPP device 提供自身负载状态和频率设置放的实现,系统中有不同的 governor,不同的 governor 有不同的管理算法

  1. devfreq_add_device 时发送 DEVFEQ_GOV_START 事件
c 复制代码
struct devfreq *devfreq_add_device(struct device *dev,
				   struct devfreq_dev_profile *profile,
				   const char *governor_name,
				   void *data) {
	......
	devfreq->governor = governor;
	err = devfreq->governor->event_handler(devfreq, DEVFREQ_GOV_START,
				NULL);

	goto err_init;
}
  1. governor 的 event_handler 收到 DEVFREQ_GOV_START事件,调度工作队列,运行负载监控程序

Note: 这里以 simple_ondemand governor 为例

c 复制代码
static int devfreq_simple_ondemand_handler(struct devfreq *devfreq,
				unsigned int event, void *data) {
	switch (event) {
	case DEVFREQ_GOV_START:
		devfreq_monitor_start(devfreq);
		break;
	......
}
  1. 负载监控程序
  • 调用 governor 的 get_target_freq 方法,获取下一次的调频目标值
  • 调用 OPP device 注册到 devfreq_framework 的 target 函数,设置新的频率信息
  • 调度延迟工作队列,延迟 OPP device 设置的轮询间隔,再次运行
c 复制代码
static void devfreq_monitor(struct work_struct *work)
{
	int err;
	struct devfreq *devfreq = container_of(work,
					struct devfreq, work.work);

	mutex_lock(&devfreq->lock);
	// monitor 程序的核心函数
	err = update_devfreq(devfreq);

    /* 以 poolling_ms 为周期进行周期监控 */
	queue_delayed_work(devfreq_wq, &devfreq->work,
				msecs_to_jiffies(devfreq->profile->polling_ms));
	mutex_unlock(&devfreq->lock);
}

int update_devfreq(struct devfreq *devfreq)
{
    /*获取要调频到的结果频率*/
    devfreq->governor->get_target_freq(devfreq, &freq);

    /*在调频前后都有通知发出来*/
    devfreq_notify_transition(devfreq, &freqs, DEVFREQ_PRECHANGE);
    /*调用 OPP devices的 target 函数设置目标频率*/
    devfreq->profile->target(devfreq->dev.parent, &freq, flags);
    devfreq_notify_transition(devfreq, &freqs, DEVFREQ_POSTCHANGE);
}
device_unregister 流程

device_unregister 注销过程中会调用 devfreq_dev_release 函数,完成下面的事务:

  1. 发送 DEVFREQ_GOV_STOP event,governor停止运行
  2. 回调 OPP device 注册到 devfreq framework 的exit函数
  3. 释放 devfreq device 申请的资源
c 复制代码
static void devfreq_dev_release(struct device *dev)
{
	struct devfreq *devfreq = to_devfreq(dev);

	......
	if (devfreq->governor)
		devfreq->governor->event_handler(devfreq,
						 DEVFREQ_GOV_STOP, NULL);

用户空间节点

在 devfreq framework 初始化时,会创建下面的节点:

c 复制代码
static struct attribute *devfreq_attrs[] = {
	&dev_attr_governor.attr, 
	&dev_attr_available_governors.attr, // 可用的 governor
	&dev_attr_cur_freq.attr, //当前频率
	&dev_attr_available_frequencies.attr,// 可用频率列表
	&dev_attr_target_freq.attr, // 目标频率
	&dev_attr_polling_interval.attr, // 调度间隔
	&dev_attr_min_freq.attr, // 最小频率
	&dev_attr_max_freq.attr, // 最大频率
	&dev_attr_trans_stat.attr, // 状态调整记录表
	NULL,
};
ATTRIBUTE_GROUPS(devfreq);

available_frequencies: 可用的频率列表
available_governors:可用的governor
cur_freq:当前频率
governor: 当前governor
max_freq:最大频率
min_freq :最小频率
polling_interval:governor调度的时间间隔,单位是ms.
target_freq:目标频率
trans_stat:状态调整表记录

参考文章

https://mp.weixin.qq.com/s/TI3ryUewRgt9LFKr-tDkJQ

https://www.cnblogs.com/hellokitty2/p/13061707.html

相关推荐
像风一样自由20201 小时前
52.Docker部署大模型应用从本地开发到服务化运行
运维·人工智能·docker·容器·大模型·智能体
sxgzzn2 小时前
如何利用电缆故障定位在线监测系统提升供电安全?
运维·重构·无人机·开关柜局放在线监测·gis局放在线监测
曦夜日长2 小时前
Git系统篇,Git本地使用(一):从“究极进化版“到三个区域的底层原理
大数据·linux·git·elasticsearch
mengchanmian2 小时前
VSFTP 服务器搭建(Centos)
运维·服务器·centos
shimly1234562 小时前
给断网无 sudo ubuntu22.04 机器安装 nodejs
linux
滕州市燕猫虎计算机科技工作室个体工商户3 小时前
《Rust程序设计》学习笔记八:结构体
笔记·学习·rust·rust程序设计
别动我齐刘海4 小时前
Linux 系统调试学习1
linux·运维·服务器·c++·学习·机器学习·机器人
Lsetea4 小时前
OpenSSL报certificate chain too long:error 22与verify_depth深度排查
linux·https·ssl证书·openssl·证书链
不像程序员的程序媛5 小时前
linux常见文本操作命令
linux·运维·服务器
xier_ran5 小时前
【infra之路】AI 编译器学习笔记(二):重拾 C++——移动语义与 LLVM 风格的类型转换
人工智能·笔记·学习