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

读取复制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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 16:34:56