防伪查询网站,个人网站对应网站网址,网站开发了解客户需求,域名网站排名W...Y的主页 #x1f60a;
代码仓库分享#x1f495; 目录
编辑
进程等待
进程等待必要性
进程等待的方法
wait方法
waitpid方法
获取子进程status 阻塞与非阻塞
进程程序替换
替换原理
替换函数 进程等待
进程等待必要性
之前讲过#xff0c;子进程退出
代码仓库分享 目录
编辑
进程等待
进程等待必要性
进程等待的方法
wait方法
waitpid方法
获取子进程status 阻塞与非阻塞
进程程序替换
替换原理
替换函数 进程等待
进程等待必要性
之前讲过子进程退出父进程如果不管不顾就可能造成‘僵尸进程’的问题进而造成内存泄漏。 另外进程一旦变成僵尸状态那就刀枪不入“杀人不眨眼”的kill -9 也无能为力因为谁也没有办法杀死一个已经死去的进程。 最后父进程派给子进程的任务完成的如何我们需要知道。如子进程运行完成结果对还是不对或者是否正常退出。 父进程通过进程等待的方式回收子进程资源获取子进程退出信息
上篇博客中我们讲到进程退出时有两个非常重要的信息退出信号和退出码。当我们进程退出时退出信息会被放入进程的PCB中进行保存等待父进程的回收。
因为进程拥有独立性所以我们想通过参数或返回值将信息转交给父进程那是不可能的所以我们才要进行回收资源。
进程等待的方法
wait方法
返回值 成功返回被等待进程pid失败返回-1。 参数输出型参数获取子进程退出状态,不关心则可以设置成为NULL
下面是测试子进程变僵尸后wait是否进行回收资源 我们可以看出父进程可以将僵尸进程回收
waitpid方法 返回值当正常返回的时候waitpid返回收集到的子进程的进程ID 如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0 如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在参数pid Pid-1,等待任一个子进程。与wait等效。 Pid0.等待其进程ID与pid相等的子进程。status: WIFEXITED(status): 若为正常终止子进程返回的状态则为真。查看进程是否是正常退出 WEXITSTATUS(status): 若WIFEXITED非零提取子进程退出码。查看进程的退出码options: WNOHANG: 若pid指定的子进程没有结束则waitpid()函数返回0不予以等待。若正常结束则返回该子进程的ID。
#include stdio.h
#include stdlib.h
#include unistd.h
#include sys/types.h
#include sys/wait.hint main()
{pid_t id fork();if(id 0){// childint cnt 5;while(cnt){printf(Child is running, pid: %d, ppid: %d\n, getpid(), getppid());sleep(1);cnt--;}exit(1);}int status 0;pid_t rid waitpid(id, status, 0); // 阻塞等待if(rid 0){printf(wait success, rid: %d, status: %d\n, rid, status);}} 如果子进程已经退出调用wait/waitpid时wait/waitpid会立即返回并且释放资源获得子进程退出信息。 如果在任意时刻调用wait/waitpid子进程存在且正常运行则进程可能阻塞。 如果不存在该子进程则立即出错返回。 获取子进程status
wait和waitpid都有一个status参数该参数是一个输出型参数由操作系统填充。 如果传递NULL表示不关心子进程的退出状态信息。 否则操作系统会根据该参数将子进程的退出信息反馈给父进程。 status不能简单的当作整形来看待可以当作位图来看待具体细节如下图只研究status低16比特位
当我们的子进程exit(1)时子进程的退出状态status为256。256代表什么呢 任何进程最终执行状态我们可以使用两个数字具体表明情况 我们为了直观一点可以使用位操作将退出信号和退出码分别打印出来
printf(wait success, rid: %d, status: %d, exit signo: %d, exit code: %d\n, rid, status, status0x7F, (status 8)0xFF); 我们也可以使用宏来获取退出信号与退出码
int main()
{pid_t id fork();if(id 0){// childint cnt 5;while(cnt){printf(Child is running, pid: %d, ppid: %d\n, getpid(), getppid());sleep(1);cnt--;}exit(1);}int status 0;pid_t rid waitpid(id, status, 0); // 阻塞等待if(WIFEXITED(status)){printf(wait success, rid: %d, status: %d, exit code: %d\n, rid, status, WEXITSTATUS(status));}} 阻塞与非阻塞
在waitpid函数中我们发现在上面的代码中我们一直默认第三个参数为0。其就是对应的阻塞状态。而WNOHANG这个宏在第三个参数中可以进行填入代表非阻塞等待。
阻塞等待时父进程不能做任何事情只能等待子进程变成僵尸进程后进行回收。而非阻塞等待可以轮转时进行行为。
非阻塞等待代码
int main()
{pid_t id fork();if(id 0){// childint cnt 5;while(cnt){printf(Child is running, pid: %d, ppid: %d\n, getpid(), getppid());sleep(1);cnt--;}exit(1);}int status 0;pid_t rid waitpid(id, status, WNOHANG); while(1){if(rid 0){printf(wait success, rid: %d, status: %d, exit code: %d\n, rid, status, WEXITSTATUS(status));break;}else if(rid 0){printf(father say: child is running, do other thing\n);}else{perror(waitpid);break;}}return 0;
}
进程程序替换
替换原理
用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。 当我们要进行替换时新程序的数据和代码会将原来物理内存中的数据段和代码段进行替换。其实程序替换工作本质就是加载
替换函数
其实有六种以exec开头的函数,统称exec函数 #include unistd.h int execl(const char *path, const char *arg, ...); int execlp(const char *file, const char *arg, ...); int execle(const char *path, const char *arg, ...,char *const envp[]); int execv(const char *path, char *const argv[]); int execvp(const char *file, char *const argv[]); int execve(const char *path, char *const argv[], char *const envp[]);
这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回。 如果调用出错则返回-1所以exec函数只有出错的返回值而没有成功的返回值。
这些函数原型看起来很容易混,但只要掌握了规律就很好记。 l(list) : 表示参数采用列表 v(vector) : 参数用数组 p(path) : 有p自动搜索环境变量PATH e(env) : 表示自己维护环境变量 我们强调一下结尾带e的exec*的函数其就是表示自己维护环境变量。我们想要子进程全部继承父进程的全部环境变量直接可以。如果单纯再父进程的环境变量中添加一些环境变量可以使用putenv函数。但是我们子进程如果想要自己拥有一个全新的环境变量我们可以使用exec*函数中后面带e的他会将父进程继承的环境变量覆盖掉
#include stdio.h
#include stdlib.h
#include unistd.h
#include sys/types.h
#include sys/wait.hint main()
{char *const env[] {(char*)hahahehe,(char*)PATH/,NULL};printf(I am a process, pid: %d\n, getpid());//putenv(MYVALbbbbbbbbbbbbbbbbbbbbbbbbbbbb);pid_t id fork();if(id 0){//extern char**environ;sleep(1);//execle(./mytest, mytest, NULL, environ); // 我们传递环境变量表了吗no. 子进程默认就拿到了.他是怎么做到的execle(./mytest, mytest, NULL, env); // 我们传递环境变量表了吗no. 子进程默认就拿到了.他是怎么做到的//execl(/usr/bin/python3, python3, test.py, NULL);//execl(/usr/bin/bash, bash, test.sh, NULL);//execl(./mytest, mytest, NULL); // 我们传递环境变量表了吗no. 子进程默认就拿到了.他是怎么做到的//char *const argv[] {// (char*)ls,// (char*)-a,// (char*)-l//};//sleep(3);//printf(exec begin...\n);//execvp(ls, argv);//execv(/usr/bin/ls, argv);//execl(/usr/bin/ls, ls, -a, -l, NULL); //NULL 不是 NULL//execlp(ls, ls, -a, -l, NULL); //NULL 不是 NULL//execl(/usr/bin/top, /usr/bin/top, NULL); //NULL 不是 NULLprintf(exec end ...\n);exit(1);}pid_t rid waitpid(id, NULL, 0);if(rid 0){printf(wait success\n);}exit(1);
} exec调用举例如下:
#include unistd.h
int main()
{
char *const argv[] {ps, -ef, NULL};
char *const envp[] {PATH/bin:/usr/bin, TERMconsole, NULL};
execl(/bin/ps, ps, -ef, NULL);
// 带p的可以使用环境变量PATH无需写全路径
execlp(ps, ps, -ef, NULL);
// 带e的需要自己组装环境变量
execle(ps, ps, -ef, NULL, envp);
execv(/bin/ps, argv);
// 带p的可以使用环境变量PATH无需写全路径
execvp(ps, argv);
// 带e的需要自己组装环境变量
execve(/bin/ps, argv, envp);
exit(0);
} 事实上,只有execve是真正的系统调用,其它五个函数最终都调用 execve,所以execve在man手册 第2节,其它函数在man手册第3节。这些函数之间的关系如下图所示。 总结 细节1程序替换一旦成功exec*后续代码不再执行因为被替换掉了。 细节2exec*只有失败有返回值没有成功返回值。 细节3替换完成不再创建新的程序。 细节4创建一个进程先创建PCB、地址空间、页表再将程序加载到内存中。 这些函数功能上没有任何区别区别就在于传递的参数不同 以上就是全部内容感谢大家观看