当前位置: 首页 > news >正文

Linux内存管理实战:从Page Cache到OOM Killer的深度解析与调优

1. 项目概述:从“内存不够用”说起

干了这么多年运维和开发,最怕半夜响起的告警里带着“OOM”三个字母。Linux系统的内存管理,听起来是个挺底层的技术话题,但它的表现直接关系到我们手头每一个应用的生死存亡。你可能也遇到过:明明free -m看到还有不少空闲内存,系统却开始疯狂使用Swap,导致应用响应慢如蜗牛;或者某个进程悄无声息地吃光了所有内存,触发OOM Killer把数据库给“杀”了,留下一地鸡毛。这些问题的根源,都藏在Linux那套复杂又精巧的内存管理机制里。

“浅析”这个词用得好,因为Linux内存管理确实是个深水区,涉及内核态、硬件架构、算法策略,真要深究起来,一本书都写不完。但我们的目标不是成为内核开发者,而是作为一线的系统管理员、后端工程师或者性能调优专家,能看懂内存指标背后的故事,能精准定位问题,并实施有效的优化策略。这篇文章,我就结合自己踩过的无数个坑,带你拨开/proc/meminfo里那些数字的迷雾,理解Buffer和Cache到底在干嘛,Swap是不是洪水猛兽,以及如何让OOM Killer成为你的帮手而不是“猪队友”。无论你是刚接触Linux的新手,还是想深化理解的老兵,相信这些从实战中提炼出来的视角和技巧,都能让你对系统内存的掌控力上一个台阶。

2. 内存管理核心机制拆解:不只是“可用”与“已用”

很多人对Linux内存的第一印象来自free命令,盯着usedfree两栏,觉得free少了就是内存紧张。这个看法太片面了,甚至可以说是错误的起点。Linux内核的内存管理哲学是“不用白不用”,它会充分利用内存来提升系统性能,因此设计了一套层次化的管理策略。

2.1 物理内存的“三省划分法”

我们可以把系统的物理内存想象成一个大仓库,内核把它分成了三个主要区域,我习惯称之为“三省”:

内核省(Kernel Space):这是操作系统的“自留地”,通常位于内存的高地址区域。内核自身的代码、数据结构(如进程表、内存映射表)、以及直接为内核服务的缓存(如Slab分配器管理的对象)都住在这里。用户程序无法直接访问。通过/proc/meminfoKernelStackSlabVmallocUsed等字段可以窥见其使用情况。

用户省(User Space):这是我们应用程序活动的主舞台。每个进程看到的都是独立的虚拟地址空间,由内核通过页表映射到物理内存上。这部分内存是动态分配的,也是我们日常关注的重点。

页缓存省(Page Cache):这是Linux性能优化的关键所在,也是最容易被误解的区域。当你读写文件时,内核并不会立刻去碰慢吞吞的磁盘,而是把文件数据在内存中保留一份副本,这就是Page Cache。它的存在,使得后续的读请求可以直接从内存命中,速度极快。free命令里被算在used里的buff/cache,绝大部分就是它和Buffer。

这里有个关键点:Page Cache是“可回收”的内存。当应用程序需要分配更多内存时,如果物理内存不足,内核会智能地释放这部分缓存(丢弃干净的缓存页,或将脏页写回磁盘后释放),将其空间让给应用程序。所以,一个被Page Cache占满内存的系统,很可能正处于性能最佳状态,而非危机状态。

2.2 虚拟内存:给每个进程一个“独立王国”的幻觉

物理内存有限,而进程的需求是无限的。虚拟内存技术通过引入一层间接寻址,给每个进程营造了一个“独占整个内存地址空间”的假象。每个进程都有自己的页表,由内存管理单元(MMU)负责将虚拟地址转换为物理地址。

核心好处有三点

  1. 隔离与保护:进程A无法访问进程B的数据,提高了系统稳定性与安全性。
  2. 简化编程:程序员不用关心物理内存的实际布局,链接器和加载器可以假设从地址0开始加载程序。
  3. 超越物理限制:通过Swap机制,可以将暂时不用的内存页换出到磁盘,从而在逻辑上扩展了可用内存容量。

/proc/[pid]/smapspmap命令的输出里,你可以看到一个进程虚拟内存空间的详细布局,包括代码段(r-xp)、数据段(rw-p)、堆([heap])、栈([stack])以及内存映射文件(mmap)等区域。

2.3 Swap:救火队长还是性能杀手?

Swap(交换空间)的名声有点两极分化。它本质上是磁盘上的一块预留区域,用于存放被“换出”的物理内存页。它的存在,使得系统在物理内存耗尽时,不至于立刻崩溃,而是通过牺牲一些性能(因为磁盘IO比内存慢几个数量级)来换取继续运行的能力。

什么时候Swap是好事?当系统内存压力主要来自一次性或低频访问的内存时。例如,你启动了一个大型IDE但暂时不用,它占用的部分内存被换出,为其他活跃进程腾出空间,这是合理的资源调度。

什么时候Swap是坏事?高频访问的热数据被频繁换入换出时,就会引发“Swap Thrashing”(交换颠簸)。这时CPU大量时间花在等待IO上,系统整体响应速度急剧下降,si/so(Swap In/Out)值在vmstat 1命令中持续高位。这通常是内存严重不足的红色警报。

实操心得:完全禁用Swap在现代系统上并不总是明智的。有些应用(如某些版本的Java应用)或内核特性会假设Swap存在。我的建议是:配置Swap,但通过vm.swappiness参数(通常设置在10-60之间,值越低越倾向释放Page Cache而非Swap)控制其积极性,并密切监控Swap使用率。一旦发现si/so持续活动,首要任务是查找内存泄漏或扩容内存,而不是调大swappiness

3. 关键指标深度解读与监控实践

理解了基本概念,我们得学会看“仪表盘”。Linux提供了丰富的工具和文件接口来观察内存状态,但数据贵在关联解读。

3.1 /proc/meminfo:内存的“全景体检报告”

这是最权威的内存信息源。我们挑几个核心字段,结合实战场景来看:

  • MemTotal / MemFree:总物理内存和字面意义的空闲内存。单独看MemFree价值不大,因为它不包含可回收的缓存。
  • MemAvailable(关键!):这是内核估算的、可用于启动新应用而无需交换的内存总量。它考虑了MemFreePageCache以及部分可回收的Slab。这是判断内存是否真紧张的最重要指标。如果MemAvailable持续很低(例如小于总内存的10%),就需要警惕。
  • Buffers / CachedBuffers主要关联块设备(如磁盘)的元数据缓存;Cached就是前面说的Page Cache,用于文件内容缓存。两者都是可回收的。
  • SwapCached:已被换出、但又被换入且未修改的内存页。它仍然存在于Swap空间,但在内存中有副本。如果这个值较大,说明之前发生过Swap,但相关数据现在又需要了,是性能曾受影响的痕迹。
  • Active(file) / Inactive(file):活跃与非活跃的文件缓存页。内核倾向于回收Inactive(file)列表中的页。
  • Active(anon) / Inactive(anon):活跃与非活跃的匿名页(如进程堆栈、堆数据,与文件无关)。当内存紧张时,Inactive(anon)列表中的页是Swap的主要候选者。
  • Dirty / Writeback:已被修改但未写回磁盘的缓存页(Dirty),以及正在写回磁盘的页(Writeback)。如果Dirty值长期很高,可能意味着磁盘IO存在瓶颈。
  • Slab / SReclaimable:Slab分配器使用的内存,用于缓存内核对象(如目录项dentry、索引节点inode)。SReclaimable是其中可回收的部分。在内存极度紧张时,这部分也可以被回收。

监控脚本示例: 你可以写一个简单的脚本定期抓取关键指标,用于趋势分析。

#!/bin/bash # 抓取核心内存指标 grep -E 'MemTotal|MemAvailable|Cached|SwapTotal|SwapFree|Dirty|Active\(anon\)|Inactive\(anon\)' /proc/meminfo # 计算可用内存百分比 MemAvailable=$(grep MemAvailable /proc/meminfo | awk '{print $2}') MemTotal=$(grep MemTotal /proc/meminfo | awk '{print $2}') if [ -n "$MemAvailable" ] && [ -n "$MemTotal" ]; then AvailablePct=$(echo "scale=2; $MemAvailable * 100 / $MemTotal" | bc) echo "MemAvailable Percentage: ${AvailablePct}%" fi

3.2 进程级内存分析:VSS、RSS、PSS、USS

topps命令里,我们常看到VIRT(虚拟内存大小)、RES(常驻内存大小)、SHR(共享内存大小)。但它们不够精确。更细致的分析需要借助/proc/[pid]/smaps或工具如smem

  • VSS (Virtual Set Size):进程总的虚拟地址空间大小。几乎无直接诊断价值,因为它包含了大量未实际分配和共享的库。
  • RSS (Resident Set Size):进程实际占用的、非Swap的物理内存大小。但它重复计算了共享库。如果10个进程都用了libc.so,这个库的物理内存会被算10次进总RSS,导致你看到的系统总RSS远大于物理内存,这是正常的。
  • PSS (Proportional Set Size):将共享内存按使用进程数均分后,再加进程私有内存。这是评估进程内存占用的更公平指标。例如,一个占用30MB私有内存并使用一个被100个进程共享的10MB库的进程,其PSS约为30 + 10/100 = 30.1MB。
  • USS (Unique Set Size):进程独占的、不共享的物理内存大小。这是定位内存泄漏的关键指标。如果一个进程的USS持续增长,而PSS和RSS同步增长,很可能存在私有内存泄漏。

使用smem工具快速查看

# 按PSS排序显示进程内存 smem -s pss -r # 以百分比形式展示 smem -p

3.3 性能监控工具联动

单一的内存视图不够,需要结合CPU、IO来看。

  • vmstat 1:看si(Swap In)、so(Swap Out)、cs(上下文切换)、us/sy(用户/系统CPU时间)。si/so持续大于0是Swap活跃的信号。
  • sar -r 1:查看内存使用率、kbmemfreekbavail(类似MemAvailable)、kbbufferskbcached等历史趋势。
  • pidstat -r 1:查看每个进程的内存页错误(minflt/s次要缺页,通常无需IO;majflt/s主要缺页,需要磁盘IO)和VSZ、RSS变化。

4. 常见内存问题诊断与优化实战

理论说再多,不如解决一个实际问题。下面我们模拟几个典型场景。

4.1 场景一:系统“内存不足”,但free显示Cache很大

现象:应用响应变慢,监控显示MemAvailable极低,甚至触发OOM,但free -m看到usedbuff/cache占了大部分。

诊断:这是最经典的误解。内核已经尽力用Cache提升性能,但应用需要的是匿名页(堆内存)。内核回收Cache(尤其是Inactive(file))的速度可能跟不上应用分配的速度,或者vm.swappiness设置导致内核更倾向于Swap匿名页而非释放Cache。

排查步骤

  1. 确认压力来源vmstat 1si/so。如果很高,说明正在Swap。
  2. 查看内存详细分布cat /proc/meminfo | grep -E \"Active|Inactive|Dirty\"。关注Active(anon)是否很高,Inactive(file)是否还有存量。
  3. 检查Swappinesssysctl vm.swappiness。如果值很高(如60),在内存以Cache为主的工作负载下,可能会引发不必要的Swap。
  4. 找到消耗匿名页的进程:使用smem -s uss -rtop(按Shift+M按RES排序),查看USS/RSS高的进程。

优化与解决

  • 临时缓解:可以手动释放Page Cache(生产环境慎用,仅用于诊断):
    # 释放PageCache echo 1 > /proc/sys/vm/drop_caches # 释放dentries和inodes echo 2 > /proc/sys/vm/drop_caches # 释放PageCache, dentries and inodes echo 3 > /proc/sys/vm/drop_caches
    这相当于清空了磁盘缓存,后续的读请求会直接落盘,可能引起性能波动。
  • 长期调整:如果应用确实需要大量匿名内存,考虑:
    1. 调整vm.swappiness:对于数据库、缓存服务器等期望Cache保留的系统,可以将其设为较低值(如10-30),让内核优先释放Cache。
    2. 调整vm.vfs_cache_pressure:控制内核回收dentryinode缓存的倾向(默认100,值越大回收越快)。如果Slab占用过高且SReclaimable很大,可以适当增加此值(如200)。
    3. 应用层优化:检查应用是否存在内存泄漏或不合理的内存使用(如一次性加载超大文件到内存)。
    4. 硬件扩容:最直接的方式。

4.2 场景二:Java应用引发的OOM(非Heap)

现象:一个Java应用崩溃,日志显示OutOfMemoryError,但Heap Dump分析显示堆内存并未耗尽。

诊断:Java进程的内存不止堆(Heap)。还有栈(Stack)、元空间(Metaspace,用于类元数据)、直接内存(Direct Buffer)、JVM自身代码和数据结构占用的本地内存(Native Memory)。

排查步骤

  1. 确认OOM类型:查看JVM崩溃日志或系统日志/var/log/messages/dmesg。错误信息可能是java.lang.OutOfMemoryError: Metaspacejava.lang.OutOfMemoryError: Direct buffer memory,甚至是操作系统级别的OOM Killer日志(dmesg | grep -i kill)。
  2. 分析进程内存构成:在出问题前,使用pmap -x <pid>jcmd <pid> VM.native_memory summary(JDK8+)查看内存分布。
  3. 监控Native Memory增长:如果怀疑Native Memory泄漏,可以使用jcmd <pid> VM.native_memory baseline建立基线,然后jcmd <pid> VM.native_memory detail.diff查看变化。

优化与解决

  • Metaspace OOM:调整JVM参数-XX:MaxMetaspaceSize,限制其大小,并检查是否有类加载器泄漏(如频繁部署重启的应用服务器)。
  • Direct Buffer OOM:调整-XX:MaxDirectMemorySize,并检查代码中ByteBuffer.allocateDirect()的使用是否合理释放。
  • 栈溢出:调整-Xss参数减小单个线程栈大小(但会增加线程数上限),或检查是否有无限递归。
  • 被OOM Killer杀死:这通常是系统总内存耗尽。需要结合场景一的分析,看是哪个进程(可能是该Java进程本身,也可能是其他进程)耗尽了内存。可以调整进程的OOM分数(/proc/[pid]/oom_score_adj),降低重要进程被杀的几率(设为负数),但这是治标不治本。

踩坑实录:曾遇到一个使用Netty的应用,在高压下发生Direct Memory泄漏。原因是自定义的ByteBuf分配器未正确释放Off-Heap内存。通过-XX:MaxDirectMemorySize限制上限,并配合-XX:+ExitOnOutOfMemoryError让JVM在发生任何OOM时立即退出(便于容器化环境快速重启),同时修复代码,才最终解决。监控上,我们增加了对java.nio.Bits相关JMX指标的监控。

4.3 场景三:内存泄漏的定位与追踪

现象:系统或某个进程的MemAvailableUSS随时间推移持续缓慢下降,重启后恢复。

诊断:这是典型的内存泄漏。可能是用户态程序(如C/C++程序未释放malloc的内存,或Java堆外内存泄漏),也可能是内核模块或驱动有问题。

排查步骤(用户态进程)

  1. 确定嫌疑进程:使用smem -s uss -r或脚本定期采集进程USS,找出持续增长的进程。
  2. 使用Valgrind Massif:对于可重现的C/C++程序,使用valgrind --tool=massif ./your_program进行堆内存分析,生成内存快照图。
  3. 使用jemalloctcmalloc的Profiling功能:这些内存分配器通常自带分析工具,可以定位泄漏点。
  4. 使用straceltrace跟踪系统调用:对于怀疑是频繁分配未释放的情况,可以跟踪brkmmapmunmap等调用。
  5. 分析核心转储:如果进程崩溃,分析其核心转储文件(gcore或系统生成)中的内存状态。

排查步骤(内核态)

  1. 检查Slab占用cat /proc/meminfo | grep Slab。如果SUnreclaim(不可回收的Slab)异常高且增长,可能是内核模块泄漏。使用slabtop命令按占用排序查看具体是哪个Slab缓存异常。
  2. 使用kmemleak:在内核编译时启用CONFIG_DEBUG_KMEMLEAK,它可以检测内核中未引用但未释放的内存块。
  3. 检查/proc/slabinfo:对比不同时间点的快照,观察特定缓存对象(如dentryinode_cachebuffer_head)数量的变化。

一个简单的监控脚本

#!/bin/bash # 监控指定进程USS变化 PID=$1 INTERVAL=60 while true; do if [ -f /proc/$PID/smaps ]; then USS=$(grep -E '^Private' /proc/$PID/smaps | awk '{sum+=$2} END {print sum}') echo "$(date): PID $PID USS = ${USS} KB" else echo "Process $PID not found." break fi sleep $INTERVAL done

5. 进阶调优与配置策略

理解了问题和诊断方法,我们可以主动进行一些优化配置,防患于未然。

5.1 内核参数调优指南

以下是一些常见且重要的/etc/sysctl.conf参数调整,需要根据实际负载测试:

  • vm.swappiness(默认值通常60):控制内核使用Swap的积极性。对于数据库、缓存服务器,建议设为10-30。对于桌面系统或内存压力主要来自匿名页的应用,可以保持默认或稍高。
  • vm.vfs_cache_pressure(默认100):控制内核回收dentryinode缓存的倾向。如果系统有大量小文件操作(如Web静态服务器),且Slab占用高,可以尝试增大到200-500,让内核更积极地回收。
  • vm.dirty_ratio/vm.dirty_background_ratio:控制脏页写回磁盘的阈值。dirty_background_ratio(默认10)是后台写回开始的百分比;dirty_ratio(默认20)是强制同步写回的百分比。对于写密集型应用(如数据库),为了避免IO尖峰,可以降低这两个值(如分别设为5和10),让写回更平缓。但会稍微增加内存中脏页的数量。
  • vm.overcommit_memory:内存分配策略。
    • 0(默认):启发式过度分配,内核会估算是否有足够内存。
    • 1:总是允许过度分配,适用于科学计算等场景。
    • 2:禁止过度分配,分配的内存总量不超过swap + RAM * vm.overcommit_ratio。对于要求绝对稳定的生产环境(如金融交易),可以考虑设为2,但需要合理设置vm.overcommit_ratio(默认50),并确保应用不会因分配失败而异常。
  • vm.min_free_kbytes:系统保留的最小空闲内存(KB)。用于保障原子性内存分配和应对突发需求。设置太高会浪费内存,太低可能导致内存碎片化严重时分配失败。一般建议为系统总内存的1-3%,对于大内存机器(如256GB),可以设一个固定值如262144(256MB)。

应用示例:一个128GB内存的MySQL数据库服务器配置片段。

# /etc/sysctl.conf vm.swappiness = 10 vm.vfs_cache_pressure = 200 vm.dirty_ratio = 15 vm.dirty_background_ratio = 5 vm.min_free_kbytes = 1048576 # 1GB,约总内存0.8% # 使配置生效 sysctl -p

5.2 CGroup v2 内存控制

对于容器化环境(Docker, Kubernetes),内存限制是通过CGroup实现的。理解其机制有助于调试容器内存问题。

  • memory.limit_in_bytes:设置内存使用硬限制。超过此限制,进程会被OOM Killer终止(在容器内)。
  • memory.soft_limit_in_bytes:软限制。当系统内存紧张时,超过此限制的CGroup内的进程会更可能被回收内存。
  • memory.oom_control:可以禁用CGroup的OOM Killer(echo 1 > memory.oom_control),但风险很高,可能导致整个节点不稳定。
  • memory.stat:查看CGroup详细内存统计,包括rsscacheswapactive_anon等,是分析容器内存使用的关键文件。

在Kubernetes中,limits.memory对应硬限制,requests.memory是调度和软限制的参考。务必设置limits,否则单个容器可能吃光节点内存。

5.3 NUMA架构下的内存考量

在多CPU插槽(NUMA架构)的服务器上,内存访问有“本地”和“远程”之分,远程访问延迟更高。对于内存敏感型应用(如高性能数据库、科学计算),需要优化内存分配策略。

  • 查看NUMA状态numactl --hardware
  • 策略选择
    • numactl --interleave=all:交错分配,在所有节点上均匀分配内存。适用于内存带宽密集型应用。
    • numactl --membind=节点:将内存绑定到特定节点。适用于追求极致延迟、且CPU也绑定到同节点的应用。
    • numactl --cpunodebind=节点 --membind=节点:将进程的CPU和内存都绑定到同一节点。

对于MySQL、MongoDB等数据库,在NUMA系统上,有时需要在内核启动参数添加numa=off(禁用NUMA优化)或使用numactl --interleave=all来启动,以避免因内存分配不均导致的性能问题。

6. 内存问题排查工具箱与思维导图

最后,我总结一个快速排查内存问题的思维导图和工具链,方便你在紧急情况下按图索骥。

第一步:现象确认

  • 工具:top,htop,free -h
  • 看什么:系统负载、MemAvailable、Swap使用率(Si/So)。

第二步:定位压力源

  • 工具:smem -s uss -r,ps aux --sort=-%mem,vmstat 1,sar -r 1
  • 看什么:哪个进程USS/RSS高?是匿名页活跃(Active(anon)高)还是文件缓存多(Cached高)?Swap是否活跃?

第三步:深入进程分析

  • 工具:pmap -x [pid],cat /proc/[pid]/smaps,jcmd(Java),valgrind,strace
  • 看什么:进程内存具体分布(堆、栈、共享库、mmap),是否存在异常映射。对于Java,看各内存池使用情况。

第四步:内核与系统级分析

  • 工具:cat /proc/meminfo(详细字段),slabtop,dmesg | tail -50,perf record -g -p [pid]
  • 看什么:Slab使用是否异常?内核日志有无OOM或异常?是否存在系统调用或内核路径上的瓶颈?

第五步:调整与优化

  • 工具:sysctl,numactl, CGroup接口
  • 做什么:根据分析结果,调整内核参数、进程绑定、资源限制,或进行应用代码优化。

记住,内存管理是一个权衡的艺术:在速度(Cache)和容量(Swap)之间,在分配效率(Buddy System)和碎片化之间,在进程隔离和共享资源之间。没有放之四海而皆准的最优解,最好的策略源于对自身应用负载的深刻理解,以及一套完整的监控、告警和诊断体系。当你再看到内存使用率“居高不下”时,希望你的第一反应不再是焦虑,而是能从容地打开工具链,像老中医一样,开始一次系统的“望闻问切”。

http://www.jsqmd.com/news/830940/

相关文章:

  • 告别内置ADC的烦恼:手把手教你用ADS1119实现高精度电压采样(附TMS28335代码)
  • CTF流量分析实战:从一道DNS题看Base64隐写与数据拼接(附Wireshark过滤技巧)
  • Unity之Animation窗口:从零到一的动画创作指南
  • 深入解析ADC噪声系数:从概念到系统级设计与优化
  • FanControl:Windows平台智能风扇控制软件完整指南
  • Linux网络运维实战:从ifconfig、ethtool到网络状态深度诊断
  • 番茄小说下载器:为什么这款工具能成为你的离线阅读神器?
  • CMAQ建模者的效率工具:ISAT.M Linux版从环境配置到清单生成全记录
  • 量子网络架构设计:挑战、原理与工程实践
  • 从V8引擎限制到项目实战:深度解析Node.js打包内存溢出与--max-old-space-size调优策略
  • 【Midjourney进阶】四大核心操作精讲:Remix模式调优、图片管理、收藏与私信获取
  • Windows 10系统下PL-2303串口驱动修复指南:告别单向通信,重获双向数据传输能力
  • Point Transformer V3 牙齿语义分割测试结果为0问题:完整调试与修复方案
  • 保姆级教程:PrintExp高级设置里的‘厂家模式’怎么进?CTRL+F12到底有啥用?
  • Python版本兼容性实战:从subprocess.run的capture_output参数迁移到通用解决方案
  • 告别浏览器兼容烦恼:手把手教你用Firefox配置Kerberos访问大数据平台WebUI
  • FreeSimpleGUI:让Python GUI开发变得像写诗一样简单
  • 从EulerOS到openEuler:一个国产开源操作系统的演进与生态构建
  • 嵌入式调试实战:波特律动串口助手硬件通信优化方案
  • 3分钟搞定音频格式转换:FlicFlac如何让Windows用户告别格式兼容烦恼
  • 别再只盯着PageRank了!用Python实战特征向量、Katz和PageRank三大中心性算法
  • UE5 3D Widget重影别头疼!手把手教你修改材质和蓝图,让UI清晰又稳定
  • PyTorch模型无缝迁移昇腾平台:从环境配置到性能调优实战
  • 题解:AT_abc458_e [ABC458E] Count 123
  • 如何快速掌握EVE Online舰船配置:3个实用技巧与Pyfa工具完整指南
  • Koikatsu Sunshine增强补丁:5步打造完美游戏体验的终极指南
  • Bili2text完整指南:免费开源B站视频转文字神器,3步提升学习效率10倍!
  • 告别混乱工程!用STM32CubeIDE管理Inc和Src文件夹的正确姿势
  • 【HSPICE仿真进阶】.measure语句实战:从基础测量到自动化结果提取
  • 基于龙芯2K3000的国产工控机在数据中心动环监控中的实践