目录
1.进程的创建
1.1fork函数
1.2fork创建子进程,OS做了什么?
1.3为什么要写实拷贝?
2.进程的终止
2.1进程终止,操作系统做了什么?
2.2进程常见的退出方式
2.3进程常见的退出方法
3.进程的等待
3.1为什么进行进程等待
3.2如何等待?
wait方法
waitpid方法
3.3测试代码
4.进程的替换
4.1概念以及原理
4.2 怎么做?
1.替换函数exec
2.测试代码
4.3为什么要有程序替换
1.场景需要
2.补充:为什么要创建子进程
学习目标:1.进程的创建 2.进程的终止 3.进程的等待 4.进程的替换
1.功能:在已有的进程下创建一个新的进程。新进程:子进程 , 原进程:父进程
2.引用的头文件:#include
3.函数:pid_t fork(void); 使用:pid_t id = fork();
4.返回值:创建成功:a.把子进程的id返回给父进程,b.把0返回给子进程
创建失败:返回-1
5.特点:
- 父子进程代码共享,数据各自开辟空间,私有一份(采用写时拷贝)
- 分配新的内存块和数据结构给子进程
- 将父进程部分数据结构内容拷贝给子进程(写时拷贝)
- 添加子进程到系统进程列表中
- fork返回,开始调度器调度
--因为有写实拷贝技术得存在, 所以, 父子进程得以彻底分离! 完成了进程独立性得技术保证
--写实拷贝是一种延时申请技术, 可以提高整机内存得使用率
1.用的时候,再给你分配,是高效使用内存的一种表现
2.OS 无法再代码执行前, 预知哪些空间会被访问
释放 进程申请的相关内核数据结构和对应的数据和代码(本质:释放系统资源)
a. 代码跑完, 结果正确
b. 代码跑完, 结果不正确 ---->main函数的返回值?有什么意义?
c. 代码没有跑完, 程序崩溃了
--正常终止(可以通过 echo $? 查看进程退出码):1.return(main函数内) 2.exit函数
--非正常终止:ctrl + c ,信号终止
exit函数:头文件:#include
函数: void exit(int status); _exit函数:头文件:#include
函数: void _exit(int status); 参数:status 定义了进程的终止状态,父进程通过wait来获取该值
exit会执行用户定义的清理函数, 然后刷新缓冲, 关闭流等, 然后再退出
_exit直接退出
1.处理僵尸进程,解决内存泄漏的问题
2.父进程可以通过等待知道派给子进程的任务完成的状况:正确与否,是否异常
3.父进程通过等待,回收子进程资源,获取子进程退出信息
1.头文件
#include
#include
函数:pid_t wait(int*status);
2.返回值:成功返回被等待进程pid,失败返回-1。
3.参数:输出型参数,获取子进程退出状态,不关心则可以设置成为NULL
pid_ t waitpid(pid_t pid, int *status, int options);
1.返回值:
当正常返回的时候waitpid返回收集到的子进程的进程ID;
如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;
2.参数:--.pid:
Pid=-1,等待任一个子进程。与wait等效。
Pid>0.等待其进程ID与pid相等的子进程。
--.status:获取信号(即低7位):status & 0x7F
WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
--.options:默认为0:阻塞等待
WNOHANG(非阻塞等待):若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID。
1.阻塞式等待
阻塞式等待,即把waitpid中的参数中options设为0
代码:
1 #include
2 #include 3 #include //wait的头文件 4 #include //fork的头文件 5 #include 6 7 int main() 8 { 9 pid_t id = fork(); //创建子进程 10 if(id == 0) 11 { 12 //子进程 13 int cnt = 5; 14 while(cnt) 15 { 16 printf("我是子进程: %d\n",cnt--); 17 sleep(1); 18 } 19 exit(11); 20 } 21 else 22 { 23 //父进程 24 int status = 0; 25 pid_t ret = waitpid(id,&status,0);//默认是阻塞式等待子进程 26 if(ret >0) 27 { 28 if(WIFEXITED(status)) 29 printf("父进程等待成功,退出码: %d\n",WEXITSTATUS(status)); 30 else 31 printf("子进程异常退出: %d\n",WIFEXITED(status)); 32 } 33 } 34 return 0; 35 } 效果:
2.非阻塞式等待
非阻塞式等待:即把waitpid中的参数中options设为WNOHANG
我们想要父进程在等待的过程中干点别的事情:
1.这里我们定义了一个类型为函数指针的vector
2.定义了两个函数
3.定义了一个load函数,用来往实例化的vector中填充函数指针
4.当我们waitpid返回0,表示等待成功,但子进程还没有退出,若vector不为空,往里加载函数指针,让父进程调用这些函数(用来模拟做其它的事情)
注意:这里使用了范围for去遍历vector,是C++11里面的,若是C98编译可能会出错
使用下面这段代码:g++ -std=c++11 -0 myproc myproc.cc
生成的执行文件 编译的文件
代码:
1 #include
2 #include 3 #include 4 #include 5 #include //wait的头文件 6 #include //fork的头文件 7 #include 8 9 typedef void (*handler_t)();//函数指针类型 10 std :: vector handlers;//函数指针数组 11 12 void func_one() 13 { 14 printf("这是第一个临时任务\n"); 15 } 16 17 void func_two() 18 { 19 printf("这是第二个临时任务\n"); 20 } 21 22 void load() 23 { 24 handlers.push_back(func_one); 25 handlers.push_back(func_two); 26 } 27 28 int main() 29 { 30 pid_t id = fork(); //创建子进程 31 if(id == 0) 32 { 33 //子进程 34 int cnt = 5; 35 while(cnt) 36 { 37 printf("我是子进程: %d\n",cnt--); 38 sleep(1); 39 } 40 exit(11); 41 } 42 else 43 { 44 int quit = 0; 45 while(!quit) 46 { 47 int status = 0; 48 pid_t ret = waitpid(id,&status,WNOHANG); 49 50 if(ret > 0) 51 { 52 quit = 1; 53 printf("等待成功,退出码: %d\n",WEXITSTATUS(status)); 54 } 55 else if(ret == 0) 56 { 57 printf("等待成功,但子进程还没有退出,父进程可以做其它的事情\n"); 58 if(handlers.empty()) load(); 59 for(auto& iter:handlers) 60 { 61 iter(); 62 } 63 } 64 else 65 { 66 //等待失败 67 printf("等待失败\n"); 68 quit = 1; 69 } 70 sleep(1); 71 } 72 } 73 74 return 0; 75 } 效果:
1.概念
程序替换:是通过特定的接口,加载磁盘上一个全新的程序(代码+数据)
2.原理
#include
`//头文件 1.函数
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 envp[]);
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[]);
2.命名解释
l(list) : 表示参数采用列表
v(vector) : 参数用数组
p(path) : 有p自动搜索环境变量PATH
e(env) : 表示自己维护环境变量3.返回值
这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回。
如果调用出错则返回-1
所以exec函数只有出错的返回值而没有成功的返回值(这是因为exec函数是进程的替换,调用该函数成功之后,会将当前进程的所有代码和数据都进行替换! 包括已执行的和没有执行的!)
加载 , 所谓的exec*函数, 本质就是如何加载程序的函数
1.int execl(const char *path, const char *arg, ...);
代码:
1 #include
2 #include 3 #include 4 #include 5 #include 6 7 8 const char* path = "/usr/bin/ls"; 9 10 int main() 11 { 12 pid_t id = fork();//创建子进程 13 if(id == 0) 14 { 15 printf("我是子进程\n\n"); 16 execl(path,"ls","-a","-l",NULL);//调用exec函数执行其它程序 17 18 exit(1); 19 } 20 else 21 { 22 int status = 0; 23 pid_t ret = waitpid(id,&status,0);//阻塞式等待 24 25 if(ret > 0) 26 { 27 printf("\n我是父进程,等待成功,退出码:%d\n",WEXITSTATUS(status)); 28 } 29 } 30 31 32 return 0; 33 } 效果:
2.int execlp(const char *file, const char *arg, ...);
和上面基本一致,就是可以不用写绝对路径,path:环境变量,OS能直接找到
3.int execv(const char *path, char *const argv[]);
同上,只用把要执行的命令写入字符指针数组内,然后传递这个数组就行(最后一个参数必须是NULL)
4.int execvp(const char *file, char *const argv[]);
同上不用带绝对路径
5.int execle(const char *path, const char *arg, ...,char *const envp[]);
这个可以给其它程序传递环境变量
--调用execle函数
--mycmd.c 可以获取val这个环境变量
--效果:
一定和应用场景有关, 我们有时候, 必须让子进程执行新的程序 !!!
例如:1.如何执行其他或我自己写的C. C++二进制程序2.如何执行其它语言的程序
1.如何执行其他或我自己写的C. C++二进制程序
--makefile:
--mycmd.c
--exec.c
--效果:
2.如何执行其它语言的程序
--效果:
为什么要创建子进程
1.如果不创建, 那么进程替换的就是父进程,创建了,替换的就是子进程,而不影响父进程(进程的独立性,且加载属于改写了,由于写时拷贝的特点,会在物理内存上开辟一块新的内存,把新的代码加载到上面,并通过页表,改变映射关系)
2.我们想要父进程聚焦于读取数据,解析数据,指派进程进程执行代码的功能
(父进程读取分析数据,子进程执行代码的功能)