织梦软件网站模板下载如何找到外包推广公司
织梦软件网站模板下载,如何找到外包推广公司,广西南宁网站策划,wordpress时间中文版目录
进程等待
wait接口
status
waitpid接口
进程替换
exec系列接口
execl接口#xff1a;
execlp接口#xff1a;
execle接口#xff1a;
execv接口#xff1a; 当一个进程死亡后#xff0c;会变成僵尸进程#xff0c;此时进程的PCB被保留#xff0c;等待父进…目录进程等待wait接口statuswaitpid接口进程替换exec系列接口execl接口execlp接口execle接口execv接口当一个进程死亡后会变成僵尸进程此时进程的PCB被保留等待父进程将该PCB回收。那么父进程要如何回收这个僵尸进程的PCB呢父进程通过进程等待的方式来回收子进程的PCB并得知子进程的退出信息。进程等待进程等待用于回收子进程的资源避免子进程的PCB一直占用资源并且可以获取子进程的退出信息得知子进程任务的执行情况进程等待主要通过两个系统调用接口wait和waitpid来完成。wait接口使用wait接口需要包含头文件sys/types.h和sys/wait.h其函数原型为pid_t wait(int* stat_loc);其接收一个int*指针该参数是一个输出型参数用于返回子进程的相关推出信息。而wait的返回值是一个int类型返回值大于0返回等待到的子进程的pid返回值小于0等待失败用一段代码来演示一下#include stdio.h #include unistd.h #include sys/types.h #include sys/wait.h int main() { pid_t id fork(); if(id 0) { int cnt 5; printf(Im child, pid %d\n, getpid()); while(cnt--) { sleep(1); printf(%d\n, cnt); } return 5; } int status 0; int ret wait(status); printf(wait over! status %d, ret %d\n, status, ret); return 0; }展开代码以上代码中先通过fork创建了一个子进程子进程进入if语句进行五秒倒计时然后退出并且退出码为-5。父进程则通过wait函数进行等待传入指针status接收返回值ret最后输出status和ret的值。输出结果首先子进程的pid为4117而wait的返回值就是子进程的pid。其次status一开始被初始化为0wait之后status 1280可知wait确实会修改传入的参数。而这中间还有一个细节那就是子进程总共sleep了五秒而父进程在等待的这五秒中啥事也没干就等着子进程结束然后对它进行回收这个过程父进程处于阻塞状态称为阻塞等待。简单了解wait后那么现在的问题就是status为什么是1280statusstatus要当作一个位图来看灰色部分status是一个int类型占32比特但是后16比特是无效的不填入任何内容黄色部分第8 - 15位共8比特用于表示wait到的子进程的退出码绿色部分第7位core dump标志位本博客不关心该位置蓝色部分第0 - 6位共7比特用户表示wait到的子进程的退出信号那么我们要从status中提取出退出码和退出信号就要对其进行位操作status直接与01111111进行按位与就能得到退出信号01111111的十六进制表示为0X7Fint sig status 0x7F;status右移8位后与11111111进行按位与就能得到退出码11111111的十六进制表示为0XFFint code (status 8) 0xFF;现在在代码的最后加上这样一段int sig status 0x7F; int code (status 8) 0xFF; printf(exit code %d, signal %d\n, code, sig);现在运行一下进程现在我们可以看到子进程的退出码为5退出信号为0了。你也可以尝试在另外一个窗口对进程发送信号看看信号接收是否准确本博客不演示了。Linux还给用户提供了两个宏函数用于检测statusWIFEXITED检测进程是否正常退出返回一个布尔值如果进从正常退出返回真WEXITSTATUS提取子进程的退出码也就是第8 - 15位if(WIFSIGNALED(status)) printf(exit code %d\n, WEXITSTATUS(status)); else printf(子进程退出异常...\n);这样就可以更简单的提取错误码了。waitpid接口进程等待的另外一个接口是waitpid接口需要包含头文件sys/types.h和sys/wait.h其函数原型为pid_t waitpid(pid_t pid, int* stat_loc, int options);相比于wait接口该接口功能更丰富和强大但是使用也更加麻烦。一个进程是可以有多个子进程的一个wait只能等待一个子进程如果有多个子进程那么wait函数等待第一个结束的子进程。而waitpid则是针对pid来对进程进行等待。其第一个参数传入子进程的pid第二个参数用于接收推出信息也就是刚刚的status第三个参数用于控制等待的模式。现在我们先用以下代码来验证一下wait和waitpid的区别#include stdio.h #include unistd.h #include sys/types.h #include sys/wait.h int main() { pid_t id1 fork(); if(id1 0) { printf(Im child1, pid %d\n, getpid()); sleep(5); return 0; } pid_t id2 fork(); if(id2 0) { printf(Im child2, pid %d\n, getpid()); sleep(1); return 0; } int status 0; int ret wait(status); printf(wait over! pid %d\n, ret); sleep(10); return 0; }以上代码中我们通过fork创建了两个子进程第一个子进程输出自己的pid后会sleep五秒而第二个子进程输出pid后sleep一秒。父进程只wait一次最后父进程输出wait的返回值而返回值就是等待到的子进程的pid这样就可以判断wait到了哪一个子进程。输出结果child1的pid 10053child2的pid 10054而wait的返回值为10054说明wait到了第二个进程。因为第二个进程先结束所以被wait先接收了。现在我们把wait改为waitpidint status 0; int ret waitpid(id1, status, 0);现在我们通过waitpid的第一个参数指定等待id1也就是第一个子进程其第三个参数先设为0后续讲解该参数的作用。输出结果这一次返回值和child1匹配上了可以说明虽然child1更晚结束但是waitpid只会等待指定的进程如果有子进程先结束了waitpid也不会回收它。简单了解waitpid后我们再来看看第三个参数。第三个参数用于控制进程等待的模式0进行阻塞等待WNOHANG进行非阻塞等待我在讲解wait时简单提到了阻塞等待也就是父进程在wait的时候什么也不做进入阻塞状态直到wait成功。而非阻塞等待不一样进行非阻塞等待时如果本次waitpid没有等待到那么父进程不会阻塞waitpid直接返回0表示本次等待没有等待到子进程。此时父进程就可以空出时间去完成别的任务而不是傻乎乎地死等了。示例#include stdio.h #include unistd.h #include sys/types.h #include sys/wait.h int main() { pid_t id fork(); if(id 0) { printf(Im child, pid %d\n, getpid()); sleep(5); return 0; } int status 0; while(1) { sleep(1); int ret waitpid(id, status, WNOHANG); if(ret 0) { printf(子进程未结束执行其他任务...\n); //执行其他任务 } else if (ret 0) { printf(wait over! pid %d\n, ret); break; } else { printf(waitpid错误!\n); break; } } return 0; }以上代码中先通过fork创建了一个子进程子进程sleep五秒。父进程陷入一个while死循环每次循环开始都waitpid一次以WNOHANG模式。由于该模式不会阻塞只要当前子进程没有结束那么waitpid直接返回去执行后面的if语句。如果当前返回值为0说明当前子进程没有结束那么父进程可以去做些别的事情一秒后再回来检测子进程有没有结束。如果当前返回值 0说明子进程结束了waitpid也成功了此时返回值就是子进程的pid跳出循环。输出结果子进程一共执行五秒后才退出以非阻塞等待的模式父进程就可以把这五秒拿去做其他事情。进程替换通过fork创建的子进程会继承父进程的代码和数据因此本质上还是在执行父进程的代码。但是我们大部分时候创建子进程的目的是用于执行其它代码的而不是父进程自己的代码那么此时就要有操作让进程去执行其他进程的代码这个操作就叫做进程替换。进程替换可以将别的进程的代码替换到自己的代码区让自己去执行别人的代码。进程替换是通过exec系列系统调用接口实现的。exec系列接口先看看man手册中的execexec系列接口整体还是比较复杂的它们包含在unistd.h中总共有六个接口我们一个一个来讲解。execl接口函数原型如下int execl(const char* pathname, const char* arg, ... /* (char *) NULL */);其接收两个固定的参数pathname和arg以及一个可变参数...也许你先前没了解过这个...就是指可以接收任意个数的参数。pathname用于指定替换的进程的路径arg以何种方式运行进程...以何种方式运行该进程另外的函数声明中还有一小段备注/* (char *) NULL */其意图告诉使用者使用可变参数...时必须以NULL空指针来结尾。也许你现在还不能很好理解这个接口的用法我们先看一个示例当前目录下有一个test.c在dir目录下有一个process.exe进程该进程中的代码如下#include stdio.h int main() { for(int i 0; i 5; i) { printf(I am process.c!\n); } return 0; }也就是说process.exe进程会输出五条I am process.c!现在我们的目的是把进程process.exe替换到test.c中。代码如下#include stdio.h #include unistd.h int main() { printf(execl start!\n); execl(./dir/process.exe, dir/process.exe, NULL); printf(execl over!\n); return 0; }其中execl(dir/process.exe, dir/process.exe, NULL);就是进程替换的语句第一个参数dir/process.exe用于指明该进程的路径第二个参数dir/process.exe它和第一个参数虽然一样但只是一个巧合如果你在当前目录下要运行process.exe你会执行什么样的指令应该就是dir/process.exe也就是说这个参数相当于你在命令行中输入的内容这里只是碰巧路径和命令行输入的内容是一致的第三个参数NULL格式要求以NULL结尾那么我们的代码就完成了先输出execl start!然后替换process.exe到当前进程后输出五条I am process.c!最后输出execl over!是这样吗看看结果可以看到在execl start!之后发送进程替换把process.exe替换到当前进程后输出了五条I am process.c!但是最后一句execl over!消失了。这是因为进程替换不是简单的执行别的进程的代码而是用别的进程的代码区覆盖掉自己原先的代码区所以execl一旦执行整个进程的代码都被替换了那么printf(execl over!\n);就会被覆盖掉最后不输出。刚刚的例子意图展示在自己写的两个进程中发送进程替换。那么我们在shell中执行的指令是不是也是进程呢是的所以我们也可以尝试去替换一些指令当我们自己的进程中比如ls,pwd等指令。现在我们尝试替换系统自带的一些进程到自己的进程中#include stdio.h #include unistd.h int main() { printf(----------- execl start! -----------\n); execl(/usr/bin/ls, ls, -l, -a, NULL); return 0; }我们现在要替换ls指令到自己的进程中ls指令在/usr/bin/ls中我们希望以ls -l -a的形式来调用这个进程因此我们的三个参数ls, -l, -a就是这个指令拆分出来的三个字符串。现在你应该更好地理解了中间这部分参数的作用最后以NULL结尾。输出结果我们成功在当前进程中替换了ls指令并且是以ls -l -a的形式调用的。execlp接口函数原型如下int execlp(const char* file, const char* arg, ... /* (char *) NULL */);file用于指定替换的进程名称arg以何种方式运行进程...运行该进程的选项最后以NULL结尾与刚刚的execl不同的是第一个参数从pathname路径变成了file文件名。该接口的意思是不用指明路径只需指明替换的进程的名称然后会自动去环境变量PATH指定的路径中查找。也就是说可以在系统中直接执行的指令无需指明路径只需要指明文件名就可以替换。示例#include stdio.h #include unistd.h int main() { printf(----------- execl start! -----------\n); execlp(ls, ls, -l, -a, NULL); return 0; }现在我们依然要执行ls -l -a但是我们用了execlp接口ls是系统自带的指令所以不用指明路径系统会自己去查找。ls要替换的进程名称为lsls, -l, -a以ls -l -a形式执行以NULL结尾执行结果和刚才一样我们成功替换了ls指令到当前进程。execle接口函数原型如下int execle(const char *pathname, const char *arg, ... /*, (char *) NULL, char *const envp[] */);从函数原型我们可以看到一些熟悉的参数pathname用于指定替换的进程的路径arg以何种方式运行进程...以何种形式执行进程NULL唯一不同的是要求我们在NULL后面额外加一个char* const envp[]。这个envp是一个指针数组存储的是环境变量。一般来说进程替换后进程的环境变量是会用原先的环境变量的。示例现在我们在process.exe中执行以下代码#include stdio.h int main(int argc, char* argv[], char* env[]) { for(int i 0; env[i] ! NULL; i) { printf(%s\n, env[i]); } return 0; }process.exe会输出所有的环境变量然后我们再在test.c中替换这个进程#include stdio.h #include unistd.h int main() { printf(----------- execl start! -----------\n); execl(dir/process.exe, dir/process.exe, NULL); return 0; }输出结果test.c输出了一句----------- execl start! -----------后就去替换了process.exe随后输出了默认的环境变量表。而execle可以给替换后的进程指定环境变量表。示例#include stdio.h #include unistd.h int main() { printf(----------- execl start! -----------\n); char* const envp[] {Aaaa, Bbbb, NULL}; execle(dir/process.exe, dir/process.exe, NULL, envp); return 0; }我自己伪造了一个环境变量表envp并把它作为最后一个参数传递给替换后的进程。输出结果可以看到此时替换后的进程环境变量表就变成了我们指定的变量表。接下来我带大家回顾一下以上三个接口execl指定路径进行进程替换execlp指定文件名进行进程替换execle指定路径进行进程替换并给替换后的进程指定环境变量表字符含义p用文件名代替路径到环境变量PATH指定的路径查找e指定环境变量看到后面的三个接口可以看到一些熟悉的身影int execv(const char *pathname, char *const argv[]); int execvp(const char *file, char *const argv[]); int execvpe(const char *file, char *const argv[], char *const envp[]);除去v字符p和e的功能我们都了解那么我就只以execv为案例execv接口函数原型如下int execv(const char *pathname, char *const argv[]);相比于execl其少了一个...的可变参数改为了一个argv数组而...就是用来指定以何种方式调用进程或者说指定选项的带有v系列的接口将这些选项存储在一个数组中然后把数组传入。示例#include stdio.h #include unistd.h int main() { printf(----------- execl start! -----------\n); char* const argv[] {ls, -l, -a, NULL}; execv(/usr/bin/ls, argv); return 0; }我希望以ls -l -a形式调用ls于是把ls-l-a三个字符串存储到数组argv中并以NULL结尾。字符含义llist以列表的形式把选项一个一个以参数形式传入vvector以数组的形式把选项都存在数组中将整个数组传入汇总一下六个接口//list系列 int execl(const char* pathname, const char* arg, ... /* (char *) NULL */); int execlp(const char* file, const char* arg, ... /* (char *) NULL */); int execle(const char *pathname, const char *arg, ... /*, (char *) NULL, char *const envp[] */); //vector系列 int execv(const char *pathname, char *const argv[]); int execvp(const char *file, char *const argv[]); int execvpe(const char *file, char *const argv[], char *const envp[]);字符含义p用文件名代替路径到环境变量PATH指定的路径查找e指定环境变量llist以列表的形式把选项一个一个以参数形式传入vvector以数组的形式把选项都存在数组中将整个数组传入