暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

简析 Online Judge 原理

haha视界 2022-08-04
162

0. 目录

  • 1.场景

  • 2. 原理分析

  • 3. 代码简析

1. 场景

常见的在线判题大概流程如图:


①:选择语言,编写代码
②:提交代码判题
③:返回判题结果

后台通过多个维度进行判断:

  1. 编译通过与否

  2. 测试用例通过情况

  3. cpu耗时

  4. 内存消耗

2. 原理分析

如上图,

  1. 用户提交代码后,后端服务器进行任务调度,选择空闲的判题服务器,发送编译请求。

  2. 判题服务器首先根据选择的语言,把提交的代码嵌入代码模板,进行编译,编译文件放入exe_dir可执行文件目录返回编译结果。

  3. 后端服务器根据编译结果判断是否进行运行请求。

  4. 判题服务器收到运行请求后,启动Judger(父)进程。

  5. Judger进程fork子进程后,启动超时监听线程,超时杀死子进程,并wait子进程退出状态。

  6. 子进程根据题目要求,配置cpu、memory等资源限制。

  7. 子进程进行测试用例验证,完成后退出。

  8. 父进程统计子进程实际运行时间,退出状态、cpu耗时、内存消耗等,返回给后端服务器。

  9. 后端服务器获取结果后存入数据库,并返回给客户端呈现结果。

  10. 通过seccomp限制恶意代码。


    seccomp是Linux内核中的计算机安全性工具。seccomp允许进程单向转换到“安全”状态,在该状态下,除了exit,sigreturn,读取和写入已打开的文件描述符之外,该进程无法进行任何系统调用。如果尝试任何其他系统调用,内核将仅记录事件或使用SIGKILL或SIGSYS终止进程。

3. 代码简析

1. 启动父进程

    void run(struct config *_config, struct result *_result) {

    // record current time
    struct timeval start, end;
    gettimeofday(&start, NULL);


    pid_t child_pid = fork();


    if (child_pid == 0) {
    child_process(log_fp, _config);
    }
    else if (child_pid > 0){
    // create new thread to monitor process running time
    pthread_t tid = 0;
    if (_config->max_real_time != UNLIMITED) {
    struct timeout_killer_args killer_args;


    killer_args.timeout = _config->max_real_time;
    killer_args.pid = child_pid;
    if (pthread_create(&tid, NULL, timeout_killer, (void *) (&killer_args)) != 0) {
    kill_pid(child_pid);
    ERROR_EXIT(PTHREAD_FAILED);
    }
    }


    int status;
    struct rusage resource_usage;


    // wait for child process to terminate
    // on success, returns the process ID of the child whose state has changed;
    // On error, -1 is returned.
    if (wait4(child_pid, &status, WSTOPPED, &resource_usage) == -1) {
    kill_pid(child_pid);
    ERROR_EXIT(WAIT_FAILED);
    }


    // 真实运行时间
    gettimeofday(&end, NULL);
    _result->real_time = (int) (end.tv_sec * 1000 + end.tv_usec 1000 - start.tv_sec * 1000 - start.tv_usec 1000);


    // 子进程退出,killer线程也退出
    if (_config->max_real_time != UNLIMITED) {
    if (pthread_cancel(tid) != 0) {
    // todo logging
    };
    }
    {
    // 获得:子进程退出原因,cpu耗时、内存使用
    _result->exit_code = WEXITSTATUS(status);
    _result->cpu_time = (int) (resource_usage.ru_utime.tv_sec * 1000 +
    resource_usage.ru_utime.tv_usec 1000);
    _result->memory = resource_usage.ru_maxrss * 1024;
    }
    }
    }

    2. 超时看门狗

      void *timeout_killer(void *timeout_killer_args) {
      pid_t pid = ((struct timeout_killer_args *)timeout_killer_args)->pid;
      int timeout = ((struct timeout_killer_args *)timeout_killer_args)->timeout;


      // 超时看门狗
      if (sleep((unsigned int)((timeout + 1000) 1000)) != 0) {
      kill_pid(pid);
      return NULL;
      }
      if (kill_pid(pid) != 0) {
      return NULL;
      }
      return NULL;
      }

      3. 子进程

        void child_process(FILE *log_fp, struct config *_config) 
        {
        FILE *input_file = NULL, *output_file = NULL, *error_file = NULL;


        setrlimit(RLIMIT_STACK, &max_stack);


        setrlimit(RLIMIT_AS, &max_memory);


        setrlimit(RLIMIT_CPU, &max_cpu_time);


        setrlimit(RLIMIT_NPROC, &max_process_number);
        setrlimit(RLIMIT_FSIZE, &max_output_size);
        // redirect file -> stdin
        dup2(fileno(input_file), fileno(stdin);




        // redirect stdout -> file
        dup2(fileno(output_file), fileno(stdout);




        // redirect stderr -> file
        dup2(fileno(error_file), fileno(stderr);




        // set gid
        _config->gid != -1 && (setgid(_config->gid) == -1 || setgroups(sizeof(group_list) / sizeof(gid_t), group_list) == -1);


        // set uid
        _config->uid != -1 && setuid(_config->uid);


        //执行代码测试子进程
        execve(_config->exe_path, _config->args, _config->env);
        }


        文章转载自haha视界,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

        评论