Linux 控制组(cgroups)与实时操作系统(RTOS)转换指南
1. 控制组(cgroups)概述
控制组(cgroups)是 Linux 内核的一个强大特性,用于限制和监控一组进程对系统资源的使用。cgroups 有两个主要版本:v1 和 v2。
- cgroups v1 控制器 :包括 cpu、cpuacct、cpuset、memory、devices、freezer、net_cls、blkio、perf_event、net_prio、hugetlb、pids 和 rdma。例如,PIDS 控制器可防止 fork 炸弹攻击,限制从某个 cgroup 或其子组派生的进程数量。在运行 cgroups v1 的 Linux 系统上,可通过查看
/proc/cgroups文件了解可用的 v1 控制器及其当前使用情况。 - cgroups v2 控制器 :支持的控制器有 cpu、cpuset、io、memory、pids、perf_event 和 rdma,前五个较为常用。cgroups v2 将所有控制器挂载在单个层次结构中,而 cgroups v1 可以将多个控制器挂载在多个层次结构或组中。现代的 init 框架 systemd 同时使用 v1 和 v2 cgroups,在启动时会自动挂载 cgroups v2 文件系统(通常位于
/sys/fs/cgroup/unified)。
2. 在 Linux 系统中查找 cgroups v2
要查找可用的 cgroups v2 控制器,首先需要找到 cgroups v2 的挂载点,通常可以使用以下命令:
$ mount | grep cgroup2
cgroup2 on /sys/fs/cgroup/unified type cgroup2
(rw,nosuid,nodev,noexec,relatime,nsdelegate)
然后查看 /sys/fs/cgroup/unified/cgroup.controllers 文件:
$ sudo cat /sys/fs/cgroup/unified/cgroup.controllers
$
如果没有看到任何控制器,这是因为默认情况下系统可能同时存在 v1 和 v2 cgroups。要仅使用 v2 版本并使所有配置的控制器可见,需要在启动时通过内核命令行参数禁用 cgroups v1: cgroup_no_v1=all 。所有可用的内核参数可在 https://www.kernel.org/doc/Documentation/admin-guide/kernel-parameters.txt 查看。
重启系统后,可通过以下命令检查内核是否已解析指定的参数:
$ cat /proc/cmdline
BOOT_IMAGE=/boot/vmlinuz-4.15.0-118-generic root=UUID=<...> ro console=ttyS0,115200n8 console=tty
再次查找 cgroup2 控制器,此时挂载点通常为 /sys/fs/cgroup/ :
$ cat /sys/fs/cgroup/cgroup.controllers
cpu io memory pids
3. 使用 cgroups v2 CPU 控制器
下面通过一个示例展示如何使用 cgroups v2 CPU 控制器限制 CPU 带宽分配,具体步骤如下(所有步骤需要以 root 权限运行):
1. 确保内核支持 cgroups v2 :运行的内核版本应为 4.5 或更高。如果系统同时存在 v1 和 v2 cgroups,需要确保内核命令行包含 cgroup_no_v1=all 。这里假设 cgroup v2 层次结构已支持并挂载在 /sys/fs/cgroup 。
2. 添加 cpu 控制器 :以 root 身份执行以下命令:
echo "+cpu" > /sys/fs/cgroup/cgroup.subtree_control
需要注意的是,cgroup2 目前不支持对实时进程的控制,只有当所有实时进程都位于根 cgroup 时才能启用 cpu 控制器。系统管理软件可能在系统启动过程中将实时进程放置在非根 cgroups 中,需要将这些进程移动到根 cgroup 后才能启用 cpu 控制器。
3. 创建子组 :在 cgroup v2 层次结构下创建一个目录作为子组,例如创建名为 test_group 的子组:
mkdir /sys/fs/cgroup/test_group
- 设置最大允许的 CPU 带宽 :通过写入
<cgroups-v2-mount-point>/<sub-group>/cpu.max伪文件来设置。该文件是一个读写的双值文件,默认值为 “max 100000”,格式为$MAX $PERIOD,表示该组在每个$PERIOD时间段内最多可消耗$MAX的 CPU 时间。例如,设置MAX = 300,000和PERIOD = 1,000,000,表示子控制组内的所有进程在 1 秒内总共可以运行 0.3 秒。 - 将进程插入新的子控制组 :将进程的 PID 写入
<cgroups-v2-mount-point>/<sub-group>/cgroup.procs伪文件。可以通过查看每个进程的/proc/<PID>/cgroup伪文件来验证进程是否属于该子组,如果文件中包含0::/<sub-group>行,则表示该进程属于该子组。 - 完成操作 :新子组下的进程将在施加的 CPU 带宽约束下工作,完成后会正常退出。可以使用
rmdir <cgroups-v2-mount-point>/<sub-group>删除子组。
以下是一个实现上述步骤的 bash 脚本示例: ch11/cgroups_v2_cpu_eg/cgv2_cpu_ctrl.sh 。该脚本允许传入最大允许的 CPU 带宽(即步骤 4 中的 $MAX 值)。例如,运行脚本时传入参数 800000,表示在 1000000 的时间段内允许 800000 的 CPU 带宽,即 80% 的 CPU 利用率。
$ sudo ./cgv2_cpu_ctrl.sh
[sudo] password for <username>:
Usage: cgv2_cpu_ctrl.sh max-to-utilize(us)
This value (microseconds) is the max amount of time the processes in the sub-control
group we create will be allowed to utilize the CPU; it's relative to the period,
which is the value 1000000;
So, f.e., passing the value 300,000 (out of 1,000,000) implies a max CPU utilization
of 0.3 seconds out of 1 second (i.e., 30% utilization).
The valid range for the $MAX value is [1000-1000000].
$
通过不同的参数运行脚本可以看到明显的效果。例如,当传入参数 1000 时,进程的 CPU 利用率极低,生成的整数数量也很少,证明了 cgroups v2 CPU 控制器的有效性。
4. 容器与 cgroups
容器本质上是轻量级的虚拟机,目前是热门技术。大多数容器技术(如 Docker、LXC、Kubernetes 等)实际上是 Linux 内核的两个内置技术——命名空间(namespaces)和 cgroups 的结合。
5. 将主线 Linux 转换为实时操作系统(RTOS)
主线 Linux(从 https://kernel.org 下载的内核)不是实时操作系统(RTOS),而是通用操作系统(GPOS)。在 RTOS 中,不仅要求软件获得正确的结果,还需要保证在规定的期限内完成任务。主线 Linux 虽然不是 RTOS,但可以被视为软实时操作系统,即在大多数情况下能够满足期限要求。
然而,真正的硬实时领域(如军事行动、运输、机器人、电信、工厂自动化、证券交易所、医疗电子等)需要 RTOS。RTOS 的一个关键特性是确定性,即系统响应时间应保持一致,抖动(实际响应时间与期望时间的偏差)应尽可能小。
Thomas Gleixner 及其社区多年来一直致力于将 Linux 内核转换为 RTOS 的工作。从 2.6.18 内核开始,就有离线补丁可用于此目的。这些补丁可以在 https://mirrors.edge.kernel.org/pub/linux/kernel/projects/rt/ 找到。该项目最初名为 PREEMPT_RT,2015 年 10 月起,Linux 基金会接管并将其更名为实时 Linux(RTL)协作项目。
6. 构建主线 5.x 内核的 RTL(x86_64)
以下是在 x86_64 Linux VM 或本地系统上构建 RTL 内核的详细步骤:
6.1 获取 RTL 补丁
导航到 https://mirrors.edge.kernel.org/pub/linux/kernel/projects/rt/5.4/ (如果使用其他内核版本,需进入上一级目录并选择相应版本)。可以看到两种类型的补丁文件:
- patch-<kver>rt[nn].patch.[gz|xz] :前缀为 patch- ,是将主线内核(版本 <kver> )打补丁所需的完整补丁集合,以统一的压缩文件形式存在。
- patches-<kver>-rt[nn].patch.[gz|xz] :前缀为 patches- ,压缩文件包含构成该版本 RTL 补丁系列的每个单独补丁。
建议下载 patch-<kver>rt[nn].patch.xz 文件到目标系统,可以通过点击链接或使用 wget 命令下载。
需要注意的是,对于 5.4.x 内核,截至目前,RTL 补丁仅适用于 5.4.54 和 5.4.69 版本。如果使用的是 5.4.0 内核,有两种选择:一是将 5.4.0 内核逐步打补丁到 5.4.69 版本,但这需要下载并依次应用从 5.4.0 到 5.4.69 的所有 69 个补丁;二是直接下载 5.4.69 内核并对其应用 RTL 补丁。为了简化操作,建议选择后者。
以下是需要下载的两个文件:
- 主线 5.4.69 内核的压缩源代码: https://mirrors.edge.kernel.org/pub/linux/kernel/v5.x/linux-5.4.69.tar.xz
- 5.4.69 版本的 RTL 补丁: https://mirrors.edge.kernel.org/pub/linux/kernel/projects/rt/5.4/patches-5.4.69-rt39.tar.xz
6.2 提取文件
将 RTL 补丁文件和内核代码库的 tar.xz 文件解压缩,以获得内核源代码树(这里是 5.4.69 版本)。完成后,工作目录的内容应如下所示:
linux-5.4.69/
patches-5.4.69-rt39/
总结
通过本文,我们了解了 Linux 控制组(cgroups)的基本概念和使用方法,包括如何查找和使用 cgroups v2 控制器,以及如何使用 cgroups v2 CPU 控制器限制 CPU 带宽分配。同时,我们还探讨了将主线 Linux 转换为实时操作系统(RTOS)的相关知识,以及在 x86_64 系统上构建 RTL 内核的具体步骤。这些技术对于优化系统资源使用和满足特定的实时需求具有重要意义。
以下是一个 mermaid 流程图,展示了使用 cgroups v2 CPU 控制器的步骤:
graph TD;
A[确保内核支持 cgroups v2] --> B[添加 cpu 控制器];
B --> C[创建子组];
C --> D[设置最大允许的 CPU 带宽];
D --> E[插入进程到子组];
E --> F[进程在约束下工作];
F --> G[完成后删除子组];
以下是一个表格,总结了 cgroups v1 和 v2 的区别:
| 特性 | cgroups v1 | cgroups v2 |
| ---- | ---- | ---- |
| 控制器挂载方式 | 多个控制器可挂载在多个层次结构或组中 | 所有控制器挂载在单个层次结构中 |
| 常用控制器 | cpu、cpuacct、cpuset 等 | cpu、cpuset、io 等 |
| 实时进程控制 | 支持 | 目前不完全支持,需所有 RT 进程在根 cgroup |
通过这些技术和步骤,你可以更好地管理系统资源,满足不同的应用需求。
Linux 控制组(cgroups)与实时操作系统(RTOS)转换指南
7. 配置和构建 RTL 内核
在获取并提取了 RTL 补丁和内核源代码后,接下来需要对内核进行配置和构建。
7.1 配置内核
进入内核源代码目录,运行以下命令进行配置:
cd linux-5.4.69
make menuconfig
这将打开一个图形化的配置界面,你可以在其中选择所需的内核选项。对于 RTL 内核,需要确保以下选项被启用:
- CONFIG_PREEMPT_RT :启用实时预emption 支持。
- CONFIG_RT_MUTEXES :启用实时互斥锁。
- CONFIG_RT_GROUP_SCHED :启用实时组调度。
你可以使用箭头键在菜单中导航,使用空格键选择或取消选择选项,使用回车键进入子菜单。完成配置后,按 Esc 键退出并保存配置。
7.2 构建内核
配置完成后,运行以下命令构建内核:
make -j$(nproc)
-j$(nproc) 选项表示使用系统的所有可用核心进行并行编译,以加快编译速度。编译过程可能需要一些时间,具体取决于系统性能。
7.3 安装内核
编译完成后,运行以下命令安装内核:
sudo make modules_install
sudo make install
这将安装内核模块和内核映像到系统中。
8. 启动 RTL 内核
安装完成后,需要更新引导加载程序(如 GRUB)以使用新的 RTL 内核。运行以下命令更新 GRUB:
sudo update-grub
然后重启系统,在引导菜单中选择 RTL 内核启动。
9. 测量系统延迟
启动 RTL 内核后,可以使用 cyclictest 应用程序来测量系统延迟。 cyclictest 是一个用于测试系统实时性能的工具,它可以测量系统在处理周期性任务时的延迟。
9.1 安装 cyclictest
在 Ubuntu 系统上,可以使用以下命令安装 cyclictest :
sudo apt-get install rt-tests
9.2 运行 cyclictest
运行以下命令启动 cyclictest :
sudo cyclictest -p 80 -n -m -t 1
-
-p 80:设置测试任务的优先级为 80。 -
-n:不使用实时调度策略。 -
-m:多线程模式。 -
-t 1:使用 1 个线程进行测试。
运行一段时间后, cyclictest 将输出系统的延迟统计信息,包括最小延迟、最大延迟和平均延迟。
10. 使用 BPF 工具测量调度延迟
除了 cyclictest ,还可以使用现代的 BPF(Berkeley Packet Filter)工具来测量调度延迟。BPF 是一种在内核中运行的轻量级虚拟机,可以用于监控和分析系统性能。
10.1 安装 BPF 工具
在 Ubuntu 系统上,可以使用以下命令安装 BPF 工具:
sudo apt-get install bpfcc-tools
10.2 测量调度延迟
运行以下命令使用 BPF 工具测量调度延迟:
sudo bpftrace -e 't:kvm:kvm_clock_ack { @[comm] = hist(nsecs); }'
这将跟踪 kvm_clock_ack 事件,并记录每个进程的调度延迟。运行一段时间后,按 Ctrl + C 停止跟踪,bpftrace 将输出延迟的直方图。
11. 总结与展望
通过以上步骤,我们成功地将主线 Linux 转换为 RTL 内核,并使用 cyclictest 和 BPF 工具测量了系统延迟。RTL 内核为需要实时性能的应用程序提供了更好的支持,能够满足军事、运输、机器人等领域的实时需求。
以下是一个 mermaid 流程图,展示了构建和启动 RTL 内核的步骤:
graph TD;
A[获取 RTL 补丁和内核源代码] --> B[提取文件];
B --> C[配置内核];
C --> D[构建内核];
D --> E[安装内核];
E --> F[更新 GRUB];
F --> G[重启系统并选择 RTL 内核];
G --> H[测量系统延迟];
以下是一个表格,总结了测量系统延迟的工具和方法:
| 工具 | 功能 | 使用方法 |
| ---- | ---- | ---- |
| cyclictest | 测量系统在处理周期性任务时的延迟 | sudo cyclictest -p 80 -n -m -t 1 |
| bpftrace | 监控和分析系统性能,测量调度延迟 | sudo bpftrace -e 't:kvm:kvm_clock_ack { @[comm] = hist(nsecs); }' |
未来,随着实时技术的不断发展,RTL 内核将在更多领域得到应用。同时,我们也可以进一步探索如何优化 RTL 内核的性能,提高系统的实时性和确定性。通过不断学习和实践,我们可以更好地掌握这些技术,为实时应用开发提供更强大的支持。
超级会员免费看

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



