Online Judge 实现 —— 后台判题

Online Judge 实现 —— 后台判题


0. 一些闲话

  • 使用JSP实现了一个简易的OJ系统,前端和交互就不提了,里面关键的地方就是后台判题,所以在此记录一下。实现出来的系统中,用户提交代码后,由Servlet把数据存到数据库,并且将判题状态设为Queueing,然后就不需要管了,全权交给Linux端的C++程序解决。后台的判题我是扔到Docker中运行的。
  • 在实现的过程中,资源的限制监听参考了HUST OJ,在此感谢大佬们。深刻体会到阅读源代码对于自身学习的帮助。
  • 项目源码已传到Github中,欢迎大家一起探讨改进。
  • 下面开始进入正题,记录一下我在实现时的后台判题功能。

1 轮询

要能即时获取用户提交的信息,就需要在后台运行一个程序,不断轮询数据库。为了运行方便,程序的参数由命令行的方式传入,参数保存在一个shell脚本文件,启动时直接运行该shell脚本就行了。 该程序进行以下几部分操作。

  • 首先当然就是要连接数据库啦,我这里使用了Mysql-Connector-C++;
  • 连接完数据库,接下来就是不断轮询,获取数据库中状态为Queueing的提交,将它们存到队列中;
  • 逐个获取队列中的数据,将代码保存到文件中后,进行评判,评判的方法是创建一个子进程去启动Docker容器运行评判程序,父进程阻塞等待;
  • (暂时还没有研究如何用多台测评机进行判题)。

2 评判

评判程序在Docker中运行,实现隔离。为此,我下载了一个Ubuntu镜像,启动容器安装gcc, g++, oracle-java8, 导入Mysql-Connector-C++环境,并创建一个普通用户,之后将这个容器保存为镜像,在判题时直接以这个新的镜像启动容器。
评判程序包括编译,运行程序,限制程序编译及运行的资源,监听程序的编译、运行的资源使用,判题,更新判题结果等操作。

  • 根据传入的参数,判断用户提交的编程语言,在子进程中使用setrlimit限制编译资源,用alarm定时,并使用execvp调用gcc、g++或javac进行编译。同时,在编译之前,将数据库中的运行结果更改为Compiling。资源限制一定要限制编译时间,否则,当使用#include “/dev/random”之类的头文件时,会永远无法结束编译。其他资源也需要限制一下。在这一过程中,当我限制RLIMIT_AS的硬限制值时,编译会出问题,原因暂时不明。如果有大佬知道原因的话,敬请指教一番,感谢。
  • 当编译正常结束时,进行运行操作;如果是系统抛出异常的话,就把结果改为System Error;如果是用户代码出错,就将编译错误信息存到数据库中,将运行结果更改为CE。
  • 进入判题操作时,先把运行结果修改为Running,然后获取数据库中该题目的时间限制、内存限制,以及测试数据。对于每组测试数据,将输入数据写为工作目录的文本文件后,在子进程中限制资源,用freopen重定向输入输出,并使用execl运行程序,在父进程中使用ptrace监听控制子进程。这里有个坑,就是Docker默认是不能使用ptrace的,因此要在启动容器的时候加上启动选项 –cap-add=SYS_PTRACE。(一把辛酸泪。。。)
  • java运行的时候,使用policy来限制权限。
    参考https://blog.csdn.net/zhoche2008/article/details/7101830
  • 程序运行花费的时间可以通过rusage获取得到。
  • 在进程启动的时候,会生成/proc/pid/status这个文件,其中pid是该进程的PID。文件中含有程序使用的内存等信息,这里我们使用VmPeak这个数据,即进程运行时的内存峰值。不过这只针对C、C++程序。所以,只需要在监听控制的时候读取这个文件即可。这里又有一个坑,VmPeak记录的值会包括程序链接使用的值,所以在编译C、C++的时候,编译选项要加上–static,否则最终得到的内存值会偏大。对于JAVA程序,由于JVM会在刚启动的时候跟系统预约大量内存,因此上述方法获得的内存信息不准。目前是参考HUST OJ使用页错误方式处理。
  • 当监控发现时间、内存或者输出文件大小超出限制时,修改结果,并使用ptrace(PTRACE_KILL, pid, nullptr, nullptr)结束子进程,退出监控。
  • 使用ptrace(PTRACE_PEEKUSER, pid,ORIG_RAX*8, nullptr)获取系统调用id,若不是许可id,则结束子进程,将结果修改为RE。
  • 若一切正常,则使用ptrace(PTRACE_SYSCALL, pid, nullptr, nullptr)继续执行子进程。
  • 当次判题结束之后,若没有TLE、MLE、OLE、RE等情况出现,则将用户程序的输出文件与数据标准输出做比对。判断本组数据是AC还是WA还是OLE。
  • 若测试数据均测试完毕,或判题结果不再是AC,则停止判题,更新数据库信息,结束本次评判。

你可能感兴趣的:(C/C++,Linux,OJ,后台)