运行评测程序
Seele 提供了两种用于执行评测相关任务的动作任务。它们分别通过给 action
指定 seele/run-judge/compile@1
和 seele/run-judge/run@1
来被使用。前者主要是为了使用一些源文件进行编译,保存产生的文件。后者主要是为了运行待评测的程序。
为了方便,我们在这篇文档里用“编译任务”指代前者,用“执行任务”指代后者。
编译任务和执行任务通过 Linux 容器技术来构建安全沙箱,从而确保要运行的程序在一个隔离的环境中运行,这不仅能够避免不同的评测任务之间相互干扰,还可以在限制程序能够使用的资源的同时防止恶意程序破坏评测系统。要正确使用编译任务和执行任务,你可能需要掌握一些有关 Linux 容器的知识。
编译任务
编译任务通常结合添加文件任务使用,将后者添加的文件挂载到容器中供编译程序使用。当编译程序运行成功后,编译任务会将指定的文件从容器中复制到根文件夹中。
参数列表
编译任务接受的参数列表如下表所示:
名称 | 类型 | 默认值 | 简介 |
---|---|---|---|
sources | string[] | [] | 从根文件夹向容器中挂载的文件列表 |
saves | string[] | [] | 当任务执行成功时,从容器中保存的文件列表 |
cache | object | 见下文 | 缓存配置 |
其它属性 | ContainerConfig | 见下文 | 其它容器配置 |
cache
属性
Seele 支持通过 cache
属性来为编译任务启用缓存功能。它的参数如下表所示:
名称 | 类型 | 默认值 | 简介 |
---|---|---|---|
enabled | boolean | false | 是否启用缓存功能 |
extra | string[] | [] | 缓存功能需要额外纳入哈希值计算的参数列表 |
编译任务会按顺序使用下面列出的值计算 SHA-256 哈希值来决定是否命中缓存。当缓存命中时,编译任务会跳过运行容器,并直接复用缓存中的 saves
指定的文件。
command
的值。extra
中的每个字符串。saves
中的每个字符串,按字典序排序。sources
中的每个字符串,按字典序排序。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"]
执行任务
执行任务一般用于运行需要评测的程序,用户常常需要限制评测程序的运行时间、内存占用等。
参数列表
执行任务接受的参数列表如下表所示:
名称 | 类型 | 默认值 | 简介 |
---|---|---|---|
files | string[] | [] | 从根文件夹向容器中挂载的文件列表 |
其它属性 | 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
编译任务和执行任务共用的一些用于构建安全沙箱的参数,它的参数如下表所示:
名称 | 类型 | 默认值 | 简介 |
---|---|---|---|
image | string | 无 | 使用的容器镜像 |
cwd | string | [] | 在容器中运行程序时的当前目录 |
command | string 或 string[] | 无 | 需要在容器中运行的程序 |
fd | object | null | 对运行的程序的输入和输出流的配置 |
paths | string[] | [] | 对容器运行程序提供的额外的 PATH 环境变量项 |
mounts | string[] 或 object[] | [] | 从根文件夹向容器中挂载的文件列表 |
limits | object | 见下文 | 对容器设置的一些资源限制 |
当一个属性的默认值为 无
时,你必须为它提供一个值,否则 Seele
可能会无法解析评测任务。
Seele 解析 image
中指定的容器镜像的方式和 Docker 类似,下面是一些合法的
image
取值例子: gcc
、debian:slim
、library/ubuntu:focal
、
quay.io/foo/bar:latest
fd
配置项
fd
配置项能够将评测程序的标准输入、标准输出和标准错误流重定向到根文件夹的文件中。它的参数如下表所示:
名称 | 类型 | 简介 |
---|---|---|
stdin | string | 将程序的标准输入流重定向自给定的文件 |
stdout | string | 将程序的标准输出流重定向到给定的文件,安全沙箱会自动创建文件 |
stderr | string | 将程序的标准错误流重定向到给定的文件,安全沙箱会自动创建文件 |
stdout_to_stderr | boolean | 将程序的标准输出流重定向到标准错误流 |
stderr_to_stdout | boolean | 将程序的标准错误流重定向到标准输出流 |
如果用户未设置某个流的重定向关系,安全沙箱会将流重定向到 Linux 内核提供的
/dev/null
。
limits
配置项
limits
配置项能够限制评测程序使用的资源等。安全沙箱会在程序使用超出限制的资源时终止程序。
它的参数如下表所示:
名称 | 类型 | 默认值 | 简介 |
---|---|---|---|
time_ms | number | 10s | 用户态 CPU 时间限制。单位为 ms |
memory_kib | number | 256 MiB | 内存占用量限制。单位为 KiB |
pids_count | number | 32 | 程序能够创建的子进程数量 |
fsize_kib | number | 64 MiB | 程序能够输出的最大数据量 |
安全沙箱会在启动程序后,额外启动一个时间为 time_ms * 3
的定时器。当定时器到期时若程序执行仍未结束,安全沙箱会通过 SIGKILL
信号终止程序。
请勿将 memory_kib
设置为低于 20 MiB 的数值。Seele
的安全沙箱由于原理限制,总是会在启动评测程序之前占用大约 16 MiB 的内存。若
memory_kib
的值过小,可能导致安全沙箱创建失败。
评测报告
编译任务和执行任务返回的评测报告包含以下属性:
名称 | 类型 | 简介 |
---|---|---|
status | string | 程序退出的状态 |
exit_code | number | 程序退出时返回的代码 |
signal | string | 仅当程序被信号终止时提供,程序终止对应的信号名称。参见 zerrors_linux_amd64.go (opens in a new tab) |
wall_time_ms | number | 程序从启动到终止经过的时间,单位为 ms |
cpu_user_time_ms | number | 程序执行总共消耗的用户态 CPU 时间,单位为 ms |
cpu__time_ms | number | 程序执行总共消耗的内核态 CPU 时间,单位为 ms |
memory_usage_kib | number | 程序从启动到终止消耗的最大内存占用。单位为 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 |