赵 健
基于Windows内核的在线评测系统的设计与实现
赵 健
(长治学院,山西 长治 046011)
在线评测系统是一种广泛应用在教学和编程比赛中的在线系统。该系统可以接受用户在线提交的源代码并在线编译、运行,利用事先提供好的测试数据进行正确性判定。针对教学过程中的实际需要,本文提出了一种基于Windows内核的在线评测系统的设计方案,其中,Web前台使用了SSM框架(Spring+SpringMVC+MyBatis),判题内核用C语言开发。为了保证系统多任务和安全性,判题内核使用了沙箱技术并设计了判题队列。经测试,该系统能够满足一般的教学和编程比赛需要。
OJ系统;SSM;Windows API编程;沙箱
在线评测系统(Online Judge,缩写OJ)是一种广泛应用在教学和编程比赛中的在线系统。该系统可以接受用户在线提交的源代码并在线编译、运行,利用事先提供好的测试数据进行正确性判定[1]。用户提交的源码可以是各种语言写就的,如C/C++,Java,Python等。
最早的在线评测系统是西班牙的巴亚多利德大学1995年开发的(UVAOJ)。目前比较流行的OJ有西班牙的UVAOJ、俄罗斯的SGU、Timus、波兰的SPOJ、美国的TopCoder等。我国也有一些出色的OJ系统,如北京大学的POJ、浙江大学的ZOJ、杭州电子科技大学的HDOJ等[2]。
OJ可以应用在在线作业练习和算法类编程竞赛的训练上,能够极大地提高编程语言类教学科目的教学管理效率[3]。笔者在教学过程中深感OJ系统的必要性,但目前笔者所在的学校还没有成熟的OJ系统。目前流行的OJ系统大多使用Linux平台,鉴于Windows 平台在管理上较为方便的特点,以及考虑到目前学生大量的编程实践都运行在Windows平台,开发一个基于Windows内核的在线评测系统势在必行。
本文基于Windows内核提供了一个可行的OJ系统解决方案,实现了一个B/S模式的在线评测系统。经测试,该系统可以在Windows环境下安全稳定地运行,满足日常的教学和编程练习需要。
本系统搭建在Windows平台上,Windows Server 2008系统及以上,Web服务器使用Apache Tomcat6及以上,数据库使用MySQL5.1以上,Java JDK5.1以上。
本系统最核心的是两部分:1Web端与数据库服务(前台)、2判题核心(后台服务)。其数据库中除存储一般网站中的信息如用户表、权限表之外,最重要的是存储了题目相关信息:题目信息表存储编程题目的基本信息如题目id、输入/输出描述等,代码表存储了用户提交的源代码。将题目信息和源代码分离存储是为了减轻通信负担。此外,判题结果状态也存储在数据库中。
前台的开发为用户提供了系统界面,此部分当下流行的开发框架均可胜任,如PHP、Python的Django框架、Java中SpringMVC等。前台可以与数据库服务通信以提交代码到数据库中,也可从数据库中提取判题结果并将之显示到系统界面上。
判题核心是OJ系统最关键的部分。它主要根据代码ID从数据库中取出具体代码并对之进行后续处理,包括编译、运行、判时(判断运行时间)、比对(与测试数据比对)等步骤。此外,判题核心也负责将运行的状态结果存到数据库中,以便前台进行评测结果显示。系统整体结构如图1所示。
图1 系统整体结构图
Web端主要负责系统界面的展示以及与内核的通信,其中界面简单实用即可,因此本系统使用了目前最流行的SSM框架(Spring+SpringMVC+MyBatis)[4],以便进行快速开发。
判题内核是OJ系统最关键的部分,在系统的整个运行期间都由其为系统提供题目的评测服务,它设计的好坏直接影响了系统的速度、安全性和稳定性。该部分使用C语言开发,内核通过Socket技术与Web端通信[5]。
本系统的判题内核大量使用了WinAPI编程技术。最主要的是以下两个线程:
(1)监听线程:该线程负责监听Web端提交的代码。当接受到代码ID后,会将代码ID压入队列。
(2)调度处理线程:判断监听线程产生的队列是否为空,当队列非空时取出队列中的代码ID,进行处理(编译运行判时比对)。
图2为内核的线程交互图。
在Web端本OJ系统与其他普通网站相比并没有太多特殊之处,主要是为用户提供交互界面,界面简洁,满足功能即可。所以我们系统的Web端采用了SSM(Spring+SpringMVC+Mybatis)开发框架。SSM框架使用了MVC架构,技术成熟,配置方便,相关插件和开发工具完备,运行稳定。其中,Web前端使用了Jquery 和 Ajax技术。对于代码的编辑界面,有一些开源的插件如Syntaxhighlight、CKEditor等[6]。为了增强交互性,前端还可以使用HTML批注技术[7-8]。
图2 内核线程交互图
本系统的Web端实现了练习模块、竞赛模块、通信模块、搜索模块等主要模块。
3.2.1 监听线程
该线程主要负责与接受web端提交过来的题目ID。这里引入了判题队列,web端提交的ID会按照监听线程接受的顺序依次压倒队列中,调度处理线程则从该队列中取题目ID以便后续处理。socket监听和判题队列的引入使得系统可以处理多任务,即web端多个用户同时提交代码不会导致判题失败[9]。
该线程的伪代码如下:
unsigned _stdcall listenThread(*LPVOID lpParameter)
{
init();//做一些初始化工作如初始化socket,建立缓冲区等
while(True)
{
accept(); //接受socket建好的连接
recvData();//从socket接受数据,主要接受problem_id即题目ID
push();//将题目id压入队列,即图中的队列
sleep(1);
}
closesocket();
return 0;
}
3.2.2 调度处理线程
本线程实现了判题内核的最核心内容:首先判断判题队列是否为空,若非空则取出队列头存储的题目ID,根据此ID从数据库中得到用户提交的源代码,然后就可以执行编译和运行过程。在此过程中,调度处理内核可以得到用户的运行时间、编译错误信息和运行时错误信息(RE)。若进程通过了编译运行,再根据实现准备好的若干组测试数据和运行结果进行比对,全部匹配成功则认为用户提交的代码正确。
该进程的伪代码如下:
void DispatchProcessingThread()
{
pop(ID);//从判题队列中取出题目ID
SQL_getSource(ID);//根据题目ID从数据库中获得源代码
if(False == CompileProc()) //编译
{
错误处理;
SQL_UpdateCompileInfo(); //将编译信息插入到数据库中
}
BeginThreadex(RunProcess);//新开一个线程以执行编译好的程序
}
void RunProcess()
{
CreatePipe(); //创建匿名管道
handle hSandBox = CreateSandBox();//创建沙箱
handle hProcess = CreateProcess();//创建进程,该进程的主线程以暂停的状态被创建
if(True == AssignProcessToSandBox(hProcess, hSandBox))//将进程放进沙箱中
{
ResumeThread(); //恢复暂停态的进程继续执行
计时开始;
PipeInputRead();//
PipeOutputWriteToFile();
计时结束;
GetProcessMemoryInfo();//获得进程所占用的内存状态
GetExitCodeProcess(); //获取进程退出码,得到运行时错误信息
MatchResult();//用事先提供好的测试数据做匹配,所有数据都匹配才认为判题成功
}
}
上面伪代码中所描述的调度处理线程需要完成的核心子功能有:
(1)编译
考虑到编译环境的不确定性,如果将编译命令直接写到内核代码中,会极大地降低系统稳定性,提高系统维护成本[10]。另外,不同类型语言的编译命令和参数都不一样,在代码级别也很难对之精确描述。于此同时,也不利于系统的可扩展性(如动态添加/删除编译语言等)。为此,本系统将运行环境和编译的命令预先存在配置文件Config.ini中,有效解决了这一问题。该文件对在线处理用户提交代码的环境做了一些配置,如支持的语言、编译命令和路径、编译后可执行文件的格式、运行命令等。需要使用这些信息时,可以由内核通过调用Windows API对Config.ini文件进行操作:
GetPrivateProfileInt函数:读取配置文件中的数字。
GetPrivateProfileString函数:读取配置文件中的字符串。
WritePrivateProfileString函数:将配置信息写入配置文件中。
本系统支持C/C++,Java,Python等主流编程语言,配置文件中相关的配置命令如下(见表1):
表1 部分语言的编译命令行表
Tab.1 Compiling command line table of partical language
(2)运行
在程序的运行环节,除了完成调用相关命令运行程序外,还应考虑运行的安全性问题。在运行程序时,可能有多种操作危及到系统的安全,轻则导致判题内核无法正常运作,重则危及整个系统的安全。比如当用户提交携带有死循环的代码时,会导致执行进程无休止地占用内核资源;又如用户提交的代码可能含有一些危险操作如系统重启、删除系统文件等[11]。对这些危险操作事先加以限制也是判题内核的核心工作。为此我们引入了判时模块和沙箱[12]。判时模块可以计算程序运行所消耗的时间以及内存占用情况,以便当程序运行异常时加以干预。沙箱则可以让一个进程独立运行,并限制其可以做什么,不可以做什么。
(3)判时模块
系统每运行一个用户提交的用例都会计算其运行时间:使用clock()函数,用程序结束运行的时间减去开始运行的时间即为本次测试用例的耗时。
系统还可以统计测试用例的内存占用情况:通过WindowsAPI中的GetProcessMemoryInfo函数即可获取内存的使用情况。
(4)沙箱
系统使用了沙箱,每个用户提交的代码经编译后,要放到沙箱中运行,一般地,OJ的一个沙箱中只能运行一个进程,沙箱能够对进程的运行时间、消耗内存、访问文件权限等做限制,进而保证程序运行时的安全性。
我们这里的沙箱使用了Windows的作业内核对象生成,用到的主要API有[13]:
CreateJobObject函数:创建工作对象。
SetInformationJobObject函数:可以对工作对象中的进程加以限制,通过设置其参数,可以对进程数、进程数量、进程耗时限额以及消耗内存限额、UI和安全等加以限制。
CreateProcess函数启动进程,本系统的进程都是以CREATE_SUSPENDED方式启动。
AssignProcessToJobObject函数:将进程放到工作对象中。
ResumeThread函数恢复CreateProcess中暂停的进程。
QueryInformationJobObject函数获取工作对象的最终状态。
运行时错误是程序在运行过程中因堆栈溢出、数组越界等原因造成的程序异常中断或崩溃。Windows API函数GetExitCodeProcess即可获取退出码。
TerminateJobObject函数结束当前的工作对象。
本系统能得到常见的运行时错误如堆栈溢出、除零错误、运行超时、内存超限、输出超限等。
(5)内核与文件的I/O
用户提交的代码运行时默认从标准输入读入数据并将结果输出到标准输出,但OJ的比对环节需要读取预先设计好的测试数据集并与运行结果进行匹配,这里读取测试数据使用了匿名管道技术。
计算机的管道可以实现数据流的重定向,管道技术分为命名管道和匿名管道。我们这里使用匿名管道技术,运行测试代码时,主进程会创建数据输入管道和数据输出管道,用来代替程序的标准输入输出,数据输入管道可以从文件中获取数据,数据输出管道可以将结果数据输出到文件中。
常用的函数有:CreatePipe函数创建管道,ReadFile和WriteFile函数分别实现了从管道中读取数据和向管道写入数据,CloseHandle函数关闭管道。
提交语法正确的源代码,在“提交状态”页面中,状态字段显示为“Accepted”。
提交语法错误的源代码,在“提交状态”页面中,状态字段显示为“Compilation Error”。
提交答案匹配正确的源代码,在“提交状态”页面中,状态字段显示为“Accepted”。
提交答案错误的源代码,在“提交状态”页面中,状态字段显示为‘Wrong Answer on test 1’。
图3 源代码语法正确结果图
图4 源代码语法正确结果图
图5 答案匹配正确结果图
图6 答案匹配错误结果图
本系统在笔者所在班级进行了数次测试运行,期间运行稳定。经测试,该系统能够满足日常的教学及学生练习需要,其中竞赛模块曾承担64位同学同时在线提交代码,网站无异常,能够正确稳定地进行判题服务。
[1] Andy Kurnina, Andrew Lim, Bernda Cheang. Online Judge[J]. Computers Education, 2001, 4(36): 2-3.
[2] 晏燕. 在线编程评测系统设计与实现[D]. 吉林大学, 2017: 3-4.
[3] 苗桂君, 刘勇, 许南山, 等. 在线评测系统在程序设计类教学中的应用研究[J]. 计算机教育, 2016(09): 157-162.
[4] 李洋. SSM框架在Web应用开发中的设计与实现[J]. 计算机技术与发展, 2016, 26(12): 190-194.
[5] 谭文, 陈铭霖. Windows内核安全与驱动开发[M]. 北京: 电子工业出版社; 2015: 81-85
[6] 张睿, 文福安. 基于web的试题编辑技术的研究与实现[J]. 软件, 2015, 36(9): 21-24.
[7] 梁瑞仕, 魏铮淑, 陈光琳. 高校在线教学与评测平台的设计与实现[J]. 中国教育信息化, 2017(11): 83-85.
[8] 黄晓华, 沈健, 常晋义, 等. 基于Online Judge与HTML批注技术的实验教学平台设计[J]. 计算机与现代化, 2014(11): 117-121.
[9] 黄洪波, 宋鸿陟, 彭红星, 等. 大规模程序评判系统的设计与实现[J]. 计算机工程与设计, 2016, 37(03): 825-831.
[10] 刁铭智, 周渊李, 舟军, 等. 基于Wine的Windows安全机制模拟及沙箱系统实现[J]. 计算机科学, 2017, 44(11): 246-252+267.
[11] 肖红玉, 贺辉, 陈红顺. 在线评测教学辅助系统设计[J]. 计算机技术与发展, 2017, 27(11): 141-145.
[12] 李定才, 瞿绍军, 胡争, 等. 基于Windows的在线判题系统的安全性研究[J]. 计算机技术与发展, 2011, 21(09): 204-207.
[13] 王险峰, 刘宝宏. Windows环境下的多线程编程原理与应用[M]. 北京: 清华大学出版社; 2002: 52-84.
Design and Implementation of Online Evaluation System Based on Windows Kernel
ZHAO Jian
(Changzhi College, Changzhi, Shanxi 046011)
Online evaluation system is an system widely applied in teaching and programming competitions, which can accept, compile and run source code submitted by users online, and determine correctness based on test data provided in advance. In view of practical needs of teaching process, the article proposes an online evaluation system design scheme based on Windows kernel, with SSM framework (Spring+SpringMVC+MyBatis) of Web frontend, and C language development of marking kernel. To ensure multitask and security of system, sandbox technology and queue was designed for marking kernel. After testing, the system can meet needs of general teaching and programming competitions.
OJ system; SSM; Windows API programming; Sandbox
TP313
A
10.3969/j.issn.1003-6970.2018.09.039
赵健(1986-),男,硕士,助教,主要研究方向:软件工程及数据分析,基于SSM的J2EE应用系统自主开发。
本文著录格式:赵健. 基于Windows内核的在线评测系统的设计与实现[J]. 软件,2018,39(9):194-199