评测任务
运行评测程序

运行评测程序

Seele 提供了两种用于执行评测相关任务的动作任务。它们分别通过给 action 指定 seele/run-judge/compile@1seele/run-judge/run@1 来被使用。前者主要是为了使用一些源文件进行编译,保存产生的文件。后者主要是为了运行待评测的程序。

为了方便,我们在这篇文档里用“编译任务”指代前者,用“执行任务”指代后者。

编译任务和执行任务通过 Linux 容器技术来构建安全沙箱,从而确保要运行的程序在一个隔离的环境中运行,这不仅能够避免不同的评测任务之间相互干扰,还可以在限制程序能够使用的资源的同时防止恶意程序破坏评测系统。要正确使用编译任务和执行任务,你可能需要掌握一些有关 Linux 容器的知识。

编译任务

编译任务通常结合添加文件任务使用,将后者添加的文件挂载到容器中供编译程序使用。当编译程序运行成功后,编译任务会将指定的文件从容器中复制到根文件夹中。

参数列表

编译任务接受的参数列表如下表所示:

名称类型默认值简介
sourcesstring[][]从根文件夹向容器中挂载的文件列表
savesstring[][]当任务执行成功时,从容器中保存的文件列表
cacheobject见下文缓存配置
其它属性ContainerConfig见下文其它容器配置

cache 属性

Seele 支持通过 cache 属性来为编译任务启用缓存功能。它的参数如下表所示:

名称类型默认值简介
enabledbooleanfalse是否启用缓存功能
extrastring[][]缓存功能需要额外纳入哈希值计算的参数列表

编译任务会按顺序使用下面列出的值计算 SHA-256 哈希值来决定是否命中缓存。当缓存命中时,编译任务会跳过运行容器,并直接复用缓存中的 saves 指定的文件。

  1. command 的值。
  2. extra 中的每个字符串。
  3. saves 中的每个字符串,按字典序排序。
  4. sources 中的每个字符串,按字典序排序。
  5. sources 中的每个字符串指向的文件内容。

示例

下面的示例在 prepare 步骤中向根文件夹添加了一个 main.c 文件,然后在 compile 步骤中向容器挂载这个 main.c 文件并执行容器中的 gcc 程序进行编译,最后将输出的 main 程序保存到根文件夹,供后续步骤使用。

steps:
  prepare:
    action: "seele/add-file@1"
    files:
      - path: "main.c"
        plain: |
          #include <stdio.h>
          int main(void) {
            printf("Hello, world!\n");
            return 0;
          }
 
  compile:
    action: "seele/run-judge/compile@1"
    image: "gcc:11-bullseye"
    command: "gcc -O2 -Wall main.c -o main"
    sources: ["main.c"]
    saves: ["main"]

执行任务

执行任务一般用于运行需要评测的程序,用户常常需要限制评测程序的运行时间、内存占用等。

参数列表

执行任务接受的参数列表如下表所示:

名称类型默认值简介
filesstring[][]从根文件夹向容器中挂载的文件列表
其它属性ContainerConfig见下文其它容器配置

对于可执行文件的挂载,我们推荐为向 files 中的文件名末尾添加 :exec 后缀,从而令 Seele 确保这个可执行文件被添加了执行权限。

示例

下面的示例在 prepare 步骤中向根文件夹添加了一个 main 可执行文件,然后在 run 步骤中执行这个程序。

steps:
  prepare:
    action: "seele/add-file@1"
    files:
      - path: "main"
        url: "http://darkyzhou.net/main"
 
  compile:
    action: "seele/run-judge/run@1"
    image: "gcc:11-bullseye"
    command: "main"
    files: ["main:exec"]

公共配置

ContainerConfig

编译任务和执行任务共用的一些用于构建安全沙箱的参数,它的参数如下表所示:

名称类型默认值简介
imagestring使用的容器镜像
cwdstring[]在容器中运行程序时的当前目录
commandstringstring[]需要在容器中运行的程序
fdobjectnull对运行的程序的输入和输出流的配置
pathsstring[][]对容器运行程序提供的额外的 PATH 环境变量项
mountsstring[]object[][]从根文件夹向容器中挂载的文件列表
limitsobject见下文对容器设置的一些资源限制
⚠️

当一个属性的默认值为 时,你必须为它提供一个值,否则 Seele 可能会无法解析评测任务。

Seele 解析 image 中指定的容器镜像的方式和 Docker 类似,下面是一些合法的 image 取值例子: gccdebian:slimlibrary/ubuntu:focalquay.io/foo/bar:latest

fd 配置项

fd 配置项能够将评测程序的标准输入、标准输出和标准错误流重定向到根文件夹的文件中。它的参数如下表所示:

名称类型简介
stdinstring将程序的标准输入流重定向自给定的文件
stdoutstring将程序的标准输出流重定向到给定的文件,安全沙箱会自动创建文件
stderrstring将程序的标准错误流重定向到给定的文件,安全沙箱会自动创建文件
stdout_to_stderrboolean将程序的标准输出流重定向到标准错误流
stderr_to_stdoutboolean将程序的标准错误流重定向到标准输出流

如果用户未设置某个流的重定向关系,安全沙箱会将流重定向到 Linux 内核提供的 /dev/null

limits 配置项

limits 配置项能够限制评测程序使用的资源等。安全沙箱会在程序使用超出限制的资源时终止程序。

它的参数如下表所示:

名称类型默认值简介
time_msnumber10s用户态 CPU 时间限制。单位为 ms
memory_kibnumber256 MiB内存占用量限制。单位为 KiB
pids_countnumber32程序能够创建的子进程数量
fsize_kibnumber64 MiB程序能够输出的最大数据量

安全沙箱会在启动程序后,额外启动一个时间为 time_ms * 3 的定时器。当定时器到期时若程序执行仍未结束,安全沙箱会通过 SIGKILL 信号终止程序。

⚠️

请勿将 memory_kib 设置为低于 20 MiB 的数值。Seele 的安全沙箱由于原理限制,总是会在启动评测程序之前占用大约 16 MiB 的内存。若 memory_kib 的值过小,可能导致安全沙箱创建失败。

评测报告

编译任务和执行任务返回的评测报告包含以下属性:

名称类型简介
statusstring程序退出的状态
exit_codenumber程序退出时返回的代码
signalstring仅当程序被信号终止时提供,程序终止对应的信号名称。参见 zerrors_linux_amd64.go (opens in a new tab)
wall_time_msnumber程序从启动到终止经过的时间,单位为 ms
cpu_user_time_msnumber程序执行总共消耗的用户态 CPU 时间,单位为 ms
cpu__time_msnumber程序执行总共消耗的内核态 CPU 时间,单位为 ms
memory_usage_kibnumber程序从启动到终止消耗的最大内存占用。单位为 KiB

wall_time_ms 是安全沙箱在外部进行测量的时间,与程序执行从开始到结束实际经过的时间相比存在一定偏差。测试表明这种误差一般在数毫秒的级别。我们建议用户使用 cpu_user_time_ms 来对比不同的评测程序在执行时间上的优劣。

⚠️

由于安全沙箱的上述原因,评测报告的 memory_usage_kib 往往大于约 12 MiB。当评测程序使用的内存小于安全沙箱在启动评测程序之前占用的内存时,memory_usage_kib 不能反映评测程序真实占用的内存大小。

status 属性

表示程序终止运行的原因。它的取值如下表所示:

取值简介
NORMAL程序运行正常结束,未出现运行崩溃、超出资源限制等情况
RUNTIME_ERROR程序运行结束,出现运行崩溃的情况,退出码(exit code)不为 0
SIGNAL_TERMINATE程序被 Linux 内核发送的信号终止运行。例如,当出现除以 0 的算数运算时,程序会被 SIGFPE 信号终止运行
USER_TIME_LIMIT_EXCEEDED程序消耗的用户态 CPU 时间超出了限制
WALL_TIME_LIMIT_EXCEEDED程序因长时间未结束运行而被终止
MEMORY_LIMIT_EXCEEDED程序因尝试分配超出限制的内存而被终止
OUTPUT_LIMIT_EXCEEDED程序输出了超过限制的数据量
UNKNOWN未知原因,安全沙箱可能出现 bug