进程控制知识点和函数应用

一、进程控制理论

1.进程的定义:

进程是一个具有一定独立功能的程序的一次运行活动,同时也是资源分配的最小单元

程序是放到磁盘的可执行文件
进程是指程序执行的实例
进程是动态的,程序使静态的:程序是有序代码的集合;进程是程序的执行,通常进程不可在计算机之间迁移;而程序通常对应着文件、静态和可以复制
进程是暂时的,程序是长久的:进程是一个状态变化的过程,程序可长久保存
进程与程序组成不同:进程的组成包括程序、数据和进程控制块
进程与程序的对应关系:通过多次执行,一个程序可对应多个进程;通过调用关系,一个进程可包括多个程序。

2.进程的生命周期

创建:每个进程都是由其父进程创建进程可以创建子进程,子进程又可以创建子进程的子进程
运行:多个进程可以同时存在进程间可以通讯
撤销:进程可以被撤销,从而结束一个进程的运行

3.进程的状态

执行状态:进程正在占用cpu
就绪状态:进程已具备一切条件,正在等待分配cpu的处理时间片
等待状态:进程因为等待某个资源而睡眠,进程不能使用CPU,若等待事件发生则可将其唤醒

在这里插入图片描述

4.进程ID

进程ID(PID):标识进程的唯一数字
父进程的ID(PPID)
启动进程的用户ID(UID)

5.进程互斥

进程互斥是指当有若干进程都要使用某一共享资源时,任何时刻最多允许一个进程使用,其他要使用该资源的进程必须等待,直到占用该资源者释放了该资源为止

6。临界资源

操作系统中将一次只允许一个进程访问的资源称为临界资源

7.临界区

进程中访问临界资源的那段程序代码称为临界区,为实现对临界资源的互斥访问,应保证诸进程互斥的进入各自的临界区

8.进程同步

一组并发进程按一定的顺序执行的过程称为进程间的同步
具有同步关系一组并发进程称为合作进程,合作进程间互相发送的信号称为消息或事件

9.进程调度

概念:按一定算法,从一组待运行的进程中选出一个来占有CPU运行
调度方式:1)抢占式 2)非抢占式

10.调度算法

先来先服务调度算法
短进程优先调度算法
高优先级先调度算法
时间片轮转法

在这里插入图片描述在这里插入图片描述
在这里插入图片描述

二、进程创建

1.fork()

子进程与父进程复制父进程的变量
有自己的变量空间

在这里插入图片描述

返回的两次分别是:
返回给父进程的是 创建的子进程的ID 可以通过父进程找到子进程
返回给子进程的是 0 这0表示子进程没有下一代子进程了

对于小面的例子来说 两次返回值给pid
子进程ID肯定是正整数 所以pid>0时 就是父进程
pid等于0时 是子进程

三种返回值:-1失败 >=0成功

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.第二种创建子进程vfork()

vfork()

子进程与父进程共享父进程的变量
所以变量不能同时被父进程和子进程使用
那么谁先运行
子线程先运行 如果这个变量父进程也要用那么父进程需要等待

在这里插入图片描述

进程结束有两个函数
exit(0)在头文件<stdlib.h>中 会清空文件缓冲区
_exit(0)在头文件<unistd.h>中 不会清空文件缓冲区 更保险一点 什么都不清空。
第二个是使用的系统调用 放在子进程执行语句的最后 结束子进程

为什么不用return 0
因为 子进程结束使用return 0时 会将变量全部释放 而它用的是父进程的变量 子进程先运行 释放变量后 父进程没有变量用了

3.fork PK vfork

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.exec函数族(有六个函数 掌握一个 其他派生)

在这里插入图片描述

exec函数后面就不要再写代码了 写了也不执行 因为被替换掉了 这使得子进程不再执行父进程的代码,去执行exec函数中新的任务

在这里插入图片描述
第一个参数 是含路径的要执行的文件名 从第二个参数是我想要在命令行写的命令
在这里插入图片描述

在这里插入图片描述

这里的a是要执行的程序 而1 2 是为想要在命令行输入的命令
./ 是a程序所在路径 而a程序作用就是算显示在命令行上的命令和个数
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

4.system函数

在这里插入图片描述

system函数是调用 与exec的区别 exec是替换

5.进程等待

作用:处理僵尸进程

参数status一般传NULL 其作用是返回子进程的状态的 当父进程不需要了解子进程状态时 就传NULL

当父进程有很多子进程时候 父进程遇到wait时候 等待的是第一个子进程结束,但是不知道第一个结束的是哪个子进程 这时候status参数就有用了 其返回的就是哪个子进程结束 如果wait出错返回的就是-1

6.第二种进程等待

pid_t waitpid(pid_t pid,int *status,int options)

第一个参数是等待哪个子进程
当去<-1时 意思是取这个值的绝对值 看哪个进程组的ID和这个绝对值相同 等待这个进程组中第一个结束的进程结束
当=-1时 等待当前第一个结束的子进程结束 但需要第三个参数配合
当=0时 等待当前进程组里的第一个结束的子进程结束
当>0就是子进程的ID

第二个参数是
返回结束的子进程的状态

第三个参数
取 0 是阻塞等待
取WNOHANG是非阻塞等待 只是看一眼子进程有没有结束 没结束 也之间返回

返回的是0 表示等待的子进程没有结束
返回的>0 是结束的子进程的ID
返回-1 是没有子进程 或者指定的pid 不存在 或者等待的子进程ID存在 但不是我这个父进程的子进程

7.分析子进程的运行例子代码

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/wait.h>
#include<sys/types.h>

void die(const char *msg){
	perror(msg);
	exit(1);
}

void child2_do(){
	printf("in child2:execute 'date'\n");

	sleep(5);
	if(execlp("date","date",NULL) < 0){
		perror("child2 execlp");
	}
}

void child1_do(pid_t child2,char *argv){
	pid_t pw;

	do{
		if(*argv == '1'){
			pw = waitpid(child2,NULL,0);//阻塞等待child2子进程的结束
		}
		else{
			pw = waitpid(child2,NULL,WNOHANG);//非阻塞等待 看一眼child2子进程是否结束
			                                  //没结束父进程继续运行 waitpid返回给pw的值是0
		}
		if(pw == 0){
			printf("in child1 process:\nThe child2 process has not exited!\n");
			sleep(1);
		}
	}while (pw == 0);

	if(pw == child2){
		printf("get child2 id = %d\n",pw);
		sleep(5);
		if(execlp("pwd","pwd",NULL) < 0){
			perror("child1 execlp");
		}
	}
	else{
		printf("erreor occured!\n");
	}
}

void father_do(pid_t child1,char *argv){
	pid_t pw;
	do{
		if(*argv == '1'){
			pw = waitpid(child1,NULL,0);
		}
		else{
			pw = waitpid(child1,NULL,WNOHANG);
		}
		if(pw == 0){
			printf("in father process:\nThe child1 process has not exited!\n");
			sleep(1);
		}
	}while(pw == 0);

	if(pw == child1){
		printf("get child1 id = %d\n",pw);
		if(execlp("ls","ls","-l",NULL) < 0){
			perror("father execlp\n");
		}
	}
	else{
		printf("error occured\n");
	}
}
int main(int argc , char *argv[]){
	pid_t child1,child2;
	if(argc < 3){
		printf("input error! Usage waitpid [0 1] [0 1]\n");
		exit(1);
	}
	
	child1 = fork();

	if(child1 < 0){
		die("child1 fork");
	}
	else if(child1 == 0){
		child2 = fork();

		if(child2 < 0){
			die("child2 fork");
		}
		else if(child2 == 0){
			child2_do();
		}
		else{
			child1_do(child2,argv[1]);
		}
	}
	else{
		father_do(child1,argv[2]);
	}
	return 0;
	
}

此函数 产生了三个进程 其中child2 孙子进程 执行的是查看系统日期 需要睡眠5s
child1子进程是查看路径 然后睡5s 但是他使用了waitpid 如果给第三个参数传入的是 0 则是阻塞等待 传WNOHANG 是非阻塞等待 具体看上面第二种进程的等待
father进程是查看目录 其要等待child1结束
如果命令行输入的0 0 即阻塞等待 那么输出的是在这里插入图片描述
如果输入 1 1 即非阻塞等待
在这里插入图片描述
在这里插入图片描述

三、僵尸进程与孤儿进程的产生和处理

1.僵尸进程

首先 我们要知道什么是僵尸进程?
所谓的僵尸进程就是 子进程先于父进程退出后 需要父进程帮其释放ID 但是父进程没有这样做 这时子进程就会成为僵尸进程

使用代码看一下

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
 
int main()
{
	pid_t pid;
	pid = fork();
 
	if(pid==0)  //子进程
	{
     	printf("child id is %d\n",getpid());
		printf("parent id is %d\n",getppid());
	}
	else  //父进程不退出,使子进程成为僵尸进程
	{
        while(1)
		{}
	}
	exit(0);
}

用ps可以看到子进程后有一个 ,defunct是已死的,僵尸的意思,可以看出这时的子进程已经是一个僵尸进程了。因为子进程已经结束,而其父进程并未释放其ID,所以产生了这个僵尸进程。
在这里插入图片描述

一个进程在调用exit命令结束自己的生命的时候,其实它并没有真正的被销毁,而是留下一个称为僵尸进程的数据结构。在Linux进程的状态中,僵尸进程是非常特殊的一种,它已经放弃了几乎所有内存空间,没有任何可执行代码,也不能被调度,仅仅在进程列表中保留一个位置,记载该进程的退出状态等信息供其他进程收集,除此之外,僵尸进程不再占有任何内存空间。这个僵尸进程需要它的父进程来为它收尸,如果他的父进程没有处理这个僵尸进程的措施,那么它就一直保持僵尸状态,如果这时父进程结束了,那么init进程自动会接手这个子进程,为它收尸,它还是能被清除的。但是如果如果父进程是一个循环,不会结束,那么子进程就会一直保持僵尸状态,这就是为什么系统中有时会有很多的僵尸进程。

2.僵尸进程的危害

如果系统中存在大量的僵尸进程 这会占用大量的系统资源 而系统资源是有限的 所以严重时会导致系统崩溃

3.僵尸进程的处理方法

任何一个子进程(init除外)在exit()之后,并非马上就消失掉,而是留下一个称为僵尸进程的数据结构,等待父进程处理。这是每个子进程在结束时都要经过的阶段。如果子进程在exit()之后,父进程没有来得及处理,这时用ps命令就能看到子进程的状态是“defunct”。如果父进程能及时处理,可能用ps命令就来不及看到子进程的僵尸状态,但这并不等于子进程不经过僵尸状态。如果父进程在子进程结束之前退出,则子进程将由init接管。init将会以父进程的身份对僵尸状态的子进程进行处理。所以孤儿进程不会占资源,僵尸进程会占用资源危害系统。我们应当避免僵尸进程的出现。

解决方式如下:

1):一种比较暴力的做法是将其父进程杀死,那么它的子进程,即僵尸进程会变成孤儿进程,由系统来回收。但是这种做法在大多数情况下都是不可取的,如父进程是一个服务器程序,如果为了回收其子进程的资源,而杀死服务器程序,那么将导致整个服务器崩溃,得不偿失。显然这种回收进程的方式是不可取的,但其也有一定的存在意义。

2):SIGCHLD信号处理

我们都知道wait函数是用来处理僵尸进程的,但是进程一旦调用了wait,就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经退出,(当然如果不存在子进程 那么wait函数会返回-1 而wait唯一会出错返回-1 只有不存在子进程这种情况)如果让它找到了这样一个已经变成僵尸的子进程,wait就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。

由于调用wait之后,就必须阻塞,直到有子进程结束,所以,这样来说是非常不高效的,我们的父进程难道要一直等待你子进程完成,最后才能执行自己的代码吗?难道就不能我父进程执行自己的代码,你子进程什么时候完成我就什么时候去处理你,不用一直等你?当然是有这种方式了。

实际上当子进程终止时,内核就会向它的父进程发送一个SIGCHLD信号,父进程可以选择忽略该信号,也可以提供一个接收到信号以后的处理函数。对于这种信号的系统默认动作是忽略它。我们不希望有过多的僵尸进程产生,所以当父进程接收到SIGCHLD信号后就应该调用 wait 或 waitpid 函数对子进程进行善后处理,释放子进程占用的资源

下面是一个处理僵尸进程的简单的例子:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<signal.h>
 
void deal_child(int num)
{
	printf("deal_child into\n");
	wait(NULL);
}
 
int main()
{
	signal(SIGCHLD,deal_child);
	pid_t pid=fork();
	int i;
 
	if(pid==0)
	{
		printf("child is running\n");
		sleep(2);
		printf("child will end\n");
	}
	else
	{
		sleep(1);   //让子进程先执行
		printf("parent is running\n");
		sleep(10);    //一旦被打断就不能再进入睡眠
		printf("sleep 10 s over\n");
		sleep(5);
		printf("sleep 5s over\n");
	}
 
	exit(0);
}

进行测试后确定了是在父进程睡眠10s时子进程结束,父进程接收到了SIGCHLD信号,调用了deal_child函数,释放了子进程的PCB后又回到自己本身的代码中执行。我们看看运行结果
在这里插入图片描述

我们再来看看signal函数(不是阻塞函数)

signal(参数1,参数2);

参数1:我们要进行处理的信号。系统的信号我们可以再终端键入 kill -l查看(共64个)。其实这些信号是系统定义的宏。

参数2:我们处理的方式(是系统默认还是忽略还是捕获)。

eg: signal(SIGINT ,SIG_ING ); //SIG_ING 代表忽略SIGINT信号

eg:signal(SIGINT,SIG_DFL); //SIGINT信号代表由InterruptKey产生,通常是CTRL +C或者是DELETE。发送给所有ForeGroundGroup的进程。 SIG_DFL代表执行系统默认操作,其实对于大多数信号的系统默认动作是终止该进程。这与不写此处理函数是一样的。

我们也可以给参数2传递一个信号处理函数的地址,但是这个信号处理函数需要其返回值为void,并且默认自带一个int类型参数

这个int就是你所传递的第一个信号参数的值(你用kill -l可以查看)

测试一下,如果创建了5个子进程,但是销毁的时候仍然有两个仍是僵尸进程,这又是为什么呢?

这是因为当5个进程同时终止的时候,内核都会向父进程发送SIGCHLD信号,而父进程此时有可能仍然处于信号处理的deal_child函数中,那么在处理完之前,中间接收到的SIGCHLD信号就会丢失,内核并没有使用队列等方式来存储同一种信号

所以为了解决这一问题,我们需要调用waitpid函数来清理子进程。

void deal_child(int sig_no)
 
{
 
    for (;;) {
 
        if (waitpid(-1, NULL, WNOHANG) == 0)
 
            break;
 
    }  
 
}

这样的话,只有检验没有僵尸进程,他才会返回0,这样就可以确保所有的僵尸进程都被杀死了。具体wait和waitpid之后会发详细的。

4.孤儿进程

所谓的孤儿进程 如其名 就是没有父亲 也就是父进程先于子进程结束
这时候没有父进程帮助子进程释放ID 所以其成为了孤儿进程
孤儿进程会被init进程(也就是ID为1的进程)所接收 帮助其完成后续工作
所以孤儿进程不像僵尸进程一样对系统造成危害 其不占用资源 最终还是被系统所回收
所以一般而言 子进程是先于父进程结束的

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Alex、WY

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值