【操作系统】6/35子进程

EXEC函数

execl进程功能重载。

execl函数有很多个组成,做的事情都是差不多的。

开发者可以使用它,加载一个进程的用户空间(读取一个现有程序的功能),赋予子进程。

一个进程的核心内容都在用户层。而不是内核层。

如果想让子进程有浏览器进程,那么就可以把浏览器贴到子进程里。

也就是说,直接拿过来用,贴给子进程。

例如将一个父进程processA,没有浏览器功能,但是想给一个,因此使用execl()函数,浏览器会有路径,例如是/bin/browser,那么就利用execl函数,启动加载浏览器程序的用户层,浏览器也有用户层,函数就可以读取浏览器的用户层,然后复制给自己,用的函数是load(覆盖掉进程原有的用户层),此时,我们的processA进程,就可以使用浏览器了。

此时,重载完毕,该进程具备了浏览器特征以及功能。

可以对现成的数据进行重载,就可以直接拿过来了。


execl()函数

用which查看路径.

execl("/bin/browser", "firefox", "https://www.baidu.com", NULL);

参数最后必须传NULL,否则不能执行。

参数:

  1. 程序位置,就是绝对路径
  2. 参数数量根据需求或命令数量不同,因为每个命令或进程的命令参数不同,例如ls有一个,浏览器有两个(假设)
  3. execl每个参数都是字符串

用which找到浏览器的绝对路径,然后用execl函数在子进程中编写。

execl("/usr/bin/firefox","firefox","www.baidu.com",NULL);
exit(0);

因此在执行之后,先父进程进行子进程创建,然后子进程进入else if,实现execl函数,也就是进入了浏览器的百度页面。

因此,子进程就是浏览器。子进程是重载出来的浏览器。

exec是一个覆盖,进行了重载操作之后,原来的内容就彻底消失了。

execl:如果子进程有自定义任务和工作,需要在fork之后,和exec之前完成。也就是pid=0的里面。

正是因为exec之后,子进程就被覆盖了。exec就不能执行原有的功能了。

因此,exit(0)这句话根本没有执行,我们进程的退出是关闭浏览器的操作才退出的。


execlp()

是exec函数变体。

区别:就是第一个参数并不用放置绝对路径了,因此,直接写进程名字就好了,并没有什么区别。

execv()

没有l了,l是每一个参数都是一个字符串。

但是v是数组,都是放入数组里。

const char* array[]={"firefox","www.baidu.com"};
execv("/usr/bin/firefox",array);

作用和效果都是一样的。根据用户喜好。

execvp()

两种的结合。

execvp("firefox", array);

execle(), execve()也是两种新的函数,只是细微的变化。


关键字

l:每个参数都是字符串。

v:这种exec接口需要用户将命令参数封装在数组中,传参时传入数组首地址。

p:path,,可以不用填写绝对位置,可以自行查找程序位置。

e:进程创建时,有环境变量,系统有一个环境变量表,进程创建后,系统会拷贝一份环境变量给进程。

e表示的就是自定义环境变量,替换进程默认的环境变量。


僵尸进程

正常fork的流程之后,如果查看进程状态,会发现,父进程是./app,但是子进程却是:[app] <defunct>,父进程状态是S+,是睡眠,而子进程是Z+,是Linux中的僵尸进程。

丧尸进程:人——病毒致死——结束——攻击性极强(危害)

可以用来描述这个僵尸进程。

进程——异常原因——内存泄漏(危害)

是因为没有及时处理。

这些问题的解决方案就是:回收处理

例子:两个进程,父子pc。

描述:子进程先于父进程结束,然后父进程未对子进程进行有效的回收操作,子进程变成僵尸进程,导致内存泄漏。

与孤儿进程不同:是父进程先于子进程结束。

子先死,是僵尸,父先死,是孤儿。

exit函数的底层有另一个函数:_EXIT函数,负责释放回收进程资源的。是系统内核kernel负责的,执行并释放。

完全释放用户层,不会有残留。

但是内核空间,并没有回收干净,pcb没有去动。内核层部分回收。

因此,PCB残留,就是内存泄漏。危害是最小的。

pcb不小,有的东西还有额外的内容。

进程创建前提:有可用pcb。

也就是说,你得能先创建出一个pcb,创建之后,才能创建进程。

危害:

  1. 内存泄漏危害,占用系统内存,消耗资源
  2. 进程创建的前提是有可用pcb,如果僵尸进程过多,占用pcb,影响进程创建

wait函数

回收作用,回收函数。

用来处理pcb。

pid_t zpid=wait(NULL)

wait函数是一个阻塞函数,阻塞回收僵尸进程(的PCB),成功一次回收一个pcb,而不是调用一次全部回收,如果回收1000个就调用1000次。

成功返回僵尸进程pid,失败返回-1,绝大多数不会失败,如果没有子进程调用了wait,就失败了。

头文件<sys/wait.h>

此时执行完wait函数,僵尸进程消失了。

zpid=wait(NULL);
if(zpid>0){
    printf("zpid is %d\n",zpid);
}

wait函数会等待子进程结束之后,再退出。

也就是说是阻塞函数,父进程到wait之后阻塞了,子进程退出之后开始执行父进程。

为什么内核不把pcb回收掉?

pcb让父进程回收是有原因的,例如子意外了,父就需要处理,但是子的遗骸不可能会消失,也就是说,必须父进程回收,父进程可以通过pcb分析出子进程回收的愿意的。

父进程是子进程的直接负责人。内核并没有这个权限。

退出信息存在PCB中。

wait(int* status),验shi信息存入status,NULL就不进行验。

如果父进程需要对子进程进行退出校验,那么wait函数必须使用status参数传出子进程退出信息。

WIFEXITED(status)/WEXITSTATUS(status)

status退出信息,系统会提供一些校验函数:

WIFEXITED(int* status)

exit(0)和return 0都属于正常的退出。正常退出范畴。

if(WIFEXITED(status))

如果是真的,就是正常退出,反之是假的。

获取退出码:WEXITSTATUS(status),一般是上一个是真的,才会调用这个。

返回status的退出码或返回值。

WIFSIGNALED(status)/WTERMSIG(status)

除了正常退出,还会有异常退出,是信号查找。

Linux经典是信号,一般都是用于杀死进程。

WIFSIGNALED(status),判断是否是信号杀死,返回真假。

WTERMSIG(status),返回杀死子进程的信号编号。

子进程退出校验

int main(void){
	pid_t pid;
	int i=0;
	for(i;i<2;++i){
		pid=fork();
		if(!pid)break;
	}
	if(pid>0){
		pid_t zpid;
		int status;
		printf("im father, my pid is %d\n,im waiting...",getpid());
		while((zpid=wait(&status))>0){
			//PCB推出校验
			if(WIFEXITED(status))printf("zombie pid %d return value exitcode %d\n",zpid,WEXITSTATUS(status));
			if(WIFSIGNALED(status))printf("zombie pid %d kill signal NO %d\n",zpid,WTERMSIG(status));
		}
	}else if(!pid){
		if(!i){
			printf("im child%d,my pid is %d\n,im sleeping...",i,getpid());
			sleep(2);
			exit(0);//正常推出
		}else{
			//异常推出
			while(1){
				printf("im child%d,my pid is %d\n",i,getpid());
				sleep(2);
			}
		}
	}else{
		perror("call failed\n");
		exit(1);
	}
	return 0;
}

结果就是,0号子进程通过正常的方式回收并退出。

但是1号子进程一直在执行,我们通过kill -11 zpid进行异常退出。

就返回了异常退出的信息。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值