重点:
- 进程创建,fork/vfork
- 进程等待
- 进程替换,微型shell,重新认识shell运行原理
- 进程终止,认识$
一、进程创建:
fork函数: 从已存在进程中创建一个新进程。新进程为子进程,原进程为父进程。
2、 进程调用fork,当控制到内核中的fork代码后,内核做:
分配新的内存块和内核数据结构给子进程
将父进程部分数据结构内容拷贝至子进程
添加子进程到系统进程列表当中
fork返回,开始调度器调度
3、写时拷贝
通常,父子代码共享,父子再不写入时,数据也是共享的。
4、fork常规用法
- 一个父进程希望复制自己,使父子进程同时执行不同的代码段。例如:父进程等待客户端请求,生成子进程来处理请求。
- 一个进程要执行一个不同的程序。例如进程从fork返回后,调用exec函数。
5、进程调用失败的原因
资源不够用
1、系统中有太多进程
2、实际用户的进程数超过了限制
6、vfork
也是用来创建子进程,但是 vfork用于创建一个子进程,而
子进程和父进程共享地址空间
,fork的子进程具有独立地址空间。
vfork
图解:
代码验证结果:
父子进程地址一样得原因?
物理内存地址不一样,但其对应的虚拟内存地址一样
fork
图解:
代码验证结果:
vfork保证子进程先运行
,在它调用exec或(exit)之后父进程才可能被调度运行。
代码:
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<unistd.h>
4 int g_val=100;
5 int main()
6 {
7 pid_t id=vfork();
8 if(id==0){//child
9 // g_val=200;
10 sleep(5);
11 printf("I am child,%d, %p\n",g_val,&g_val);
12 exit(0);
13 }
14 else if(id>0)
15 {//parent
16 sleep(2);
17 printf("I am parent,%d,%p\n",g_val,&g_val);
18 }
19 else{
20 perror("vfork");
21 exit(1);
22 }
23 return 0;
}
不加exit(0),会出现死循环。为什么?
怎么验证子进程在父进程的地址空间中运行?
代码:
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<unistd.h>
4 int g_val=100;
5 int main()
6 {
7 pid_t id=vfork();
8 if(id==0){//child
9 g_val=200;
10 sleep(5);
11 printf("I am child,%d, %p\n",g_val,&g_val);
12 exit(0);
13 }
14 else if(id>0)
15 {//parent
16 sleep(2);
17 printf("I am parent,%d,%p\n",g_val,&g_val);
18 }
19 else{
20 perror("vfork");
21 exit(1);
22 }
23 return 0;
}
代码运行结果:
总结:
子进程直接改变了父进程的变量值,因为子进程在父进程的地址空间中运行。
二、进程终止:
进程常见退出方法
正常终止(可以通过 echo $? 查看进程退出码):
1、从main返回
2、调用exit
3、_exit
异常退出:
- ctrl + c,信号终止
进程的退出码?
main函数中return的返回值称为程序的退出码,想通过退出码来知道程序运行正确与否。
在linux下如何获得程序的退出码?
$+? 称之为程序的退出码
No such file or directory
程序执行完了,但是结果不对,因为它的退出码不对,退出码就转化成为错误消息。
1、正常退出
1)return退出:
main函数return和其他函数return有啥区别?
其他函数return表明该函数退出,main函数还表示进程结束,因此要使进程结束必须在main函数中return。
测试:
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<unistd.h>
4 int fun()
5 {
6 return 32;
7 }
8 int main()
9 {
10 fun();
11
12 }
测试结果:
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<unistd.h>
4 int fun()
5 {
6 return 32;
7 }
8 int main()
9 {
10 fun();
11 return 0;
12 }
测试结果:
事实证明main函数的返回值是进程结束的标志,其他函数只表示此函数的退出码
2)exit
总结:
在函数的任何地方调用exit都会导致进程退出
在main函数return才会导致进程退出
在程序结束时的数字叫做程序的退出码
3)_exit
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<unistd.h>
4 int main()
5 {
6 printf("hello world");
7 sleep(3);
8 exit(12);
9 //return 12;
10 }
exit 和return 在程序退出时都会刷新缓冲区,输出hello world
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<unistd.h>
4 int main()
5 {
6 printf("hello world");
7 sleep(3);
8 _exit(12);
9
10 }
_exit不会刷新缓冲区,不会输出hello world
进程调用exit时,系统要做哪些工作?
数据和代码都要清理,所对应的PCB数据结构里面的内容也要清理。
2、异常退出:
在任意位置退出称之为异常退出
三、进程等待:
1、存在意义:
1)子进程退出,父进程若不管,就造成僵尸进程,造成内存泄漏
2)僵尸进程无法杀死
3)父进程给子进程的任务完成得如何。
4)父进程通过进程等待的方式,回收进程资源,获取子进程退出信息。
2、方法
1)wait
#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int *status);
- 返回值:成功返回被等待进程pid,失败返回-1.
- 参数:输出型参数,获取子进程退出状态,不关心则可设置为NULL
例;
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<unistd.h>
4 #include<sys/types.h>
5 #include<sys/wait.h>
6 int main()
7 {
8 pid_t id=fork();
9 if(id==-1){
10 perror("fork");
11 exit(1);
12 }
13 else if(id==0){//子进程
14 printf("I am child: I am pid: %d, I am ppid:% d ",getpid(),getppid());
15 sleep(3);
16 printf("child is done!\n");
17 exit(1);
18 }
19 else{//父进程
20 printf("I am parent: I am pid:%d,I am ppid :%d ",getpid(),getppid());
21 pid_t id=wait(NULL);
printf("father is done!\n");
23 }
24
25 return 0;
26
27 }
加上 pid_t id=wait(NULL)之前:
分析: 由于sleep(3)秒,所以子进程存活时间较长,等待父进程结束后,子进程才结束
。
之后:
由于wait的缘故,父进程必须等待子进程结束后,才能结束
2)waitpid方法
pid_t waitpid(pid_t pid, int *status, int options);
1、返回值:
当正常返回的时候waitpid返回收集到的子进程的进程ID;
如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
如果调用中出错,则返回-1,这时error会被设置成相应的值以指示错误所在。
2、参数:pid:
pid=-1,等待任一个子进程。与wait等效。
pid_t id=waitpid(-1,NULL,0);
运行结果:
pid>0. 等待其进程ID与pid相等的子进程。
status:
WIFEXITED(status):若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
WEXITSTATUS(status):若WIFEXITED非零,提取子进程退出码。options:
WNOHANG:若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID。
获取子进程status:
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<unistd.h>
4 #include<sys/types.h>
5 #include<sys/wait.h>
6 int main()
7 {
8 int status=0;
9 pid_t id=fork();
10 if(id==-1){
11 perror("fork");
12 exit(1);
13 }
14 if(id==0){//子进程
15 printf("I am child: I am pid: %d, I am ppid:% d \n",getpid(),getppid());
16 sleep(3);
17 exit(0);
18 }
19 else{//父进程
20 pid_t pid = waitpid(id,&status,0);
21 if(WIFEXITED(status))
22 printf("%d\n",WEXITSTATUS(status));
23 printf("I am parent: I am pid:%d,I am ppid :%d \n",getpid(),getppid());
24
25 }
26
27 return 0;
28
29 }
没收到信号,结果对不对就要通过子进程的退出码来决定
8 int status=0;
9 pid_t id=fork();
10 if(id==-1){
11 perror("fork");
12 exit(1);
13 }
14 if(id==0){//子进程
15 printf("I am child: I am pid: %d, I am ppid:%d \n",getpid(),getppid());
16 sleep(1);
17 printf("child is done...!\n");
18 exit(1);
19 }
20 else{//父进程
21 pid_t pid = waitpid(id,&status,0);
22 printf("I am parent: I am pid:%d,I am ppid:%d \n",getpid(),getppid());
23 if(pid>0)
24 {
25 printf("child get sig is :%d,exit code:%d\n",status&0x7F,(status>>8)&0xFF);
26 }
27 printf("father done..!,my son is quit ,pid is:%d\n",pid);
28 }
父进程没收到子进程被杀的信号,父进程知道子进程事有没有办好
异常退出,退出码毫无意义
阻塞式:调度器一直调用子进程,不管父进程,子进程不结束,父进程就一直在等。
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<unistd.h>
4 #include<sys/types.h>
5 #include<sys/wait.h>
6 int main()
7 {
8 int status=0;
9 pid_t id=fork();
10 if(id==-1){
11 perror("fork");
12 exit(1);
13 }
14 if(id==0){//子进程
15 printf("I am child: I am pid: %d, I am ppid:%d \n",getpid(),getppid());
16 while(1){
17 sleep(1);
18
19 }
20 printf("child is done...!\n");
21 exit(1);
22 }
23 else{//父进程
24
25 pid_t pid = waitpid(id,&status,0);
printf("I am parent: I am pid:%d,I am ppid:%d \n",getpid(),getppid());
26 if(pid>0)
27 {
28 printf("child get sig is :%d,exit code:%d\n",status&0x7F,(status>>8)&0xFF);
29 }
30 printf("father done..!,my son is quit ,pid is:%d\n",pid);
31 }
32
33 return 0;
34
35 }
-- INSERT --
再开一个终端:
则:父进程收到子进程被杀掉的信号,则父进程回收子进程的id
子进程一直运行,父进程一直等待
非阻塞式:
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<unistd.h>
4 #include<sys/types.h>
5 #include<sys/wait.h>
6 int main()
7 {
8 int status=0;
9 pid_t id=fork();
10 if(id==-1){
11 perror("fork");
12 exit(1);
13 }
14 if(id==0){//子进程
15 printf("I am child: I am pid: %d, I am ppid:%d \n",getpid(),getppid());
16 while(1){
17 sleep(1);
18 }
19 printf("child is done...!\n");
20 exit(1);
21 }
22 else{//父进程
23 // pid_t pid = waitpid(id,&status,0);
printf("I am parent: I am pid:%d,I am ppid:%d \n",getpid(),getppid());
25 pid_t ret;
26 do{
27 ret=waitpid(-1,&status,WNOHANG);
28 if(ret==0){
29 printf("father do other things!\n");
30 }
31 else if(ret>0)
32 {
33 printf("father wait success!\n");
34 if(WIFEXITED(status)){
35 printf("child exit code is:%d\n",WEXITSTATUS(status));
36 }
37 else{
38 printf("child is quit by signal!\n");
39 }
40 break;
41 }
42 else{
43 printf("waitpid error!\n");
44 break;
45 }
46 sleep(1);
47 }while(1);
48 // if(pid>0)
49 // {
50 // printf("child get sig is :%d,exit code:%d\n",status&0x7F,(status>>8)&0xFF);
51 // }
52 printf("father done..!");
53 }
54 return 0;
55 }
父进程再检测子进程,但子进程一直没退出,
信号中断:
开另一个终端:
结果:
父进程成功退出,子进程被信号中断
正常退出:
去掉while(1){ }
,让子进程睡5秒
等待失败:
1、ret=waitpid(id+4,&status,WNOHANG);
2、else{
printf("waitpid error!,%d\n",ret);
}
接受失败,返回-1
进程替换见下篇文章。