前言:
前面我们对进程已经有了一个初步的了解与认识,现在让我们学习一下进程中一些函数的具体使用,比如exec可以执行一些指定的程序,wait / waitpid可以回收子进程,什么是孤儿进程,什么是僵尸进程,下面让我们一起对这些进行中的操作进行学习吧
目录
一、exec函数族
1.execlp:
2.execl:
3.execvp
4.exec函数族的一般规律:
二、回收子进程
1.孤儿进程:
2 .僵尸进程:
编辑
3.wait:
4.waitpid
5.waitpid回收多个子进程
一、exec函数族
将当前进程的.text、.data替换为所要加载的程序的.text、.data,然后让进程从新的.text第一条指令开始执行,但进程ID不变,换核丕换壳。
1.execlp:
int execlp(const char *file, const char *arg, ...); 借助 PATH 环境变量找寻待执行程序
参1: 程序名
参2: argv0
参3: argv1
...: argvN
哨兵:NULL
该函数通常用来调用系统程序。如: ls、date、cp、cat等命令。
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main(int argc,char *argv[])
{
int i;
pid_t pid; //创建子进程
if(pid == -1){
perror("fork error");
exit(1);
}else if(pid == 0){ //子进程
//execlp("ls","-l","-d","-h",NULL);//错误写法
/************************************/
execlp("ls","ls","-l","-h",NULL);
/************************************/
perror("exec error");
exit(1);
}
else if(pid > 0){ //父进程
sleep(1);
printf("I'm parent : %d\n",getpid());
}
return 0;
}
date命令的实现:
execlp("date","date",NULL);
2.execl:
int execl(const char *path, const char *arg, ...); 自己指定待执行程序路径。(路径+程序名)
#include <stdio.h>
int main(int argc, char **argv)
{
printf("Hello, %s!\n", argv[1]);
printf("Hello, world!\n");
return 0;
}
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main(int argc,char *argv[])
{
int i;
pid_t pid; //创建子进程
if(pid == -1){
perror("fork error");
exit(1);
}else if(pid == 0){ //子进程
//execlp("ls","-l","-d","-h",NULL);
//execlp("date","date",NULL);
/************************************/
execl("./a.out","./a.out","linux",NULL);
/************************************/
perror("exec error");
exit(1);
}
else if(pid > 0){ //父进程
sleep(1);
printf("I'm parent : %d\n",getpid());
}
return 0;
}
3.execvp
加载一个进程,使用自定义环境变量env
int execvp(const char*file, const char *argv[]);
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main(int argc,char *argv[])
{
int i;
pid_t pid; 创建子进程
if(pid == -1){
perror("fork error");
exit(1);
}else if(pid == 0){ //子进程
//execlp("ls","-l","-d","-h",NULL);
//execlp("date","date",NULL);
//execl("./a.out","./a.out","linux",NULL);
/************************************/
char *argv[] = {"date",NULL};
execvp("date",argv);
/************************************/
perror("exec error");
exit(1);
}
else if(pid > 0){ //父进程
sleep(1);
printf("I'm parent : %d\n",getpid());
}
return 0;
}
4.exec函数族的一般规律:
l:命令行参数列表
p:使用PATH环境变量
v:使用命令行参数数组
exec函数一旦调试成功即执行新的程序,不返回。只要失败才返回,错误值-1。所以通常我们直接在exec函数调用后调用 perror()和exit()。无需if判断。·
二、回收子进程
1.孤儿进程:
父进程死亡子进程进孤儿院
孤儿进程:父进程先于子进程结束,则子进程成为孤儿进程,子进程的父进程成为init进程,称为init进程领养孤儿进程。
模拟孤儿进程:
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
int main(void)
{
pid_t pid;
pid = fork();
if (pid == 0) {
while (1) {
printf("I am child, my parent pid = %d\n", getppid());
sleep(1);
}
} else if (pid > 0) {
printf("I am parent, my pid is = %d\n", getpid());
sleep(9);
printf("------------parent going to die------------\n");
} else {
perror("fork");
return 1;
}
return 0;
}
查看进程状态:ps ajx
进程孤儿院:
1 2035 2035 2035 ? -1 Ss 1001 0:00 /lib/systemd/systemd --user
解决方法:
杀死子进程: kill -9 4871
2 .僵尸进程:
子进程死亡,父进程一直不管
僵尸进程:进程终止,父进程尚未回收,子进程残留资源(PCB)存放于内核中,变成僵尸(zombie)进程。(死亡以后没有回收)
特别注意,僵尸进程是不能使用kill命令清除掉的。因为kill命令只是用来终止进程的,而僵尸进程已经终止。
模拟僵尸进程:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main(void)
{
pid_t pid;
pid = fork();
if (pid == 0) {
printf("---child, my parent= %d, going to sleep 10s\n", getppid());
sleep(10);
printf("-------------child die--------------\n");
} else if (pid > 0) {
while (1) {
printf("I am parent, pid = %d, myson = %d\n", getpid(), pid);
sleep(1);
}
} else {
perror("fork");
return 1;
}
return 0;
}
查看进程状态:ps ajx
解决方法:
杀死父进程: kill -9 4770
*3.wait:
wait函数: 回收子进程退出资源, 阻塞回收任意一个。
pid_t wait(int *status)
参数:(传出) 回收进程的状态。
返回值:成功: 回收进程的pid
失败: -1, errno
函数作用1: 阻塞等待子进程退出
函数作用2: 清理子进程残留在内核的 pcb 资源
函数作用3: 通过传出参数,得到子进程结束状态
获取子进程正常终止值:
WIFEXITED(status) --》 为真 --》调用 WEXITSTATUS(status) --》 得到 子进程 退出值。
获取导致子进程异常终止信号:
WIFSIGNALED(status) --》 为真 --》调用 WTERMSIG(status) --》 得到 导致子进程异常终止的信号编号。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main(void)
{
pid_t pid, wpid;
int status;
pid = fork();
if (pid == 0) {
printf("---child, my id= %d, going to sleep 10s\n", getpid());
sleep(10);
printf("-------------child die--------------\n");
return 73;
} else if (pid > 0) {
//wpid = wait(NULL); // 不关心子进程结束原因
wpid = wait(&status); // 如果子进程未终止,父进程阻塞在这个函数上
if (wpid == -1) {
perror("wait error");
exit(1);
}
if (WIFEXITED(status)) { //为真,说明子进程正常终止.
printf("child exit with %d\n", WEXITSTATUS(status));
}
if (WIFSIGNALED(status)) { //为真,说明子进程是被信号终止.
printf("child kill with signal %d\n", WTERMSIG(status));
}
printf("------------parent wait finish: %d\n", wpid);
} else {
perror("fork");
return 1;
}
return 0;
}
正常终止:
被信号终止:
*4.waitpid
waitpid函数: 指定某一个进程进行回收。可以设置非阻塞。
waitpid(-1, &status, 0) == wait(&status);
pid_t waitpid(pid_t pid, int *status, int options)
参数:
pid:指定回收某一个子进程pid
> 0: 待回收的子进程pid
-1:任意子进程
0:同组的子进程。
status:(传出) 回收进程的状态。
options:WNOHANG 指定回收方式为,非阻塞。
返回值:
> 0 : 表成功回收的子进程 pid
0 : 函数调用时, 参3 指定了WNOHANG, 并且,没有子进程结束。
-1: 失败。errno
回收任意子进程:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/wait.h>
int main(int argc,char *argv[])
{
int i;
pid_t pid,wpid;
for(i = 0;i < 5;i++){
if(fork()==0) //循环期间,子进程不fork
break;
}
if(i == 5){ //父进程
//wait(NULL);//一次wait/waitpid函数调用,只能回收一个子进程
/*****************************************/
wpid = waitpid(-1,NULL,WNOHANG);//回收任意子进程,没有结束的子进程,父进程直接返回0
/****************************************/
if(wpid == -1)
{
perror("waitpid error");
exit(1);
}
printf("I'm parent ,wait a child finish :%d\n",wpid);
}else{ //子进程,从break跳出
sleep(i);
printf("I'm %dth child\n",i+1);
}
return 0;
}
回收指定进程:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/wait.h>
int main(int argc,char *argv[])
{
int i;
pid_t pid,wpid,tmpid;
for(i = 0;i < 5;i++){
pid = fork();
if(pid == 0){ //循环期间,子进程不fork
break;
}
if(i == 2){
tmpid = pid;
printf("*************pid= %d***************\n",pid);
}
}
if(i == 5){ //父进程,从表达式2跳出
sleep(5); //设置睡眠,等所有子进程结束后再回收
//wait(NULL); //一次wait/waitpid函数调用,只能回收一个子进程
//wpid = waitpid(-1,NULL,WNOHANG); //回收任意子进程,没有结束的子进程,父进程直接返回0
printf("I am parent , before waitpid , pid = %d\n",tmpid);
/********将前面sleep(5)屏蔽***************/
//wpid = waitpid(tmpid,NULL,0); //指定一个进程回收,阻塞回收
/****************************************/
/*****************************************/
wpid = waitpid(tmpid,NULL,WNOHANG); //指定一个进程回收,不阻塞
/****************************************/
if(wpid == -1)
{
perror("waitpid error");
exit(1);
}
printf("I'm parent ,wait a child finish :%d\n",wpid); //wpid回收的是真正的子进程id
}else{ //子进程,从break跳出
sleep(i);
printf("I'm %dth child,pid = %d\n",i+1,getpid());
}
return 0;
}
注意:
一次wait/waitpid调用只能回收一个子进程,无法回收他孙子辈的进程,多次清理需要while
5.waitpid回收多个子进程
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/wait.h>
int main(int argc,char *argv[])
{
int i;
pid_t pid,wpid;
for(i = 0;i < 5;i++){
pid = fork();
if(pid == 0){ //循环期间,子进程不fork
break;
}
}
if(i == 5){ //父进程
/**********使用阻塞回收子进程********/
while((wpid = waitpid(-1,NULL,0))){
printf("wait child %d\n",wpid);
}
/***********************************/
}else{ //子进程
sleep(i);
printf("I'm %dth child ,pid =%d\n",i+1,getpid());
}
return 0;
}
结束一个回收一个
之后返回-1,表示没有失败了
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/wait.h>
int main(int argc,char *argv[])
{
int i;
pid_t pid,wpid;
for(i = 0;i < 5;i++){
pid = fork();
if(pid == 0){ //循环期间,子进程不fork
break;
}
}
if(i == 5){
/*********使用阻塞回收子进程***********/
/*
while((wpid = waitpid(-1,NULL,0))){
printf("wait child %d\n",wpid);
}
*/
/***********************************/
/*******使用非阻塞方式回收子进程******/
while((wpid = waitpid(-1,NULL,WNOHANG)) != -1){
if(wpid > 0){
printf("wait child %d\n",wpid);
}else if(wpid == 0){
sleep(1);
continue;
}
/************************************/
}
}else{
sleep(i);
printf("I'm %dth child ,pid =%d\n",i+1,getpid());
}
return 0;
}