一、进程等待
在之前笔者介绍过进程状态相关内容 , 其中进程等待和进程状态部分相关联 , 以下具体介绍 !
● 是什么 ?
是指一个进程因为某些原因暂时无法继续执行,必须等待某个条件满足或某个事件发生后才能继续执行的状态 。
光看概念还是很抽象的 , 以下所有部分会详细介绍 !
● 为什么有进程等待 ? (重要)
这里就和进程状态部分有关了 , 若需要具体了解进程状态部分 , 请看 : 进程状态详解

● 等待方式(重要)

1 . wait()

这里先看一个代码 : 子进程正常退出 , 父进程没有回收资源 , 子进程僵尸了
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
//验证wait 可以解决僵尸问题
int main()
{
printf("我是父进程 , 我的 pid : %d , ppid : %d\n",getpid(),getppid());
//创建子进程
pid_t id = fork();
if(id == 0)
{
// 子进程走 5 s
int cns = 5;
while(cns)
{
printf("我是子进程 , 我的 pid : %d , ppid : %d\n",getpid(),getppid());
sleep(1);
cns--;
}
// 子进程正常退出
exit(0);
}
// 父进程不退出 , 也不回收子进程 pcb
while(1)
{
//father
printf("我是父进程 , 我的 pid : %d\n",getpid());
sleep(1);
}
return 0;
}

● 父进程 wait() 资源 , 即 : 回收资源 !
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
///// 第一次演示 wait , 验证wait 可以解决僵尸问题
int main()
{
printf("我是父进程 , 我的 pid : %d , ppid : %d\n",getpid(),getppid());
//创建子进程
pid_t id = fork();
if(id == 0)
{
// 子进程走 5 s
int cns = 5;
while(cns)
{
printf("我是子进程 , 我的 pid : %d , ppid : %d\n",getpid(),getppid());
sleep(1);
cns--;
}
// 子进程正常退出
exit(0);
}
sleep(10);
// 父进程不退出 , 回收资源
pid_t rid = wait(NULL); // NULL 意思是不获取退出信息
if(rid > 0)
{
printf("success !\n");
}
sleep(30);
return 0;
}
所以 , 通过这里可以看到子进程的僵尸状态就不存在了 , 因为父进程回收了子进程的资源 , 这也是标准的用法 , 父进程需要回收资源的 !!!
2 . waitpid()



● 父进程 waitpid() 成功
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main()
{
printf("我是父进程 , 我的 pid : %d , ppid : %d\n",getpid(),getppid());
//创建子进程
pid_t id = fork();
if(id == 0)
{
// 子进程走 5 s
int cns = 5;
while(cns)
{
printf("我是子进程 , 我的 pid : %d , ppid : %d\n",getpid(),getppid());
sleep(1);
cns--;
}
// 子进程正常退出
exit(0);
}
sleep(10);
// any 不获取 阻塞
pid_t rid = waitpid(-1 , NULL , 0 );
if(rid > 0)
{
printf("wait success ! , rid = %d\n",rid);
}
sleep(20);
return 0;
}

返回的是子进程的 ID !!!
● 父进程 waitpid() 失败
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <errno.h>
#include <string.h>
// errno 存的是错误码 , strerrno 打印错误码信息 !
int main()
{
printf("我是父进程 , 我的 pid : %d , ppid : %d\n",getpid(),getppid());
//创建子进程
pid_t id = fork();
if(id == 0)
{
// 子进程走 5 s
int cns = 5;
while(cns)
{
printf("我是子进程 , 我的 pid : %d , ppid : %d\n",getpid(),getppid());
sleep(1);
cns--;
}
// 子进程正常退出
exit(0);
}
sleep(5);
int wstatus = 0;
//父进程等待 , waitpid 等待错误
pid_t rid = waitpid(id+1 , &wstatus , 0);
if(rid > 0)
{
printf("Wait Success ! , rid : %d\n",rid);
}
else
{
printf("Wait Failed ! , err coed : %d -> %s , rid : %d\n",errno,strerror(errno),rid);
}
sleep(10);
return 0;
}

失败返回的是 -1 , 并同时设置错误码 !!
● 详解 wait 系列接口的参数 wstatus(重要)

1 . wstatus 到底是怎么获取子进程的退出状态信息的 ??

所以 , 系统会写入父进程中定义的 wstatus 中的次低 8 位比特位用来表示该进程的退出状态(退出码) .
2 . 父进程 wstatus 查看退出状态
- 用宏 - WEXITSTATUS(wstatus)
- 手动 - ( wstatus >> 8 ) & 0XFF
- 等宏查看 man waitpid 手册说明 !
这里看一个代码 :
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <wait.h>
#include <stdlib.h>
int main()
{
pid_t id = fork();
if(id == 0)
{
printf("我是子\n");
//退出码设置为 1
exit(1);
}
//父进程等待获取子进程退出信息
int wstatus = 0;
pid_t rid = waitpid(id,&wstatus , 0);
if(rid > 0)
{
printf("Success ! rid : %d , exit code : %d\n",rid , wstatus);
}
return 0;
}
运行结果 :

?????????? 退出码怎么是 256 , 不是说 wstatus 就会带回退出状态吗 ?? 子进程的退出码明明是 1 啊 , 怎么回事 ??????


#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <wait.h>
#include <stdlib.h>
int main()
{
pid_t id = fork();
if(id == 0)
{
printf("我是子\n");
// int num = 1/0;
//退出码设置为 1
exit(100);
}
//父进程等待获取子进程退出信息
int wstatus = 0;
pid_t rid = waitpid(id,&wstatus , 0);
if(rid > 0)
{
// 这个计算下来就是退出状态
// 因为退出信号就在低 8 位 , 所以直接 & 0x7F
printf("Success ! rid : %d , exit code : %d , exit signal : %d\n", rid , (wstatus>>8) & 0XFF , wstatus&0X7F);
printf("Success ! rid : %d , exit code : %d , exit signal : %d\n", rid , WEXITSTATUS(wstatus) , wstatus&0X7F);
//查看子进程是否正常退出
printf("%d\n" , WIFEXITED(wstatus));
}
return 0;
}
● 详解 waitpid 中的 optinons (重要)
这个表示的是选项 , 即 : 阻塞选项 !
1 . 阻塞
这个很好理解 , 阻塞就是停滞在这里 , 这里就好比我们写了 scanf () 函数 , 会停下来等用户输入 !

2 . 非阻塞轮询

3 . options

二、进程的阻塞等待和非阻塞等待
阻塞等待 :
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <errno.h>
#include <string.h>
//////// 阻塞等待 - 父进程啥也不干
int main()
{
printf("我是父进程 , 我的 pid : %d , ppid : %d\n",getpid(),getppid());
//创建子进程
pid_t id = fork();
if(id == 0)
{
// 子进程走 5 s
int cns = 5;
while(cns)
{
printf("我是子进程 , 我的 pid : %d , ppid : %d\n",getpid(),getppid());
sleep(1);
cns--;
}
// 子进程正常退出
exit(0);
}
sleep(5);
int wstatus = 0;
//父进程等待
pid_t rid = waitpid(id , &wstatus , 0);
if(rid > 0)
{
printf("Wait Success ! , exit code : %d , rid : %d\n", WEXITSTATUS(wstatus) ,rid);
}
else
{
printf("Wait Failed ! , err coed : %d -> %s , rid : %d\n",errno,strerror(errno),rid);
}
sleep(10);
return 0;
}
非阻塞等待 :(了解)
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <errno.h>
#include <string.h>
// 函数指针类型
typedef void (*func_t)();
// 任务表数组
#define NUM 5
func_t handlers[NUM+1];
// 如下是任务
void DownLoad()
{
printf("我是一个下载的任务...\n");
}
void Flush()
{
printf("我是一个刷新的任务...\n");
}
void Log()
{
printf("我是一个记录日志的任务...\n");
}
// 注册每个任务 任务表 注册的任务函数
void registerHandler(func_t h[], func_t f)
{
int i = 0;
for(; i < NUM; i++)
{
if(h[i] == NULL) break;
}
if(i == NUM) return;
h[i] = f;
h[i+1] = NULL;
}
int main()
{
// 开始注册任务
registerHandler(handlers, DownLoad);
registerHandler(handlers, Flush);
registerHandler(handlers, Log);
//创建子进程
pid_t id = fork();
if(id == 0)
{
// 子进程走 5 s
int cns = 5;
while(cns)
{
printf("我是子进程 , 我的 pid : %d , ppid : %d\n",getpid(),getppid());
sleep(1);
cns--;
}
// 子进程正常退出
exit(0);
}
while(1)
{
int wstatus = 0;
//父进程等待 , 非阻塞
pid_t rid = waitpid(id , &wstatus , WNOHANG);
// 走这个证明等到了 !!!
if(rid > 0)
{
printf("Wait Success ! , exit code : %d , rid : %d\n", WEXITSTATUS(wstatus) ,rid);
break; // 等待结束 , 跳出
}
// 等待的进程状态没有改变 , 但存在该进程
else if(rid == 0 )
{
// 这里面做自己的事情
// 函数指针进行回调处理 , 开始做注册表中的每个任务 !!!!!
int i = 0;
for(; handlers[i]; i++)
{
// 函数回调
handlers[i]();
}
printf("本轮调用结束,子进程没有退出\n");
sleep(1);
}
// 等待失败
else
{
printf("Wait Failed ! , err coed : %d -> %s , rid : %d\n",errno,strerror(errno),rid);
break; // 等待失败 , 跳出 !
}
}
return 0;
}

三、进程的程序替换(重要)
进程的程序替换也是需要我们重点掌握的内容, 其还是有点用的 !
● 是什么 ?
fork() 之后,父子各自执行父进程代码的一部分如果子进程就想执行一个全新的程序呢?进程的程序替换来完成这个功能!
进程的程序替换(Process Program Replacement)是指在Linux系统中,一个正在运行的进程通过使用exec 系列函数系列来执行另一个程序的过程 !
也许概念有些抽象, 这里看一个例子 , 见见程序替换可以带来什么效果 !!
// 程序替换初步见一见
#include <stdio.h>
#include <unistd.h>
int main()
{
printf(" 我要开始执行程序替换了 !\n");
execl("/usr/bin/ls" , "ls" , "-a" , "-l" , NULL); // 这个系列函数必须以 NULL 结尾
printf(" 程序替换执行完毕了 !\n");
printf("Success !\n");
return 0;
}

● 深入探讨进程程序替换(重要)
上面笔者演示了 , 进程程序替换带来的效果 , 但是仔细观察会发现一个问题 .
以下将进行探讨 !

1 . 替换的原理



所以 , 就能解释为什么上面样例代码 , 在执行 execl 调用后后面的程序不执行 , 因为后面的程序已经被替换(覆盖)掉了 , 后面的已经不存在了 !!!
2 . exec 系列接口
在 Linux 中替换用的是 exec 系列的接口 !

1 . execl

// 写法 1
execl("/usr/bin/ls" , "ls" , "-a" , "-l" ,NULL); // 这个系列函数必须以 NULL 结尾
//写法 2
execl("/usr/bin/ls" , "ls" , "-al" ,NULL); // 这个系列函数必须以 NULL 结尾
2 . execlp

//写法 1
execlp("ls" , "ls" , "-a" , "-l" , NULL);
//写法 2
execlp("ls" , "ls" , "-al", NULL);
3 . execv

首先得有一个命令行参数表 ,然后直接传表名就可以 ,系统会自动执行 !!
// 这里要特别注意 , 这个表的结尾必须也是 NULL !!!!!!!!!!!!!!!!!
char* argv[] = {"/usr/bin/ls" ,"-a" , "-l" , NULL};
execv("/usr/bin/ls" , argv);
4 . execvp

// 注意 : v - vector , 要数组 , NULL 结尾
char* argv[] = {"ls" ,"-a" , "-l" , NULL};
execvp("ls" , argv);
5 . execvpe

总结

3 . putenv 函数

● exec 系列常规写法
一般都是让子进程去程序替换的 !
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
int main()
{
printf("程序要运行了!\n");
pid_t id = fork();
if(id == 0)
{
//child
printf("I am Child, My Pid Is: %d\n", getpid());
sleep(1);
char* argv[] = {"ls" , "-l" , "-a" , NULL};
execvp("ls" , argv);
}
// father
waitpid(-1, NULL, 0);
printf("我的程序运行完毕了\n");
}
总结

2243

被折叠的 条评论
为什么被折叠?



