EXEC函数
execl进程功能重载。
execl函数有很多个组成,做的事情都是差不多的。
开发者可以使用它,加载一个进程的用户空间(读取一个现有程序的功能),赋予子进程。
一个进程的核心内容都在用户层。而不是内核层。
如果想让子进程有浏览器进程,那么就可以把浏览器贴到子进程里。
也就是说,直接拿过来用,贴给子进程。
例如将一个父进程processA,没有浏览器功能,但是想给一个,因此使用execl()函数,浏览器会有路径,例如是/bin/browser,那么就利用execl函数,启动加载浏览器程序的用户层,浏览器也有用户层,函数就可以读取浏览器的用户层,然后复制给自己,用的函数是load(覆盖掉进程原有的用户层),此时,我们的processA进程,就可以使用浏览器了。
此时,重载完毕,该进程具备了浏览器特征以及功能。
可以对现成的数据进行重载,就可以直接拿过来了。
execl()函数
用which查看路径.
execl("/bin/browser", "firefox", "https://www.baidu.com", NULL);
参数最后必须传NULL,否则不能执行。
参数:
- 程序位置,就是绝对路径
- 参数数量根据需求或命令数量不同,因为每个命令或进程的命令参数不同,例如ls有一个,浏览器有两个(假设)
- 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,创建之后,才能创建进程。
危害:
- 内存泄漏危害,占用系统内存,消耗资源
- 进程创建的前提是有可用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进行异常退出。
就返回了异常退出的信息。