linux 下 fork 后的文件资源处理问题

我们都知道 linux 下 fork 一个子进程出来,他能够继承父进程的文件资源,网络资源等,也从父进程那里拷贝了代码段,数据段,缓冲区等等到自己这里有了新的一份,那么,如果父子进程对于打开的文件资源操作不同,会是怎样的结果呢,先看正常的使用代码

#include <stdio.h>
#include <unistd.h>

int main(int argc, char **argv) {
    FILE *fp;
    if ((fp=fopen("test.out", "w+")) == NULL) {
        printf("open file failedn");
        return -1;
    }
    fprintf(fp, "hello worldn");

    pid_t fpid;
    fpid = fork();
    if (fpid < 0) {
        printf("error in forkn");
        return -1;
    } else if (fpid == 0) {
        // in son process
        fprintf(fp, "from son processn");
    } else {
        // in father process
        fprintf(fp, "from father processn");
    }

    fprintf(fp, "donen");

    fclose(fp);
    return 0;
}

这份代码可以生成以下的文件

hello world
from father process
done
hello world
from son process
done

需要注意的是第 4 行和第 6 行,多了一个 hello world 和 done

那么,接下来试试在父进程中关掉文件句柄

一开始,我是这样尝试着关闭的

#include <stdio.h>
#include <unistd.h>

int main(int argc, char **argv) {
    FILE *fp;
    if ((fp=fopen("test.out", "w+")) == NULL) {
        printf("open file failedn");
        return -1;
    }
    fprintf(fp, "hello worldn");

    pid_t fpid;
    fpid = fork();
    if (fpid < 0) {
        printf("error in forkn");
        return -1;
    } else if (fpid == 0) {
        // in son process
        fprintf(fp, "from son processn");
    } else {
        // in father process
        fprintf(fp, "from father processn");
        if (fp != NULL) {
            fclose(fp);
        }
    }

    fprintf(fp, "donen");

    if (fp != NULL) {
        fclose(fp);
    }

    return 0;
}

跑起来报

*** glibc detected *** ./fork: double free or corruption (top): 0x092fe008 ***
======= Backtrace: =========
/lib/i386-linux-gnu/libc.so.6(+0x73e42)[0xb7699e42]
/lib/i386-linux-gnu/libc.so.6(fclose+0x154)[0xb7689384]
./fork[0x80485dd]
/lib/i386-linux-gnu/libc.so.6(__libc_start_main+0xf3)[0xb763f4d3]
./fork[0x8048421]
======= Memory map: ========
08048000-08049000 r-xp 00000000 08:01 1063761    /home/zrj/c/fork/fork
08049000-0804a000 r--p 00000000 08:01 1063761    /home/zrj/c/fork/fork
0804a000-0804b000 rw-p 00001000 08:01 1063761    /home/zrj/c/fork/fork
092fe000-0931f000 rw-p 00000000 00:00 0          [heap]
b75f7000-b7613000 r-xp 00000000 08:01 1049526    /lib/i386-linux-gnu/libgcc_s.so.1
b7613000-b7614000 r--p 0001b000 08:01 1049526    /lib/i386-linux-gnu/libgcc_s.so.1
b7614000-b7615000 rw-p 0001c000 08:01 1049526    /lib/i386-linux-gnu/libgcc_s.so.1
b7625000-b7626000 rw-p 00000000 00:00 0
b7626000-b77c5000 r-xp 00000000 08:01 1049505    /lib/i386-linux-gnu/libc-2.15.so
b77c5000-b77c7000 r--p 0019f000 08:01 1049505    /lib/i386-linux-gnu/libc-2.15.so
b77c7000-b77c8000 rw-p 001a1000 08:01 1049505    /lib/i386-linux-gnu/libc-2.15.so
b77c8000-b77cb000 rw-p 00000000 00:00 0
b77da000-b77dd000 rw-p 00000000 00:00 0
b77dd000-b77de000 r-xp 00000000 00:00 0          [vdso]
b77de000-b77fe000 r-xp 00000000 08:01 1049485    /lib/i386-linux-gnu/ld-2.15.so
b77fe000-b77ff000 r--p 0001f000 08:01 1049485    /lib/i386-linux-gnu/ld-2.15.so
b77ff000-b7800000 rw-p 00020000 08:01 1049485    /lib/i386-linux-gnu/ld-2.15.so
bf940000-bf961000 rw-p 00000000 00:00 0          [stack]
已放弃 (核心已转储)

既然他说 double free,那就试试只 free 一次,改成这样后可以正常运行

#include <stdio.h>
#include <unistd.h>

int main(int argc, char **argv) {
    FILE *fp;
    if ((fp=fopen("test.out", "w+")) == NULL) {
        printf("open file failedn");
        return -1;
    }
    fprintf(fp, "hello worldn");

    pid_t fpid;
    fpid = fork();
    if (fpid < 0) {
        printf("error in forkn");
        return -1;
    } else if (fpid == 0) {
        // in son process
        fprintf(fp, "from son processn");
    } else {
        // in father process
        fprintf(fp, "from father processn");
        if (fp != NULL) {
            fclose(fp);
        }
    }

    fprintf(fp, "donen");

    return 0;
}

结果是

hello world
from father process
hello world
from son process
done

从结果看,父进程关闭文件句柄,对于子进程应该是没有影响的。背后的原因,不知道是否与 copy-on-write 有关。

============================================

update:上面的第一次 fclose 之前虽然有判空,但是在 fclose 之后没有置空,典型的野指针问题,那么试试该改成这样?

#include <stdio.h>
#include <unistd.h>

int main(int argc, char **argv) {
    FILE *fp;
    if ((fp=fopen("test.out", "w+")) == NULL) {
        printf("open file failedn");
        return -1;
    }
    fprintf(fp, "hello worldn");

    pid_t fpid;
    fpid = fork();
    if (fpid < 0) {
        printf("error in forkn");
        return -1;
    } else if (fpid == 0) {
        // in son process
        fprintf(fp, "from son processn");
    } else {
        // in father process
        fprintf(fp, "from father processn");
        if (fp != NULL) {
            fclose(fp);
            fp = NULL;
        }
    }

    fprintf(fp, "donen");

    if (fp != NULL) {
        fclose(fp);
        fp = NULL;
    }

    return 0;
}

结果直接段错误

段错误 (核心已转储)

===============================================

update 最开始的那份代码中,hello world 出现了两次,这个其实是缓冲区引起的,具体的解释可以看这篇文章,http://coolshell.cn/articles/7…,改成这样之后

#include <stdio.h>
#include <unistd.h>

int main(int argc, char **argv) {
    FILE *fp;
    if ((fp=fopen("test.out", "w+")) == NULL) {
        printf("open file failedn");
        return -1;
    }
    fprintf(fp, "hello world, %dn", getpid());
    fflush(fp);

    pid_t fpid;
    fpid = fork();
    if (fpid < 0) {
        printf("error in forkn");
        return -1;
    } else if (fpid == 0) {
        // in son process
        fprintf(fp, "from son processn");
        fflush(fp);
    } else {
        // in father process
        fprintf(fp, "from father processn");
        fflush(fp);
    }

    fprintf(fp, "donen");
    fflush(fp);

    return 0;
}

输出变成这样

hello world, 13047
from father process
done
from son process
done

这就符合预期了

Leave a Reply

Your email address will not be published. Required fields are marked *