Linux内核:动手实现一个CPU频率调节器
本文代码地址:https://github.com/Shibo-Zhu/linux-try/tree/master/kernel_cpufreq_ctl
在Linux系统中,CPU频率调节(cpufreq)是一个至关重要的子系统。它在性能和功耗之间取得平衡,允许系统根据当前负载动态调整CPU的时钟频率。理解其工作原理并能通过代码与之交互,是Linux内核开发和系统性能优化的关键技能。
本文将首先介绍cpufreq的基本概念,然后深入探讨其核心内核API,最后通过编写一个内核模块和一个用户态命令行工具,带你一步步实现一个自定义的CPU频率控制器。
1. CPU工作模式 (Governors)
Linux内核的cpufreq子系统通过不同的 调节策略(Governors) 来管理CPU频率。Governor决定了何时以及如何提高或降低CPU频率。常见的Governors有:
performance: 性能优先。将CPU频率锁定在支持的最高频率上,以获得最大处理能力。powersave: 节能优先。将CPU频率锁定在支持的最低频率上,以最大限度地节省电能。userspace: 用户空间模式。允许任何用户态程序通过sysfs接口手动设置特定的CPU频率。我们今天的实践就类似于实现一个userspace模式的控制器。ondemand: 按需模式。周期性地检查CPU负载,当负载超过某个阈值时,迅速将频率提升至最高;空闲时则降低频率。这是一种反应迅速的动态调节策略。conservative: 保守模式。与ondemand类似,但频率的提升和降低是渐进式的,而不是一步到位,这样可以避免因负载突增突降导致的频率剧烈波动。schedutil: 基于调度器的调节策略。利用内核调度器中的CPU负载信息来更精细、更高效地进行频率决策,是现代内核中推荐的默认策略。
你可以通过以下命令查看当前CPU支持和正在使用的governor:
# 查看支持的governors
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors
# 查看当前使用的governor
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor改变当前系统cpu的工作模式:
# 切换到userspace模式
echo userspace | sudo tee /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor2. 内核核心接口:cpufreq_driver_target()
要在内核代码中直接设置CPU频率,cpufreq子系统提供了一个核心函数 cpufreq_driver_target()。这个函数是驱动程序与cpufreq核心交互的关键。
它的原型如下:
int cpufreq_driver_target(struct cpufreq_policy *policy,
unsigned int target_freq,
unsigned int relation);参数详解:
struct cpufreq_policy *policy: 指向一个CPU的频率调节策略对象的指针。每个受cpufreq管理的CPU(或CPU集群)都有一个对应的policy对象。我们可以通过cpufreq_cpu_get(cpu_id)来获取指定CPU的policy。unsigned int target_freq: 你期望设置的目标频率,单位是 KHz。unsigned int relation: 定义了target_freq与实际可设置频率之间的关系。常用的值有:CPUFREQ_RELATION_L: (Lowest) 设置为小于等于target_freq的最大可用频率。CPUFREQ_RELATION_H: (Highest) 设置为大于等于target_freq的最小可用频率。
在使用 cpufreq_cpu_get() 获取 policy 后,必须在操作完成时调用 cpufreq_cpu_put() 来释放它,这是一种典型的引用计数管理模式。
3. 动手实践:自定义CPU频率控制器
接下来,我们将通过一个内核模块和一个用户态程序,实现从命令行设置任意CPU核心频率的功能。
工作原理
- 内核模块 (
cpufreq_ctl.ko):- 创建一个字符设备
/dev/cpufreq_ctl。 - 提供一个
ioctl接口,用于接收来自用户空间的请求。 - 当接收到请求时,解析出目标CPU ID和频率,并调用
cpufreq_driver_target()函数来完成设置。
- 创建一个字符设备
- 用户态程序 (
cpufreq_ctl_user):- 一个简单的命令行工具。
- 接收用户输入的CPU ID和目标频率作为参数。
- 打开
/dev/cpufreq_ctl设备文件,并通过ioctl系统调用将参数发送给内核模块。
内核模块代码 (cpufreq_ctl.c)
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/cpufreq.h>
#define DEVICE_NAME "cpufreq_ctl"
#define CLASS_NAME "cpufreq"
// 定义ioctl命令和数据结构
#define IOCTL_SET_FREQ _IOW('q', 1, struct cpufreq_ioctl_data)
struct cpufreq_ioctl_data {
unsigned int cpu;
unsigned int freq;
};
static struct class *cpufreq_class;
static struct cdev cpufreq_cdev;
static dev_t devt;
// ioctl处理函数
static long cpufreq_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
struct cpufreq_ioctl_data data;
struct cpufreq_policy *policy;
int ret = 0;
if (cmd != IOCTL_SET_FREQ)
return -EINVAL;
// 从用户空间拷贝数据
if (copy_from_user(&data, (void __user *)arg, sizeof(data)))
return -EFAULT;
pr_info("cpufreq_ctl: set CPU%u -> %u kHz\n", data.cpu, data.freq);
// 获取指定CPU的policy对象
policy = cpufreq_cpu_get(data.cpu);
if (!policy)
return -EINVAL;
// 调用核心API设置频率
ret = cpufreq_driver_target(policy, data.freq, CPUFREQ_RELATION_L);
// 释放policy对象
cpufreq_cpu_put(policy);
if (ret)
pr_err("cpufreq_ctl: failed to set freq, ret=%d\n", ret);
else
pr_info("cpufreq_ctl: success\n");
return ret;
}
static struct file_operations fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = cpufreq_ioctl,
};
// 模块初始化
static int __init cpufreq_ctl_init(void)
{
int ret;
// 1. 动态分配设备号
ret = alloc_chrdev_region(&devt, 0, 1, DEVICE_NAME);
if (ret) return ret;
// 2. 初始化并添加cdev
cdev_init(&cpufreq_cdev, &fops);
ret = cdev_add(&cpufreq_cdev, devt, 1);
if (ret) goto err_unregister;
// 3. 创建设备类
cpufreq_class = class_create(THIS_MODULE, CLASS_NAME);
if (IS_ERR(cpufreq_class)) {
ret = PTR_ERR(cpufreq_class);
goto err_cdev;
}
// 4. 创建设备文件 /dev/cpufreq_ctl
device_create(cpufreq_class, NULL, devt, NULL, DEVICE_NAME);
pr_info("cpufreq_ctl: module loaded, /dev/%s ready\n", DEVICE_NAME);
return 0;
err_cdev:
cdev_del(&cpufreq_cdev);
err_unregister:
unregister_chrdev_region(devt, 1);
return ret;
}
// 模块卸载
static void __exit cpufreq_ctl_exit(void)
{
device_destroy(cpufreq_class, devt);
class_destroy(cpufreq_class);
cdev_del(&cpufreq_cdev);
unregister_chrdev_region(devt, 1);
pr_info("cpufreq_ctl: module unloaded\n");
}
module_init(cpufreq_ctl_init);
module_exit(cpufreq_ctl_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_VERSION("1.0");
MODULE_DESCRIPTION("Simple CPU frequency control via ioctl");用户态程序 (cpufreq_ctl_user.c)
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#define DEVICE_PATH "/dev/cpufreq_ctl"
// ioctl命令和数据结构必须与内核模块中的定义完全一致
#define IOCTL_SET_FREQ _IOW('q', 1, struct cpufreq_ioctl_data)
struct cpufreq_ioctl_data {
unsigned int cpu;
unsigned int freq;
};
int main(int argc, char *argv[])
{
int fd;
struct cpufreq_ioctl_data data;
if (argc != 3) {
fprintf(stderr, "Usage: sudo %s <cpu_id> <freq_khz>\n", argv[0]);
return 1;
}
data.cpu = atoi(argv[1]);
data.freq = atoi(argv[2]);
// 打开设备文件
fd = open(DEVICE_PATH, O_RDWR);
if (fd < 0) {
perror("open");
return 1;
}
// 发送ioctl请求
if (ioctl(fd, IOCTL_SET_FREQ, &data) < 0) {
perror("ioctl");
close(fd);
return 1;
}
printf("Request to set CPU%u frequency to %u kHz sent successfully.\n", data.cpu, data.freq);
close(fd);
return 0;
}编译与运行
1. 编译内核模块
创建一个Makefile文件:
obj-m := cpufreq_ctl.o
# KDIR需要指向你的Linux内核源码树路径
# 通常是 /lib/modules/$(shell uname -r)/build
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
all:
$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
$(MAKE) -C $(KDIR) M=$(PWD) clean
执行编译:
make成功后会生成cpufreq_ctl.ko文件。
2. 加载模块与检查设备
加载内核模块:
sudo insmod cpufreq_ctl.ko查看内核日志,确认加载成功:
dmesg | tail你应该能看到 cpufreq_ctl: module loaded... 的信息。
检查设备文件是否已创建:
ls -l /dev/cpufreq_ctl3. 编译并运行用户态程序
编译:
gcc cpufreq_ctl_user.c -o cpufreq_ctl_user重要提示: 要想手动设置频率,必须先将对应CPU的governor切换到userspace模式。
echo userspace | sudo tee /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor运行程序,设置CPU 0的频率为1.1GHz (1100000 KHz):
sudo ./cpufreq_ctl_user 0 1100000再次查看内核日志 dmesg,可以看到模块打印的成功信息。
4. 验证结果
通过sysfs查看CPU 0当前的实际频率:
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq如果输出接近1100000,说明我们的频率控制器工作正常!
卸载模块:
sudo rmmod cpufreq_ctl