这节主要介绍,父子进程共享文件、fork基于的copy on write、exit(0)与_exit(0)的区别、atexit()终止处理程序。
首先父子进程共享文件:
直接献上一个例子:
#include <unistd.h> #include <sys/stat.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdlib.h> #include <stdio.h> #include <errno.h> #include <string.h> #include <signal.h> #define ERR_EXIT(m) \ do \ { \ perror(m); \ exit(EXIT_FAILURE); \ } while(0) int main(int argc, char *argv[]) { signal(SIGCHLD, SIG_IGN); printf("before fork pid = %d\n", getpid()); int fd; fd = open("test.txt", O_WRONLY); //在父进程中打开一个文件 if (fd == -1) ERR_EXIT("open error"); pid_t pid; pid = fork(); if (pid == -1) ERR_EXIT("fork error"); if (pid > 0) { printf("this is parent pid=%d childpid=%d\n", getpid(), pid); write(fd, "parent", 6); //从父进程中向文件写 sleep(3); } else if (pid == 0) { printf("this is child pid=%d parentpid=%d\n", getpid(), getppid()); write(fd, "child", 5); //从子进程中向文件写 } return 0; }
可以看出,在子进程中不用重新打开文件,直接利用父进程中的文件描述符就可以对文件进行操作,这里值得注意的是有可能父进程先写进去子进程再写的时候直接把
父进程写的内容给覆盖了,这是犹豫子进程写的时候父进程可能已经结束,这是用sleep控制一下会看出效果,这个大家自己动手试一试。
fork的copy on write特性:
意思就是父子进程各自都有自己的地址空间,但是都共享一些初始未变的东西,当子进程中有改变的时候就会拷贝一份放到自己的地址空间中去,看例子:
#include <unistd.h> #include <sys/stat.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdlib.h> #include <stdio.h> #include <errno.h> #include <string.h> #include <signal.h> #define ERR_EXIT(m) \ do \ { \ perror(m); \ exit(EXIT_FAILURE); \ } while(0) int gval = 100; int main(int argc, char *argv[]) { signal(SIGCHLD, SIG_IGN); printf("before fork pid = %d\n", getpid()); pid_t pid; pid = fork(); if (pid == -1) ERR_EXIT("fork error"); if (pid > 0) { sleep(1); printf("this is parent pid=%d childpid=%d gval=%d\n", getpid(), pid, gval); sleep(3); } else if (pid == 0) { gval++; printf("this is child pid=%d parentpid=%d gval=%d\n", getpid(), getppid(), gval); } return 0; }
打印的结果:父进程还是100而子进程是101。
exit(0)与_exit(0)的区别:
1、exit(0)是C函数库提供的,而_exit(0)是linux系统的系统调用。
2、
这个就是执行时候的区别,_exit(0)会直接去到内核中去,而exit(0)会调用终止程序和清除i/o缓冲。
int main(int argc, char *argv[]) { printf("hello world"); //fflush(stdout); //_exit(0); exit(0); }
int main(int argc, char *argv[]) { printf("hello world"); //fflush(stdout); _exit(0); //exit(0); }
int main(int argc, char *argv[]) { printf("hello world\n"); //fflush(stdout); _exit(0); //exit(0); }
int main(int argc, char *argv[]) { printf("hello world"); fflush(stdout); _exit(0); //exit(0); }
可以看出请没清缓冲去的区别,这里\n和fflush都是有清的功能,只是为了让大家了解一下。
下面是终止处理程序,简单了解一下:
#include <unistd.h> #include <sys/stat.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdlib.h> #include <stdio.h> #include <errno.h> #include <string.h> #include <signal.h> #define ERR_EXIT(m) \ do \ { \ perror(m); \ exit(EXIT_FAILURE); \ } while(0) void my_exit1(void) { printf("my exit1 ...\n"); } void my_exit2(void) { printf("my exit2 ...\n"); } int main(int argc, char *argv[]) { //下面是两次安装 atexit(my_exit1); atexit(my_exit2); _exit(0); }
可以看出终止处理程序的调用次序和安装次序正好相反,了解了解即可。
最后给出一个有意思的小程序,供大家分享一下父子进程的运行流程:
int main(int argc, char *argv[]) { fork(); fork(); fork(); printf("ok\n"); return 0; }
运行一下看看能打印多少个OK,自己动手画一下运行线图可以理解清楚。
答案是:8个。