创建子进程
如何创建一个进程:当我们执行一个可执行文件时,便会创建一个进程。
如何创建一个子进程:
pid_t fork(void);
返回值:失败返回-1 成功:父进程返回子进程的pid,子进程返回0
注:不是fork能够返回两个值,是在父子进程中返回的值不同。
那如何做到的呢?我们看下fork()创建子进程做了哪些事情。(我们前面是不是说过每一进程都会有自己的一个虚拟地址空间)
循环创建子进程:
先想一个问题:最后有几个进程????
for(int i=0;i<3;i++){ fork(); }
卧槽,8个进程。。。。。。。2^n个进程
我们是不是不让子进程去创建子进程呀,不让子进程去fork()
fork()不是在子进程和父进程中的返回值不一样嘛?那我们不就可以通过 if 来控制嘛!
for(int i=0;i<3;i++){ pid_t pid = fork(); if(pid == 0){ break; } }
进程共享(重点 重点 重点)
说起探讨进程共享,其实就是fork()之后,父子进程之间的相同与不同。
fork()会把父进程的虚拟地址空间复制一份给子进程(可以理解为深拷贝哦),因为每一个进程都有自己的虚拟地址空间。但是每一个进程的ID都不一样,所以不是简单的复制。
父子进程相同之处:(用户区基本一样、内核区大部分一样)
全局变量(.data .bss) .text 栈 堆 环境变量 用户ID 宿主目录 进程工作目录 信号处理方式。
父子进程的不同之处:进程ID fork()返回值 父进程ID 进程运行时间 闹钟 未决信号集
总结:似乎,子进程复制了父进程0-3G用户空间内容,以及父进程的PCB,但是pid不同。真的fork()一个子进程都要复制一个父进程的嘛,然后映射到物理内存。。。。
父子进程之间的全局变量是共享的嘛??
父子进程之间的全局变量当然不是共享的,因为它们处在不同的虚拟地址空间中。
为了节省系统开销,父子进程之间采用读时共享写时复制的机制。也就是说,只有读操作的时候,父子之间共享同一块物理内存(也就是虚拟地址空间映射的那块),但是一旦有写操作了,就会进行复制物理内存。
父子之间共享(非常重要,进程间通信的基础):
文件描述符表 mmap建立的映射区
特别的:父子进程之间谁先执行,谁后执行是根据内核中调度算法决定。
多进程GDB调试(-g 能够将二进制指令对应的源代码对应上,GDB调试的基础)
使用GDB调试的时候,GDB默认只能跟踪一个进程,可以在fork()函数调用之前,通过指令设置GDB调试工具跟踪父进程或者跟踪子进程。如果不设置,默认跟踪父进程。
设置调试父进程还是子进程:
set follow-fork-mode parent/child
设置调试模式:
set detach-on-fork on/off
默认认为on,表示调试当前的时候,其它进程继续执行。
off,调试当前进程的时候,其它进程被GDB挂起。
查看调试的进程:info inferiors
切换当前调试的进程: inferios id
使进程脱离GDB调试:defach inferiors id
gdb 8版本的好像多进程调试有问题
exec函数族
exec并没有创建新的进程,主要更换用区中的数据,内核区几乎没有变化。进程还是那个进程。
exec函数族的作用是根据指定的文件名找到可执行文件,并用它来替换调用进程的内容,换句话说,就是子啊调用进程内部执行一个可执行文件。
exec函数族的函数执行成功后是不会有返回值的,因为调用进程的实体,包括代码段,数据段和堆栈等都已经被新的内容取代,只留下进程ID等一些表面上的信息,仍保持一样。所以就算我们判断成功也毫无意义,因为那条if不存在了。所以通常我们直接在exec函数调用后直接perror和exit,无需if
失败返回-1
exec函数族的一般规律:
l(list) 命令行参数列表
p(path) 搜索file时使用path
v 使用命令行参数数组
e 使用环境变量数组
int execl(const char*path,const char *arg...); int execlp(const char *file,const char *arg...); int execle(const char *path,const char *arg,...,char *const encp[]); int execv(const char *path,char *const argv[]); int execvp(const char *file,char *const argv[]); int execve(const char *path,char *const argv[],char *const envp[]); int execrpe(const char *file,char *const arg[],char *const envp[]);
int execl(const char*path,const char *arg...);
-参数
path:需要指定执行的文件路径或者名称
arg:可执行文件所需的命令行参数列表,第一个参数一般没有什么作用,为了方便写的是执行的程序名字,从第二个参数开始往后,就是程序执行所需的参数列表。参数列表最后需要以NULL结束(哨兵)。
-返回值
只有当调用失败,才会有返回值,返回-1,并且设置error。
如果调用成功,没有返回值。
#include <stdio.h> #include <sys/types.h> #include <unistd.h> #include <stdlib.h> int main(){ //创建一个子进程 pid_t pid = fork(); if(pid == -1){ perror("fork"); exit(0); } if(pid > 0){//父进程 printf("I am parent,id:%d\n",getpid()); }else if(pid == 0){//子进程 printf("I am child,id:%d\n",getpid()); execl("/home/chen/MyWorkingSpace/exec_test/b","b",NULL);//规范:写绝对路径,避免切换了工作目录就无法运行 perror("execl"); exit(0); } return 0; }
int execlp(const char *file,const char *arg...);
-会在path中查找,通常用来执行shell命令
#include <stdio.h> #include <sys/types.h> #include <unistd.h> #include <stdlib.h> int main(){ pid_t pid = fork(); if(pid == -1){ return 0; } if(pid > 0){ printf("I am parent,id:%d\n",getpid()); }else if(pid == 0){ printf("I am child,id:%d\n",getpid()); execlp("ls","ls","-la",NULL); } return 0; }
其它几个基本用不上,用法都差不多。
进程退出
#include <stdlib.h> void exit(int status); ================================================== #include <unistd.h> void _exit(int status); ================================================== status:是进程退出时的一个状态信息。父进程回收子进程资源的时候可以获取到(witpid)
一个时系统函数 一个是C库函数