添加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
相关产品推荐
相关产品推荐

