一、进程空间的回收
wait/waitpid
1.wait
pid_t wait(int *status);
功能:该函数可以阻塞等待任意子进程退出
并回收该进程的状态。
一般用于父进程回收子进程状态。
参数:status 进程退出时候的状态
如果不关心其退出状态一般用NULL表示
如果要回收进程退出状态,则用WEXITSTATUS回收。
返回值:成功 回收的子进程pid
失败 -1;
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(int argc, const char *argv[])
{
pid_t ret=fork();
if(ret>0)
{
printf("father's pid:%d ppid:%d\n",getpid(),getppid());
wait(NULL);//不会产生僵尸进程,因为一旦子消亡,父就立马回收了
printf("after wait\n");
sleep(5);
}
else if(ret==0)
{
printf("child's pid:%d ppid:%d\n",getpid(),getppid());
sleep(3);
printf("child terminal\n");
exit(1);
}
else
{
perror("fork");
return 1;
}
return 0;
}
WIFEXITED(status) 是不是正常结束(宏返回真代表子进程正常结束)
WEXITSTATUS(status) 从传入的 status 中提取出进程的退出状态。这个宏通常与 wait() 或 waitpid() 函数一起使用,用于获取子进程的终止状态。
当一个进程(通常是子进程)结束时,它会返回一个状态码给其父进程。这个状态码被封装在一个整数值中,父进程可以通过 wait() 或 waitpid() 函数获取这个状态。状态值可能包含多种信息,包括进程的退出状态、是否被信号终止等。
WIFSIGNALED(status) 判断是不是收到了信号而终止的
WTERMSIG(status)如果是信号终止的,那么是几号信号。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(int argc, const char *argv[])
{
pid_t ret=fork();
if(ret>0)
{
printf("father's pid:%d ppid:%d\n",getpid(),getppid());
int status;
while(1)
{
pid_t pid=waitpid(ret,&status,WNOHANG);
if(ret==pid)
{
if(WIFEXITED(status))//判断子进程是否正常结束
{
//正常结束的子进程才能获得返回值
printf("child quit values:%d\n",WEXITSTATUS(status));
}
if( WIFSIGNALED(status))//异常结束
{
printf("child abnormal,signal num:%d\n",WTERMSIG(status));
}
break;
}
else if(0==pid)
{
printf("child is alive,wait latter\n");
}
}
printf("after wait,%d\n",status);
}
else if(ret==0)
{
printf("child's pid:%d ppid:%d\n",getpid(),getppid());
sleep(10);
printf("child terminal\n");
exit(50);
}
else
{
perror("fork");
return 1;
}
return 0;
}
pid_t wait(int *status);
1)如果所有的子进程都在运行,在阻塞
2)如果一个子进程终止,正在等待的父进程则获得终止状态,获得子进程的状态后,立刻返回。
3)如果没有子进程,则立即出错退出。
2.waitpid
waitpid(-1,status,0)=wait(status);(两个种写法等效)
pid_t waitpid(pid_t pid, int *status, int options);
参数:
1)pid:要等待的子进程的进程 ID(PID)。
- pid > 0:等待指定的子进程(PID 为 pid 的子进程)。
- pid == 0:等待与当前进程属于同一个进程组的任意子进程。
- pid < -1:等待进程组 ID 为 -pid 的所有子进程。
- pid == -1:等待所有子进程(类似于 wait 函数的行为)
waitpid (-1,a,0) == wait(a);
2)status 子进程退出时候的状态,
如果不关注退出状态用NULL;
3)options 选项:
0 表示回收过程会阻塞等待
WNOHANG 表示非阻塞模式回收资源。
返回值:成功 返回接收资源的子进程pid
失败 -1
0,EAGAIN
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(int argc, const char *argv[])
{
pid_t ret=fork();
if(ret>0)
{
printf("father's pid:%d ppid:%d\n",getpid(),getppid());
int status;
while(1)
{
pid_t pid=waitpid(ret,&status,WNOHANG);
if(ret==pid)
{
if(WIFEXITED(status))//判断子进程是否正常结束
{
//正常结束的子进程才能获得返回值
printf("child quit values:%d\n",WEXITSTATUS(status));
}
if( WIFSIGNALED(status))//异常结束
{
printf("child abnormal,signal num:%d\n",WTERMSIG(status));
}
break;
}
else if(0==pid)
{
printf("child is alive,wait latter\n");
}
}
printf("after wait,%d\n",status);
}
else if(ret==0)
{
printf("child's pid:%d ppid:%d\n",getpid(),getppid());
sleep(10);
printf("child terminal\n");
exit(50);
}
else
{
perror("fork");
return 1;
}
return 0;
}
练习:
设计一个多进程程序,用waitpid函数指定回收
其中的某个进程资源并将其状态打印输出。
其他的进程都以非阻塞方式进行资源回收。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(int argc, const char *argv[])
{
pid_t ret=fork();
if(ret>0)
{
printf("father's pid:%d ppid:%d\n",getpid(),getppid());
int status;
while(1)
{
pid_t pid=waitpid(ret,&status,WNOHANG);
if(ret==pid)
{
if(WIFEXITED(status))//判断子进程是否正常结束
{
//正常结束的子进程才能获得返回值
printf("child quit values:%d\n",WEXITSTATUS(status));
}
if( WIFSIGNALED(status))//异常结束
{
printf("child abnormal,signal num:%d\n",WTERMSIG(status));
}
break;
}
else if(0==pid)
{
printf("child is alive,wait latter\n");
}
}
printf("after wait,%d\n",status);
}
else if(ret==0)
{
printf("child's pid:%d ppid:%d\n",getpid(),getppid());
sleep(10);
printf("child terminal\n");
exit(50);
}
else
{
perror("fork");
return 1;
}
return 0;
}
二、exec函数族
execute 执行
exec函数是调用一个外部的可执行程序(只能是可执行文件)
用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),
子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的
用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建
新进程,所以调用exec前后该进程的id并未改变。使用exec相当于代码段被替换。
1.execl
execl 函数的原型通常为:
int execl(const char *path, const char *arg0,..., NULL);
path 是要执行的程序的路径。 arg0 及后续的参数是传递给新程序的命令行参数,以空指针结尾。
execl 函数只有在执行失败时才会返回 -1 ,成功时不会返回。
eg:使用execl打开火狐的百度网页
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
execl("/usr/bin/firefox","firefox","www.baidu.com",NULL);
printf("看见就错了\n");
exit(1);
return 0;
}
2.execlp
execlp 函数的原型通常为:
int execlp(const char *file, const char *arg, ...);
与 execl 函数的主要区别在于, execlp 会在环境变量 PATH 所指定的目录中搜索要执行的文件。
例如,如果环境变量 PATH 中包含了可执行文件所在的目录,要执行 ls 命令,可这样使用
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, const char *argv[])
{
//cat execl.c
//execlp("cat","cat","execl.c",NULL);
// env(环境变量)或$echo $PATH(只看PATH内容)
// ls -l --color=auto相当于ll
execlp("ls","ls","-l","--color=auto",NULL);
printf("看见就错了\n");
exit(1);
return 0;
}
3.execv
execv 函数的原型通常为:
int execv(const char *path, char *const argv[]);
path 是要执行的程序的路径, argv 是一个字符指针数组,其中 argv[0] 通常是程序名,后续的元素是传递给新程序的命令行参数,数组的最后一个元素是 NULL
eg:利用execv 执行cat 命令
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
char * const args[]={"cat","execl.c",NULL};
execv("/bin/cat",args);
//char * const args[]={"./test","123","hello",NULL};
//execv("/home/linux/ld/820/test",args);
printf("看见就错了\n");
exit(1);
return 0;
}
4.execvp
execvp 函数的原型通常为:
int execvp(const char *file, char *const argv[]);
与 execv 函数的主要区别在于, execvp 会在环境变量 PATH 所指定的目录中搜索要执行的文件,而 execv 则需要指定完整的文件路径。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
char *const args[]={"ls","-l","--color=auto",NULL};
execvp(args[0],args);//vector+path
//char *const args[]={"./test","343","balabala",NULL};
//execvp("/home/linux/ld/820/test",args);
printf("看见就错了\n");
exit(1);
return 0;
}
5.区别
1)execl和execv会搜索当前目录;execlp和execvp,只写文件名的话,只会去PATH下找,不找当前目录。如果文件不在PATH下,就写路径+文件名
2)相关的参数表传递
l表示list,v表示vector
execl,execlp需要将参数一个一个列出,并以NULL结尾。
execv,execvp需要构造一个参数指针数组,然后将数组的地址传入。