一、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() {
// root
if (getuid() != 0) {
return EXIT_FAILURE;
}
const std::string cgroupName = "test_cgroup";
const std::string cgroupPath = "/sys/fs/cgroup/" + cgroupName;
createCgroup(cgroupPath);
// limit
setCpuLimit(cgroupPath, 50000); // 50ms/100ms
// add cgroup
pid_t pid = getpid();
addProcess(cgroupPath, pid);
// test
volatile int i = 0;
while (i < 1000000000) {
i++;
if (i % 10000000 == 0) {
std::cout << "run task... " << i << std::endl;
}
}
// rm cgroup
rmdir(cgroupPath.c_str());
return EXIT_SUCCESS;
}
代码非常简单,创建cgroup目录,添加当前进程到组并限制CPU资源,最后弄了一个测试的负载。
四、总结
开发者不但应该把代码掌握的深入,对应用环境,包括各种平台的实际情况也要相当清楚。这就和打仗一样,再好的军事技术如果无法与实践技术结合,结果可想而知。赵括其实就是一个活生生的例子。项目的实践是一个系统的工程,开发者切记不能只看到其中部分,而忽略全局。与大家共勉!

4412

被折叠的 条评论
为什么被折叠?



