You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

添加alarm超时后,自动评分程序CSV输出重复及子进程执行异常问题

问题原因分析

你的核心问题是fork出的子进程未被正确终止,导致它从grade函数返回后继续执行main函数中的代码,进而重复写入CSV表头和学生成绩。

具体细节:

  • fork系统调用会复制父进程的完整地址空间,包括程序计数器(PC)。子进程会从fork返回的位置继续执行,而非从头启动程序。
  • 如果grade函数中,子进程的代码路径没有通过_exit()(注意不是标准库的exit())直接终止,子进程会执行完grade函数后返回main函数,继续执行main里的fprintf语句——这就是重复输出的直接根源。
  • 常见遗漏场景:子进程执行execvp失败后未调用终止函数、alarm信号处理函数用return而非_exit、子进程完成任务后未主动终止等。
  • 额外冲突:子进程复制了父进程打开的gradestest.csv文件描述符,若子进程无意写入该文件,会和父进程的写入操作叠加,加剧重复内容问题。
解决方法

1. 强制子进程所有执行路径调用_exit()终止

在grade函数的子进程分支中,所有可能的执行结束点都必须调用_exit(),绝对不能让子进程返回main函数:

  • 用_exit()而非exit():exit()会刷新并关闭父进程打开的标准I/O流(包括CSV文件),可能导致父进程输出被截断或重复;_exit()是系统调用,直接终止进程,不处理标准库缓冲区。

2. 修正alarm信号处理逻辑

若用alarm实现超时,信号处理函数不能用return,需直接调用_exit()终止子进程:

void timeout_handler(int sig) {
    _exit(1); // 用非0码标记超时状态
}

3. 父进程正确等待子进程退出

父进程调用waitpid()等待子进程结束,避免僵尸进程,同时可获取子进程退出状态(判断正常完成、超时或执行失败):

pid_t pid = fork();
if (pid == 0) {
    // 子进程逻辑
    alarm(5); // 设置5秒超时
    signal(SIGALRM, timeout_handler);
    execvp(student_program, args);
    // 若execvp返回,说明执行失败
    _exit(2); // 标记执行失败
} else if (pid > 0) {
    int status;
    waitpid(pid, &status, 0);
    // 根据status处理评分逻辑
    if (WIFEXITED(status)) {
        int exit_code = WEXITSTATUS(status);
        // 0:正常完成,1:超时,2:执行失败
    }
}

4. 隔离父子进程的文件操作

所有CSV写入操作仅由父进程完成,子进程不打开或写入gradestest.csv,避免文件描述符复制导致的写入冲突。

核心代码修正示例

假设你的grade函数原本存在子进程终止遗漏,修正后如下:

void grade(char *student_id, char *program_path) {
    pid_t pid = fork();
    if (pid == -1) {
        perror("fork failed");
        return;
    }
    if (pid == 0) {
        // 子进程:执行学生程序+超时处理
        alarm(10); // 设置10秒超时
        signal(SIGALRM, timeout_handler);
        
        char *args[] = {program_path, NULL};
        execvp(program_path, args);
        
        // 走到此处说明execvp执行失败
        fprintf(stderr, "Failed to execute %s\n", program_path);
        _exit(EXIT_FAILURE); // 强制终止,绝不返回
    } else {
        // 父进程:等待子进程、计算成绩、写入CSV
        int status;
        waitpid(pid, &status, 0);
        int score = 0;
        if (WIFEXITED(status)) {
            int code = WEXITSTATUS(status);
            score = (code == 0) ? 100 : (code == 1) ? 0 : 50;
        }
        // 父进程单独写入CSV,避免子进程干扰
        FILE *fp = fopen("gradestest.csv", "a");
        if (fp) {
            fprintf(fp, "%s,%d\n", student_id, score);
            fclose(fp);
        }
    }
}
额外注意事项
  • 表头仅在程序启动时写入一次,不要放在处理学生的循环中。
  • 打开CSV文件时用"a"(追加)模式,避免每次写入清空文件。

内容的提问来源于stack exchange,提问作者Henry Chen

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 02:01:32