☰
SGE调度系统一个问题
2026/10/1 2:56:58 网站建设 项目流程

这是我的一个程序,用qsub方式投递:

qsub -cwd -V -q bigmem,genelab.q -l h="!cn16.local" -pe smp 1 -l h_vmem=10G -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), slot=64, mem=2T.

一个是genelab.q, 包含8个计算节点(cn13,cn14...cn20),每个计算节点 slot=32, mem=128G.

上述命令投递后, 如果程序被投到了fat01节点上, 铁定报错segmentation fault, 并产生core.12345 这类文件。

几个诡异的地方:

1. 同一个流程的各个程序中,只有R01.ExtractUMI_Filt.*.sh这类使用了umi_tools extract的程序会发生这个错误

2. 这个错误只在程序被qsub到fat01节点上运行时发生。

3. 如果手动ssh fat01, 并sh 程序运行, 也不会出错。

4. 这个错误只在-l h_vmem=10G 发生,如果设置为-l h_vmem=15G, 或者更高的值,就不会出错。

5. 但同样的-l h_vmem=10G设置,被qsub到其他节点运行时,仍然不会出错。

总结原因:

1. 我的单个 umi_tools extract 程序的内存占用就是超过了 10G, 但应该是在 15G 以下。

2. fat01 节点 h_vmem 绑定 RLIMIT_AS,所以会用 h_vmem 对单个程序的VmAS进行限制, 而其他节点 h_vmem 没有绑定 RLIMIT_AS, 并不会限制单个程序的VmAS,而只是 “算总账”?

  1. 单个umi_tools extract,进程虚拟地址空间 VmAS 峰值大于 10G、小于 15G; 它的物理内存 RSS(真实占用内存)不一定超过 10G。

    这是最容易混淆的点:触发 fat01 崩溃的是 VmAS,不是 RSS。哪怕物理内存只用 4G,只要 VmAS>10G,就触发 RLIMIT_AS,core。

  2. ✅ fat01:h_vmem两件事
    • ① SGE 调度记账(算总账,用来挑选节点投递任务)
    • ② 任务拉起时设置RLIMIT_AS = h_vmem,限制单个进程的虚拟地址空间上限,一旦 VmAS 超限直接 SIGSEGV core。
  3. ✅ 其他节点:h_vmem只做 SGE 调度记账(算总账,判断节点配额够不够,决定能不能投递任务)
    • 不会设置 RLIMIT_AS,不会限制进程的 VmAS;
    • 程序的虚拟地址空间可以自由增长,不受 h_vmem 约束;
    • 唯一风险:多个任务真实物理 RSS 叠加,吃光节点物理内存,触发 Linux OOM 杀手(SIGKILL,一般不生成 core)。

所以当投递到其他genelab.q节点时,虽然单个程序的虚拟地址空间 (VmAS)也超过了10G, 但由于我设置的 -pe smp 6 (其他节点的slot数=32)导致能并行运行的程序数量只含有5个,5*15=75G,远小于128G, 总账上面,还没有溢出,所以没问题。 而且,genelab.q节点不限制单个程序的 h_vmem,当多个程序的实际运行内存总和(而不是 VmAS 总和)超过节点物理内存的时候,才会触发 Linux OOM 杀手。

投递到fat01节点就不一样了, 同样是-pe smp 6(slot=64),能并行运行10个, 10*15=150G。 总账上面仍然远没有饱和。但由于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();

$pid==0, 子进程

$pid>0, 父进程, $pid的值就是子进程的编号

对比wait和waitpid

详见:

对比wait和waitpid

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询