进程等待
前面我们了解了如果父进程没有回收子进程, 那么当子进程接收后, 就会一直处于僵尸状态, 导致内存泄漏, 那么我们如何让父进程来回收子进程的资源.
waitpid
我们可以通过 linux 提供的系统调用函数 wait 系列函数来等待子进程死亡, 并回收资源.
wait
函数用于等待任何一个子进程结束, 并回收其资源.
status
:指向整数的指针, 用于存储子进程的退出状态. 如果不需要这个信息, 可以传递null.
- 成功时返回被等待的子进程的pid, 失败时返回 -1, 并设置 errno.
waitpid函数允许父进程等待特定的子进程结束
- pid:子进程的pid. 如果为 -1, 则等待任何一个子进程。
- status:同wait函数.
- options:等待选项, 常用的有 wnohang (非阻塞等待).
- 成功时返回被等待的子进程的pid. 失败时返回-1,并设置errno。
一般来说, 用 waitpid 多一点, 因为 waitpid 提供的更为细致的操作.
int main() { pid_t id = fork(); if(id<0) { perror("fork"); exit(1); } if(id==0)//子进程代码 { int count = 5; while(count) { printf("[%d]我是子进程,我的pid是: %d\n",count,getpid()); sleep(1); count--; } exit(0);//子进程执行完代码后退出, exit 会直接终止本进程 } //父进程代码 waitpid(id,null,0); printf("等待子进程成功!\n"); return 0; }
可以观察到, 在等待子进程结束之前, 父进程卡在了 waitpid(), 直到子进程都被等待成功, 父进程才会继续向后执行.
status 参数
在 wait 和 waitpid 函数中都存在一个 status 的参数.
在 status 中存储着子进程的退出码和退出信号.
如果父进程想要了解子进程的退出信息, 可以通过 status 来了解.
status 是一个 int 类型的变量, 一共有 32 个bit, 我们主要看它的低 16 位bit.
那么如何获取退出状态 (退出码) 和 信号
退出状态 (退出码): (status >> 8) & 0xff 退出信号: status & 0x7f
int main() { pid_t id = fork(); if(id<0) { perror("fork"); exit(1); } if(id==0)//子进程代码 { int count = 5; while(count) { printf("[%d]我是子进程,我的pid是: %d\n",count,getpid()); sleep(1); count--; } exit(55);//子进程执行完代码后退出 } //父进程代码 int status = 0; waitpid(id,&status,0); printf("等待子进程成功!\n"); printf("进程退出码: %d,进程退出信号: %d\n",(status >> 8) & 0xff,status & 0x7f); return 0; }
当子进程在运行时, 使用 kill -9 617714命令, 来终止子进程, 那么也就能观察到, 退出信号为 9.
option
在前面的参数解释中说到, 这是一个等待选项.
父进程可以选择一直阻塞下去, 直到等到子进程的死亡,
或者当子进程还没死亡时, 父进程去执行其他的代码. 等一会再来查看子进程是否死亡.
waitpid(pid,&status,wnohang); // 非阻塞等待 waitpid(pid,&status,0); // 阻塞等待
int main() { pid_t id = fork(); if(id<0) { perror("fork"); exit(1); } if(id==0)//子进程代码 { int count = 5; while(count) { printf("[%d]我是子进程,我的pid是: %d\n",count,getpid()); sleep(1); count--; } exit(55);//子进程执行完代码后退出 } //父进程代码 while(1)//循环访问子进程退出情况 { int wait = waitpid(id,null,wnohang); if(wait>0)//子进程退出成功 { printf("子进程退出成功,子进程pid: %d\n",wait); break; } else if(wait==0)//子进程还没退出,父进程干自己的事情 { //此处简单模拟父进程干的事情 printf("我是父进程\n"); } else //等待子进程退出失败 { perror("waitpid"); exit(1); } sleep(1); } return 0; }
执行上面的代码就能观察到, 在子进程结束前, 父进程还在向频幕上打印文字.
进程替换
创建子进程是为了完成一些工作, 但是子进程的代码和父进程是一样的,
有可能子进程并不需要执行父进程的代码, 而是执行一些其他代码.
这种场景下, 就可以使用进程替换.
我们为什么不直接将子进程要执行的代码写在父进程中呢, 还要去使用进程替换?
1. 如果所有的代码都放在父进程中, 那么父进程的代码会有多么的庞大,
这会提高代码编写和维护的成本.
2. 进程所执行的一定是我们的c/c++程序吗? 进程也可以执行其他的非 c/c++ 程序,
那对于那些非 c/c++ 程序 (java程序), 我们无法将他们和我们所写的 c/c++ 代码整合在一起, java 和 c/c++ 的运行环境都不同.
exec 系列函数
如果想要创建出来的子进程执行全新的程序, 可以使用 exec 系列函数进行程序替换.
一共有6个函数, 其中主要分为两类
1. execl 系列
2. execv 系列
execl
int main() { printf("进行程序替换了\n"); int n = execl("/usr/bin/ls","ls","-a","-l",null); if(n==-1) { perror("execl"); } printf("程序替换完毕!\n"); return 0; }
execl参数: 第一个是要执行程序的路径 (/usr/bin/ls),
第二个参数是要执行的程序的名称 (ls),后面的参数到 null 之前, 都是要替换的程序参数 (-a, -l, 都是 ls 程序的参数).
execl 中 l, 表示如何将参数传递要替换的程序. l 表示通过一个列表的方式,
即向上面的 "-l", "-a"..., 一个列表的形式.
execlp 和 execle 两个函数则分别多了 p 和 e.
p 则代表要执行的程序可以从环境变量 path 中找到, 所以不用写执行程序的路径.
e 则表示, 可以传入用户自己定义的环境变量 (_env[]) 给程序使用.
int main() { printf("我要进行程序替换了...\n"); int n = execlp("ls","-l",null); if(n==-1) { perror("execl"); } printf("程序替换完毕!\n"); return 0; } int main() { const char* _env[]={"my_env=666",null}; printf("我要进行程序替换了...\n"); int n = execle("/usr/bin/ls","ls","-l",null,_env);//自己定义一个环境变量my_env=666传递给要去执行的程序 if(n==-1) { perror("execl"); } printf("程序替换完毕!\n"); return 0; }
execv
上面的 execl 中的 l, 代表传参使用列表的形式.
那么 v 很容易就想到了是vector.
所以 execv 函数在给替换的程序传参时, 是通过一个 vector 来传参的.
int main() { char* const set[]={"ls","-a","-l",null}; printf("我要进行程序替换了...\n"); int n = execv("/usr/bin/ls",set); if(n==-1) { perror("execl"); } printf("程序替换完毕!\n"); return 0; }
那么剩下的 execvp 和 execvpe 和之前的 execl 系列中的一样.
p 代表在环境变量 path 中查找, e 可以传入自己定义的环境变量.
- l (list): 传参的方式为使用列表来传递
- v (vector): 使用数组来传递参数
- p (path): 会在环境变量 path 中查找程序
- e (env): 可以传递自己定义的环境变量
以上就是linux进程等待和进程替换详解的详细内容,更多关于linux进程等待和替换的资料请关注代码网其它相关文章!
发表评论