读取复制0字节文件时fread函数为何出现阻塞?
跨文件夹文件复制程序处理空文件时阻塞的问题
我开发了一个跨文件夹复制文件的程序,采用fork创建父子进程,分别调用read_file和write_file函数,通过消息队列传递数据。所有非空文件都能正常复制,但遇到0字节的空文件时,read_file函数必然发生阻塞。按下CTRL+C后,程序可以继续完成其余非空文件的复制。我的预期是能复制所有文件,包括空文件。
相关代码
读取函数(read_file)
int read_file(char *ficheiro) { FILE *fp = fopen(ficheiro, "rb"); if (fp == NULL) { exit_on_error(-1, "fopen falhou"); } MSG_STRUCT msg; int total_bytes = 0, bytes_lidos; while ((bytes_lidos = fread(msg.buffer, 1, TAMANHO_BUFFER, fp)) > 0) { msg.type = 1; msg.bytes_lidos = bytes_lidos; if (msgsnd(msg_id, &msg, sizeof(MSG_STRUCT) - sizeof(long), 0) == -1) { perror("Erro ao enviar mensagem"); fclose(fp); exit(1); } total_bytes += bytes_lidos; } // 发送结束消息,在循环外 msg.bytes_lidos = 0; if (msgsnd(msg_id, &msg, sizeof(MSG_STRUCT) - sizeof(long), 0) == -1) { perror("Erro ao enviar mensagem de finalização"); fclose(fp); exit(1); } fclose(fp); return total_bytes; }
写入函数(write_file)
int write_file(char *ficheiro) { FILE *fp = fopen(ficheiro, "wb"); if (fp == NULL) { exit_on_error(-1, "fopen falhou"); } MSG_STRUCT msg; int total_bytes = 0; while (1) { if (msgrcv(msg_id, &msg, sizeof(MSG_STRUCT) - sizeof(long), 1, 0) == -1) { perror("Erro ao receber mensagem"); fclose(fp); exit(1); } if (msg.bytes_lidos == 0) { printf("msg.bytes == 0\n"); break; } fwrite(msg.buffer, 1, msg.bytes_lidos, fp); total_bytes += msg.bytes_lidos; } fclose(fp); return total_bytes; }
主函数(main)
int main(int argc, char **argv) { if (argc < 3) { fprintf(stderr, "Número de argumentos inválido\n"); fprintf(stderr, "Sintaxe: %s <origem> <destino>\n", argv[0]); exit(-1); } msg_id = cria_msg(MSG_KEY); int status; if (fork() != 0) { read_file(argv[1]); wait(&status); } else { write_file(argv[2]); exit(0); } remove_msg(MSG_KEY); return 0; }
测试用Bash脚本
#!/bin/bash CP_CMD=cp-msg TESTE_DIR=teste OUTPUT_DIR=output if [ $# == 2 ] then TESTE_DIR=$1 OUTPUT_DIR=$2 fi if [ ! -d $TESTE_DIR ] then echo "Não existe pasta $TESTE_DIR" exit 1 fi if [ ! -d $OUTPUT_DIR ] then echo "Criar pasta $OUTPUT_DIR" mkdir $OUTPUT_DIR fi rm -f $OUTPUT_DIR/* for ficheiro in $TESTE_DIR/* do nome=`basename $ficheiro` tamanho_ficheiro=$(stat -c%s "$ficheiro") echo -n "Copiar $ficheiro ($tamanho_ficheiro bytes) para $OUTPUT_DIR/$nome: " /usr/bin/time -f "em %E" ./$CP_CMD $ficheiro $OUTPUT_DIR/$nome echo -n "Verificar cópia: " diff $ficheiro $OUTPUT_DIR/$nome >/dev/null if [ $? != 0 ] then echo "ficheiros diferentes" else echo "ficheiros iguais" fi echo done
问题原因及解决方案
问题根源
处理空文件时,read_file函数中fread直接返回0,跳过循环后尝试发送结束消息,但此时父子进程的执行逻辑存在漏洞:
- 循环未执行时,
msg.type未被赋值为1,导致发送的结束消息类型不符合子进程msgrcv的接收条件(只接收类型为1的消息)。 - 子进程卡在
msgrcv等待类型1的消息,父进程发送的结束消息类型不匹配,双方陷入永久阻塞。
修复方案
在发送结束消息前,显式设置msg.type = 1,保证子进程能识别并接收结束信号。
修改后的read_file函数:
int read_file(char *ficheiro) { FILE *fp = fopen(ficheiro, "rb"); if (fp == NULL) { exit_on_error(-1, "fopen falhou"); } MSG_STRUCT msg; int total_bytes = 0, bytes_lidos; while ((bytes_lidos = fread(msg.buffer, 1, TAMANHO_BUFFER, fp)) > 0) { msg.type = 1; msg.bytes_lidos = bytes_lidos; if (msgsnd(msg_id, &msg, sizeof(MSG_STRUCT) - sizeof(long), 0) == -1) { perror("Erro ao enviar mensagem"); fclose(fp); exit(1); } total_bytes += bytes_lidos; } // 发送结束消息前必须设置type为1 msg.type = 1; msg.bytes_lidos = 0; if (msgsnd(msg_id, &msg, sizeof(MSG_STRUCT) - sizeof(long), 0) == -1) { perror("Erro ao enviar mensagem de finalização"); fclose(fp); exit(1); } fclose(fp); return total_bytes; }
额外优化建议
- 可在
msgsnd和msgrcv中添加IPC_NOWAIT标志,并处理对应错误,避免非预期阻塞。 - 空文件场景下,也可直接让写进程创建空文件,无需发送消息,但上述修改已能统一处理所有文件类型,逻辑更简洁。
内容的提问来源于stack exchange,提问作者user20432234
相关产品推荐
相关产品推荐

