9.fork函数和父子进程共享文件
2016-07-27 18:19
423 查看
9.1.程序的开始和结束
(1)编译链接时叠加的引导代码;操作系统下的应用程序需要先执行一段引导代码才能去执行main函数,我们编写应用程序时无需考虑引导代码问题;程序在编译连接时使用链接器,交叉编译工具链在编译链接时由链接器将编译器中事先准备好的引导代码给链接进去和我们的应用程序一起构成最终的可执行程序。
(2)程序运行时的加载器;程序在运行时使用加载器,加载器是操作系统中的程序,当我们去执行某个程序时(./a.out或代码中使用exec族函数),加载器负责将该程序加载到内存中去执行。
(3)argc和argv的实现细节;譬如说在shell命令行下运行./a.out(argc==2,argv[0]==”./a.out”),当前shell进程接收到”./a.out”参数后将该参数传递给加载器,加载器在加载运行a.out程序时会将参数传递给该程序的引导代码,引导代码在合适的时候会将该参数传递给该程序的main函数。
(4)程序结束分为正常终止和非正常终止;正常终止是return/exit/_exit;非正常终止是自己或它人发信号终止进程;我们可使用atexit函数注册进程终止处理函数,在进程终止时会自动运行该进程终止处理函数,atexit也可注册多个进程终止处理函数,先注册的后执行,后注册的先执行,与栈的堆栈和出栈类似;return和exit终止进程时效果相同都会执行进程终止处理函数,但用_exit终止进程时并不执行atexit注册的进程终止处理函数。
9.2.进程运行环境
(1)进程运行的环境变量;在shell中可通过export命令查看当前shell进程的所有环境变量;进程环境表介绍,每个进程中都有1份由所有环境变量构成的表,在当前进程中我们可以直接使用这些环境变量;进程环境表即1个字符串数组,通过environ二重指针变量指向它,在程序中我们可通过environ全局变量使用该进程中所有的环境变量;一旦程序中用到了环境变量那么程序就和操作系统环境有关了,getenv函数可获取指定环境变量。
(2)进程运行的虚拟地址空间;操作系统中每个进程在独立地址空间中运行;操作系统分配给每个进程的逻辑地址空间均为4GB(32位系统,2-3G为OS,0-2G为应用);操作系统实现虚拟地址到物理地址空间的映射(即MMU)的意义;进程隔离(各个进程之间彼此互不干扰,保证安全性);提供多进程同时运行(所有应用程序都从虚拟地址0开始开始运行,链接地址和运行地址都设置为虚拟地址0,然后操作系统的MMU模块会将虚拟地址0转换成物理地址,即保证了程序的运行地址和链接地址相同)(MMU的细节建议参考《图解Linux操作系统架构设计与实现》新设计团队)。
9.3.进程的正式引入
(1)进程是动态过程而不是静态实物,进程即程序的1次运行过程;1个静态的可执行程序a.out的1次运行过程就是1个进程;进程的生命周期是开始执行该程序到该程序执行完毕这段时间;进程控制块PCB(process-control-block)即内核中专门用来管理某个进程的数据结构。
(2)进程ID的作用是唯一标识某个进程;ps-a(打印输出现行终端机下的所有程序);ps-aux(打印输出操作系统所有的进程);ps-aux|grep”1990”(在操作系统的所有进程中查找进程ID为1990的进程);getpid(获取当前进程的ID)、getppid(获取当前进程的父进程的ID)、getuid(获取当前进程的用户ID,譬如root用户ID是0)、geteuid(获取当前进程的有效用户ID)、getgid(获取当前进程的用户所在的组ID)、getegid(获取当前进程的用户所在的有效组ID);实际用户ID和有效用户ID区别可百度之。
(3)多进程调度原理;操作系统被设计为同时运行多个进程;多个进程同时运行造成宏观上的并行和微观上的串行;实际上现代操作系统最小的调度单元是线程而不是进程。
9.4.fork创建子进程
(1)操作系统运行某个程序需要付出代价,即操作系统需要创建新进程,然后把该程序加载到进程里去运行,则每次程序的运行都需要1个进程;操作系统维护某个进程的运行需要耗费的原材料是PCB(进程控制块结构体)。
(2)fork的内部原理;进程的分裂生长模式,若操作系统需要1个新进程来运行1个程序,则操作系统会利用1个现有的进程来复制生成1个新进程;老进程叫父进程,复制生成的新进程叫子进程。
(3)fork函数调用1次会返回2次,返回值等于0的就是子进程,而返回值大于0的就是父进程;当fork函数成功创建1个子进程并返回后,进程调度表中会有当前的父子两个进程处于轮番调度,即父进程和子进程fork之后的代码段完全相同。
(4)典型的使用fork的方法即使用fork后然后用if判断返回值,返回值大于0时即父进程执行入口,返回值等于0时即子进程执行入口;fork的返回值在子进程中等于0,在父进程中等于本次fork创建的子进程的进程ID。
(5)子进程和父进程相互独立,互不依赖;子进程有自己独立的PCB;子进程被内核同等调度。
9.5.父子进程对文件的操作
(1)子进程继承父进程中打开的文件;父进程先open打开1个文件得到fd,然后再fork创建子进程,之后在父子进程中各自write向fd中写入内容;测试结论是接续写,实际上本质原因是父子进程之间的fd对应的文件指针是彼此关联。
(2)父子进程各自独立打开相同文件实现共享;父进程open打开test.txt然后写入,子进程打开test.txt然后写入,测试结论是分别写,原因是父子进程分离后才各自打开的test.txt,这时候该两个进程的PCB已经独立了,文件表也独立了,则2次读写是完全独立的。
(3)open时使用O_APPEND标志;实际测试结果标明O_APPEND标志可以把父子进程各自独立打开的fd的文件指针给关联起来,实现分别写。
(4)父子进程间终究多了一些牵绊;父进程在没有fork之前自己做的事情对子进程有很大影响,但是父进程fork之后在自己的if里做的事情就对子进程没有影响了,本质原因就是因为fork内部实际上已经复制父进程的PCB生成了1个新的子进程,并且fork返回时子进程已经完全和父进程脱离并且独立被OS调度执行;子进程最终目的是要独立去运行另外的程序。
(1)编译链接时叠加的引导代码;操作系统下的应用程序需要先执行一段引导代码才能去执行main函数,我们编写应用程序时无需考虑引导代码问题;程序在编译连接时使用链接器,交叉编译工具链在编译链接时由链接器将编译器中事先准备好的引导代码给链接进去和我们的应用程序一起构成最终的可执行程序。
(2)程序运行时的加载器;程序在运行时使用加载器,加载器是操作系统中的程序,当我们去执行某个程序时(./a.out或代码中使用exec族函数),加载器负责将该程序加载到内存中去执行。
(3)argc和argv的实现细节;譬如说在shell命令行下运行./a.out(argc==2,argv[0]==”./a.out”),当前shell进程接收到”./a.out”参数后将该参数传递给加载器,加载器在加载运行a.out程序时会将参数传递给该程序的引导代码,引导代码在合适的时候会将该参数传递给该程序的main函数。
(4)程序结束分为正常终止和非正常终止;正常终止是return/exit/_exit;非正常终止是自己或它人发信号终止进程;我们可使用atexit函数注册进程终止处理函数,在进程终止时会自动运行该进程终止处理函数,atexit也可注册多个进程终止处理函数,先注册的后执行,后注册的先执行,与栈的堆栈和出栈类似;return和exit终止进程时效果相同都会执行进程终止处理函数,但用_exit终止进程时并不执行atexit注册的进程终止处理函数。
9.2.进程运行环境
(1)进程运行的环境变量;在shell中可通过export命令查看当前shell进程的所有环境变量;进程环境表介绍,每个进程中都有1份由所有环境变量构成的表,在当前进程中我们可以直接使用这些环境变量;进程环境表即1个字符串数组,通过environ二重指针变量指向它,在程序中我们可通过environ全局变量使用该进程中所有的环境变量;一旦程序中用到了环境变量那么程序就和操作系统环境有关了,getenv函数可获取指定环境变量。
(2)进程运行的虚拟地址空间;操作系统中每个进程在独立地址空间中运行;操作系统分配给每个进程的逻辑地址空间均为4GB(32位系统,2-3G为OS,0-2G为应用);操作系统实现虚拟地址到物理地址空间的映射(即MMU)的意义;进程隔离(各个进程之间彼此互不干扰,保证安全性);提供多进程同时运行(所有应用程序都从虚拟地址0开始开始运行,链接地址和运行地址都设置为虚拟地址0,然后操作系统的MMU模块会将虚拟地址0转换成物理地址,即保证了程序的运行地址和链接地址相同)(MMU的细节建议参考《图解Linux操作系统架构设计与实现》新设计团队)。
9.3.进程的正式引入
(1)进程是动态过程而不是静态实物,进程即程序的1次运行过程;1个静态的可执行程序a.out的1次运行过程就是1个进程;进程的生命周期是开始执行该程序到该程序执行完毕这段时间;进程控制块PCB(process-control-block)即内核中专门用来管理某个进程的数据结构。
(2)进程ID的作用是唯一标识某个进程;ps-a(打印输出现行终端机下的所有程序);ps-aux(打印输出操作系统所有的进程);ps-aux|grep”1990”(在操作系统的所有进程中查找进程ID为1990的进程);getpid(获取当前进程的ID)、getppid(获取当前进程的父进程的ID)、getuid(获取当前进程的用户ID,譬如root用户ID是0)、geteuid(获取当前进程的有效用户ID)、getgid(获取当前进程的用户所在的组ID)、getegid(获取当前进程的用户所在的有效组ID);实际用户ID和有效用户ID区别可百度之。
(3)多进程调度原理;操作系统被设计为同时运行多个进程;多个进程同时运行造成宏观上的并行和微观上的串行;实际上现代操作系统最小的调度单元是线程而不是进程。
9.4.fork创建子进程
(1)操作系统运行某个程序需要付出代价,即操作系统需要创建新进程,然后把该程序加载到进程里去运行,则每次程序的运行都需要1个进程;操作系统维护某个进程的运行需要耗费的原材料是PCB(进程控制块结构体)。
(2)fork的内部原理;进程的分裂生长模式,若操作系统需要1个新进程来运行1个程序,则操作系统会利用1个现有的进程来复制生成1个新进程;老进程叫父进程,复制生成的新进程叫子进程。
(3)fork函数调用1次会返回2次,返回值等于0的就是子进程,而返回值大于0的就是父进程;当fork函数成功创建1个子进程并返回后,进程调度表中会有当前的父子两个进程处于轮番调度,即父进程和子进程fork之后的代码段完全相同。
(4)典型的使用fork的方法即使用fork后然后用if判断返回值,返回值大于0时即父进程执行入口,返回值等于0时即子进程执行入口;fork的返回值在子进程中等于0,在父进程中等于本次fork创建的子进程的进程ID。
(5)子进程和父进程相互独立,互不依赖;子进程有自己独立的PCB;子进程被内核同等调度。
9.5.父子进程对文件的操作
(1)子进程继承父进程中打开的文件;父进程先open打开1个文件得到fd,然后再fork创建子进程,之后在父子进程中各自write向fd中写入内容;测试结论是接续写,实际上本质原因是父子进程之间的fd对应的文件指针是彼此关联。
(2)父子进程各自独立打开相同文件实现共享;父进程open打开test.txt然后写入,子进程打开test.txt然后写入,测试结论是分别写,原因是父子进程分离后才各自打开的test.txt,这时候该两个进程的PCB已经独立了,文件表也独立了,则2次读写是完全独立的。
(3)open时使用O_APPEND标志;实际测试结果标明O_APPEND标志可以把父子进程各自独立打开的fd的文件指针给关联起来,实现分别写。
(4)父子进程间终究多了一些牵绊;父进程在没有fork之前自己做的事情对子进程有很大影响,但是父进程fork之后在自己的if里做的事情就对子进程没有影响了,本质原因就是因为fork内部实际上已经复制父进程的PCB生成了1个新的子进程,并且fork返回时子进程已经完全和父进程脱离并且独立被OS调度执行;子进程最终目的是要独立去运行另外的程序。
9.atexit /* * 公司:XXXX * 作者:Rston * 博客:http://blog.csdn.net/rston * GitHub:https://github.com/rston * 项目:fork函数和父子进程共享文件 * 功能:使用atexit函数注册进程终止处理函数。 */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> // 进程终止处理函数1 void exit_func1(void) { printf("Is in exit_func1.\n"); } // 进程终止处理函数2 void exit_func2(void) { printf("Is in exit_func2.\n"); } int main(int argc, char **argv) { int ret = -1; printf("hello world.\n"); ret = atexit(exit_func1); if (ret != 0) { printf("atexit exit_func1 error.\n"); exit(-1); } ret = atexit(exit_func2); if (ret != 0) { printf("atexit exit_func2 error.\n"); exit(-1); } printf("my name is Rston.\n"); #if 1 return 0; // 终止进程时执行atexit注册的进程终止处理函数 #endif #if 0 exit(0); // 终止进程时执行atexit注册的进程终止处理函数 #endif #if 0 _exit(0); // 终止进程时并不执行atexit注册的进程终止处理函数 #endif }
9.environ /* * 公司:XXXX * 作者:Rston * 博客:http://blog.csdn.net/rston * GitHub:https://github.com/rston * 项目:fork函数和父子进程共享文件 * 功能:在程序中使用environ变量查看进程中的所有环境变量。 */ #include <stdio.h> extern char **environ; // 声明environ变量 int main(int argc, char **argv) { int i = 0; for (i=0; environ[i]!=NULL; i++) { printf("%s.\n", environ[i]); } return 0; }
9.getpid_getppid /* * 公司:XXXX * 作者:Rston * 博客:http://blog.csdn.net/rston * GitHub:https://github.com/rston * 项目:fork函数和父子进程共享文件 * 功能:在程序中使用getpid和getppid获取进程ID。 */ #include <stdio.h> #include <unistd.h> int main(int argc, char **argv) { pid_t id = -1; id = getpid(); // 获取当前进程的ID printf("getpid id = %d.\n", id); id = getppid(); printf("getppid id = %d.\n", id); // 获取当前进程的父进程的ID return 0; }
9.fork /* * 公司:XXXX * 作者:Rston * 博客:http://blog.csdn.net/rston * GitHub:https://github.com/rston * 项目:fork函数和父子进程共享文件 * 功能:使用fork函数创建子进程。 */ #include <sys/types.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> int main(int argc, char **argv) { pid_t ret = -1; ret = fork(); if (0 == ret) { // 子进程入口 printf("my name is child.\n"); printf("child pid = %d.\n", getpid()); } if (ret > 0) { // 父进程入口 printf("my name is parent.\n"); printf("parent pid = %d.\n", getpid()); } if (ret < 0) { // fork错误 perror("fork error"); exit(-1); } return 0; }
9.file_share /* * 公司:XXXX * 作者:Rston * 博客:http://blog.csdn.net/rston * GitHub:https://github.com/rston * 项目:fork函数和父子进程共享文件 * 功能:演示父子进程共享文件。 */ #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #define PATHNAME "test.txt" int main(int argc, char **argv) { int fd = -1; pid_t pid = -1; #if 1 // 子进程继承父进程中打开的文件 // 打开test.txt文件,若该文件不存在则创建(权限666),若该文件存在则截断 fd = open(PATHNAME, O_RDWR | O_CREAT | O_TRUNC, 0666); if (fd < 0) { perror("open error"); exit(-1); } pid = fork(); if (pid > 0) { // 父进程入口 printf("parent.\n"); write(fd, "hello", strlen("hello")); sleep(1); // 交出CPU控制权,让CPU调度子进程 } else if (0 == pid) { // 子进程入口 printf("child.\n"); write(fd, "world", strlen("world")); sleep(1); // 交出CPU控制权,让CPU调度父进程 } else { perror("fork error"); exit(-1); } close(fd); #endif #if 0 // 父子进程各自独立打开相同文件实现共享 pid = fork(); if (pid > 0) { // 父进程入口 #if 1 fd = open(PATHNAME, O_RDWR); // 分别写 #endif #if 0 fd = open(PATHNAME, O_RDWR | O_APPEND); // 接续写 #endif if (fd < 0) { perror("open error"); exit(-1); } printf("parent.\n"); write(fd, "hello", strlen("hello")); sleep(1); } else if (0 == pid) { // 子进程入口 #if 1 fd = open(PATHNAME, O_RDWR); // 分别写 #endif #if 0 fd = open(PATHNAME, O_RDWR | O_APPEND); // 接续写 #endif if (fd < 0) { perror("open error"); exit(-1); } printf("child.\n"); write(fd, "world", strlen("world")); sleep(1); // 交出CPU控制权,让CPU调度父进程 } else { perror("fork error"); exit(-1); } close(fd); #endif return 0; }
相关文章推荐
- 浅析结束程序函数exit, _exit,atexit的区别
- 举例讲解C语言的fork()函数创建子进程的用法
- Linux下C语言的fork()子进程函数用法及相关问题解析
- 简单掌握Linux系统中fork()函数创建子进程的用法
- C语言的fork函数在Linux中的进程操作及相关面试题讲解
- 使用C语言的fork()函数在Linux中创建进程的实例讲解
- 浅谈Linux环境下并发编程中C语言fork()函数的使用
- exit和atexit的区别详细解析
- Linux中使用C语言的fork()函数创建子进程的实例教程
- git fork同步是什么意思?
- Linux系统中C语言编程创建函数fork()执行解析
- python创建进程fork用法
- 小结Python用fork来创建子进程注意事项
- Python中atexit模块的基本使用示例
- Python中的进程分支fork和exec详解
- fork, exec, source
- 使用 GDB 调试多进程程序
- Android架构纵横谈之二——基于性能的考虑
- linux下面fork 函数的详细讲解
- fork的执行过程