目录
一、进程终止
1、进程退出场景
2、进程常见退出方法
a. 正常终止(可以通过 echo $? 查看进程退出码):
b. 异常退出:
3、分类
a. main函数返回值(return 退出)
I. 退出码:
II. 错误码:
III. 异常信号:用 kill -l 查看
b. _exit 函数&&exit 函数
二、进程等待
1、为什么要进行进程等待?
2、进程等待的方法
a. wait && waitpid
b. 获取子进程status
编辑 3、阻塞等待&&非阻塞等待
a. 阻塞等待
b. 非阻塞等待
三、进程程序替换
1、替换原理
2、替换函数
execlp:
execv:
execvp:
execle,execve,execvpe:
总结:
一、进程终止
1、进程退出场景
1、代码运行完毕,结果正确 2、代码运行完毕,结果错误 3、代码异常终止
2、进程常见退出方法
a. 正常终止(可以通过 echo $? 查看进程退出码):
I. 从main返回 II. 调用 exit III. _exit
b. 异常退出:
ctrl + c,信号终止
3、分类
a. main函数返回值(return 退出)
main函数返回值,叫做进程的退出码,一般0,表示进程执行成功,非0表示失败。
I. 退出码:
进程的退出码(exit_code),也称为退出状态或返回值,是一个整数,用于表示进程执行结束时的状态,通常由进程的主函数(C语言中是main函数)返回,或者是在进程遇到异常或者错误是由操作系统进行设置。可以使用 echo $? 命令查看最近一次进程退出的退出码信息。$? 是一个特殊的shell变量,保存了上一个命令的退出码。
II. 错误码:
进程的错误码( errno ) 用于表示进程在执行系统调用或库函数时遇到的错误情况,每个错误码都对应一个特定的错误情况,是的程序能够识别并处理这些错误。而进程的退出码是一个整数,用于表示进程执行结束时的状态。
错误码errno通常是一组预定义的数值,每个值对应一个特定的错误情况。这些定义通常包含在系统的头文件<errno.h>中。当系统调用或库函数失败时,它们会设置全局变量errno为相应的错误码。
如果errno的值为0,则表示系统调用成功,如果errno的值不为0,则表示系统调用失败,并且其值对应着特定的错误代码。常见处理errno的方法,如使用perror函数或者strerror函数,perror函数可以将errno的值映射为对应错误信息,并将其打印到标准错误流(stderr),而strerror函数则可以将errno的值转化为对应的错误字符串。
III. 异常信号:用 kill -l 查看
所以,任何进程最终的执行情况,我们都可以使用两个数字表明具体的执行情况
b. _exit 函数&&exit 函数
#include <stdio.h>#include <errno.h>#include <string.h>#include <unistd.h>#include <stdlib.h>int main(){while(1){printf("I am a process: %d\n", getpid());sleep(1);exit(3); // exit 终止进程,status:进程退出时候,退出码 } }
#include <stdio.h>#include <errno.h>#include <string.h>#include <unistd.h>#include <stdlib.h> void Print(){printf("hello\n");exit(5);}int main(){while(1){printf("I am a process: %d\n", getpid());sleep(1);Print(); //exit(3); // exit 终止进程,status:进程退出时候,退出码}}
这就告诉我们,exit就是用来终止进程的,exit(退出码)。
那么这个_exit又是什么呢?把上面的代码中的 exit 改成 _exit 验证一下,结果和exit是一样。难道说exit 和 _exit 是一样的吗?它们到底有什么区别?
所以,可以得出,exit 会支持刷新缓冲区,而_exit不支持。
二、进程等待
1、为什么要进行进程等待?
2、进程等待的方法
a. wait && waitpid
对于waitpid中的参数:
status:
WIFEXITED(status): 若为正常终止子进程返回的状态,则为真(用于查看进程是否退出)。
WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码(用于查看你进程的退出码)。
options:
WNOHANG: 若pid指定的子进程还没有结束,则waitpid()函数返回0,不予等待。若正常结束,则返回该子进程的ID。
对于waitpid中的返回值:
正常返回的时候waitpid返回收集到的子进程的进程ID;
如果设置了选项WNOHANG,而调用中的waitpid发现没有已退出的子进程可收集,则返回0;
如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;
如果子进程已经退出,调用wait/waitpid时,wait/waitpid会立即返回,并且释放资源,获得子进程退出信息。
如果在任意时刻调用wait/waitpid,子进程存在且正常运行,则进程可能阻塞。
如果不存在该子进程,则立即出错返回。
#include <stdio.h>#include <errno.h>#include <string.h>#include <unistd.h>#include <stdlib.h>#include <sys/types.h>#include <sys/wait.h> int main() { pid_t id = fork(); if(id == 0) { //child int cnt = 5; while(cnt) { printf("Child is running, pid: %d, ppid: %d\n",getpid(),getppid()); sleep(1); cnt--; } exit(1); } int status = 0; pid_t rid = waitpid(id, &status, 0);//阻塞等待 if(rid > 0) { printf("wait sucess, rid: %d, status: %d\n", rid, status); } return 0; }
等待成功,但是我们发现这个status怎么是256呢?这256从哪来的?
b. 获取子进程status
wait 和 waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充。
如果传递NULL,则表示不关心子进程的退出状态信息,否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。status不能简单的当作整型来看待,可以当作位图来看待。
可以用下图表示(只研究status低16比特位):
那么回到刚刚的问题,上面那个代码的256是怎么算的呢?
我们上面说过,任何进程最终执行情况,我们都可以使用两个数字表明具体执行情况。
3、阻塞等待&&非阻塞等待
阻塞和非阻塞指的是调用者(程序)在等待返回结果(或输入)时的状态。阻塞时,在调用返回结果前,当前线程会被挂起,并在得到结果之后返回。非阻塞时,如果不能立刻得到结果,则该调用者不会阻塞当前进程。
a. 阻塞等待
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h> int main()
{ //阻塞等待 pid_t id = fork(); if(id<0) return 1; else if(id == 0) { printf("I am child, pid: %d, ppid: %d\n",getpid(), getppid()); sleep(3); exit(100); } else{ int status = 0; pid_t rid = waitpid(id, &status, 0); if(rid > 0) { printf("wait sucess, rid: %d, status: %d, exitnum: %d, signo1: %d, signo2:: %d, isexit: %d\n", rid, status, status&0x7f, (status>>8)&0xff, WEXITSTATUS(status), WIFEXITED(status)); } } return 0;
}
看这串代码中,status&0x7f,(status>>8)&0xff,这两个是什么啊?
b. 非阻塞等待
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>int main()
{pid_t id= fork();if(id < 0) return 1;else if( id == 0){printf("child is running..., pid is: %d\n",getpid());sleep(5);exit(10);}else{int status = 0;pid_t rid = 0;do{rid = waitpid(-1, &status, WNOHANG);//非阻塞等待if(rid == 0){printf("child is running...\n");}sleep(1); }while(rid == 0);if(WIFEXITED(status) && rid == id){printf("wait child 5s success, child return code is: %d\n",WEXITSTATUS(status));}else{printf("wait child failed\n");return 1;}}
三、进程程序替换
1、替换原理
用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另外一个程序。当进程调用一种exec函数时,该进程的用户空间和数据被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。
下面我们来用一个最简单的exec的接口来表示一下替换原理:
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h> int main()
{ printf("I am a process, pid: %d\n",getpid()); printf("exec begin...\n"); execl("/usr/bin/ls", "ls", "-a", "-l", NULL);//注意这里是NULL,不是"NULL" printf("exec end ...\n"); return 0;
}
这里代码最后的printf并没有被执行?为什么?
上面替换原理说了:当进程调用一种exec函数时,该进程的用户空间和数据被新程序替换,从新程序的启动例程开始执行。
2、替换函数
这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回。如果调用出错则返回-1。
所以exec函数只有出错的返回值,而没有成功的返回值。
那这么多我们该如何记住它们呢?---看命名
带p:PATH,你不用告诉系统,程序在哪里,只要告诉我名字是什么,系统替换的时候,会自动去PATH环境变量中查找。
execlp:
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h> int main()
{ printf("I am a process, pid: %d\n",getpid());pid_t id=fork();if(id == 0) { sleep(3);printf("exec begin...\n");// execl("/usr/bin/ls", "ls", "-a", "-l", NULL);//注意这里是NULL,不是"NULL"execlp("ls", "ls","-a","-l",NULL); printf("exec end...\n"); exit(1); } pid_t rid= waitpid(id,NULL,0);if(rid>0) { printf("wait sucess\n");} exit(1);
}
带v:vector 数组 带l:list,列表,参数列表
execv:
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h> int main()
{ printf("I am a process, pid: %d\n",getpid()); pid_t id=fork(); if(id == 0) { char *const argv[] = { (char*)"ls", (char*)"-a", (char*)"-l",NULL }; sleep(3); printf("exec begin...\n"); execv("/usr/bin/ls", argv); printf("exec end...\n"); exit(1); } pid_t rid= waitpid(id,NULL,0); if(rid>0) { printf("wait sucess\n");}exit(1);
}
execvp:
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h> int main()
{ printf("I am a process, pid: %d\n",getpid()); pid_t id=fork(); if(id == 0) { char *const argv[] = { (char*)"ls", (char*)"-a", (char*)"-l",NULL }; sleep(3); printf("exec begin...\n"); execvp("ls", argv); printf("exec end...\n"); exit(1); } pid_t rid= waitpid(id,NULL,0); if(rid>0) { printf("wait sucess\n");}exit(1);
}
当然对于所有的替换函数,我们都不仅可以和上面一样执行系统的指令,也可以执行自己的程序。
//test.c#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h> int main()
{ printf("I am a process, pid: %d\n",getpid()); pid_t id=fork(); if(id == 0) { char *const argv[] = { (char*)"ls", (char*)"-a", (char*)"-l",NULL }; sleep(3); printf("exec begin...\n"); execl("./mytest", "mytest", "-a", "-b", NULL);//注意这里是NULL,不是"NULL" printf("exec end...\n");exit(1); } pid_t rid= waitpid(id,NULL,0); if(rid>0) { printf("wait sucess\n");}exit(1);
}//mytest.cc#include <iotream>
using namespace std;int main()
{cout<<"hello world"<<endl;cout<<"hello world"<<endl;cout<<"hello world"<<endl;return 0;
}
execle,execve,execvpe:
带e:自己维护环境变量 execle,execve,execvpe
先观察下面代码:
//test.c
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main
{printf("I am a process, pid: %d\n", getpid());pid_t id=fork();if(id == 0){sleep(1);execl("./mytest","mytest",NULL);printf("exec end...\n");exit(1);}pid_t rid = waitpid(id, NULL, 0);if(rid > 0){printf("wait sucess\n");}exit(1);
}//mytest.cc
#include <iostream>
#include <unistd.h>
using namespace std; int main()
{ for(int i = 0; environ[i]; i++) { printf("env[%d]: %s\n", i, environ[i]); } cout<<"hello world"<<endl; return 0;
}
这里我们传递环境变量表了吗?? ---没有,子进程默认就拿到了,它是怎么做到的?
默认可以通过地址空间继承的方式,让所有子进程拿到环境变量,进程替换不会替换环境变量数据
1、如果我们想让子进程继承全部的环境变量,直接能拿到
2、如果单纯的新增我们可以使用putenv
3、那么我想设置全新的环境变量给子进程呢?
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>int main()
{ char* const env[]={ (char*)"one=1111111111111", (char*)"two=2222222222222", NULL}; printf("I am a process, pid: %d\n",getpid()); pid_t id=fork(); if(id == 0) { sleep(1); execle("./mytest","mytest",NULL,env); printf("exec end...\n");exit(1);}pid_t rid= waitpid(id,NULL,0);if(rid>0){printf("wait sucess\n");}exit(1);
}
这样我就设置了全新的环境变量给子进程!