1.进程相关概念
1.1 程序和进程
程序:是指编译好的二进制文件,存放在磁盘上,占用磁盘空间,是一个静态的概念。
进程:一个启动的程序,进程占用的是系统资源,如物理内存,CPU,终端,是一个动态的概念
程序---->剧本
进程---->唱戏(舞台,灯光,道具,人等资源)
同一个程序可以在多个终端执行,类似同一台戏可以在多个舞台演出
每启动一个程序都会产生一个进程PID,即使是相同的进程多次启动也会产生不同的PID
1.2 并行和并发
并发:在一个时间段内,同一个CPU上,同时运行多个程序
并行:在一个时间片内,有多个程序在执行(前提是有多个CPU)。
CPU会将一个大的时间段分成多个小的时间片,让进程轮流使用CPU的时间片
1.3 PCB-进程控制块
1:进程id,系统中每个进程有唯一的id,在C语言中用pid_t类型表示,其实就是一个非负整数。
2:进程的状态:就绪,运行,挂起,停止,等状态
3:进程切换时需要保存和恢复的一些CPU寄存器(保存上下文)
4:描述虚拟地址空间的信息
5:当前工作目录
6:umask掩码
7:文件描述符
2.fork/getpid/getppid函数的使用
fork:父进程调用fork函数创建一个子进程,子进程的用户区和父进程的用户区完全一样,但是内核区不完全一样,如父进程的PID和子进程的PID不一样。
一个子进程只有一个父进程,但一个父进程可以有多个子进程。
fork函数的返回值:父进程返回的是子进程的PID,这个值大于0,子进程返回0;
注意:并不是一个进程返回两个值,而是父子进程各自返回一个值。
父子进程执行逻辑:父进程执行pid>0的逻辑,子进程执行pid==0的逻辑。
父子进程的执行顺序:谁先抢到cpu时间片谁就先执行
fork函数测试(linux环境,包含getpid,getppid函数)
#include<stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
printf("before fork,pid:[%d]\n",getpid());//查看这句话打印多少次,谁打印的
//创建子进程
//函数原型: pid_t fork(void)
pid_t pid = fork();
if(pid<0) //fork函数失败的情况
{
perror("fork error");
return -1;
}
else if(pid>0) //父进程
{
printf("father:pid==[%d],fpid==[%d]\n",getpid(),getppid());
sleep(1);
}
else if(pid==0)//子进程
{
printf("child:pid==[%d],fpid==[%d]\n",getpid(),getppid());
}
printf("after fork,pid:[%d]\n",getpid());//查看这句话打印多少次,谁打印的
return 0;
}
3.ps/kill命令的使用(部分)
ps命令:查看进程相关信息。
kill -9 2117:杀死pid=2117的进程
4.execl/execlp函数的使用
int execl(path,"ls","-l",NULL)
调用execl函数以后,子进程 的代码段会被ls命令的代码段替换,但子进程的地址空间没有变化,子进程的PID也没有变化。
execl函数原型:int execl(const char * path,const char * arg,.../*(char)NULL*/);
execlp函数原型: int execlp(const char * file,const char * arg,.../*(char)NULL*/);
5.孤儿进程和僵尸进程
5.1为什么要进行进程资源的回收
当一个进程退出之后,进程能够回收自己的用户区的资源,但是不能回收内核空间的PCB资源,必须由它的父进程调用wait或者waitpid函数完成对子进程的回收,避免造成系统资源的浪费。
5.2孤儿进程
孤儿进程:父进程先退出,子进程就变成了孤儿进程。
当去掉父进程中的sleep函数,子进程可能会变成孤儿进程(父进程比子进程先退出),此时,init进程(1号进程)将成为该子进程的父进程,负责回收子进程的资源
#include<stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
printf("before fork,pid:[%d]\n",getpid());//查看这句话打印多少次,谁打印的
//创建子进程
//函数原型: pid_t fork(void)
pid_t pid = fork();
if(pid<0) //fork函数失败的情况
{
perror("fork error");
return -1;
}
else if(pid>0) //父进程
{
printf("father:pid==[%d]\n"getpid());
//sleep(1);
}
else if(pid==0)//子进程
{
printf("child:pid==[%d]\n".getpid());
}
printf("after fork,pid:[%d]\n",getpid());//查看这句话打印多少次,谁打印的
return 0;
}
5.3僵尸进程
僵尸进程:子进程先退出,父进程没有完成对子进程的回收,此时子进程就变成了僵尸进程。
在父进程中添加sleep函数,让子进程先退出,父进程后退出(延长父进程在系统中的存在时间),执行代码后,子进程就变成了僵尸进程(zombie进程),该进程无法用kill命令杀死(僵尸进程是一个死掉的进程,接收不了信号)。
解决办法:使用kill -9 命令杀死父进程,让init进程(1号进程)回收该子进程
僵尸进程带来的问题:当系统中出现大量的僵尸进程时,会占用大量的系统资源,此时系统将无法产生新的进程,需要避免此问题。
#include<stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
//创建子进程
//函数原型: pid_t fork(void)
pid_t pid = fork();
if(pid<0) //fork函数失败的情况
{
perror("fork error");
return -1;
}
else if(pid>0) //父进程
{
sleep(20);
printf("father:pid==[%d]\n"getpid());
}
else if(pid==0)//子进程
{
printf("child:pid==[%d]\n".getpid());
}
return 0;
}
6.wait函数的使用
函数原型:pid_t wait(int * status);
函数作用:
1 阻塞并等待子进程退出
2 回收子进程残留资源
3 获取子进程结束状态(退出原因)
返回值:
成功:返回清理掉的子进程ID;
失败:-1(没有子进程)可表示子进程已经全部回收
status参数:子进程退出状态 ----传出参数
WIFEXITED(status): 为非0 ->进程正常结束
WEXITSTATUS(status): 获取进程退出状态
WIFSIGNALED(status): 为非0 ->进程异常终止
WTERMSIG(status): 取得进程终止的信号编号。
//父进程调用wait函数完成对子进程的回收
#include<stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
//创建子进程
//函数原型: pid_t fork(void)
pid_t pid = fork();
if(pid<0) //fork函数失败的情况
{
perror("fork error");
return -1;
}
else if(pid>0) //父进程
{
printf("father:pid==[%d],fpid==[%d]\n",getpid(),getppid());
int status;
pid_t wpid=wait(&status);
printf("wpid==[%d]\n",wpid);
if(WIFEXITED(status)) //正常退出
{
printf("child normal exit,status==[%d]\n",WEXITSTATUS(status));
}
else if(WIFSIGNALED(status)) //被信号杀死
{
printf("child killed by signal,signo==[%d]\n",WTERMSIG(status));
}
}
else if(pid==0)//子进程
{
printf("child:pid==[%d],fpid==[%d]\n",getpid(),getppid());
sleep(5);
return 9;
}
}
7.waitpid函数的使用
函数原型:pid_t waitpid(pid_t pid,int * status,int options);
函数作用:
1 阻塞并等待子进程退出
2 回收子进程残留资源
3 获取子进程结束状态(退出原因)
函数参数:
pid:
pid=-1:等待任意子进程,与wait等效
pid>0:等待其进程ID,与PID相等的子进程
pid=0:等待进程组ID与目前进程相同的任何子进程,也就是说任何和调用waitpid()函数的进程在同一个进程组的进程。
pid<-1:等待其组ID等于pid的绝对值的任一子进程。(适用于子进程在其他组的情况)
starus:子进程的退出状态,用法同wait函数。
options:设置为WNOHANG,函数非阻塞,设置为0,函数阻塞。
返回值:
>0:返回清理掉的子进程ID;
-1:(没有子进程)可表示子进程已经全部回收
0 :参数3为WNOHANG,且子进程正在运行。
注意:调用一次waitpid或者wait函数只能回收一个子进程
//父进程调用waitpid函数完成对子进程的回收
#include<stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
//创建子进程
//函数原型: pid_t fork(void)
pid_t pid = fork();
if(pid<0) //fork函数失败的情况
{
perror("fork error");
return -1;
}
else if(pid>0) //父进程
{
printf("father:pid==[%d],fpid==[%d]\n",getpid(),getppid());
int status;
while(1)
{
pid_t wpid=waitpid(-1,&status,WNOHANG); //-1表示等待任意子进程,WNOHANG:不阻塞
printf("wpid==[%d]\n",wpid);
if(wpid>0)
{
if(WIFEXITED(status)) //正常退出
{
printf("child normal exit,status==[%d]\n",WEXITSTATUS(status));
}
else if(WIFSIGNALED(status)) //被信号杀死
{
printf("child killed by signal,signo==[%d]\n",WTERMSIG(status));
}
}
else if(wpid==0)//子进程还活着
{
pintf("child is living,wpid==[%d]\n",wpid);
}
else if(wpid==-1) //没有子进程了
{
printf("no child is living,wpid ==[%d]\n",wpid);
break;
}
}
else if(pid==0)//子进程
{
printf("child:pid==[%d],fpid==[%d]\n",getpid(),getppid());
sleep(5);
return 9;
}
}