进程编程中的fork起步
来源:互联网 发布:linux必备软件 编辑:程序博客网 时间:2024/06/14 22:15
进程编程中的fork起步
父子进程的关系
使用fork(或者其他能创建子进程的API)函数得到的子进程从父进程的继承了整个进程的地址空间,包括:进程上下文、进程堆栈、代码段、数据段、以及PCB和内存信息、打开的文件描述符、信号控制设置、进程优先级、进程组号、当前工作目录、根目录、资源限制、控制终端等。
子进程与父进程的区别在于:
(1)父进程设置的锁,子进程不继承
(2)PID和父进程PID不同
(3)子进程的未决警告被清除
(4)子进程的未决信号集设置为空集。
fork函数
- 头文件:
<sys/types.h> 和 <unistd.h>
- 函数功能:创建一个子进程
- 函数原型:pid_t fork(void);
- 返回值:
(1)成功创建一个子进程,对于父进程来说返回子进程ID
(2)成功创建一个子进程,对于子进程来说返回值为0
(3)如果为-1表示创建失败
- 示例代码
#include <stdio.h>#include <stdlib.h>#include <string.h>#include <unistd.h>#include <sys/types.h>#include <errno.h>#include <signal.h>int main(){ pid_t pid;//记录子进程的PID int tmp = 250;//测试子进程拷贝了父进程的堆栈段(临时变量存在于栈空间) //--但是拷贝以后,各自操作自己的堆栈,互不影响 //--意味着父子进程各有一个tmp signal(SIGCHLD,SIG_IGN);//用于处理僵尸进程 //--操作系统会通知父进程子进程结束了 //--使用该函数会让父进程忽略该通知 printf("before fork...pid: %d\n",getpid());//打印fork之前的进程PID--即父进程PID pid = fork();//创建子进程 if(pid == -1)//创建失败 { perror("fork"); return -1; } else if(pid > 0)//父进程从内核空间返回到用户空间--捕获到一个大于零的数--子进程的PID { tmp++;//在父进程的地址空间处理局部变量 printf("this is parent pid: %d\ttmp = %d\n",getpid(),tmp); //打印父进程的信息 } else if(0 == pid)//子进程从内核空间的就绪队列被调度开始执行--从内核空间返回到用户空间 { tmp++;//在子进程的地址空间处理局部变量--这里的tmp是基于fork之前从父进程栈空间拷贝的--原始值位250,已经与父进程的tmp无关 printf("this is child pid: %d\tparent : %d\ttmp = %d\n",getpid(),getppid(),tmp); //打印子进程的信息 } printf("after fork...pid: %d\n",getpid());//fork之后的代码会被拷贝到子进程,所以这条语句会被父子进程都执行 //--根据pid可以判断是父进程还是子进程执行的以及它们的执行顺序 return 0;}
几个需要理解的问题
fork系统调用之后,父子进程将交替执行。顺序是随机的!
一次调用2次返回?
问题的本质是:两次返回,是在各自的进程空间中返回的。子进程和父进程各有自己的内存空间/进程空间 (fork:代码段、数据段、堆栈段、PCB进程控制块的copy)。fork返回值大于零的是父进程,为什么这样设计?
父进程可能产生多个子进程,为了更好的管理控制子进程,父进程需要知道自己要控制哪一个子进程,用子进程的PID来给父进程识别子进程是最好的方法,所以返回一个大于零的数,作为子进程的PID,是为了方便父进程对子进程的管理。怎么样理解分支在fork之后,而不是父进程main函数的开始?
子进程在创建以后,就复制了父进程的堆栈段,而在fork之前,面入口后的这部分代码是对堆栈的处理和初始化!父进程已经做好了初始化的工作,子进程的堆栈要和父进程完全一样,所以子进程不必再进行重复的初始化工作!
难点分析
- fork循环
父进程中执行了n次fork将会产生2的n次方个进程(包括最开始的父进程),fork后面的代码将会呗每个进程都执行!这种情况下,父生子,子生孙,不好控制!有点像二叉树的关系!
#include <stdio.h>#include <stdlib.h>#include <string.h>#include <unistd.h>#include <sys/types.h>#include <errno.h>#include <signal.h>int main(){ int i = 0; for(i = 0;i < 3;i++)//父进程中执行了n次fork将会产生2的n次方个进程(包括最开始的父进程), //fork后面的代码将会呗每个进程都执行 { fork(); } printf("Aloha!Fork!\n"); return 0;}
- 一个父亲生出多个儿子但不允许生出孙子
#include <stdio.h>#include <stdlib.h>#include <string.h>#include <unistd.h>#include <sys/types.h>#include <errno.h>#include <signal.h>void Test(int var){ printf("Aloha!Fork()....%d\n",var);}int main(){ int loop_num = 0; int proc_num = 0; int i = 0,j = 0; pid_t pid ; signal(SIGCHLD,SIG_IGN); printf("Input the procnum:\n"); scanf("%d",&proc_num); printf("Input the loopnum:\n"); scanf("%d",&loop_num); for(i= 0; i < proc_num;i++) { pid = fork(); if(0 == pid) { printf("child : %d\tparent : %d\n",getpid(),getppid());//父进程如果退出以后,会将创建的子进程托孤给init进程--1号进程--父进程的PID就是1 for(j = 0;j < loop_num;j++) { Test(j); } exit(0);//一旦子进程完成任务立即结束子进程--避免子进程执行下一次循环的fork--避免子生孙 } } return 0;}
- 一个父亲只生出一个儿子,世代单传
#include <stdio.h>#include <stdlib.h>#include <string.h>#include <unistd.h>#include <sys/types.h>#include <errno.h>#include <signal.h>void Test(int var){ printf("Aloha!Fork()....%d\n",var);}int main(){ int loop_num = 0; int proc_num = 0; int i = 0,j = 0; pid_t pid ; printf("Input the procnum:\n"); scanf("%d",&proc_num); printf("Input the loopnum:\n"); scanf("%d",&loop_num); for(i= 0; i < proc_num;i++) { pid = fork(); if(0 == pid) { printf("child : %d\tparent : %d\n",getpid(),getppid());//父进程如果退出以后,会将创建的子进程托孤给init进程--1号进程--父进程的PID就是1 for(j = 0;j < loop_num;j++) { Test(j); } } else if(pid > 0) { exit(0);//一旦父进程创建完子进程,从内核空间返回用户空间就结束父进程--避免父进程执行下一次循环的fork--避免子多余的儿子--保证单传 }else if(pid == -1) { perror("fork"); return -1; } } return 0;}
- 父子进程共享文件
实际上,在创建子进程以后,文件描述符属于堆栈空间或者数据段内容,确实是从父进程复制到了子进程,也就是说确实存在两份文件描述符,而且互不相关!只不过两个文件描述符都指向磁盘上的同一份文件,所以看起来是共享文件描述符!类似于两个指针指向同一片内存空间!正是由于文件描述符是互不相关的两个变量,所以在父子进程需要分别关闭文件!不然有一个文件描述符还指向磁盘上的文件!这不安全!
#include <stdio.h>#include <stdlib.h>#include <string.h>#include <unistd.h>#include <sys/types.h>#include <errno.h>#include <signal.h>#include <fcntl.h>#include <sys/stat.h>int main(){ pid_t pid;//记录子进程的PID int fd ; signal(SIGCHLD,SIG_IGN);//用于处理僵尸进程 //--操作系统会通知父进程子进程结束了 //--使用该函数会让父进程忽略该通知 fd = open("test.txt",O_CREAT|O_RDWR,0755);//在创建子进程之前先打开文件--具备一个文件描述符 if(fd == -1) { perror("Open"); return -1; } printf("before fork...pid: %d\n",getpid());//打印fork之前的进程PID--即父进程PID pid = fork();//创建子进程 if(pid == -1)//创建失败 { perror("fork"); return -1; } else if(pid > 0)//父进程从内核空间返回到用户空间--捕获到一个大于零的数--子进程的PID { write(fd,"parent",6);//父进程向已打开的文件写入内容 printf("this is parent pid: %d\n",getpid()); //打印父进程的信息 close(fd); } else if(0 == pid)//子进程从内核空间的就绪队列被调度开始执行--从内核空间返回到用户空间 { write(fd,"child",5);//子进程向已打开的文件写入内容--如果该内容追加到父进程的内容之后--说明父子进程共享文件 printf("this is child pid: %d\tparent : %d\n",getpid(),getppid()); //打印子进程的信息 close(fd); } printf("after fork...pid: %d\n",getpid());//fork之后的代码会被拷贝到子进程,所以这条语句会被父子进程都执行 //--根据pid可以判断是父进程还是子进程执行的以及它们的执行顺序 return 0;}
写时复制简介
- 如果多个进程要读取它们自己的那部分资源的副本,那么复制是不必要的。–只读取就不进行复制!加快子进程创建速度!
- 每个进程只要保存一个指向这个资源的指针就可以了。–子进程如果不修改堆栈段和数据段内容,只需要有一个指针指向父进程相应的资源即可!并不是执行完毕fork之后立即复制数据段和堆栈段等资源!
- 如果一个进程要修改自己的那份资源的“副本”,那么就会复制那份资源。这就是写时复制的含义–子进程执行写操作就会拷贝资源,为了避免影响其他进程对自己资源的管理和访问嘛!
原因分析:加快速度,linux内核是段页式管理机制(因段管理从0开始),也可叫页式管理机制。只复制对应的页。缺页,在中断查询,再赋值。
0 0
- 进程编程中的fork起步
- Linux系统编程-----进程fork()
- 多进程编程中fork
- 进程中的连续两个fork
- 进程编程—fork,getpid,exit,_exit
- Linux进程控制编程之fork、wait
- 进程编程—fork,getpid,exit,_exit
- 进程编程—fork,getpid,exit
- 进程编程—fork,getpid,exit,_exit
- 【Linux编程】进程标识符与fork函数
- 多进程编程:fork()函数
- 防止僵尸进程的fork编程
- 多进程编程——fork()
- 面试题,进程中的两个fork
- 【计算机原理】进程中的fork函数
- Linux多进程中的fork与vfork
- fork()&&进程的创建与终止--多进程编程
- 小何讲进程: Linux进程控制编程 (fork、vfork)
- Vue2+WebPack2使用css/sass+postcss的autoprefixer
- nyoj1047欧几里得
- 关系型数据库和非关系型数据库区别
- 详解 MapReduce 在 Yarn 中的调度细节
- windows 的使用 —— 注册表(软件的安装和卸载)
- 进程编程中的fork起步
- xxx.so has text relocations. This is wasting memory and is a security risk. Please fix
- 蓝桥杯嵌入式USART调试
- Linux服务器JDK安装时出现的问题解决办法
- 操作系统与网络实现 之十九(己)
- 第一篇博客
- 对于C++函数指针的理解
- 弹棉花
- 【Android】【小工具】使用Random生成一个指定长度的随机整数列