创建多进程
1.进程终止
编写程序的return的值代表什么呢?
答:退出码,会被返回给父进程(一般程序的父进程都是bash,在命令行中输入echo $?可查看上一次的退出码)
0表示正确,其他的数字表示不同的出错原因(正确的方式唯一,错位的方式有很多)
2.
Char *strerror(int errnum) //将错误码转换成错误原因描述
经测试,一共133中错误类型
Tips:
如上,不同的指令程序对应的错误码是不一样的,图中同样是没有那个文件与目录,错误码一个是1,一个是2
cd是内建指令,不好查询
但是ls,可以用man ls查询他的手册,得到其对应错误码
另外,strerror()的错误码信息是被定义在其对应的头文件中(应该)
errno, - number of last error
上一次错误的错误码
3.异常码:
代码没有正常运行,出现了异常终止,大概率没有运行完,退出码没有意义
进程出现了异常,本质是收到了硬件发来的某个信号(比如:程序计算1/0,对应的运算器会发出异常,这个异常被操作系统捕获,转换成信号发给进程,然后进程退出)(退出信号可用kill -l)查看
例如:
8)SIGFPE,
浮点错误(Signal Floating Point Exception)
这个信号通常是在执行浮点运算过程中发生异常时由操作系统发送的,尽管名字中有“浮点”,它也用于整数除零错误。
11)SIGSEGV
野指针(Signal Segmentation Violation)
这个信号是在进程尝试访问其内存地址空间之外的内存区域时发送的。通常,这是指针错误或数组越界引起的。
4.
exit(),表示进程退出
exit用在主函数中,效果与return类似,但用在自定义函数中,一旦运行到exit,整个进程就会直接退出
5.exit与_exit的区别
exit:这是一个高级的标准 C 库函数,用于结束程序。它在退出前执行多项清理工作,包括调用所有注册的退出函数、刷新所有标准 I/O 缓冲区,并关闭所有标准 I/O 流。适合大多数常规程序终止场景,确保数据完整性和资源正确释放。
_exit:这是一个低级的系统调用,直接结束进程,跳过 exit 的清理步骤,如不调用注册的退出函数,不刷新 I/O 缓冲区。主要用于需要快速终止的场景,例如在子进程中防止继承的文件描述符和缓冲区带来的问题。
6.进程等待:
为什么?
僵尸进程无法被杀死,需要通过进程等待来回收资源,才能杀死他
父进程需要通过进程等待获取子进程的退出情况(也可能父进程不关注子进程退出情况)
是什么?
系统调用wait/waitpid,来进行进程状态监测与回收
实验:
使用top -p pid1,pid2的方法可以监控两个进程
显然,在不使用wait的情况下,子进程退出后,会变成僵尸(Z)模式
7.wait()
wait-------等待一个进程(他的子进程)直到它的进程状态改变,返回值是改变的进程的pid
使用wait
在监控中可以看见,子进程变成僵尸,然后终止消失,然后父进程终止消失
wait运行有两种情况:
子进程尚未改变状态:如果子进程在父进程调用 wait() 时尚未结束或状态未改变,父进程将会阻塞在 wait() 调用处。父进程将等待直到至少有一个子进程结束或状态改变(例如,子进程终止)。这保证了父进程能够处理子进程的退出和相关资源回收,避免了僵尸进程的产生。
子进程已经改变状态:如果一个或多个子进程在父进程调用 wait() 之前已经结束并成为僵尸进程,wait() 将会立即返回。在这种情况下,wait() 函数会回收这些僵尸进程的资源,并清除它们在进程表中的条目。父进程不会在 wait() 调用时阻塞,因为已经有可回收的子进程状态改变等待处理。
等待回收多进程
8.waitpid
pid_ t waitpid(pid_t pid, int *status, int options);
返回值:
当正常返回的时候waitpid返回收集到的子进程的进程ID;
如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;
参数:
pid:
Pid=-1,等待任一个子进程。与wait等效。
Pid>0.等待其进程ID与pid相等的子进程。
status:
WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
options:
0,阻塞等待
WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进
程的ID。
在使用WNHONG时,返回值等于0说明子进程没完事,大于0说明完事了,小于0说明等待错误出问题了
9.status是一个32位数,现在只考虑低16位比特位
0-7位表示出现异常时,是因为收到了什么异常终止信号,低七位为0表示没有异常
8-15位表示退出码,0表示正常,其余为错误退出的退出码
实验
子进程正常退出
子进程以自己设置的退出码退出(代表某种结果错误)
子进程出现浮点运算错误异常退出
当信号(异常)码不为0时,退出码没有意义
WIFEXITED(status):
代表一个子进程是正常退出,还是错误退出
正常退出,则调用WEXITSTATUS(status)查看退出码
异常退出,WIFSIGNALED(status)和WTERMSIG(status)查看异常退出的信号信息,其中,WIFSIGNALED(status)判断是否有异常信号,WTERMSIG(status)输出异常信号是啥
调用一般方法:
if(ret > 0){
if (WIFEXITED(status)) {
printf("子进程退出代码:%d\n", WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
printf("子进程异常终止,终止信号:%d\n", WTERMSIG(status));
}
waitpid()等待失败,返回-1,当等待了一个不属于自己的子进程,就会等待失败
进程替换
execl 后面的l可以理解为list,传参方式是一个一个的,并以NULL结尾,如
execl("/bin/ls", "ls", "-a", "-l", NULL);
第一个是地址,后面的指令列表,和在命令行中敲的一样,但要用,与“”分开
实验1
结果
进程替换原理:将需替换的进程的代码与数据直接替换物理内存中原进程的代码与数据(没有更改或者新增进程)
exec只有失败的返回值,没有成功的,因为成功了,就被替换了
用子进程继续程序替换
int execlp(const char *file, const char *arg, ...
/* (char *) NULL */);
p就是path,
与execl不同,execlp会去末日的环境变量里找程序,如:
execlp("ls", "ls", "-a", "-l", NULL);
第一个ls是找到这个程序,第二个是指令时什么样的
int execv(const char *path, char *const argv[]);
v表示vector,即用数组的形式传递参数,例如:
char*const myargv[]={"ls","-l","-a",NULL};
execv("/bin/ls", myargv);
int execle(const char *path, const char *arg, ...
/*, (char *) NULL, char * const envp[] */);
e就是enviroment环境变量