资源限制是沙箱的重要的一部分, 另一部分是系统调用的拦截. 资源限制包括CPU时间, 内存, 写文件大小, 打开文件个数等等。
时间
时间限制
时间限制算是 算法竞赛 中要求最严格的部分了。 看了那么久的Unix环境高级编程, 一想到的就是拿setrlimit来限制;但是 setrlimit 不仅精度不够,而且他计算的时间是是CPU上运行的时间, 一来个sleep就不计入时间,这样很不好。看到老的judge_client是通过死循环不断看着的,我觉得不合适。参考了 QingdaoU/Judger 他们使用一个线程的方式去看着子线程, 一旦超时则发信号让系统杀掉。我选择了SIGSTOP信号,这个信号是程序不能忽略的,SIGKILL在系统各处用的很多,不便于判断中断原因。
线程中使用的是nanosleep这个高精度计时器,可以精确到纳秒。然后一旦醒来则发信号杀掉进程,但如果在其中运行的程序没有超时,则会有函数负责将这个线程取消掉。
此线程几乎不会影响子进程的时间。
时间获取
考虑到有可能有sleep的存在,所以时间不仅通过getrusage获取,还在主线程中设定了时间戳记录,记录等待的时间,并作为输出结果的一部分,可根据需要进行取舍或加权求值, 一般两个时间相差不大,但如果是资源超限被杀,有可能导致时间戳记录的时间偏大。
内存
内存限制
内存当然也不是无限大的, 默认也打算直接使用setrlimit限制,但是在后面的测试中发现有个问题,linux 会检测申请的内存是否会超限,一旦超限则发送 SIGSEGV 信号,这个信号跟内存越界的信号相同,就会导致判断出错。当然这是最安全的方式。为了能获取到实时的内存,就又开了一个线程,通过/proc/{pid}/stamt
文件读取实时的内存使用状况。一旦超出限制就也发 SIGSEGV 信号杀掉。这时候通过getrusage就能获取到超过的信号。
内存使用获取
直接使用 getrusage 获取,但获取的时候只会获取使用了的内存量,如果用户申请一个大的空间,但没有使用,这部分是不会计入内存的,但也可以通过stamt文件获取到申请的空间。
此线程对时间有一定影响,如果发出SIGSEGV信号,会使得主线程中的时间记录增大,暂时不知道原因。有可能是内存清理带来的时间负担。
文件
文件打开控制是属于系统调用,这部分会在别的文章说。
文件相关的是用户代码的输出, 有可能输出太多。 所以使用 setrlimit 也进行了限制, 当然是得指定的情况下。但是限制的时候只能限制输出到文件,如果不是输出到文件中这部分限制并不会起作用。一旦超出限制,系统会发送 SIGXFSZ 信号直接杀掉。
资源限制的部分大概是这些。 以上
-- EOF --
comments