• 实现在线评测系统(一)


    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 用于存放 ''
        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 '	'
                    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实现了。详情下回分解。凌晨了。。。

  • 相关阅读:
    使用不同方法输出一维整型数组a的各元素
    从键盘输入一个字符串与一个指定字符,将字符串中出现的指定字符全部删除
    指向函数的指针
    输入一个十进制正整数,将其转换成二进制、八进制、十六进制数输出
    java速成一篇博文搞定基础
    倒排索引lucene
    Manacher's ALGORITHM: O(n)时间求字符串的最长回文子串
    C++对象内存布局2进阶篇C++ 对象的内存布局(下)
    笔试面试设计模式&软件测试&软件工程
    字典树
  • 原文地址:https://www.cnblogs.com/Q1143316492/p/10355819.html
Copyright © 2020-2023  润新知