1.wait函数与waitpid函数
1.1 这两个函数的功能
1.2 僵尸进程产生的原因
- 子进程先于父进程终止,父进程没有对子进程进行回收,就会产生僵尸进程
1.3 在子进程回收时,内核会回收用户空间资源,同时系统对子进程的内核空间绝大多数资源进行回收释放,但是仍然会有 PCB 残留
- PCB 不回收会对系统造成影响,可能导致创建进程失败。
1.4 编写一个僵尸进程
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
pid_t pid;
pid = fork();
if(pid > 0)
{
printf("Parent Proc Running...\n");
while(1);
}
else if(pid == 0)
{
printf("Child Proc Running...\n");
sleep(5);
exit(1);
}
else
{
perror("fork call error:");
exit(1);
}
return 0;
}
- 我们运行上述程序,在一个新终端中输入 ps aux 来查看当前的所有进出,找到 ./app 进程,发现有两个,这是我们创建出来的一对父子进程,在5秒后重新输入 ps aux 发现,子进程变为了 [app] ,这就是僵尸进程。
1.5 回收僵尸进程
- 1.可以使用 kill 命令杀死僵尸进程;
- 2.使用 wait 函数杀死僵尸进程;
- 1.对于wait函数,需要两个头文件, sys/types.h 和 sys/wait.h ;
- 2.函数形式: pid_t wait(int *status);
- 3.返回值是被杀死的子进程的进程id;
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
pid_t pid;
pid_t wpid;
pid = fork();
if(pid > 0)
{
printf("Parent Proc Running...\n");
}
else if(pid == 0)
{
printf("Child Proc Running...\n");
sleep(5);
exit(1);
}
else
{
perror("fork call error:");
exit(1);
}
wpid = wait(NULL);
printf("%d has been killed.\n", wpid);
return 0;
}
- 3.可以使用 waitpid 函数杀死僵尸进程;
- 1.函数形式: pid_t waitpid(pid_t pid, int *status, int options) ;
- 2.对于 waitpid 的第一个参数:
- pid > 0 时表示回收指定的子进程的资源;
- pid = -1 时表示回收任意子进程的资源;
- pid = 0 时表示回收当前调用进程同组的所有子进程;
- pid < -1 时表示回收指定组的所有子进程;
- 3.对于 waitpid 的第二个参数:传出子进程的终止原因,若不关心则设置为NULL;
- 4.对于 waitpid 的第三个参数:通过 options 改变 waitpid 的工作方式,例如改为非阻塞轮询回收,一般传入 WNOHANG ,意为非阻塞。
- 5.对于 waitpid 的返回值:
- 没有任何子进程则返回 -1 ;
- 没有可回收子进程返回 0 ;
- 回收成功返回回收的进程的 pid ;
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
pid_t pid;
pid_t wpid;
pid = fork();
if(pid > 0)
{
printf("Parent Proc Running...\n");
}
else if(pid == 0)
{
printf("Child Proc Running...\n");
sleep(5);
exit(1);
}
else
{
perror("fork call error:");
exit(1);
}
while((wpid=waitpid(-1, NULL, WNOHANG)) != -1)
{
if(wpid > 0)
printf("%d has been killed.\n", wpid);
}
return 0;
}
2.exec族
2.1 使用 exec 族完成功能的重载,用某个程序的功能替换当前程序的功能
2.2 使用 exec 族函数
- 1.首先使用 which 命令找到某个命令的路径(可执行文件所在的路径),本次使用的 firefox 火狐浏览器的可执行文件;
- 2.编写使用 exec 族函数的代码;
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
pid_t pid;
pid = fork();
if(pid > 0)
{
printf("Parent:%d process running...\n", getpid());
}
else if(pid == 0)
{
printf("Child:%d process running...\n", getpid());
execl("/usr/bin/firefox", "firefox", "www.github.com", NULL);
}
else
{
perror("fork call error");
exit(0);
}
return 0;
}
2.3 exec 族函数后缀的作用
- 1.l :后缀含有 l 表示该函数的每一个参数都是字符串;
- 2.p :默认在环境变量中查找程序位置;
- 3.v :每一个参数都是数组中的元素;
- 4.e :可以让被重载的进程构造新的环境变量;
2.4 exec 族函数有以下6个
- int execl(const char* path, const char* arg, …
/(char) NULL*/);
- 这个函数的第一个参数是命令的路径(绝对路径),从第二个参数开始,依次为 命令 命令行参数 ,NULL,这里的NULL是哨兵,作用是让系统知道到此命令行参数结束;
- int execlp(const char* file, const char* arg, …
/(char) NULL*/);
- 这个函数与第一个函数大致类似,只是第一个参数不同,这个函数的第一个参数为命令,不需要写全命令路径;
- int execle(const char* path, const char* arg, …
/, (char) NULL, char* const envp[]*/); - int execv(const char* path, char* const argv[]);
- 这个函数的第一个参数为命令的绝对路径,第二个参数为一个字符串数组,该字符串数组是保存第一个函数从第二个参数开始到NULL的所有字符串;
- int execvp(const char* file, char* const argv[]);
- int execvpe(const char* file, char* const argv[],
char* const envp[]); - 以上所有函数的底层函数都是 execve 函数,都是对这个函数的封装引用。
2.4 其他几个 exec 族函数的例子
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
pid_t pid;
pid = fork();
char* argvs[] = {"firefox", "www.github.com", NULL};
if(pid > 0)
{
printf("Parent:%d process running...\n", getpid());
}
else if(pid == 0)
{
printf("Child:%d process running...\n", getpid());
execv("/usr/bin/firefox", argvs);
}
else
{
perror("fork call error");
exit(0);
}
return 0;
}