设计实现OJ平台的遇到的一些问题和解决方法

需求

毕业设计,实现一个能够自动编译、运行、监测程序运行使用资源、恶意系统调用的监控的一个OJ平台。
在设计实现的过程中的想法、碰到的问题、求解的过程以及方法,在这里记录下来。

基础结构

OJ主要由前端系统(WEB)和后端的判题程序构成,想法是后端的裁判程序做通用点,减少和前端系统的耦合,所以把后端给分离出来成一个独立的程序,大概的结构图是这样的。
系统结构图
解释下:
1. 前端其实可以由任何流行的web语言来实现。
2. 这里的代理可有可无,代理在这里可以实现很多功能,比如负载均衡、数据库的业务逻辑等都可以在这里实现。
3. 裁判模块主要实现程序的编译、运行、监控、给出判定结果,是无状态的,所以整个系统的扩展性高。

后端使用到的技术

操作系统选的是linux,原因是工具多,稳定,系统API丰富。
裁判模块用的语言是C++,主要原因是性能、系统编程方便。
裁判模块的网络i/o用的是cheetah,一个事件驱动的网络库。
模块间的通信采用的是protobuf。

裁判模块的设计

这里借鉴了nginx的设计,单线程多进程的方式,由master进程和minion进程(数量可配置)组成。

  1. master管理minion进程的生命周期,在minion进程挂掉之后会重启。minion进程,在初始化的时候创建一个监听socket,fork出minion进程。
  2. minion进程共享master进程的监听socket,每accept一个连接,加入到reactor里,每读取到一个请求执行一次判题的过程。
  3. 判题的过程是编译程序,fork一个子进程,将标准输入、标准输出、标准错误输出重定向到几个文件上,然后调用execv将编译好的程序替换掉当前的binary,运行完成后比对输出文件和答案文件,给出结果。

碰到的问题

  1. cpu时间、内存使用量的监控,限制程序的cpu时间、内存使用量。
  2. 系统调用的监控。
  3. execv系统调用的一些pitfalls。

问题的思考的过程和解决方法

1.cpu时间、内存使用量的监控,限制程序的cpu时间、内存使用量。

如何做到cpu时间和内存使用量的测量呢?linux操作系统上有很多方法可以获得一个进程的这些信息,这里列举几个方法以及各自的优缺点。

  1. 用system系统调用定期调用top,ps等工具,来分析这些工具的输出。优点,方便。缺点,这个定期的频率不好选择,如果频率低了程序的性能降低,高了的话监测的精准度不够,比如某个程序在1ms就运行完毕了,这样的话这些工具甚至都捕捉不到这个进程运行过。
  2. 定期扫描/proc文件系统里被监测程序的信息。优点,/proc文件系统提供了非常详细的信息,一些top、ps的实现都是读取这个系统。缺点,同1。
  3. 使用wait系统调用,在子进程结束的时候读取下程序的资源使用信息,具体使用方法可以参考manpage。优点,精准,在程序结束的时候,父进程会收到子进程的SIGCHLD信号,在这个时候使用wait系统调用能获得比较精准的cpu和内存使用量。缺点,由于对程序资源做了限制(见下一个问题),在内存超出了限制之后,程序malloc内存会失败,在C的程序里会导致SIGSEGV,errno被设置为ENOMEM,在c++程序里也可能会导致SIGABRT、SIGKILL(由于new失败抛出的bad_alloc没有被捕获导致abort的调用),但是也有其他的情况会导致SEGFAULT,比如访问不存在的内存地址,这样做无法准确区别到底是内存超了,还是程序访问了不存在的内存的情况。

在获得程序的资源使用量之后,可以通过一下结合这些方法方式来实现时间、内存的限制。

  1. 当top,ps返回的结果中发现内存、时间的使用量超出了限制之后,发送SIGKILL给子进程。这样做还是需要频率的问题,间隔时间大了,子进程可能分配了大量的内存,对整个系统安全造成威胁。
  2. 使用setrlimit系统调用来实现cpu、内存使用量的限制,当使用量超出限制就发送SIGXCPU(超时),malloc调用失败(内存超了,errno设置为ENOMEM)。这种做法比较清爽,因为进程的资源使用量操作系统是最清楚不过了。缺点是无法得到到底超出了多少内存,如果一次malloc调用申请大量的内存(如内存限制的一半),超了,但是进程的内存使用量却不会被操作系统维护(因为malloc失败了)。

在经过一段时间研究,资料搜寻,找不到一种完美、简单的方式完成这个功能。最后经过权衡之后还是选择了waitpid获取cpu使用资源,使用setrlimit限制CPU和内存使用量,这里通过一种比较丑陋的hack来判断是否是内存超了,通过setrlimit系统调用将进程的内存限制设置为限制的125%,然后在子程序结束时,调用waitpid之前,分析/proc文件系统,读取/proc/pid/status文件,解析出VmSize这行,然后在通过VmSize(虚拟内存空间)的大小判断是否超出了内存限制,这样做可以缓解问题,但是不能完美地解决。

    ...
    if (fork() == 0)
  • 0
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值