C++linux高并发服务器项目实践 day6
- exec函数族
- 介绍
- execl
- execlp
- 其他
- 进程控制
- 进程退出
- 孤儿进程
- 僵尸进程
- 进程回收
- wait()函数
- waitpid()函数
exec函数族
介绍
exec函数族的作用是根据指定的文件名找到可执行文件,并用它来取代调用进程的内容,换句话说,就是在调用内部执行一个可执行文件
exec函数族的函数执行成功后不会反悔,因为调用进程的实体,包括代码段,数据段和堆栈等都已经被新的内容取代,只留下进程ID等一些表面上的信息仍保持原样。只有调用失败了,他们才会返回-1,从源程序的调用点接着往下执行
前6个都是C库的函数,只有最后一个是Linux的函数
前2个是最常用的
execl
#include <unistd.h>
extern char **environ;
int execl(const char *pathname, const char *arg, …);
参数:
- path:需要指定的执行的文件的路径或者名称
a.out(相对路径) /home/Liunx/a.out (绝对路径)
两者都能使用,但是推荐使用第二个 - arg:是执行可执行文件所需要的文件列表
第一个参数一般没有什么作用,为了方便,一般写的是执行的程序的名称
从第二个参数开始往后,就是程序执行所需要的参数列表
参数最后需要以NULL结束(哨兵)
返回值:
只要当调用失败时,才会有返回值,返回-1,并且设置errno
如果调用成功,没有返回值
#include <unistd.h>
#include <stdio.h>
int main(){
//创建一个子进程,在子进程中执行exec函数族中的函数
pid_t pid = fork();
if(pid > 0){
//父进程
printf("i am parent process,pid : %d\n",getpid());
sleep(1);
}else if(pid == 0){
//子进程
//execl("hello", "hello",NULL);
execl("/bin/ps","ps","a","u","x",NULL);
printf("i am child process,pid = %d\n",getpid());
}
for(int i = 0;i < 3;i++){
printf("i = %d,pid = %d\n",i,getpid());
}
return 0;
}
execlp
#include <unistd.h>
extern char **environ;
int execl(const char *pathname, const char *arg, …);
int execlp(const char *file, const char *arg, …);
会到环境变量中查找指定的可执行文件,如果找到了就执行,找不到就执行不成功。
参数:
- file:需要指定的执行的可执行文件的文件名
a.out ps - arg:是执行可执行文件所需要的文件列表
第一个参数一般没有什么作用,为了方便,一般写的是执行的程序的名称
从第二个参数开始往后,就是程序执行所需要的参数列表
参数最后需要以NULL结束(哨兵)
返回值:
只要当调用失败时,才会有返回值,返回-1,并且设置errno
如果调用成功,没有返回值
#include <unistd.h>
#include <stdio.h>
int main(){
//创建一个子进程,在子进程中执行exec函数族中的函数
pid_t pid = fork();
if(pid > 0){
//父进程
printf("i am parent process,pid : %d\n",getpid());
sleep(1);
}else if(pid == 0){
//子进程
//execl("hello", "hello",NULL);
execlp("ps","ps","a","u","x",NULL);
printf("i am child process,pid = %d\n",getpid());
}
for(int i = 0;i < 3;i++){
printf("i = %d,pid = %d\n",i,getpid());
}
return 0;
}
其他
int execlp(const char *file, const char *arg, …);
int execle(const char *pathname, const char *arg, …);
char *envp[] = {“/home/Liunx”,“/home/bbb”,“/home/aaa”};
int execv(const char *pathname, char const argv[]);
argv是需要的参数的一个字符串数组
char argv[] = {“ps”,“aux”,NULL}
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[],char *const envp[]);
进程控制
进程退出
#include <stdlib.h>
void exit (int status);
#include <unistd.h>
void _exit(int status);
status参数:是进程退出时的一个状态信息。父进程回收子进程资源的时候可以获得到。
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
int main(){
printf("hello\n");
printf("world");
//exit(0);
_exit(0);
return 0;
}
如果是exit()函数,则会打印world,而_exit()并不会
因为c库的exit()函数会多做两部事,其中刷新缓冲区就会将缓冲区里没输出的world输出
孤儿进程
- 父进程运行结束,但子进程还在运行(未运行结束),这样的子进程就称为孤儿进程
- 每当出现一个孤儿进程的时候,内核就把孤儿进程的父进程设置为init,而init进程会循环wait()它的已经退出的子进程。这样,当一个孤儿进程结束了其生命周期的时候,init进程就出面处理它的一切善后工作
- 因此孤儿进程并不会有什么危害
这里拿之前的fork()函数相关代码来改
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
int main(){
//创建子进程
pid_t pid = fork();
//判断是父进程还是子进程
if(pid > 0){
printf("i am parent process,pid:%d,ppid:%d\n",getpid(),getppid());
}else if(pid == 0){
sleep(1);
//当前是子进程
printf("i am child process ,pid: %d,ppid :%d\n",getpid(),getppid());
}
//for循环
for(int i = 0;i < 5;i++){
printf("i:%d\n",i);
}
return 0;
}
在子进程sleep后,父进程早已执行完被释放,这时子进程就变成了孤儿进程,查看它的ppid为1,说明被init接管,且会进入前台,而不是和父进程共享
僵尸进程
- 每个进程结束之后,都会释放自己地址空间中的用户区数据,内核区的PCB没有办法自己释放掉,需要父进程去释放
- 进程终止时,父进程尚未回收,子进程残留资源(PCB)存放于内核中,变成僵尸(Zombie)进程
- 僵尸进程不能被kill -9杀死
- 这样就会导致一个问题,如果父进程不调用wait()或waitpid()的话,那么保留的那段信息就不会释放,其进程号就会一直被占用,但是系统所能使用的进程号是有限的,如果大量的产生僵尸进程,将因为没有可用的进程号而导致系统不能产生新的进程,此即为僵尸进程的危害,应当避免
依然是拿那段代码来改,当父进程进入死循环,而子进程就会执行完变成僵尸进程,新开一个命令行窗口,没办法通过kill命令来杀死它
我们可以通过ctrl+c来强行杀死父进程,这样子进程也会消失
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
int main(){
//创建子进程
pid_t pid = fork();
//判断是父进程还是子进程
if(pid > 0){
while(1){
printf("i am parent process,pid:%d,ppid:%d\n",getpid(),getppid());
sleep(1);
}
}else if(pid == 0){
sleep(1);
//当前是子进程
printf("i am child process ,pid: %d,ppid :%d\n",getpid(),getppid());
}
//for循环
for(int i = 0;i < 5;i++){
printf("i:%d\n",i);
}
return 0;
}
进程回收
- 在每个进程退出的时候,内核释放该进程所有的资源、包括打开的文件、占用的内存等。但是仍然为其保留一定的信息,这些信息主要指进程控制块PCB的信息(包括进程号、退出状态、运行时间等).
- 父进程可以通过调用wait或waitpid得到他的退出状态同时彻底清除掉这个进程
- wait()和waitpid()函数的功能意义,区别在于,wait()函数会阻塞,waitpid()可以设置不阻塞,waitpid()还可以指定等待哪一个子进程结束
- 注意:一次wait或waitpid调用只能清理一个子进程,清理多个子进程应使用循环
wait()函数
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *wstatus);
功能:等待任意一个子进程结束,如果任意一个子进程结束了,此函数会回收子进程的资源、
参数:int *wstatus
进程退出时的状态信息,传入的是一个int类型的地址,传出参数
返回值:
- 成功:返回被回收的子进程的ID
- 失败:返回-1(所有的子进程都结束,调用函数失败)
调用wait函数的进程会被挂起(阻塞),直到它的一个子进程退出或者受到一个不能被忽略的信号时才被唤醒(相当于继续往下执行)
如果没有子进程了,函数立刻返回,返回-1;如果子进程都已经结束了,也会立即返回-1
pid_t waitpid(pid_t pid, int *wstatus, int options);
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(){
//有一个父进程,创建5个子进程(兄弟)
pid_t pid;
//创建5个子进程
for(int i = 0; i < 5;i++){
pid = fork();
if(pid == 0){
break;
}
}
if(pid > 0){
//父进程
while ((1))
{
printf("parent,pid = %d\n",getpid());
//int ret = wait(NULL);
int st;
int ret = wait(&st);
if(ret == -1){
break;
}
if(WIFEXITED(st)){
//是不是正常退出
printf("退出的状态码:%d\n",WEXITSTATUS(st));
}
if(WIFSIGNALED(st)){
//是不是异常终止
printf("被哪个型号干掉了:%d\n",WTERMSIG(st));
}
printf("child dir,pid = %d\n",ret);
sleep(1);
}
}else if(pid == 0){
//子进程
// while (1)
// {
printf("child,pid = %d\n",getpid());
sleep(1);
//}
exit (0);
}
return 0;
}
waitpid()函数
#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *wstatus, int options);
功能:回收指定进程号的子进程,可以设置是否阻塞
参数:
- pid:
pid > 0:某个子进程的pid
pid = 0:回收当前进程组的所有子进程
pid = -1:回收所有的子进程,相当于wait() (最常用)
pid < -1:某个进程组的组id 的绝对值,回收指定进程组中的子进程 - options:设置阻塞或者非阻塞
0:阻塞
WNOHANG:非阻塞
返回值:
> 0:返回子进程的id
= 0:options = WNOHANG,表示还有子进程活着
= -1:错误,或者没有子进程了
wait(&wstatus) == waitpid(-1,&wstatus,0);
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(){
//有一个父进程,创建5个子进程(兄弟)
pid_t pid;
//创建5个子进程
for(int i = 0; i < 5;i++){
pid = fork();
if(pid == 0){
break;
}
}
if(pid > 0){
//父进程
while ((1))
{
printf("parent,pid = %d\n",getpid());
sleep(1);
//int ret = wait(NULL);
int st;
//int ret = waitpid(-1,&st,0);
int ret = waitpid(-1,&st,WNOHANG);
if(ret == -1){
break;
} else if(ret == 0){
//说明还有子进程存在
continue;
}else if(ret > 0){
if(WIFEXITED(st)){
//是不是正常退出
printf("退出的状态码:%d\n",WEXITSTATUS(st));
}
if(WIFSIGNALED(st)){
//是不是异常终止
printf("被哪个型号干掉了:%d\n",WTERMSIG(st));
}
}
printf("child dir,pid = %d\n",ret);
}
}else if(pid == 0){
//子进程
while (1)
{
printf("child,pid = %d\n",getpid());
sleep(1);
}
exit (0);
}
return 0;
}
子进程和父进程在运行时交替运行,在子进程被逐个kill后,父进程这边会显示被-9型号干掉了,当所有的子进程都被kill了之后,父进程会退出