我们都知道 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
这就符合预期了