实现在线评测系统(一)

目录

  • Online judge system

    • 概述:
    • 关键技术
      • 1.如何在Linux中调用另一个程序
      • 2.如何监控进程执行时间
      • 3.如何限制子进程的资源
      • 4.虚拟化技术

Online judge system

概述:

研究一下在线评测系统编写的一些细节,加深对操作系统的理解,实现一个基本能用的评测机,通过shell脚本控制评测机监控用户程序。web接口和日志功能没写。

另外PE和CE功能还没写

  • 编写语言c/c++, bash
  • 编写环境deppin linux
  • 编写工具vim gcc 7.3.0

T^T 学长牛逼!!! Orz

关键技术

1.如何在Linux中调用另一个程序

在评测系统中,我们提交一个由标准输入输出的程序,我们判断正确性的方法一部分是修改输入输出流,将输入导入程序,输出导出,和标准答案进行比对。

例如添加一下代码,在程序的开始

freopen("file_name_input", "r", stdin);
freopen("file_name_output", "w", stdout);

用户从web页面提交代码,服务器拿到代码,形成一个服务器本地的文件。那么我们如果通过评测程序去调用,监控这个用户代码即可。但是这就意味着我们需要在文件头部加上上面两句话。虽然修改用户代码是可行的,但是却比较麻烦。这个问题先放一边,我们先解决另一个问题

ps:如果修改用户代码,一种解决方案是把main函数修改,就改成适宜CppUnit库调用的形式。CppUnit是一种c++单元测试的库,虽然没用过,但是相似的Junit有提供对应的内存,时间检测。

如何让评测程序调用另一个程序

在windows下我们只需要system(cmd_commond), 在函数中填写对应cmd命令即可,linux下的system函数作用还未证实

在Linux环境下我们需要调用的是exec函数家族

当进程调用exec函数时,该进程的程序完全替换新程序,而新程序从main函数开始,创建的新程序的进程ID并未改变。exec只是从磁盘上替换了当前进程的正文段,数据段,堆段和栈段

UNIX提供了几种exe函数execl,execv,execle,execve,execlp,execvp,fexecve.这几个函数出错返回-1.若成功不返回

#include <unistd.h>

//int execv(const char* pathname, char *const argv[])

void start_bash(std::string bash) {
    // 将 C++ std::string 安全的转换为 C 风格的字符串 char *
    // 从 C++14 开始, C++编译器将禁止这种写法 `char *str = "test";`
    // std::string bash = "/bin/bash";
    char *c_bash = new char[bash.length() + 1];   // +1 用于存放 '\0'
    strcpy(c_bash, bash.c_str());

    char* const child_args[] = { c_bash, NULL };
    execv(child_args[0], child_args);           // 在子进程中执行 /bin/bash
    delete []c_bash;
}

我们可以通过封装一个函数来执行我们路径下的程序,调用的是execv。由于上面我们说的替换程序部分。是为了解释之前看到的一个现象。

ps: 程序范例来着实验楼会员课。c++虚拟化技术实现简易docker容器

主程序:
    freopen调用
    执行外部程序(exec调用)

外部程序的输入流会被改变。到这里我们解决了两个问题,评测程序执行用户程序,且修改用户程序的输入输出流。

2.如何监控进程执行时间

参考《UNIX环境高级编程》第八章

每个进程都有一些其他的标识符,下列函数返回这些标识符,注意这些函数没有出错返回,更详细的说明见原著,后面不在赘述

#include <unistd.h>

pid_t getpid(void);     //返回调用进程的ID
pid_t getppid(void);    //返回调用进程的父进程ID

下面我们介绍一个函数fork()

#include <unistd.h>

pid_t fork(void);   //出错返回-1,子进程返回0,父进程返回子进程ID

fork创建的进程成为子进程,一个程序调用id = fork(); 那么程序运行的进程会返回两次,也就是会有两个进程,同时执行,一个是父进程,一个子进程,具体那个先执行是不确定的,取决于操作系统的调度算法。同时进程是操作系统分配资源的基本单位。子进程是父进程的副本,例如子进程获得父进程的数据空间,堆,栈的副本。而不共享这一部分。

我们看一个fork的例子

#include <bits/stdc++.h>
#include <unistd.h>
#include <sys/types.h>        // 提供类型 pid_t 的定义
#include <sys/wait.h>
#include <sys/resource.h>

void start_bash(std::string bash) {
    char *c_bash = new char[bash.length() + 1];
    strcpy(c_bash, bash.c_str());
    char* const child_args[] = { c_bash, NULL };
    execv(child_args[0], child_args);
    delete []c_bash;
}

int main()
{
    pid_t pid = fork();

    if(pid < 0) {
        std::cout << "create error" << std::endl;
        exit(0);
    } else if(pid == 0) {
        //当前进程ID
        std::cout << "this is child program " << getpid() << std::endl;
        //父进程ID
        std::cout << "this is child's father " << getppid() << std::endl;
    } else if(pid > 0) {
        std::cout << "this is father program " << getpid() << std::endl;
    }
    return 0;
}
/**
this is father program 20061
this is child program 20062
this is child's father 20061
*/

fork后程序执行两个进程,注意先后顺序默认是不可控的。我们可以通过wait等控制这是后话。我们可以让子进程先去执行用户程序。在执行前设置文件输入输出流,已经进程限制等。父进程等待子进程执行结束。检测结果。

之前我们说两个进程的执行顺序是取决于操作系统调度的。我们想让父亲进程等待调用则调用wait, waitp, wait3, wait4

wait3() 和 wait4() 函数除了可以获得子进程状态信息外,还可以获得子进程的资源使用信息,这些信息是通过参数 rusage 得到的。而 wait3() 与 wait4() 之间的区别是,wait3() 等待所有进程,而 wait4() 可以根据 pid 的值选择要等待的子进程,参数 pid 的意义与 waitpid() 函数的一样

参考

于是我们就可以在父进程中调用,等待编号p_id的进程结束,并收集状态

#include <sys/wait.h>
#include <sys/types.h>  //定义pid_t
#inlcude <reasource.h>  //定义rusage
int status = 0;
struct rusage use;
wait4(p_id, &status, 0, &use);

关于status的状态的宏

说明
WIFEXITED(status) 子进程正常终止为真。可以执行WEXITSTATUS(status),获取exit的参数
WIFSIGNALED(status) 进程异常终止为真,可以调用WTERMSIG(status)获取使子进程禁止的编号
WIFSTOPPED(status) 进程暂停子进程的暂停返回为真,调用WSTOPSIG(STATUS)可以获得暂停信号的编号
WIFCONTINUED(status) 作业控制暂停后已经继续的子进程返回了状态,则为真

《UNIX高级编程》191页

如果子进程正常返回我们就可以认为用户程序在时间空间限制下完成了要求。表格第一行。如果超时,内存不足则会出现异常退出。

《UNIX高级编程》251页定义了一些异常的常量

说明 OJ判定
SIGXCPU 超过CPU限制(setrlimit)
SIGXFSZ 超过文件长度限制(setrlimit)
SIGXRES 超过资源控制
SIGKILL 终止

到此,我们解决了父进程监控子进程的目的。那么下面则需要我们解决限制资源的问题

3.如何限制子进程的资源

我们同样需要从系统调用的角度限制内存

#include <sys/resource.h>
int getrlimit( int resource, struct rlimit *rlptr );
int setrlimit( int resource, const struct rlimit *rlptr );
两个函数返回值:若成功则返回0,若出错则返回非0值
struct rlimit {
    rlim_t    rlim_cur;    /* soft limit: current limit */
    rlim_t    rlim_max;    /* hard limit: maximum value for rlim_cur */
};

在更改资源限制时,须遵循下列三条规则:

(1)任何一个进程都可将一个软限制值更改为小于或等于其硬限制值。

(2)任何一个进程都可降低其硬限制值,但它必须大于或等于其软限制值。这种降低对普通用户而言是不可逆的。

(3)只有超级用户进程可以提高硬限制值

两个参数的resource是一个宏,我们去库里面看看

enum __rlimit_resource
{
  /* Per-process CPU limit, in seconds.  */
  RLIMIT_CPU = 0,
#define RLIMIT_CPU RLIMIT_CPU

  /* Largest file that can be created, in bytes.  */
  RLIMIT_FSIZE = 1,
#define RLIMIT_FSIZE RLIMIT_FSIZE

  /* Maximum size of data segment, in bytes.  */
  RLIMIT_DATA = 2,
#define RLIMIT_DATA RLIMIT_DATA

  /* Maximum size of stack segment, in bytes.  */
  RLIMIT_STACK = 3,
#define RLIMIT_STACK RLIMIT_STACK

  /* Largest core file that can be created, in bytes.  */
  RLIMIT_CORE = 4,
#define RLIMIT_CORE RLIMIT_CORE

  /* Largest resident set size, in bytes.
     This affects swapping; processes that are exceeding their
     resident set size will be more likely to have physical memory
     taken from them.  */
  __RLIMIT_RSS = 5,
#define RLIMIT_RSS __RLIMIT_RSS

  /* Number of open files.  */
  RLIMIT_NOFILE = 7,
  __RLIMIT_OFILE = RLIMIT_NOFILE, /* BSD name for same.  */
#define RLIMIT_NOFILE RLIMIT_NOFILE
#define RLIMIT_OFILE __RLIMIT_OFILE

  /* Address space limit.  */
  RLIMIT_AS = 9,
#define RLIMIT_AS RLIMIT_AS

  /* Number of processes.  */
  __RLIMIT_NPROC = 6,
#define RLIMIT_NPROC __RLIMIT_NPROC

  /* Locked-in-memory address space.  */
  __RLIMIT_MEMLOCK = 8,
#define RLIMIT_MEMLOCK __RLIMIT_MEMLOCK

  /* Maximum number of file locks.  */
  __RLIMIT_LOCKS = 10,
#define RLIMIT_LOCKS __RLIMIT_LOCKS

  /* Maximum number of pending signals.  */
  __RLIMIT_SIGPENDING = 11,
#define RLIMIT_SIGPENDING __RLIMIT_SIGPENDING

  /* Maximum bytes in POSIX message queues.  */
  __RLIMIT_MSGQUEUE = 12,
#define RLIMIT_MSGQUEUE __RLIMIT_MSGQUEUE

  /* Maximum nice priority allowed to raise to.
     Nice levels 19 .. -20 correspond to 0 .. 39
     values of this resource limit.  */
  __RLIMIT_NICE = 13,
#define RLIMIT_NICE __RLIMIT_NICE

  /* Maximum realtime priority allowed for non-priviledged
     processes.  */
  __RLIMIT_RTPRIO = 14,
#define RLIMIT_RTPRIO __RLIMIT_RTPRIO

  /* Maximum CPU time in μs that a process scheduled under a real-time
     scheduling policy may consume without making a blocking system
     call before being forcibly descheduled.  */
  __RLIMIT_RTTIME = 15,
#define RLIMIT_RTTIME __RLIMIT_RTTIME

  __RLIMIT_NLIMITS = 16,
  __RLIM_NLIMITS = __RLIMIT_NLIMITS
#define RLIMIT_NLIMITS __RLIMIT_NLIMITS
#define RLIM_NLIMITS __RLIM_NLIMITS
};

我们可以在父亲进程中监听发生的型号


/* We define here all the signal names listed in POSIX (1003.1-2008);
   as of 1003.1-2013, no additional signals have been added by POSIX.
   We also define here signal names that historically exist in every
   real-world POSIX variant (e.g. SIGWINCH).

   Signals in the 1-15 range are defined with their historical numbers.
   For other signals, we use the BSD numbers.
   There are two unallocated signal numbers in the 1-31 range: 7 and 29.
   Signal number 0 is reserved for use as kill(pid, 0), to test whether
   a process exists without sending it a signal.  */

/* ISO C99 signals.  */
#define SIGINT      2   /* Interactive attention signal.  */
#define SIGILL      4   /* Illegal instruction.  */
#define SIGABRT     6   /* Abnormal termination.  */
#define SIGFPE      8   /* Erroneous arithmetic operation.  */
#define SIGSEGV     11  /* Invalid access to storage.  */
#define SIGTERM     15  /* Termination request.  */

/* Historical signals specified by POSIX. */
#define SIGHUP      1   /* Hangup.  */
#define SIGQUIT     3   /* Quit.  */
#define SIGTRAP     5   /* Trace/breakpoint trap.  */
#define SIGKILL     9   /* Killed.  */
#define SIGBUS      10  /* Bus error.  */
#define SIGSYS      12  /* Bad system call.  */
#define SIGPIPE     13  /* Broken pipe.  */
#define SIGALRM     14  /* Alarm clock.  */

/* New(er) POSIX signals (1003.1-2008, 1003.1-2013).  */
#define SIGURG      16  /* Urgent data is available at a socket.  */
#define SIGSTOP     17  /* Stop, unblockable.  */
#define SIGTSTP     18  /* Keyboard stop.  */
#define SIGCONT     19  /* Continue.  */
#define SIGCHLD     20  /* Child terminated or stopped.  */
#define SIGTTIN     21  /* Background read from control terminal.  */
#define SIGTTOU     22  /* Background write to control terminal.  */
#define SIGPOLL     23  /* Pollable event occurred (System V).  */
#define SIGXCPU     24  /* CPU time limit exceeded.  */
#define SIGXFSZ     25  /* File size limit exceeded.  */
#define SIGVTALRM   26  /* Virtual timer expired.  */
#define SIGPROF     27  /* Profiling timer expired.  */
#define SIGUSR1     30  /* User-defined signal 1.  */
#define SIGUSR2     31  /* User-defined signal 2.  */

/* Nonstandard signals found in all modern POSIX systems
   (including both BSD and Linux).  */
#define SIGWINCH    28  /* Window size change (4.3 BSD, Sun).  */

/* Archaic names for compatibility.  */
#define SIGIO       SIGPOLL /* I/O now possible (4.2 BSD).  */
#define SIGIOT      SIGABRT /* IOT instruction, abort() on a PDP-11.  */
#define SIGCLD      SIGCHLD /* Old System V name */

参考《UNIX高级编程》185页

我们测试如下程序。输出和预期有一些不符合

虽然限制了CPU时间,但是父进程监听的却不是SIGXCPU,通过信号我们可以查到是被KILL了。但是大致实现了父进程监听子进程设置超时信息。程序最终跑了两秒。

#include <bits/stdc++.h>
#include <unistd.h>
#include <sys/types.h>        // 提供类型 pid_t 的定义
#include <sys/wait.h>
#include <sys/resource.h>

void start_bash(std::string bash) {
    char *c_bash = new char[bash.length() + 1];
    strcpy(c_bash, bash.c_str());
    char* const child_args[] = { c_bash, NULL };
    execv(child_args[0], child_args);
    delete []c_bash;
}

int main()
{
    pid_t pid = fork();

    if(pid < 0) {
    std::cout << "create error" << std::endl;
    exit(0);
    } else if(pid == 0) {
    std::cout << "this is child program " << getpid() << std::endl;

    rlimit limit;
    limit.rlim_cur = 2;
    limit.rlim_max = 2;
    setrlimit(RLIMIT_CPU , &limit);

    unsigned int i = 0;
    while(1) {
        i++;
    }

    } else if(pid > 0) {
    std::cout << "this is father program " << getpid() << std::endl;

    int status = 0;
    struct rusage use;
    wait4(pid, &status, 0, &use);   

    if(WIFSIGNALED(status)) {
        int res = WTERMSIG(status);
        std::cout << "res = " << res << std::endl;
        std::cout << "SIGXCPU = " << SIGXCPU << std::endl;
        if(res == SIGXCPU) {
        std::cout << "超过时间限制" << std::endl;
        } else {
        std::cout << "没有超时" << std::endl;
        }
    }
    }
    return 0;
}
this is father program 24042
this is child program 24043
res = 9
SIGXCPU = 24
没有超时

另一个问题是,用上述方法监控内存没有作用,和子进程的内存不符。我们通过动态查询linux目录 /proc/进程ID/status 文件,最后status是文件,Linux会为每一个正在运行的进程在proc目录下创建文件夹,在进程结束后删除文件,其目录下status就存储这我们要的内存信息。那么我们直接去读那个文件的内容即可。

到此我通过大概300行的c++代码加上一些系统调用实现了一个简易的。能检测用户进程内存时间的评测机

//main.cpp
#include <bits/stdc++.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/resource.h>

const int INF = 0x7FFFFFFF;
const int DEFAULT_MEMERY = 1024 * 1024 * 128; // 128 MB

std::chrono::system_clock::time_point begin_time;
std::chrono::system_clock::time_point end_time;

namespace util {

    auto isnum = [](char ch) -> bool {
        return ch >= '0' && ch <= '9';
    };

    auto split_string = [](std::string str) -> std::vector<std::string> {
        std::vector<std::string> vec;
        char* ttr = new char[str.size() + 1];
        int top = 0;
        for(int i = 0; i < str.size(); i++ ) {
            ttr[i] = str[i];
            if(ttr[i] == 9 || ttr[i] == 32) {   // ' ' or '\t'
                ttr[i] = 0;
            }
        }
        ttr[str.size()] = 0;
        for(int i = 0; i < str.size(); i++ ) {
            if(i == 0 && ttr[i] != 0 || ttr[i - 1] == 0 && ttr[i] != 0) {
                vec.push_back(ttr + i);
            }
        }
        delete []ttr;
        return vec;
    };

    auto int_to_string = [](int pid) -> std::string {
        char str[20] = {0};
        int top = 0;
        if(pid == 0) {
            return std::string("0");
        } else {
            while(pid) {
                str[top++] = pid % 10 + '0';
                pid /= 10;
            }
            str[top] = 0;
            std::string number(str);
            std::reverse(number.begin(), number.end());
            return number;
        }
    };

    auto string_to_int = [](std::string number, int default_val = 0) -> int {
        int num = 0;
        for(int i = 0; i < number.size(); i++ ) {
            if(util::isnum(number[i])) {
                num = num * 10 + number[i] - '0';
            } else {
                return default_val;
            }
        }
        return num;
    };

}

void start_bash(std::string bash = "/bin/bash") {
    char *c_bash = new char[bash.length() + 1];
    strcpy(c_bash, bash.c_str());
    char* const child_args[] = { c_bash, NULL };
    execv(child_args[0], child_args);
    delete []c_bash;
}

enum class JudgeResult : unsigned int {
    AC, RE, MLE, OLE, SE, CE, PE, WA, TLE
};

struct Result {
    int tot_time;       //ms
    int tot_memery;     //kb
    JudgeResult result;
};

class Problem {
public:
    int memery_limit;   //kb
    int time_limit;     //s
    std::string pathname;
    std::string input_file;
    std::string output_file;
    std::string answer_file;

    Problem() = default;
    Problem(std::string &input_time, std::string &path,
            std::string &input_file, std::string &output_file, std::string &answer_file) {
        time_limit = util::string_to_int(input_time);
        memery_limit = DEFAULT_MEMERY;
        pathname = path;
        this->input_file = input_file;
        this->output_file = output_file;
        this->answer_file = answer_file;
    }

    static bool check_answer(const char* answer_file1, const char* answer_file2) {
        std::ifstream input1(answer_file1);
        std::ifstream input2(answer_file2);
        if(!input1.is_open() || !input2.is_open()) {
            return false;
        }
        while(1) {
            if(input1.eof() && input2.eof()) {
                return true;
            }
            if(input1.eof() || input2.eof()) {
                return false;
            }
            if(input1.get() != input2.get()) {
                return false;
            }
        }
        return true;
    }

};

class OnlineJudge {
public:

    static void father_program(const int this_pid, const int child_pid, Problem problem) {
        listen_child_program(child_pid, problem);

    }

    static void child_program(const int this_pid, Problem problem) {
        set_user_limit(problem);                                 // set problem limit
        set_freopen(problem.input_file, problem.output_file);    // set file freopen
        start_bash(problem.pathname.c_str());                    //run user problem
    }

private:

    static void set_freopen(std::string input, std::string output) {
        freopen(input.c_str(), "r", stdin);
        freopen(output.c_str(), "w", stdout);
    }

    static void set_user_limit(Problem problem) {
        struct rlimit *r = new rlimit();
        r->rlim_cur = problem.time_limit;
        r->rlim_max = problem.time_limit;
        setrlimit(RLIMIT_CPU, r);
        setrlimit(RLIMIT_CORE, NULL);   //禁止创建core文件
    }

    static void listen_child_program(const int child_pid, Problem &problem) {
        int status = 0;
        struct rusage use;
        Result result;
        result.tot_memery = get_progress_memery(child_pid);

        int wait_pid = wait4(child_pid, &status, 0, &use);

        end_time = std::chrono::system_clock::now();
        result.tot_time = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - begin_time).count();

        std::cout << "memery = " << result.tot_memery << "kb" << std::endl;
        std::cout << "time = " << result.tot_time   << "ms" << std::endl;

        // exit success spj
        if(WIFEXITED(status)) {
            //std::cout << "WIFEXITED = " << WEXITSTATUS(status) << std::endl;
            if(Problem::check_answer(problem.output_file.c_str(), problem.answer_file.c_str())) {
                result.result = JudgeResult::AC;
            } else {
                result.result = JudgeResult::WA;
            }

        }

        // exit fail
        if(WIFSIGNALED(status)) {
            switch WTERMSIG(status) {
            case SIGXCPU:   // TLE
                //std::cout << "SIGXCPU" << std::endl;
                result.result = JudgeResult::TLE;
                break;
            case SIGKILL:   // TLE
                //std::cout << "SIGKILL" << std::endl;
                result.result = JudgeResult::TLE;
                break;
            case SIGXFSZ:   // OLE
                //std::cout << "SIGXFSZ" << std::endl;
                result.result = JudgeResult::OLE;
                break;
            default:        // RE
                //std::cout << "default" << std::endl;
                result.result = JudgeResult::RE;
                break;
            }

        }

        if(result.result == JudgeResult::AC) {
            std::cout << "Accept" << std::endl;
        }
        if(result.result == JudgeResult::WA) {
            std::cout << "Wrong answer" << std::endl;
        }
        if(result.result == JudgeResult::TLE) {
            std::cout << "Time limit except" << std::endl;
        }
        if(result.result == JudgeResult::RE) {
            std::cout << "Running time error" << std::endl;
        }
        if(result.result == JudgeResult::OLE) {
            std::cout << "Output limit except" << std::endl;
        }
    }

    static int get_progress_memery(const int pid) {

        //VmPeak:   290748 kB

        auto show = [](std::vector<std::string>vec) {
            puts("");
            for(auto &str: vec) {
                std::cout << "[" << str << "]";
            }
        };

        std::string path = "/proc/";
        path += util::int_to_string(pid);
        path += "/status";

        std::ifstream fp(path);
        std::string line;
        std::string goal = "VmPeak:";
        while(getline(fp, line)) {
            std::vector<std::string>vec = util::split_string(line);
            if(vec.size() == 3 && vec[0] == goal) {
                return util::string_to_int(vec[1], INF);
            }
        }

        return INF;
    }
};

/**
    argv: time memery path
*/
int main(int argc, char *argv[]) {

    std::cout << "========================Judging begin=========================" << std::endl;

    int pid = fork();
    begin_time = std::chrono::system_clock::now();

    std::string time = argv[1];
    std::string path = argv[2];
    std::string input_file = argv[3];
    std::string output_file = argv[4];
    std::string answer_file = argv[5];

    Problem problem(time, path, input_file, output_file, answer_file);

    if(pid < 0) {
        exit(0);
    }

    if(pid == 0) {
        OnlineJudge::child_program(getpid(), problem);
    } else {
        OnlineJudge::father_program(getpid(), pid, problem);
    }

    return 0;
}

目录结构:

.
├── back.cpp
├── main
├── main.cpp
├── main.o
├── run.sh
├── test
├── test.cpp
├── test.o
└── user_pro
........├── 1.in
........├── 1.out
........├── user_ac
........├── user.out
........├── user_re
........├── user_tle
........├── user_tle2
........└── user_wa

有用的就main.cpp和run.sh

#run.sh
g++ main.cpp -std=c++11
mv a.out main
#time_limit user_problem std_in user_in std:out
./main 2 ./user_pro/user_ac ./user_pro/1.in ./user_pro/user.out ./user_pro/1.out
./main 2 ./user_pro/user_wa ./user_pro/1.in ./user_pro/user.out ./user_pro/1.out
./main 2 ./user_pro/user_tle ./user_pro/1.in ./user_pro/user.out ./user_pro/1.out
./main 2 ./user_pro/user_re ./user_pro/1.in ./user_pro/user.out ./user_pro/1.out
./main 2 ./user_pro/user_tle_2 ./user_pro/1.in ./user_pro/user.out ./user_pro/1.out

运行结果

========================Judging begin=========================
memery = 13712kb
time = 1ms
Accept
========================Judging begin=========================
memery = 13712kb
time = 1ms
Wrong answer
========================Judging begin=========================
memery = 13712kb
time = 1998ms
Time limit except
========================Judging begin=========================
memery = 13712kb
time = 21ms
Running time error
========================Judging begin=========================
memery = 13712kb
time = 2501ms
Wrong answer

上文是各种程序的测试结果,最后一个执行2.5s,我设置的时间是2s都是未超时,可能是应为监控的是cpu时间,我延时用的是让进程的主线程休眠的命令,所以没有引发异常。

运行错误是应为那个程序死递归跑死了

4.虚拟化技术

我们的评测机要创建一个沙盒,在沙盒里面跑我们的评测系统。主要是为了屏蔽一些非法代码操作。同样通过系统调用模拟docker实现了。详情下回分解。凌晨了。。。

原文地址:https://www.cnblogs.com/Q1143316492/p/10355819.html

时间: 2024-10-23 02:53:03

实现在线评测系统(一)的相关文章

关于开源OJ_在线评测系统(Online Judge)设计与实现的研究与分析

OJ是Online Judge系统的简称,用来在线检测程序源代码的正确性.著名的OJ有TYVJ.RQNOJ.URAL等.国内著名的题库有北京大学题库.浙江大学题库.电子科技大学题库.杭州电子科技大学等.国外的题库包括乌拉尔大学.瓦拉杜利德大学题库等. Online Judge系统最初使用于ACM-ICPC国际大学生程序设计竞赛和OI信息学奥林匹克竞赛中的自动判题和排名.现广泛应用于世界各地高校学生程序设计的训练.参赛队员的训练和选拔.各种程序设计竞赛以及数据结构和算法的学习和作业的自动提交判断中

几个常见的在线评测系统,及我的点评

说不上点评,有的我也没用过. http://www.zerojudge.tw/  优点,题目多,分类细致,界面简明.来这里除了编程,没别的可扯淡的.缺点:有时访问不了.(但我相信不是网站本身的原因) http://openjudge.cn/  优点:据说能创建自己的在线评测题目.国内的网站,访问速度挺快. 有谁还有自己喜欢的在线评测系统,请email给我:[email protected]    Thanks!

南洋理工大学 ACM 在线评测系统 矩形嵌套

矩形嵌套 时间限制:3000 ms  |  内存限制:65535 KB 难度:4 描述 有n个矩形,每个矩形可以用a,b来描述,表示长和宽.矩形X(a,b)可以嵌套在矩形Y(c,d)中当且仅当a<c,b<d或者b<c,a<d(相当于旋转X90度).例如(1,5)可以嵌套在(6,2)内,但不能嵌套在(3,4)中.你的任务是选出尽可能多的矩形排成一行,使得除最后一个外,每一个矩形都可以嵌套在下一个矩形内. 输入 第一行是一个正正数N(0<N<10),表示测试数据组数,每组测

在线教育系统,助你赢得网络市场

乐付宝在线教育系统是一套开放式的在线教育系统,为用户提供一个简单.快捷的课程交易的平台,以帮助用户更好的创建并推广自己的课程.系统采用网络互动直播的授课模式,突破地域和时间的限制,为广大的用户提供高效便捷的网络学习渠道,省时高效的学习新的知识.技能. 会员只要具备电脑和上网条件,即可登陆网站进行网上学习,充分体现网络共享,一站全程服务.会员可以发布他想学习的课程,讲师可以根据期待的信息,针对性的发布课程.简单.高效的全程实时直播互动教学,可以上传视频.文档在直播中展示,也可开启摄像头实时授课.学

在线考试系统测试报告

1.项目名称:                    在线考试系统 2.用户需求规格说明书URL:http://www.cnblogs.com/yinll314/p/6061359.html 3.组长博客URL:              http://www.cnblogs.com/yinll314/ 4.代码git URL:               ssh:[email protected]:handsomeman/examm.git                           

工业能耗在线监测系统

一. 系统概述 多年以来,我国对于企业能耗的收集,大多采用企业定期上报耗能报表的采集方式,企业自行上报的能耗报表,往往因为企业自身经营的需要,带有或多或少有利于企业的倾向性特征,并非完全客观反映实际能耗.能耗管理部门也没有其他直接有效的手段,获取重点企业的实际能耗信息,因此更无法做到对不同类别耗能指标的有效分析,据此制定针对性的能耗管理政策.也无法进一步提出节能方案,有效降低能耗. 工业能耗在线监测系统是一个集成Intranet/Internet网络技术.GPRS无线传输技术.Web Servi

多功能表单填报系统V1.2.1-适用于在线报名系统、调查、数据收集等

多功能表单系统V1.2.1 前台:http://www.schoolms.net/mysoft/biaodan/index.asp 后台:http://www.schoolms.net/mysoft/biaodan/admin/index.asp 本系统适用于填报收集一些数据信息,例如在线报名系统.教师获奖登记.调查等等系统应用. 数据格式有文本框输入.单选.多选,3种模式.文件上传功能暂无法实现. 支持二级管理员权限,可以分别管理各个表单项目. 可以把数据导出为Excel 其中多选,是否必选,

基于SSH框架的在线考勤系统开发的质量属性

我要开发的是一个基于SSH框架的在线考勤系统,在系统中常见的质量属性有:可用性.可修改性.性能.安全性.易用性. 可用性方面: 可用性是指系统正常运行时间的比例,是通过两次故障之间的时间长度或在系统崩溃情况下能够恢复正常运行的速度来衡量的.实现可用性的战术分为三类:错误检测(用来检测故障的健康监视).错误恢复(检测到故障时的恢复).错误预防(阻止错误演变为故障).用于检测错误的3个战术是: 信号/响应.心跳.异常.用于错误恢复的战术有7种:表决.主动冗余.被动冗余.备件.shadow操作.状态再

ZigBee接触网在线监测系统

接触网是铁路运行必不可少的高压输电线.接触网在使用中,断线会造成重大事故,如行车中断.人员伤亡等.在日常维护中,除了测量磨耗这个方案,别的方案都无法及时找到断线隐患.随着近几年接触网技术装备的投入使用,因几何参数超标造成的故障已得到改善,而断线故障却一直无法得到解决.当线索张力为0或者接近于0,则表示已经断线.根据断线的原因及物理特性,若对接触网的温湿度及线索张力进行实时检测,那么就能事先判断断线的可能性,及时采取措施. 传统的电气设备在线监测系统所采用的连接方式为有线的RS485连接,该方式不