Linux下进程控制
-01进程的创建
1.在上一篇博客中写到了进程的创建,但是讲的有点糙,这篇开篇最为补充。
2.进程的创建主要用到的是fork和vfork
3.看一个简单的实例
int main()
{
std::cout<<"fork 前"<<std::endl;
pid_t pid = fork();
std::cout<<"fork 后"<<std::endl;
return 0;
}
//结果
[skin@bogon ~]$ ./a.out
fork 前
fork 后
fork 后
从结果可以看到打印了两遍的” fork 后” ,那这是为什么呢?从下图我们来看一下这个程序的执行过程
通过上图可以看到当fork之后一个”一模一样”的子进程出现了,而且当fork之后子进程和父进程一样都执行到了fork,结果就是打印,所以会有两” fork 后” 被打印出来。
- 02.fork都做了什么?
- 看一下fork之后与之前的区别,我们可以知道,出现了一个以父进程为模板的子进程,而且这个子进程和父进程程序执行到了相同的位置
- fork之后会将父进程程序段,代码段,堆区,栈区都进行复制一份,放到子进程的虚拟地址空间上,所以父子进程有不同的地址空间,那么就可以执行不同的流,子进程一般会进行程序替换,然后去执行不同的逻辑。
- 在fork之后,子进程和父进程虽然是不同的虚拟地址空间,虽然会不影响各自的执行流,但是linux为了效率,在映射真实物理内存时采用了”写时拷贝”,这样在子进程没有改变原来的数据时,那么父子进程的通过页表进行MMU映射后其实映射到同一块物理内存上。
- 看图吧
(1)没有修改各段数据时:父子进程虽然都有自己的虚拟地址空间,但是通过MMU映射后都是指向同一块真实的物理空间的。
(2)当修改了数据时:父子进程还是都有一块自己的虚拟地址空间,通过各自的页表进行映射,但是此时代码段还是映射到同一块物理内存上,但是数据段就变成各自一块物理内存了。如图
总结一下上述所讲,fork之后会创建一个以当前进程为模板的子进程,子进程复制了当前进程的堆,栈,数据段,代码段。但是因为每个进程都有一个独立的,且相同的虚拟地址空间,通过各自的页表映射的真实物理内存也都是同一块。当其中一个进程尝试修改其各段的数据时,那么内核会复制一份真实物理内存的给这个进程,同时当然也要修改其页表,使其可以映射到新分配的物理内存上。
- 03.简述一下vfork
1.这个函数使用和fork一模一样,没啥区别
2.和vfork的区别是这个函数可以保证,vfork之后一定是子进程运行,然后父进程才去运行。而fork则不能保证那个进程先执行,那个后执行,完全由调度器来决定。
3.看一下vfork的定义
4.实例看一下,即使我的子进程等待了5s,父进程依然要让子进程先执行。看一下对比。
相信这样就很好的展示了vfork与fork的区别。
- 04.进程的终止(退出)
1.从main函数返回。
2.调用exit或者_exit函数退出。
3.异常终止,退出。(ctrl -c )
对exit进行进一步说明
- 05.进程等待
1.为什么要进程等待呢?
a. 上篇博客已经简述了僵尸进程,而进程等待可以避免僵尸进程,从而达到资源的回收,防止内存泄漏
b. 因为往往我们开辟子进程是为了让子进程进程程序替换,去做一些其他事情,可以拿到子进程退出信息,做一些对应的事情。比如子进程去负责收集影片的评分,父进程可以获取这些评分后,统一做排名,所以要等所有子进程度完成后才可以去进行排名
2. 进程等待函数wait和waitpid
返回情况:
1.正常等到了进程,wait/waitpid直接立刻返回,拿到了子进程退出信息,释放资源。
2.设置了WNOHANG,进程没有推出直接返回0,不等待
3.等待的进程不存在,立刻异常返回-1.
下面对其使用一下:
#fork
int main()
{
pid_t pid = vfork();
if(pid<0)
{
perror("vfork");
return -1;
}
else if(pid>0)
{
int sta ;
wait(&sta);
if(WIFEXITED(sta))
{
std::cout<<"子进程正常返回!"<<"子进程退出码:"<<WEXITSTATUS(sta)<<" "<<std::endl;
}
else
{
std::cout<<"子进程异常返回!"<<std::endl;
}
}
else
{
std::cout<<"子进程退出!"<<std::endl;
exit(5);
}
return 0;
}
[skin@bogon ~]$ ./a.out
子进程退出!
子进程正常返回!子进程退出码:5
#vfork
int main()
{
pid_t pid = fork();
if(pid<0)
{
perror("fork");
return -1;
}
else if(pid>0)
{
int sta ;
int ret=0;
do{
ret =waitpid(pid,&sta,WNOHANG);
if(ret==0)
{
std::cout<<"等待的进程还没有退出 !"<<std::endl;
sleep(1);
}
else if(ret<0)
{
std::cout<<"不存在的进程 !"<<std::endl;
return -1;
}
else
{
std::cout<<"等到了进程退出 !"<<std::endl;
if(WIFEXITED(sta))
{
std::cout<<"子进程正常返回!"<<"子进程退出码:"<<WEXITSTATUS(sta)<<" "<<std::endl;
}
else
{
std::cout<<"子进程异常返回!"<<std::endl;
}
}
}while(ret ==0);
}
else
{
sleep(1);
std::cout<<"子进程退出!"<<std::endl;
exit(5);
}
return 0;
}
[skin@bogon ~]$ ./a.out
等待的进程还没有退出 !
等待的进程还没有退出 !
子进程退出!
等到了进程退出 !
子进程正常返回!子进程退出码:5
- 06.进程的程序替换-exec家族
1.首先呢,这些函数都是同一个作用,就是进行程序的替换。
2.其中只有execve才是正统的系统函数,其他都是基于它封装的库函数。
3.看下man手册对他的介绍
4.程序替换的原理:进行程序替换的进程,将自己的真实物理内存上的代码段,数据段都换成新程序的代码和数据,然后进程从替换代码的程序启动地方开始执行,这样就完成了程序替换,如图
5. 来段代码,就知道了
//主程序
int main()
{
pid_t pid = fork();
if(pid<0)
{
perror("fork");
return -1;
}
else if(pid>0)
{
//防止僵尸
wait(NULL);
}
else
{
//进行程序替换
std::cout<<"子进程开始替换当前路径下的show程序 !"<<std::endl;
execl("./show","./show",NULL);
//无成功返回
perror("exec");
return -1;
}
return 0;
}
//被调用的show程序
int main()
{
std::cout<<"show 程序正在执行 !\n";
std::cout<<"show 程序执行完成 !\n";
return 0;
}
//主程序运行结果
[skin@bogon ~]$ ./a.out
子进程开始替换当前路径下的show程序 !
show 程序正在执行 !
show 程序执行完成 !
如有错误,可以私信我,在这里表示感谢!