cgroups 核心模型与实操:从 v1 到 v2 的资源限制机制详解

这段时间在帮人排查一套测试环境,机器一早就卡住,SSH 敲半天都进不去。登上去一看,好几个容器在抢 CPU,内存也快被打穿。想定位到底是哪个容器干的,光靠 namespace 根本看不出来——namespace 只解决“容器能看到什么”,不解决“容器能用多少”。真正管住资源的是 cgroups,也就是 Linux 内核里的控制组机制。这篇继续聊 Linux 内核容器技术,主题就是 cgroups,按系列规划这是第六篇,也是 cgroups 部分的第一篇,先把最核心的模型、接口和实操链路讲透。

cgroups 这块内容非常多,我打算分几篇来写。part1 聚焦在:为什么容器离不开它、cgroups v1 的架构模型、怎么手动限制 CPU 和内存、容器运行时怎么对接 cgroups、以及我在实操中踩过的几个坑。最后会花一节预告 v2 的核心变化,给后续内容留个引子。懂 v1 再看 v2,思路会顺很多。

1. 为什么说 cgroups 是容器的“粮票”,namespace 只是“眼罩”

1.1 一个典型的“吵闹邻居”现场

先还原一下我遇到的情况。那台机器上有十几个容器,跑着不同的服务。某个容器里的进程是个纯计算任务,疯狂消耗 CPU,8 个核全被占满。其他容器的接口响应时间从几十毫秒直接飙到好几秒,有的内存也开始 swap,整个机器几乎不可用。

这时候你去看进程,能看到一堆进程在跑。但问题来了:这些进程都属于哪个容器?如果你只依赖 namespace,在宿主机上看到的进程列表里,PID 命名空间已经被隔离了。每个容器里的进程号是从 1 开始的,进程视图是独立的。但宿主机上看到的还是真实 PID,你可以用 PID 反查 /proc/PID/cgroup 来确认归属,可这已经是事后定位了。

问题的本质是:namespace 管的是“视野”,cgroups 管的是“资源上限”。没有 cgroups 的情况下,任何一个容器里的进程都可以把整台机器的 CPU、内存、磁盘 IO 全部用完。容器所谓的“隔离”,在资源维度上其实是个筛子。

1.2 cgroups 回答三个问题:能用多少、用了多少、谁优先

cgroups 做的事情,可以简化成三个问题:

  • 这个进程组最多能用多少 CPU?这是限额(limit)。
  • 这个进程组到目前为止用了多少 CPU、多少内存?这是计量(accounting)。
  • CPU 不够分的时候,谁先谁后?这是优先级(weight)。

用食堂打饭来类比。namespace 是给你发了一副眼罩,让你以为自己面前只有自己的餐盘;cgroups 则是饭卡和取餐规则。饭卡上限决定了你今天最多能点多少钱的菜,刷卡记录告诉你已经花了多少,遇到大家都想吃的菜时,食堂按优先级决定谁先拿。

容器技术之所以能在一台机器上安全地共存跑业务,靠的不是“大家自觉”,而是内核强制执行的资源配额。Docker 里--memory、--cpus这些参数,翻译成内核语言,都是写进 cgroups 控制文件里的具体数值。

这一篇先把 v1 的模型讲清楚。很多老系统、老容器运行时还在用 v1,而且 v1 里的“层级”“子系统”“控制器”这些概念是理解 cgroups 一切机制的地基。

2. cgroups v1 的世界观:层级、子系统与控制器

2.1 三个绕不开的基本概念

cgroups v1 里最关键的是三个概念:子系统(subsystem)、层级(hierarchy)、控制器(controller)。很多人被这几个词绕晕,我换个说法。

子系统是内核里的一种资源维度。比如 CPU、内存、磁盘 IO、设备访问、冻结、进程数,这些都是独立的子系统。控制器是子系统的具体实现逻辑,负责处理“限制多少、统计多少、怎么调度”等内核逻辑。在实践里,这两个词经常混着用,因为一个子系统就对应一个控制器实例。

层级则是把这些子系统组织起来的树形结构。你可以挂载一棵树,把这棵树绑定给某些子系统,然后在这棵树下面创建目录,每个目录就是一个 cgroup。内核会在每个 cgroup 目录下生成一堆控制文件,读写这些文件就能调整对应子系统的资源限制。

常见的子系统列一下,后续实验和容器运行时都会用到:

子系统作用v1 关键文件
cpu限制 CPU 带宽cpu.cfs_period_us、cpu.cfs_quota_us、cpu.shares
cpuacct统计 CPU 使用量cpuacct.usage、cpuacct.stat
memory内存限制与统计memory.limit_in_bytes、memory.usage_in_bytes、memory.oom_control
cpuset绑定 CPU 和内存节点cpuset.cpus、cpuset.mems
blkio限制块设备 IOblkio.weight、blkio.throttle.*
devices设备访问白名单devices.list、devices.allow、devices.deny
freezer冻结进程组freezer.state
pids限制进程数pids.max
net_cls给网络包打标签net_cls.classid
net_prio设置网络接口优先级net_prio.ifpriomap

2.2 在 /sys/fs/cgroup 下面到底挂了什么

使用 systemd 的主流发行版,会把 cgroups v1 挂载在 /sys/fs/cgroup 下,并且每个子系统一个目录。你用mount | grep cgroup看到的东西大概是这样的:

$ mount | grep cgroup cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,blkio) cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpu,cpuacct) cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset) cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory)

注意这里有两个细节。第一,/sys/fs/cgroup 本身是个 tmpfs,而每个子系统目录才是真正的 cgroup 文件系统挂载点。第二,cpu和cpuacct经常合并成一个挂载点,所以路径可能是/sys/fs/cgroup/cpu,cpuacct。不同发行版布局不完全一样,实操时以自己机器上的 mount 输出为准,我下面的命令里会统一用/sys/fs/cgroup/cpu来写,如果你机器上是cpu,cpuacct,照葫芦画瓢替换即可。

v1 的一个关键限制是:一个子系统一旦被挂载到某个层级,就不能再挂到另一个层级。比如cpu已经绑定在/sys/fs/cgroup/cpu,cpuacct这棵树上,你就不能再单独创建一个只承载cpu子系统的树。这样设计的初衷是想让系统管理者灵活组合子系统,但实际用起来却带来了很多麻烦,这点后面讲 v2 时会具体说。

2.3 tasks、cgroup.procs 与线程组迁移

每个 cgroup 目录下都有tasks和cgroup.procs两个文件,这两个文件决定了哪些进程属于这个组。

tasks是线程维度的,里面的 ID 是线程 ID。cgroup.procs是进程维度的,里面的 ID 是线程组 ID,也就是主线程的 PID。向tasks里写入一个线程 ID 会把该线程迁移进组,但内核实际上会把这个线程所在的整个进程都迁过去,这个行为非常坑。实践里我基本只操作cgroup.procs,避免因为线程迁移带出奇怪的状态。

关于迁移,还有一个要时刻记住的点:把 PID 写进 cgroup.procs 后,这个进程未来 fork 出来的所有子进程都会留在同一个 cgroup 里。也就是说,cgroup 的归属是继承的,子进程一出生就自动落入父进程所在的组。容器的基础就是靠这个特性实现的,容器里的所有进程天然归到同一个 cgroup 下,一起被限制、一起被统计。

3. 手动搭一个受控组:CPU 配额与内存上限动手实验

3.1 先判断手上是 v1 还是 v2

动手之前先确认系统处于哪个模式。v1 的特征是 /sys/fs/cgroup 下有大量子系统目录;v2 的特征是根目录下有cgroup.controllers和cgroup.subtree_control文件,没有一长串子系统挂载点。

# v2 典型文件 $ ls /sys/fs/cgroup/ cgroup.controllers cgroup.subtree_control cgroup.procs ...

如果你看到的是这种,说明系统默认跑 cgroups v2。但别急,v1 的模型还是要学,因为很多老服务器、老容器运行时、部分内核配置仍在使用 v1。而且理解了 v1 的限制,才能明白 v2 改了什么。下面的实验在 v1 环境上操作更直观,如果你的机器是 v2,可以先跳到第六节看全景,或者临时在内核启动参数里切回 v1 来做练习。

3.2 CPU 带宽实验:cfs_period 与 cfs_quota

cgroups v1 的 CPU 限制是 CFS 调度器上的带宽控制,核心是cpu.cfs_period_us和cpu.cfs_quota_us两个参数。

cfs_period_us是一个周期,单位微秒,默认 100000,也就是 100 毫秒。cfs_quota_us是每个周期内允许进程使用的 CPU 时间,单位也是微秒。如果quota=50000,就意味着每 100 毫秒最多跑 50 毫秒,相当于只能用一个核的 50% 时间,也就是半核 CPU。quota 设为 -1 表示不限制。

来做个实验。先建一个控制组:

$ sudo mkdir /sys/fs/cgroup/cpu/cpu_demo $ echo 50000 | sudo tee /sys/fs/cgroup/cpu/cpu_demo/cpu.cfs_quota_us

然后在这个组里跑 CPU 密集型任务。如果机器上装了stress,直接用它;没有的话可以用dd if=/dev/zero of=/dev/null这种死循环凑数。

$ sudo cgexec -g cpu:cpu_demo stress --cpu 1 &

等两三秒后看 top,stress 进程的 CPU 使用率会稳定在 50% 左右。如果没装 cgroup-tools,可以用一个子 shell 把自身 PID 写进组里再执行任务:

$ sudo sh -c 'echo $$ > /sys/fs/cgroup/cpu/cpu_demo/cgroup.procs && exec stress --cpu 1'

注意这里$$在 sh 里是当前 sh 进程的 PID,没问题。如果你在 bash 里写$$,它展开的可能是外层 bash 的 PID,容易把整个 shell 拉进去,后面会踩坑。

实验结束清理:

$ sudo pkill stress $ sudo rmdir /sys/fs/cgroup/cpu/cpu_demo

3.3 内存上限与 OOM 实验

内存限制比 CPU 复杂一点,因为内存有 page cache 回收、匿名页、OOM 等机制参与。先建组,设上限:

$ sudo mkdir /sys/fs/cgroup/memory/mem_demo $ echo 134217728 | sudo tee /sys/fs/cgroup/memory/mem_demo/memory.limit_in_bytes # 128MB

然后在这个组里跑一个吃内存的任务:

$ sudo cgexec -g memory:mem_demo stress --vm 1 --vm-bytes 256M

进程会很快触发 OOM,因为 256MB 超过了 128MB 限制。查看内核日志:

$ dmesg | tail -20

你会看到类似 “Out of memory: Killed process” 的记录。这就是 cgroups 内存控制器在工作:超过硬上限且回收不掉时,内核会调用 OOM killer,杀掉组里最“胖”的进程来释放内存。

这里有个容易误解的点:memory.limit_in_bytes写进去后,内核并不会立即把已有内存压缩到限额以下。它只是在后续内存分配时按这个上限做检查。如果 cgroup 里已经有 200MB 的 cache,设限 128MB 后,内核会先尝试回收 cache;回收不掉再逐出匿名页;还是不行就 OOM。

memory.soft_limit_in_bytes则不同,它是“软上限”。超过软上限不代表立刻被杀,而是系统内存压力大时内核优先回收这个 cgroup 的页。软上限更像优先级提示,不是硬性死亡线。

3.4 更省事的两个方案:cgroup-tools 与 systemd-run

直接写 sysfs 是底层的做法,真正干活时我更推荐用 cgroup-tools,或者干脆用 systemd-run。cgroup-tools 的优点是命令简洁,语义清晰:

$ sudo cgcreate -g cpu,memory:/my_demo $ sudo cgset -r memory.limit_in_bytes=256M /my_demo $ sudo cgset -r cpu.cfs_quota_us=50000 /my_demo $ sudo cgexec -g cpu,memory:/my_demo /path/to/my_program

systemd 系统上还可以直接用 scope 单位:

$ sudo systemd-run --scope -p CPUQuota=50% -p MemoryMax=128M stress --vm 1 --vm-bytes 256M

这条命令的好处是 systemd 会帮你创建出一个临时 cgroup,并且单位定义里已经帮你把底层文件写好了。--scope表示创建的是一个 scope 单位,适合临时跑前台任务;不加--scope的话会创建一个 service 单位放到后台运行。

4. 从 docker run 到 cgroup 目录:一条完整的链路

4.1 在容器里看自己住在哪个 cgroup

容器内部看/proc/self/cgroup,能看到当前进程属于哪个控制组:

$ cat /proc/self/cgroup 0::/docker/abcdef1234567890

这是我拿某个容器运行时做的模拟输出,具体路径取决于运行时。在 v1 系统上则是这种多行结构:

3:memory:/docker/abcdef1234567890 4:cpu,cpuacct:/docker/abcdef1234567890 5:pids:/docker/abcdef1234567890

每一行的格式是:层级 ID、控制器列表、路径。这说明容器运行时把容器进程放进了以容器 ID 命名的 cgroup 目录里,所有资源限制都写在这个目录的控制文件里。

这个文件在排查问题时极其有用。宿主机上看到一个可疑进程,先cat /proc/PID/cgroup,就能直接知道它属于哪个容器、哪个 systemd 服务、或者哪个临时 scope。

4.2 runc 创建容器时的 cgroup 处理流程

现代容器运行时,比如 containerd 和 runc 配合使用时,底层大致是这样的过程:

  1. 用户执行 docker run,带上--cpus=2、--memory=512m之类的参数。
  2. 运行时把容器配置转换成 OCI 规格,也就是 config.json。资源限制会放在linux.resources字段下。
  3. runc 启动容器前,调用 cgroups 管理器。管理器根据配置创建对应的 cgroup 目录,把cpu.max、memory.max、pids.max这些文件写好。
  4. 启动容器进程后,把容器进程 PID 写进cgroup.procs。
  5. 容器进程及其所有后代进程,自动落在这个 cgroup 下。

举个例子,docker run --cpus=2在 v2 系统上对应的是cpu.max里写入"200000 100000",意思是每 100 毫秒周期最多用 200 毫秒 CPU,也就是两颗核的完整时间。在 v1 上则对应cpu.cfs_quota_us=200000和cpu.cfs_period_us=100000。

4.3 systemd 视角:scope、slice 和 cgroup 的关系

systemd 把整个用户态服务管理都建立在了 cgroup 之上。它把每个 unit 映射成一个 cgroup,用 slice 来表示层级目录,用 service 表示后台服务,用 scope 表示临时任务。

systemd-run --scope创建的 scope 单位就是一个临时 cgroup,它会被挂在当前 slice 下。systemd 的意义在于:它成为了系统里唯一的 cgroup 管理器,服务启动、停止、重启时,cgroup 目录会被正确创建和清理,不太容易出现孤儿目录。

所以容器运行时和 systemd 之间就产生了一个协作问题:cgroup 到底由谁管?这也就是 Kubernetes 圈子里经常争论的 cgroup driver 问题。

如果 kubelet 用cgroupfs驱动,它直接写 /sys/fs/cgroup 下的目录;如果 kubelet 用systemd驱动,它让 systemd 帮忙创建 transient unit。两种方式都能跑,但绝对不能混用。混用会导致 systemd 以为某个 cgroup 是自己的,实际上目录被另一个进程先占了;服务重启时可能出现目录删不掉、限制丢失、或者资源统计错乱的情况。

5. 实操中踩过的几个 cgroup 坑:排查过程与解法

5.1 内存 limit 设置了,但使用量没有立刻降下来

有次给一个 Java 服务容器设置内存上限,limit 从 2G 调到 1.5G,结果发现容器内的监控显示内存使用量还是 1.8G,而且过一会儿容器被 OOM 杀掉了。

排查过程从/sys/fs/cgroup/memory下的统计文件入手:

$ cat memory.usage_in_bytes $ cat memory.stat | head -20

memory.stat 里的 cache 和 anonymous 两个值很关键。当时 cache 占了几百 MB,匿名页占了一千多 MB。limit 调低后,内核会先尝试回收 cache,但 Java 进程还在持续分配匿名内存,cache 回收速度跟不上分配速度,于是一部分内存回收还没来得及完成,就直接触发了 OOM。

这个坑的教训是:调低内存上限要观察内存回收的滞后性,别指望写一个数字就立刻看到内存占用降下来。更稳的做法是配合memory.high(v2)或者软上限做“软顶”,让进程先被压制,而不是直接进入 OOM 急刹车。

5.2 进程已退出,cgroup 目录却 rmdir 失败

场景:测试脚本跑完,进程已经退出,想删掉临时 cgroup 目录,结果报错:

$ rmdir /sys/fs/cgroup/memory/mem_demo rmdir: failed to remove 'mem_demo': Device or resource busy

第一反应是查是不是还有进程在里面:

$ cat /sys/fs/cgroup/memory/mem_demo/cgroup.procs $ cat /sys/fs/cgroup/memory/mem_demo/tasks

如果没有输出,说明没有进程残留。那就可能是内核里还有引用,常见的是内存页还没完全释放,在 v1 的 memory 子系统里可以用 force_empty 强制回收:

$ echo 0 | sudo tee /sys/fs/cgroup/memory/mem_demo/memory.force_empty $ sudo rmdir /sys/fs/cgroup/memory/mem_demo

另外还要确认目录里没有子目录。cgroup 目录 rmdir 时要求本身是叶节点,如果里面有子 cgroup,必须先删子目录,再删父目录。遇到这种问题不要急着重启,先按这个顺序排查。

5.3 把自己所在的 shell 迁进了受控组

这是我最想提醒的一个坑。做实验时很容易为了图省事,执行这样的命令:

$ echo $$ > /sys/fs/cgroup/memory/demo/cgroup.procs

结果就是这个 shell 自己进了受控组。如果该组内存限制只有几百 MB,而系统内存已经很紧张,shell 想 fork 新命令时分配不了内存,直接进 D 状态,连ps都可能敲不出去。

自救办法有两个。一是从另一个终端登录,把 shell PID 迁回原来的 cgroup:

$ echo 12345 > /sys/fs/cgroup/cgroup.procs

写回根组后,这个 shell 就脱控了。v1 系统则要写回对应的子系统根目录,比如:

$ echo 12345 > /sys/fs/cgroup/memory/cgroup.procs $ echo 12345 > /sys/fs/cgroup/cpu/cgroup.procs

二是直接 kill 掉那个卡死的 shell。所以我的建议是:任何写 cgroup.procs 的操作,都要想清楚“待会它还能不能出来”。宁可多用一个子 shell 包裹进程,也别把自己搭进去。

6. cgroups v2 到底改了什么:part1 先看全景

6.1 v1 的三个遗留问题

v1 用起来问题不少,最核心的有三点。

第一,多层级造成管理混乱。一个进程可以同时属于内存树里的一个 cgroup、CPU 树里的另一个 cgroup、设备树里的第三个 cgroup。你想描述“这个容器整体在哪个组”都费劲,更别说让 systemd、容器运行时、监控系统统一协作。

第二,父子边界不清晰。v1 允许一个 cgroup 内部既有自己的进程,又挂载子 cgroup。这就像家里长辈和孩子住同一个房间,算资源时说不清楚哪些资源是长辈的还是孩子的。内核对这种父子混合场景的统计和调度都容易出歧义。

第三,控制器之间没有统一的协调机制。不同子系统各自为政,OOM 行为、冻结顺序、迁移路径都不一致。系统管理员需要记住十几套控制文件,出错率极高。

6.2 统一层级与 subtree_control

v2 最核心的改动是:所有控制器都挂载到同一个层级上,整个系统只有一棵 cgroup 树,挂在 /sys/fs/cgroup。

统一的代价是引入了新的授权机制:一个 cgroup 不能自动拥有所有控制器,必须由它的父亲在cgroup.subtree_control文件中显式声明“允许哪些控制器向下生效”。

举例:

$ echo '+cpu +memory +pids' > /sys/fs/cgroup/cgroup.subtree_control $ mkdir /sys/fs/cgroup/demo

这时 demo 目录下才会出现cpu.max、memory.max、pids.max这些文件。如果你没有在父级开启对应控制器,子 cgroup 里就不会有这些控制文件。

这个设计和 v1 的“一挂一大片”完全不同。好处是权限控制的粒度更细了,父 cgroup 可以只把 memory 控制器委托给子 cgroup,CPU 却由父级统一管理。

6.3 没有内部进程约束

v2 有一个原则叫 “no internal process constraint”,意思是:如果一个 cgroup 需要开启子层级,它自己就不能有内部进程。

换句话说,你要在demo下面再创建一个demo/child,并且给 child 开启 CPU 控制器,那 demo 这个 cgroup 本身必须清空,不能有进程直接挂在 demo 下。想挂进程?去 child 里挂。这样整个树的生命周期和资源归属就非常清晰:每个非叶节点只承担“容器”角色,真正跑业务的进程全部在叶节点。

这解决了 v1 父子混合导致的资源统计问题。实际使用中,systemd 和容器运行时都遵循这个约束来创建目录结构,所以 v2 下的 cgroup 树看起来干净得多。

6.4 文件设置的变化与运行时迁移

v2 把很多 v1 的接口改名了,下表是老手迁移时的速查参考:

场景v1 文件v2 文件
CPU 配额cpu.cfs_quota_us + cpu.cfs_period_uscpu.max(格式quota period)
CPU 权重cpu.sharescpu.weight(1-10000,默认 100)
内存硬上限memory.limit_in_bytesmemory.max
内存软限制memory.soft_limit_in_bytesmemory.low、memory.high
进程数限制pids.maxpids.max
OOM 状态memory.oom_controlmemory.events、cgroup.events
设备白名单devices 子系统eBPF 钩子或 systemd DeviceAllow

v2 的cpu.max写法是一次性给两个值,例如echo '200000 100000' > cpu.max表示最多占 2 核。memory.high则是一个“软节流阀”,超过后内核会尽力回收,但不保证杀死进程,适合做弹性限流。

容器运行时也在往 v2 迁移。runc 在新内核上默认使用统一的 cgroup 树,Kubernetes 从某几个版本开始也逐步转向 systemd 驱动的 cgroup 管理。对普通用户来说,v2 带来最直观的感受是:查看进程归属、清理孤儿目录、配置多渠道资源限制都变得简单了。

—

从 v1 到 v2 的切换不是一朝一夕的事。我在实际维护中,还是偶尔会遇到老系统上的 v1 路径,特别是那些长期不升级的物理机上,容器运行时还在用 cgroupfs 驱动。而这种时候,懂 v1 的架构和文件接口就特别重要,你能快速定位问题,而不是对着网上搜来的 v2 命令发呆。

下一篇我打算深入 v2 的子树控制、线程组模式以及内核对 cgroup 的源码级实现,把 controller 的加载、cgroup namespace 和资源控制器之间的协作机制铺开讲。如果你在实操中也踩过 cgroup 相关的坑,欢迎交流,尤其是那些删不掉目录、内存回收不及时的案例,多聊几次才能真正理解 cgroups 的行为边界。

相关新闻

Linux NVMe IO路径深度解析:从请求提交到中断完成的性能优化

Linux NVMe IO路径深度解析:从请求提交到中断完成的性能优化

跑了很多年存储性能测试,我一直觉得,聊NVMe设备的IO过程,比单纯比跑分有意思得多。一个普通的4K随机读,从应用程序发起,到操作系统层层剥皮,再到PCIe总线上那个小小的门铃寄存器被敲响,最后从SS…

2026/10/20 18:21:00 阅读更多 →
多任务学习驱动智能家居统一架构:控制与推荐融合

多任务学习驱动智能家居统一架构:控制与推荐融合

智能家居这几年越来越普及,但真正用起来总有一种“各管各”的割裂感。你在手机App里设置好回家模式,灯光自动打开,窗帘拉上,空调设到26度——这是“设备控制”。冰箱里的食材快过期了,系统在屏幕角落提醒你做晚餐&…

2026/10/20 18:21:00 阅读更多 →
构建HuggingFace图像-文本数据集:从Schema设计到Hub推送全流程

构建HuggingFace图像-文本数据集:从Schema设计到Hub推送全流程

做多模态模型训练的人,早晚都要跟 HuggingFace 图像-文本数据集打交道。这个标题看起来像是个文档整理任务,实际上它涵盖了从数据采集、清洗、格式设计,到用 datasets 库构建 Dataset、推到 Hub 供团队或社区使用的整套流程。我最早接触到这个…

2026/10/20 18:21:00 阅读更多 →

最新新闻

MSA存储入门指南:从SAN协议到选型场景的全面解析

MSA存储入门指南:从SAN协议到选型场景的全面解析

简介:这份文档面向备考H3C认证工程师-MSA存储(H3CNE-MSA)的技术人员与H3C代理服务工程师,聚焦MSA存储设备基础配置与维护知识,帮助读者在有限时间内系统梳理考点、检验掌握程度。资源为1个docx文件,压缩包约…

2026/10/20 19:39:14 阅读更多 →
C语言大作业高分实践:工程结构、内存安全与评分对齐

C语言大作业高分实践:工程结构、内存安全与评分对齐

简介:这是一份面向高校C语言初学者与课程设计学生的高分期末大作业实战项目,完整实现了一个具备学生端与管理员双角色的学生成绩管理系统。资源包含可直接编译运行的C/Qt源码(含18个.h头文件与18个.cpp实现文件)、图形界面.ui文件…

2026/10/20 19:39:14 阅读更多 →
Redis Java客户端选型与实战:Jedis、Lettuce、Redisson对比

Redis Java客户端选型与实战:Jedis、Lettuce、Redisson对比

1. 先搞清楚需求,再选客户端写Java应用连Redis,第一步不是去翻依赖、复制代码,而是先想清楚:你的业务到底需要Redis做什么,以及你的应用是什么形态。这话可能听起来像废话,但我在实际项目里见过太多人一上来…

2026/10/20 19:39:14 阅读更多 →
轻量级AI助手ZeroClaw实战:有限算力下的部署与Agent落地

轻量级AI助手ZeroClaw实战:有限算力下的部署与Agent落地

1. 项目定位:为什么轻量级AI助手反而更实用去年年底我拿到一台配置平平的旧工作站,8GB内存、无独显、CPU还是几年前的型号,跑几个容器就喘。当时手头正好有个工具自动化需求:定时抓取内部系统数据,做简单清洗&#xff…

2026/10/20 19:39:14 阅读更多 →
单相桥式半控整流电路原理与Matlab/Simulink仿真建模分析

单相桥式半控整流电路原理与Matlab/Simulink仿真建模分析

单相桥式半控整流电路,这个名字在电力电子课程里出现的频率极高。很多电气工程专业的朋友第一次接触晶闸管整流,就是从它开始的。最近正好有人在问Matlab仿真的思路,我把自己做这个仿真报告的全过程梳理了一遍,从拓扑结构分析到模…

2026/10/20 19:39:14 阅读更多 →
Java动漫链接生成器:批量整理番剧资源链接的轻量工具

Java动漫链接生成器:批量整理番剧资源链接的轻量工具

简介:这是一款面向动漫资源采集与二次元工具开发者的轻量级命令行程序,基于Java实现,可根据指定标签批量生成动漫图片链接,适合需要批量获取Danbooru图源、做数据采集或练手Java命令行项目的开发者。资源包共17个文件,…

2026/10/20 19:38:12 阅读更多 →

日新闻

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →
ESP32舵机控制指南:原理、多路驱动与避坑实战

ESP32舵机控制指南:原理、多路驱动与避坑实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →
位移法实现一阶低通滤波:单片机ADC采样降噪实战

位移法实现一阶低通滤波:单片机ADC采样降噪实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/20 10:02:13 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/19 17:32:41 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/20 2:50:50 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 10:02:08 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:17 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:45 阅读更多 →