Linux系统中CPU亲和性、调度策略与cgroups的深入解析
1. 使用taskset进行CPU亲和性设置
在Linux系统中,我们可以使用用户空间工具 taskset 来获取或设置进程(或线程)的CPU亲和性掩码。以下是具体操作步骤:
- 查询系统进程 systemd (PID为1)的CPU亲和性掩码 :
$ taskset -p 1
pid 1's current affinity mask: 3f
$
这里的 0x3f 转换为二进制是 0011 1111 ,表示该进程(这里是 systemd )可以在所有6个CPU核心上运行。
- 使用 taskset 运行编译器,确保GCC及其派生进程仅在头两个CPU核心上运行 :
$ taskset 03 gcc userspc_cpuaffinity.c -o userspc_cpuaffinity -Wall -O3
其中, 03 的二进制是 0011 ,代表仅使用头两个CPU核心。
此外, schedtool(8) 工具也可用于获取或设置给定线程/进程的CPU亲和性掩码。
1.1 设置内核线程的CPU亲和性掩码
若要演示一种名为每CPU变量的同步技术,需要创建两个内核线程,并确保它们分别在不同的CPU核心上运行。为实现这一点,需明确设置每个内核线程的CPU亲和性掩码,使其不同且不重叠。例如,将第一个内核线程的亲和性掩码设为0,第二个设为1,以保证它们分别仅在CPU核心0和1上执行。
1.2 非导出符号的使用问题
在模块内部设置CPU亲和性并非易事,因为内核中用于设置CPU亲和性位掩码的API sched_setaffinity() 虽存在但未导出。以往,模块开发者会使用 kallsyms_lookup_name() 函数查找内核中的符号并获取其地址,然后将该地址作为函数指针调用。然而,从内核版本5.7开始, kallsyms_lookup_name() 和 kallsyms_on_each_symbol() 函数被取消导出。
现在,我们可以通过 /proc/kallsyms 伪文件查找内核符号,但前提是拥有root权限。并且,由于内核地址空间布局随机化(KASLR)的存在,每次启动时该值都会改变,不能硬编码。因此,我们需要编写一个小包装脚本,将通过 /proc/kallsyms 查找到的 sched_setaffinity() 例程的地址作为参数传递给模块。
以下是相关代码片段:
// ch13/3_lockfree/percpu/percpu_var.c
[ … ]
static unsigned long func_ptr;
module_param(func_ptr, ulong, 0);
unsigned long (*schedsa_ptr)(pid_t, const struct cpumask *) = NULL;
[ … ]
// set up the function pointer
schedsa_ptr = (unsigned long (*)(pid_t pid, const struct cpumask *in_mask))
func_ptr;
[ … ]
/* pr_info("setting cpu mask to cpu #%u now...\n", cpu); */
cpumask_clear(&mask);
cpumask_set_cpu(cpu, &mask); // 1st param is the CPU number, not bitmask
/* !HACK! sched_setaffinity() is NOT exported, we can't call it
* sched_setaffinity(0, &mask); // 0 => on self
* so we invoke it via it's function pointer
*/
ret = (*schedsa_ptr)(0, &mask); // 0 => on self
[ … ]
虽然这种方法可行,但不建议在生产环境中使用。
2. 查询和设置线程的调度策略与优先级
2.1 用户空间查询和设置
在用户空间,我们可以使用 chrt 工具查询或设置线程的调度策略和优先级。 chrt 内部调用 sched_getattr() 系统调用进行查询,使用 sched_setattr() 系统调用进行设置。此外,内核还提供了 sched_{g,s}etscheduler() 及其 pthread 库包装API pthread_{g,s}etschedparam() 。
2.2 内核线程的调度策略和优先级设置
Linux内核包含内核线程(kthreads),它们是可调度实体,其调度策略和优先级可通过编程方式查询或设置。在用户空间,现代首选的系统调用是 sched_getattr() 和 sched_setattr() ,但在内核中,情况有所不同。
从内核版本5.9开始,旧的设计存在问题,可能导致CPU调度和资源管理混乱。因此,引入了新的API:
- sched_set_fifo(p) :创建一个FIFO任务,优先级为50。
- sched_set_fifo_low(p) :创建一个优先级高于NORMAL的任务,最终为优先级1的FIFO任务。
- sched_set_normal(p, nice) :将任务设置为正常调度策略(SCHED_NORMAL),并将nice值设置为第二个参数。
这些API是GPL导出的,只能由遵循GNU GPL许可的模块使用。
2.3 实际示例 - 线程化中断处理程序
内核在处理线程化中断时会创建专用的内核线程,使用SCHED_FIFO调度策略和优先级50。以下是相关代码路径:
static int
setup_irq_thread(struct irqaction *new, unsigned int irq, bool secondary)
{
struct task_struct *t;
if (!secondary) {
t = kthread_create(irq_thread, new, "irq/%d-%s", irq,
new->name);
} else {
t = kthread_create(irq_thread, new, "irq/%d-s-%s", irq,
new->name);
}
[ … ]
kthread_create() 宏负责创建内核线程, irq_thread() 内核API会设置调度策略和优先级:
/* Interrupt handler thread */
static int irq_thread(void *data)
{
struct callback_head on_exit_work;
struct irqaction *action = data;
[ ... ]
sched_set_fifo(current);
[ ... ]
}
2.4 操作步骤总结
| 操作 | 步骤 |
|---|---|
| 查询线程调度策略和优先级 | 使用 chrt 工具,内部调用 sched_getattr() 系统调用 |
| 设置线程调度策略和优先级 | 使用 chrt 工具或 sched_setattr() 系统调用 |
| 设置内核线程调度策略和优先级 | 使用 sched_set_fifo() 、 sched_set_fifo_low() 和 sched_set_normal() API |
2.5 流程图
graph TD;
A[开始] --> B[查询线程调度策略和优先级];
B --> C{选择操作};
C -->|查询| D[使用chrt工具];
C -->|设置用户空间线程| E[使用chrt或sched_setattr系统调用];
C -->|设置内核线程| F[使用新API];
D --> G[结束];
E --> G;
F --> G;
3. cgroups简介
3.1 背景
早期的调度算法虽承诺实现完全公平调度,但实际并非如此。例如,在多个用户登录Linux服务器的情况下,若其中一个用户编写的程序大量创建CPU密集型线程,会导致CPU带宽分配不公平。
3.2 cgroups的作用
cgroups是一种内核特性,允许系统管理员(或拥有root权限的用户)对系统的各种资源(如CPU、内存、块I/O带宽等)进行优雅的带宽分配和细粒度的资源管理。
3.3 示例
假设有十个用户登录Linux系统,若将所有进程置于同一个cgroup中并启用cgroups的CPU控制器,在CPU竞争时,每个进程将获得公平的CPU份额。系统管理员还可将系统划分为多个cgroups,为每个cgroup分配所需的资源。
3.4 启用cgroups
启用cgroups很简单,通过配置内核来实现。相关菜单为 General setup | Control Group support 。具体操作步骤如下:
1. 使用 grep 命令在内核配置文件中查找 CGROUP 。
2. 若需要,调整内核配置。
3. 重新编译内核。
4. 使用新内核重启系统并进行测试。
3.5 cgroups的发展
cgroups自2.6.24版本引入后不断发展,后来出现了cgroups v2。目前,cgroups v1和v2可共存,但许多应用和框架仍在使用cgroups v1。建议使用cgroups v2,其官方文档可参考 内核6.1的文档 。
3.6 操作步骤总结
| 操作 | 步骤 |
|---|---|
| 启用cgroups | 配置内核,通过 make menuconfig 进入 General setup | Control Group support |
| 查询cgroup控制器 | 使用 grep 命令在核配置文件中查找 CGROUP |
| 调整cgroup配置 | 修改内核配置文件,重新编译和重启内核 |
3.7 流程图
graph TD;
A[开始] --> B[查询cgroup控制器];
B --> C{是否需要调整};
C -->|否| D[结束];
C -->|是| E[调整内核配置];
E --> F[重新编译内核];
F --> G[重启系统];
G --> H[测试];
H --> D;
综上所述,通过 taskset 设置CPU亲和性、合理配置线程的调度策略和优先级,以及使用cgroups进行资源管理,能够优化Linux系统的性能和资源分配。但在实际应用中,应避免使用非标准的方法,确保系统的稳定性和安全性。
4. cgroups v2的深入使用
4.1 cgroups v2的优势
相较于cgroups v1,cgroups v2具有诸多优势。它解决了v1中存在的一些问题,提供了更简洁、更统一的接口。例如,v2的设计更加模块化,使得资源管理更加灵活。同时,它对资源的隔离性更好,能够更有效地防止资源竞争和滥用。
4.2 cgroups v2的基本概念
在cgroups v2中,有几个重要的概念需要了解:
- 控制组(Control Group) :是一组进程的集合,这些进程共享相同的资源限制和调度策略。
- 控制器(Controller) :用于管理和控制特定类型的资源,如CPU、内存、I/O等。
- 挂载点(Mount Point) :cgroups v2通过挂载点来组织和管理控制组。通常,cgroups v2会挂载到 /sys/fs/cgroup 目录下。
4.3 操作步骤:创建和配置cgroups v2控制组
以下是创建和配置cgroups v2控制组的具体步骤:
1. 挂载cgroups v2 :
sudo mount -t cgroup2 none /sys/fs/cgroup
- 创建控制组 :
sudo mkdir /sys/fs/cgroup/my_group
- 将进程添加到控制组 :
echo <pid> | sudo tee /sys/fs/cgroup/my_group/cgroup.procs
其中, <pid> 是要添加的进程的ID。
4. 配置资源限制 :
例如,限制CPU使用率:
echo 50000 | sudo tee /sys/fs/cgroup/my_group/cpu.max
这里的 50000 表示CPU使用率限制为50%。
4.4 操作步骤总结
| 操作 | 步骤 |
|---|---|
| 挂载cgroups v2 | 使用 sudo mount -t cgroup2 none /sys/fs/cgroup 命令 |
| 创建控制组 | 使用 sudo mkdir /sys/fs/cgroup/<group_name> 命令 |
| 添加进程到控制组 | 将进程ID写入 /sys/fs/cgroup/<group_name>/cgroup.procs 文件 |
| 配置资源限制 | 修改相应的资源控制文件,如 cpu.max 、 memory.max 等 |
4.5 流程图
graph TD;
A[开始] --> B[挂载cgroups v2];
B --> C[创建控制组];
C --> D[添加进程到控制组];
D --> E{是否需要配置资源限制};
E -->|否| F[结束];
E -->|是| G[配置资源限制];
G --> F;
5. 综合应用案例
5.1 场景描述
假设我们有一个Linux服务器,同时运行着多个服务,包括Web服务器、数据库服务器和计算密集型任务。为了确保各个服务能够公平地使用系统资源,我们可以使用cgroups v2进行资源管理。
5.2 配置步骤
- 创建控制组 :
sudo mkdir /sys/fs/cgroup/web_server
sudo mkdir /sys/fs/cgroup/db_server
sudo mkdir /sys/fs/cgroup/compute_task
- 将进程添加到相应的控制组 :
# 假设Web服务器进程ID为1234
echo 1234 | sudo tee /sys/fs/cgroup/web_server/cgroup.procs
# 假设数据库服务器进程ID为5678
echo 5678 | sudo tee /sys/fs/cgroup/db_server/cgroup.procs
# 假设计算密集型任务进程ID为9012
echo 9012 | sudo tee /sys/fs/cgroup/compute_task/cgroup.procs
- 配置资源限制 :
- Web服务器 :
echo 30000 | sudo tee /sys/fs/cgroup/web_server/cpu.max
表示Web服务器的CPU使用率限制为30%。
- 数据库服务器 :
echo 40000 | sudo tee /sys/fs/cgroup/db_server/cpu.max
表示数据库服务器的CPU使用率限制为40%。
- 计算密集型任务 :
echo 20000 | sudo tee /sys/fs/cgroup/compute_task/cpu.max
表示计算密集型任务的CPU使用率限制为20%。
5.3 效果分析
通过上述配置,各个服务能够按照我们设定的资源限制使用CPU资源,避免了某个服务过度占用资源导致其他服务性能下降的问题。同时,使用cgroups v2进行资源管理,使得我们可以根据实际需求灵活调整资源分配,提高了系统的稳定性和可靠性。
5.4 操作步骤总结
| 操作 | 步骤 |
|---|---|
| 创建控制组 | 使用 sudo mkdir /sys/fs/cgroup/<group_name> 命令创建多个控制组 |
| 添加进程到控制组 | 将各个服务的进程ID分别写入相应控制组的 cgroup.procs 文件 |
| 配置资源限制 | 根据需要修改各个控制组的资源控制文件,如 cpu.max |
5.5 流程图
graph TD;
A[开始] --> B[创建多个控制组];
B --> C[将进程添加到相应控制组];
C --> D[配置资源限制];
D --> E[结束];
6. 总结与展望
6.1 总结
本文详细介绍了Linux系统中CPU亲和性、线程调度策略和优先级的设置方法,以及cgroups的基本概念和使用。通过 taskset 工具可以方便地设置CPU亲和性,合理配置线程的调度策略和优先级能够优化系统性能。而cgroups,特别是cgroups v2,为系统资源管理提供了强大的功能,能够实现资源的公平分配和细粒度控制。
6.2 展望
随着计算机技术的不断发展,系统资源管理的需求也越来越高。未来,cgroups可能会进一步发展和完善,提供更多的功能和更好的性能。同时,结合其他技术,如容器化技术,cgroups将在云计算、大数据等领域发挥更加重要的作用。在实际应用中,我们应不断学习和掌握这些技术,以提高系统的性能和可靠性。
6.3 注意事项
在使用这些技术时,需要注意以下几点:
- 避免使用非标准的方法进行操作,如在设置CPU亲和性时,尽量避免使用未导出的符号,确保系统的稳定性和安全性。
- 在配置cgroups时,要根据系统的实际情况合理分配资源,避免过度限制或分配不均导致系统性能下降。
- 及时关注技术的发展和更新,如cgroups v2的使用将逐渐成为主流,应尽快掌握并应用。
| 注意事项 | 详情 |
|---|---|
| 操作规范性 | 避免使用非标准方法,确保系统稳定安全 |
| 资源分配合理性 | 根据实际情况合理分配cgroups资源 |
| 技术更新关注 | 关注cgroups v2等技术发展并及时应用 |
graph LR;
A[操作规范性] --> D[系统稳定安全];
B[资源分配合理性] --> D;
C[技术更新关注] --> D;
通过合理运用这些技术,我们可以更好地管理Linux系统的资源,提高系统的性能和可靠性,为各种应用场景提供有力的支持。
1398



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



