
这是我的一个程序用qsub方式投递qsub -cwd -V -q bigmem,genelab.q -l h!cn16.local -pe smp 1 -l h_vmem10G -e /path/R01.ExtractUMI_Filt.Normal.26SBB1000204-23.F350071577_L01_11.sh.e -o /path/R01.ExtractUMI_Filt.Normal.26SBB1000204-23.F350071577_L01_11.sh.o /path/R01.ExtractUMI_Filt.Normal.26SBB1000204-23.F350071577_L01_11.sh我有两个队列可以使用一个是bigmem 包含一个胖节点fat01 slot64, mem2T.一个是genelab.q, 包含8个计算节点(cn13,cn14...cn20)每个计算节点 slot32, mem128G.上述命令投递后 如果程序被投到了fat01节点上 铁定报错segmentation fault, 并产生core.12345 这类文件。几个诡异的地方1. 同一个流程的各个程序中只有R01.ExtractUMI_Filt.*.sh这类使用了umi_tools extract的程序会发生这个错误2. 这个错误只在程序被qsub到fat01节点上运行时发生。3. 如果手动ssh fat01, 并sh 程序运行 也不会出错。4. 这个错误只在-l h_vmem10G 发生如果设置为-l h_vmem15G 或者更高的值就不会出错。5. 但同样的-l h_vmem10G设置被qsub到其他节点运行时仍然不会出错。总结原因1. 我的单个 umi_tools extract 程序的内存占用就是超过了 10G 但应该是在 15G 以下。2. fat01 节点 h_vmem 绑定 RLIMIT_AS所以会用 h_vmem 对单个程序的VmAS进行限制 而其他节点 h_vmem 没有绑定 RLIMIT_AS 并不会限制单个程序的VmAS而只是 “算总账”单个umi_tools extract进程虚拟地址空间 VmAS 峰值大于 10G、小于 15G 它的物理内存 RSS真实占用内存不一定超过 10G。这是最容易混淆的点触发 fat01 崩溃的是 VmAS不是 RSS。哪怕物理内存只用 4G只要 VmAS10G就触发 RLIMIT_AScore。✅ fat01h_vmem两件事① SGE 调度记账算总账用来挑选节点投递任务② 任务拉起时设置RLIMIT_AS h_vmem限制单个进程的虚拟地址空间上限一旦 VmAS 超限直接 SIGSEGV core。✅ 其他节点h_vmem只做 SGE 调度记账算总账判断节点配额够不够决定能不能投递任务不会设置 RLIMIT_AS不会限制进程的 VmAS程序的虚拟地址空间可以自由增长不受 h_vmem 约束唯一风险多个任务真实物理 RSS 叠加吃光节点物理内存触发 Linux OOM 杀手SIGKILL一般不生成 core。所以当投递到其他genelab.q节点时虽然单个程序的虚拟地址空间 VmAS也超过了10G 但由于我设置的 -pe smp 6 其他节点的slot数32导致能并行运行的程序数量只含有5个5*1575G远小于128G 总账上面还没有溢出所以没问题。 而且genelab.q节点不限制单个程序的 h_vmem当多个程序的实际运行内存总和而不是 VmAS 总和超过节点物理内存的时候才会触发 Linux OOM 杀手。投递到fat01节点就不一样了 同样是-pe smp 6slot64能并行运行10个, 10*15150G。 总账上面仍然远没有饱和。但由于fat01节点 h_vmem 绑定 RLIMIT_AS, h_vmem 对单个程序的VmAS进行限制。那么算的就不是总账就是每一笔账都要算了。单个程序的VmAS不得超过h_vmem否则就会core。关于内存占用出现这个问题说到底是我对程序的内存占用估计不足。比如这个umi_tools extract。 我当初用memusg 测得的最高内存占用只有100MB, 那我设置成h_vmem 10G已经远超所需了。但实际上不是的 SGE调度系统要看的是虚拟地址空间 VmAS而非实际内存占用RSS那么memusg就不那么好使了。得靠“/proc/$pid/status”文件中的信息来获取VmAS。写个监控VmAS的脚本名为monitor.pl#!/usr/bin/perl use strict; use warnings; my cmd ARGV; die Usage: $0 command args...\n unless cmd; # 启动目标程序 my $pid fork(); if ($pid 0) {#子进程 exec(cmd) or die exec failed: $!; } my $max_vmpeak 0; while (1) { # last unless kill 0, $pid; # 进程退出就跳出 last unless ( kill(0, $pid) ); # 进程退出就跳出 if (open my $fh, , /proc/$pid/status) { while ($fh) { if (/^VmPeak:\s(\d)/) { my $val $1; $max_vmpeak $val if $val $max_vmpeak; last; } } close $fh; } select undef, undef, undef, 0.05; # 50ms 休眠 } waitpid($pid,0); my $exitcode $? 8; printf VmPeak(KB): %d \n, $max_vmpeak; printf VmPeak(GB): %.2f \n, $max_vmpeak / 1024; print exit_code: $exitcode\n; pod 可以看出while循环内部在高频反复读取/proc/$pid/status的内容。 既然/proc/$pid/status中的VmPeak只增不减为什么不等子进程结束后只读取一次呢 因为子进程结束后 /proc/$pid/status会很快就被删除掉。 到那时已经读取不到了。所以必须在程序(就是cmd)运行的时候高频反复读取。 周期是50毫秒读取一次这里用select比sleep好用。 因为有些集群不支持sleep(0.05)这类的 cut运行方式./monitor.pl umi_tools extract XXX XXX关于fork()my $pid fork();$pid0, 子进程$pid0, 父进程 $pid的值就是子进程的编号对比wait和waitpid详见对比wait和waitpid