目录
一.进程的创建
之前在进程管理那篇博客里面已经讲过fork已经它是如何使用的,详细的可以去看之前的那篇博客。一个进程被创建OS需要做什么?
1.PCB(进程控制块)页表以及mm_struct
2.将父进程的一部分数据复制到子进程的中(代码和数据共享,前提不发生修改)
3.将子进程的PCB放入允许队列中让CPU调度。
下面我们来看一下代码:
1#include<iostream> 2 using namespace std; 3 #include<sys/types.h> 4 #include<unistd.h> 5 int main() 6 { 7 if(fork()==0) 8 { 9 cout<<"I am a child pid:"<<getpid()<<"ppid:"<<getppid()<<endl; 10 } 11 else 12 { 13 cout<<"I am father pid:"<<getpid()<<"ppid:"<<getppid()<<endl; 14 sleep(1); 15 } 16 17 18 19 return 0; 20 } ~
fork是否每次创建都能成功了?答案是不是的。当系统中的进程太多了,就无法创建子进程
或者实际用户数量超出了限制等。
二.写时拷贝
linux当中父子进程代码和数据是共享的前提是建立在不发生修改的情况下,当任何一方尝试对其进行修改就会发生写时拷贝,子进程和父进程各自私有一份
发生的写时拷贝只是改变了尝试修改一方中页表的映射关系,发生写时拷贝之后也就具有写权限。而为什么要写时拷贝呢?
1.减少空间的浪费,当父子进程都不对代码进行修改时,父子进程可以共享一块空间这样就减少了空间的浪费。
2.维持进程之间的独立性,虽然父子代码是共享的但是有一方尝试对共享的数据进行修改,如果不发生写时拷贝那么就无法维持进程之间的独立性,有了写时拷贝一方对应数据进行了修改不会影响另外一方。
三.进程终止
一个进程退出有如下三种情况:
1.代码运行完毕,结果正确(逻辑正确,正常退出)
2.代码运行完毕,结果不正确(代码逻辑出现问题,结果不正确)
3.代码异常终止
如何查看进程的退出码:
echo $?可以查看进程的退出码.示例如下:
1#include<iostream> 2 using namespace std; 3 #include<sys/types.h> 4 #include<unistd.h> 5 int main() 6 { 7 if(fork()==0) 8 { 9 cout<<"I am a child pid:"<<getpid()<<"ppid:"<<getppid()<<endl; 10 } 11 else 12 { 13 cout<<"I am father pid:"<<getpid()<<"ppid:"<<getppid()<<endl; 14 sleep(1); 15 } 16 17 18 19 return 0; 20 } ~
就以上面的这个代码为例:我们将这个程序跑起来:
我们在学习c语言的时候main函数的返回值都是返回0,main函数的返回值其实就是进程的退出码。一般0代表正常,非零表示错误。
难道我们就只能在main里面return 0结束进程吗?我如果想在一个函数里面结束一个进程此时我们就不能够使用return 了因为在普通函数内执行return 是代表函数执行完毕,不是代表进程结束。如果我们想在一个普通函数内终止这个进程我们可以使用exit或者_exit.
_exit
下面我们通过一段代码来演示:
1.#include<iostream> 2 using namespace std; 3 #include<sys/types.h> 4 #include<unistd.h> 5 void func(int b,int a) 6 { 7 if(a==0) 8 { 9 cout<<"非法数据"<<endl; 10 exit(1); 11 } 12 cout<<b/a<<endl; 13 } 14 int main() 15 { 16 int a=0; 17 int b=10; 18 func(b,a); 19 cout<<"hello word"<<endl; 20 return 0; 21 22 }
如果exit能够终止进程那们helloword 不会打印出来。我们将这个程序跑起来:
我们发现helloword没有被打印并且进程的退出码也正好是传递给exit的参数。
_exit
_exit和exit的区别是exit会刷新缓冲区而_exit不会刷新缓冲区。
同样的我们使用上面那一份代码:
#include<stdlib.h> 2 #include<iostream> 3 using namespace std; 4 #include<sys/types.h> 5 #include<unistd.h> 6 void func(int b,int a) 7 { 8 if(a==0) 9 { 10 cout<<"非法数据"; 11 exit(1); 12 } 13 cout<<b/a<<endl; 14 } 15 int main() 16 { 17 int a=0; 18 int b=10; 19 func(b,a); 20 cout<<"hello word"<<endl; 21 return 0; 22 23 }
运行起来:
我们发现非法数据这一句话是可以输出出来的。如果我们使用_exit呢?
此时我们发现非法输入没有被打印出来.
异常终止:
1.使用ctr+c终止前台进程
2.使用kill命令发生终止信号
四.进程等待
一.进程等待的必要性:
1.子进程退出,父进程如果不读取子进程的退出信息,就可能造成‘僵尸进程’的问题,进而造成内存泄漏。
2.进程一旦变成僵尸状态,那就刀枪不入,“杀人不眨眼”的kill -9 也无能为力,因为谁也没有办法杀死一个已经死去的进程。
3.父进程派给子进程的任务完成的如何,我们需要知道。如,子进程运行完成,结果对还是不对,或者是否正常退出。
4.父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息进程等待的方法
二.进程等待的方法
进程等待的方法:
1.wait
2.waitpid
首先我们先来看wait:
函数返回值:
返回值有两种情况:1.等待成功返回等待进程的id,2.等待失败返回-1即成功就返回等待进程的id,失败就返回-1.
参数:status是一个输出形参数。在c语言中如果我们想拿到函数里面的值我们需要传地址过去,在函数里面解引用从而达到能够在函数外面拿到函数里面的某些值。通过stauts我们可以获取等待进程的退出状态。当然如果不关心这个可以设置为NULL。
示例:
#include<stdlib.h> 2 #include<iostream> 3 using namespace std; 4 #include<sys/types.h> 5 #include<unistd.h> 6 #include<wait.h> 7 int main() 8 { 9 pid_t id=fork(); 10 if(id<0) 11 { 12 cerr<<"fokr fail"<<endl; 13 } 14 else if(id==0) 15 { 16 //子进程 17 int cnt=10; 18 while(cnt--) 19 { 20 cout<<"I am a child pid: "<<getpid()<<"ppid:"<<getppid()<<endl; 21 sleep(1); 22 } 23 exit(1); 24 } 25 //父进程 26 cout<<"father process begin"<<endl; 27 sleep(5); 28 pid_t Id=wait(0);//不关系子进程的退出状态 29 cout<<"parent process wait finish"<<endl; 30 if(Id<0) 31 { 32 cout<<"parent wait fail"<<endl; 33 } 34 else 35 { 36 cout<<"wait success"<<endl; 37 } 38 39 }
下面我们使用一段脚本来监控进程:
while :; do ps axj | head -1 && ps ajx | grep test; sleep 2; echo "#############################################"; done
子进程退出变成僵尸状态。
父进程wait等待子进程,将子进程的资源回收之后,子进程也由z状态变为x状态
2.waitpid
说明:
1.返回值:等待成功返回等待成功的子进程的id,失败返回-1。返回值和wait是一样的,如果设置了WNOHANG而waitpid发现没有退出的子进程,则返回0。
2.参数第一个参数为你要等待那个进程当pid=-1时为等待任意一个进程和wait是一样的,当pid大于0时等待和pid相同的进程
3.第二个参数:输出型参数,如果不关心等待进程的退出状态可以设置为 NULL。WIFEXITD(status)如果子进程正常返回则为真(常用于判断进程是否正常退出)。WEXITSTATUS(status):如果WIFEXITD(status)非零则可以用于提取子进程的退出码(查看子进程的退出码)。
4.第三个参数option:若pid指定的子进程没有结束,那们waitpid的返回值为0,如果正常结束则返回子进程的id(用于基于阻塞的等待轮询访问)0代表阻塞等待,等待期间父进程不做任何事情。
同样的我们将上面的wait改为waitpid:
#include<stdlib.h> 2 #include<iostream> 3 using namespace std; 4 #include<sys/types.h> 5 #include<unistd.h> 6 #include<wait.h> 7 int main() 8 { 9 pid_t id=fork(); 10 if(id<0) 11 { 12 cerr<<"fokr fail"<<endl; 13 } 14 else if(id==0) 15 { 16 //子进程 17 int cnt=5; 18 while(cnt--) 19 { 20 cout<<"I am a child pid: "<<getpid()<<"ppid:"<<getppid()<<endl; 21 sleep(1); 22 } 23 exit(10); 24 } 25 //父进程 26 27 cout<<"father process begin"<<endl; 28 pid_t Id=waitpid(-1,NULL,0);//等待任意一个子进程 29 cout<<"parent process wait finish"<<endl; 30 if(Id<0) 31 { 32 cout<<"parent wait fail"<<endl; 33 } 34 else 35 { 36 cout<<"wait success"<<endl; 37 } 38 39 }
将程序跑起来:
3.获取子进程的status:
wait和waitpid中都有一个输出型参数,如果我们不关心的话可以设置为NULL:下面谈一下它是用来干什么的:status的32个比特位高16个比特位不研究只研究低16个比特位
如果是被信号所杀:
从图中我们可以知道低7位代表的是终止信号,第八位core_dump标志,高八位代表的是退出码(退出码在进程正常退出时才有意义)。我们将低7位的值取出来如果是0就代表进程是正常退出,获取高8位的值代表进程的退出码同样的如果是0代表正常退出,如果低7位都不是0了说明进程异常退出此时退出码也就没有什么意义了。
示例:
#include<stdio.h> 2 #include<stdlib.h> 3 #include<iostream> 4 using namespace std; 5 #include<sys/types.h> 6 #include<unistd.h> 7 #include<wait.h> 8 int main() 9 { 10 pid_t id=fork(); 11 if(id<0) 12 { 13 cerr<<"fokr fail"<<endl; 14 } 15 else if(id==0) 16 { 17 //子进程 18 int cnt=5; 19 while(cnt--) 20 { 21 cout<<"I am a child pid: "<<getpid()<<"ppid:"<<getppid()<<endl; 22 sleep(1); 23 } 24 exit(10); 25 } 26 //父进程 27 28 29 int status=0; 30 pid_t Id=waitpid(-1,&status,0 );//等待任意一个子进程默认行为阻塞等待 31 32 if(Id>0) 33 {//子进程退出了waitpid也成功了 34 printf("father wait: %d ,sucess,status:exit code:%d,status exit signal:%d\n",Id,((status)>>8)&0xFF,status&0x7F); 35 } 36 else if(Id==0) 37 { 38 39 cout<<"Do my things"<<endl; 40 } 41 else if(Id<0) NORMAL ?? test.cpp ? main() ? cpp ? ? utf-8 ?? ? 72% 35/48 : 4
4.阻塞等待和非阻塞等待
1.阻塞等待:父进程一直在等子进程,子进程不退出我就一直等,其他事情我啥也不干。
2.阻塞的本质:父进程的 PCB由R状态变成S状态,从运行队列到了等待队列中,等待子进程d。
3.等待结束的本质:父进程从等待队列到了运行队列,父进程的PCB也由S状态变为R状态。
#include<stdlib.h> 2 #include<iostream> 3 using namespace std; 4 #include<sys/types.h> 5 #include<unistd.h> 6 #include<wait.h> 7 int main() 8 { 9 pid_t id=fork(); 10 if(id<0) 11 { 12 cerr<<"fokr fail"<<endl; 13 } 14 else if(id==0) 15 { 16 //子进程 17 int cnt=5; 18 while(cnt--) 19 { 20 cout<<"I am a child pid: "<<getpid()<<"ppid:"<<getppid()<<endl; 21 sleep(1); 22 } 23 exit(10); 24 } 25 //父进程 26 27 cout<<"father process begin"<<endl; 28 int status=0; 29 pid_t Id=waitpid(-1,&status,0);//等待任意一个子进程默认行为阻塞等待 30 cout<<"parent process wait finish"<<endl; 31 if(Id>0) 32 { 33 cout<<"wait sucess"<<endl; 34 } 35 else if(Id<0) 36 { 37 cout<<"parent wait fail"<<endl; 38 } 39
非阻塞等待
非阻塞等待:父进程不断检查子进程的退出状态,而在检测子进程的期间可以干自己的事情。
#include<stdio.h> 2 #include<stdlib.h> 3 #include<iostream> 4 using namespace std; 5 #include<sys/types.h> 6 #include<unistd.h> 7 #include<wait.h> 8 int main() 9 { 10 pid_t id=fork(); 11 if(id<0) 12 { 13 cerr<<"fokr fail"<<endl; 14 } 15 else if(id==0) 16 { 17 //子进程 18 int cnt=5; 19 while(cnt--) 20 { 21 cout<<"I am a child pid: "<<getpid()<<"ppid:"<<getppid()<<endl; 22 sleep(1); 23 } 24 exit(10); 25 } 26 //父进程 27 28 while(1){ 29 int status=0; 30 pid_t Id=waitpid(-1,&status,WNOHANG );//等待任意一个子进程默认行为阻塞等待 31 32 if(Id>0) 33 {//子进程退出了wait也成功了 34 printf("father wait: %d ,sucess,status:exit code:%d,status exit signal:%d\n",Id,((status)>>8)&0xFF,status&0x7F); 35 break; 36 } 37 else if(Id==0) 38 { 39 //子进程还没有退出,但是waitpid是成功的,需要父进程重复等待 40 cout<<"Do my things"<<endl; 41 } NORMAL ?? test.cpp ? main() ? cpp ? ? utf-8 ?? ? 69% 34/49 : 5
结果如下