C++编程实践——Linux下的CPU控制
一、Linux下资源的处理
与Windows操作系统相比,在Linux下,可能由于是开源的原因,对相关资源的控制更加细致全面。比如开发者都知道,其更新升级的源地址可以进行直接编辑应用;网络支持的文件句柄数量也可以编辑相关的配置文件进行处理。而对进程、内存等的监控和管理也可以在 /proc文件中的相关的配置文件中进行处理。比如下面的方法可以对交换内存进行设置:
echo 100 > /proc/sys/vm/swappiness
不过,大家还是要注意,在Linux下,各个方面的资源管理控制往往分为读和写以及对权限的需要。这个非常重要,往往这些细节被忽略导致一些意外的问题。举一个简单的例子,有些命令或文件如果不使用 root权限操作,则会显示没有这些命令或文件。这对于一些新手,可能会觉得是没有安装相关库,从而让操作者做一些额外的操作。
二、CPU的控制
而在实际的开发中,CPU的控制是一个相当重要的场景,毕竟从有计算机以来,几乎所有的工作和任务都是围绕着CPU进行的。特别是随着CPU的功能不断增强,从单核到多核,以及更高的工作频率,更大的缓存等等,这都产生了在不同粒度上对CPU资源进行管控的必要。要了解CPU的控制,就需要开发者熟悉操作系统相关的CPU的调度和控制原理,最好能与相对应的内核中的源码对应起来。来看一下调度器的主要数据结构:
struct sched_entity {//整体权重struct load_weight load; struct rb_node run_node; u64 exec_start; u64 sum_exec_runtime;// 虚拟运行时间u64 vruntime; u64 prev_sum_exec_runtime;
};
当然,在不同的版本的内核中可能细节会有不同,大家可根据自己的实际版本来分析即可。
对CPU的控制,肯定是在内核源码控制更方便,但为了安全和方便,本文只介绍在应用层次上的CPU控制,这样会更适合广大开发者来学习和应用相关的技术。要想对CPU进行管理 ,首先需要可以查看相关的CPU信息的命令和工具,这样才能在些基础上进行CPU的管理,常见的CPU查看相关命令和工具有:
- 命令
#查看CPU的详细信息
lscpu
cat /proc/cpuinfo# 查看CPU频率
cpupower frequency-info
cat /proc/cpuinfo | grep "cpu MHz"
- 使用工具
可以安装下面的工具:
# Ubuntu/Debian
sudo apt install cpufrequtils linux-tools-common
当然,相关的工具有很多,同时由于云技术的发展,更多的云相关的工具和软件也有很多,大家可以根据自己的情况来进行安装和应用
当知晓了当前的CPU应用情况又安装了相关管理工具后,就可以在当前实际项目中通过代码或命令等操作根据CPU的控制粒度进行细节上的管控,从而让其更友好的实现设计的目的。一般来说,对CPU的管控主要有以下几个方面:
- 设置频率
这可以算是CPU应用的最基本、最粗暴的应用。特别是在某些可以超频应用的CPU上,更有效果 - 设置核心的应用
即对多核心的CPU出于某种场景的应用,禁用或启用一些核心 - 设置CPU的亲和性和中断的亲和性
这个在前面的DPDK等分析中介绍过,让某个CPU与某个进程或线程绑定,防止出现切换出现的效率问题 - 设置优先级
这个相对来说要有些鸡肋,因为虽然在不同的操作系统上都有大量的优先级设置,但实际开放给上层应用的,却非常有限
在不同的场景下,对CPU的控制有很多种方式,这就需要开发者对实际的CPU的信息有着详细的掌握,这样才能根据其可控制的粒度和相关方法实现对CPU资源利用的最优化。知己知彼,百战百胜。
三、解决方案及示例
针对上面的应用,实现的方式主要有以下几种:
- 命令处理
相关的操作命令如下:
# 查看信息
cpufreq-info
# 设置频率
sudo cpupower frequency-set -d 1.2GHz # 最小频率
sudo cpupower frequency-set -u 3.5GHz # 最大频率# 设置频率-需userspace
sudo cpupower frequency-set -f 2.4GHz# 禁用CPU核心服务
sudo systemctl stop cpuset# 查看进程的CPU亲和性
taskset -p <PID># 启动程序时绑定到特定CPU
taskset -c 0,1 ./app# 修改运行中进程的CPU亲和性
taskset -cp 0,2,4 <PID>
- 配置文件
具体的配置文件如下控制:
# 查看CPU核心状态
cat /sys/devices/system/cpu/online
ls /sys/devices/system/cpu/cpu*/online
# 禁用CPU核心(禁用CPU1)
echo 0 | sudo tee /sys/devices/system/cpu/cpu1/online
# 启用CPU核心
echo 1 | sudo tee /sys/devices/system/cpu/cpu1/online# 创建CPU控制组
sudo mkdir /sys/fs/cgroup/testgroup# 设置CPU权重(默认100)
echo 300 > /sys/fs/cgroup/testgroup/cpu.weight# 设置CPU使用上限(单位:微秒)
echo "200000 200000" > /sys/fs/cgroup/testgroup/cpu.max# 添加进程到控制组
echo <PID> > /sys/fs/cgroup/testgroup/cgroup.procs# 编辑服务文件
sudo systemctl edit myservice# 添加以下内容:
[Service]
CPUWeight=300
CPUQuota=85%
CPUShares=1024# 编辑配置文件
sudo vim /etc/sysctl.d/99-cpu.conf# 设置参数
kernel.sched_min_granularity_ns = 12000000
kernel.sched_wakeup_granularity_ns = 16000000
kernel.sched_migration_cost_ns = 6000000# 使能配置
sudo sysctl -p /etc/sysctl.d/99-cpu.conf
- 代码控制
cgroup有V1和V2版本的不同,一般来说推荐使用V2版本,特别是在云环境和虚拟环境下。判断系统使用哪个版本的方法可以使用下面的命令:
stat -fc %T /sys/fs/cgroup/
#输出 cgroup2fs,说明系统使用 cgroup v2;输出 tmpfs,说明使用 cgroup v1
注意:cgroup V2不兼容V1
下面看一下其具体的一个代码:
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <string>
#include <sys/stat.h>
#include <unistd.h>void createCgroup(const std::string &cgroupPath) {if (mkdir(cgroupPath.c_str(), 0755) == -1) {exit(EXIT_FAILURE);}
}// 设置CPU限制:微秒,100000=100ms
void setCpuLimit(const std::string &cgroupPath, int cpuQuota) {std::string cpuMaxPath = cgroupPath + "/cpu.max";std::ofstream cpuMax(cpuMaxPath);if (!cpuMax.is_open()) {exit(EXIT_FAILURE);}cpuMax << cpuQuota << " 100000";cpuMax.close();
}// add cgroup
void addProcess(const std::string &cgroupPath, pid_t pid) {std::string procsPath = cgroupPath + "/cgroup.procs";std::ofstream procs(procsPath);if (!procs.is_open()) {exit(EXIT_FAILURE);}procs << pid;procs.close();
}int main() {// rootif (getuid() != 0) {return EXIT_FAILURE;}const std::string cgroupName = "test_cgroup";const std::string cgroupPath = "/sys/fs/cgroup/" + cgroupName;createCgroup(cgroupPath);// limitsetCpuLimit(cgroupPath, 50000); // 50ms/100ms// add cgrouppid_t pid = getpid();addProcess(cgroupPath, pid);// testvolatile int i = 0;while (i < 1000000000) {i++;if (i % 10000000 == 0) {std::cout << "run task... " << i << std::endl;}}// rm cgrouprmdir(cgroupPath.c_str());return EXIT_SUCCESS;
}
代码非常简单,创建cgroup目录,添加当前进程到组并限制CPU资源,最后弄了一个测试的负载。
四、总结
开发者不但应该把代码掌握的深入,对应用环境,包括各种平台的实际情况也要相当清楚。这就和打仗一样,再好的军事技术如果无法与实践技术结合,结果可想而知。赵括其实就是一个活生生的例子。项目的实践是一个系统的工程,开发者切记不能只看到其中部分,而忽略全局。与大家共勉!
