进程终止

进程终止的本质是释放系统资源,就是释放进程申请的相关内核数据结构和对应的数据和代码
进程

进程退出场景

  • 代码运行完毕,结果正确
  • 代码运行完毕,结果不正确
  • 代码异常终止

程序运行,要么运行完了退出(运行结果正确或不正确,不正确表示没用完成派发的系统的任务),要么运行中出现问题退出,那就是代码异常终止

子进程也是进程,而子进程是由父进程创建的,父进程创建子进程就是为了完成某个功能的,那子进程完成的怎么样应该要告诉父进程吧!即子进程执行的结果

所以进程退出要讲退出场景,实际上也是要讲退出的结果,将结果返回给父进程,父进程才可以做进一步的决策安排!

int main();
我们今天通过main函数作为进程退出的切入点来讲讲

main函数也是函数,需要被调用,那么main函数返回值返回给谁了呢?而我们通常会让main函数返回一个整数,我们一般是用return(0);运行结果也就分为0和非0!用来表示程序的执行情况!

main函数的返回值,通常表明你的程序的执行情况

进程退出结果有上面讲完的三种,而main函数return是反映退出结果的前两种,即只要程序运行完且正确,进程返回的就是0,如果程序运行完,但是结果不正确就返回非0(1,2,3....)!
代码异常终止后面讲!

那我们平时在编码的时候,为什么有时候可以不写返回值?

如果我们是int main函数,我们可以不写返回值吗?可以的,有些编译器可能会警告,原因是,如果你不写,编译器会默认设置返回值,默认返回值为0!

那main函数的return是如何讲int数据return到上级函数呢?main函数内int数据不是函数内部变量吗?函数内部变量具有临时性,那到底是如何返回的呢?

实际上,是通过寄存器返回的(小数据用寄存器,大数据(对象)拷贝,也要通过寄存器来做!),上图讲解
a函数调用b函数!b函数在return返回,到汇编应该是(mov eax(寄存器) 1; ret),将1数据存储在寄存器中再返回,回到a函数中,a函数会call b来调用b函数,b函数运行完,会(mov 0x123[] exa)会将寄存器数据,移动到a函数内的内存上!

0在计算机中通常代表这成功!所以return的时候是0表示成功

程序执行完,结果不正确,OS会问为什么程序会出错,所以程序运行结果不正确,用非0返回,是因为可以根据非0数据,用非0的值表明不同的出错原因

退出码

验证:

通过linux上,编写简单C语言程序
正常情况下,运行完整个函数后,程序就直接退出
如果我们做文件操作,只读一个文件,如果文件地址为空,就return 1,
如果程序出错,即文件地址为空,这个时候程序并没有结果!那我们要怎么知道程序的运行结果呢?

我们知道,程序返回值是返回给父进程的!
通过指令echo $?查看父进程中的一个变量!结果为 1 ,为程序的返回结果
我们知道我们运行的程序实际上是进程,是父进程bash的子进程,子进程的运行结果父进程关心,父进程是如何获取子进程的运行结果呢?父进程就要获得子进程的退出码,echo $?就是打印最近一个程序(进程)退出时的退出码,(main的返回值为进程退出码)

实际上,程序的返回值先返回到自己进程的PCB中存储,进程退出不是会有僵尸状态吗?父进程就从僵尸进程中获取子进程的进程退出码的!

这就是为什么可以在bash中查看到子进程的退出码信息
而 $?中的?就是在父进程PCB中存储进程退出码的特殊变量, $是查看系统中变量的方法!

而我们紧接着再查一次退出吗,发现退出码为0 ,这是因为echo $?是查看最近一个程序运行的结果,现在echo查的是上一个echo $?进程的运行结果!

我们知道退出码非0 ,表示程序运行结果不正确,不正确我们就想知道程序运行错误的原因,非0 就是为了表示不同的程序错误信息,那么要如何了解这些信息的含义呢?
OS有一套对退出码和退出码信息的描述,计算机中往往喜欢用非0数据来表示错误的信息,但是用户要通过字符串来获取错误信息,其实在c标准库中已经提供了错误码转化错误码字符串的函数了

#include <string.h>
char *strerror(int errnum);


我们直接for循环,通过strerror来看看有那些错误信息,但是有多少个呢?不妨猜猜,200试试,发现在133后就都是Unknown了!所以错误信息有134个!

指令ls -l 文件这个文件是未知文件,输出的错误信息不就是错误信息2吗?

我们可以使用C标准库提供的错误信息,也可以使用自己设置的错误信息,可以定制自己的错误信息!

所以进程退出场景的运行完毕时的运行结果是由进程的退出码决定的

我们再来讲讲异常终止的情况!

通过一个小案例来了解

做这样的操作,整型除于0,这个是会出现异常的,我们此时随便返回一个退出码89,看看调用是啥情况?

我们可以看到进程调用后输出的错误信息为浮点数异常!为我们程序的异常问题,再来看看退出码为多少?为136,正好为错误信息!

所以出现异常的程序,return返回的退出码无意义!(考试完,及格、不及格;考试作弊,考试的结果就不重要了!)
其实进程一旦出现异常(越界、野指针),一般是进程收到了信号!

进程常见退出方法

main函数return退出

正常程序通过main函数,完成全部任务,通过main函数结尾return,将进程的运行结果返回到进程PCB,进而到僵尸状态被父进程获取进程运行结果!

exit(退出码)

在程序任何位置调用exit()都等价于main函数return即进程结束exit()括号内要填写对应的退出码,和return类似!

_exit函数


_exit的作用是那个进程调用这个语句,就把这个进程kill掉!

我们来简单讲讲这个

exit()VS _exit()
exit 是C语言提供的,_exit是系统提供的!
这两者做对比

这个案例就是之前类似,pritnf语句输出的语句会到缓冲区里,\n就立马刷新缓冲区!等待2s,然后立马退出,退出码为23!

这样的写法就是printf会输出的到缓冲区,但是没用被刷新出来,到程序退出的时候再被刷新出来!

图1把exit改成_exit运行结果也一样,我们来试试图2

此时没用打印printf数据!
prinf打印消息不带\n,数据进入缓冲区!要么是进程fflush强制刷新,要么就是进程退出时主动刷新!

所以区别在于:
进程如果exit退出的时候,exit(),进程退出的时候,会进行缓冲区的刷新
进程如果exit退出的时候,_exit(),进程退出的时候,不会进行缓冲区的刷新

关键在于:
系统调用是上下级关系!
库是通过调用相关的系统调用来完成一系列任务的!我们要知道,我们在C语言中调用个exit就可以退出进程,而进程是OS在管理的!我们使用C语言的exit就把进程终止,凭什么?这个世界上能真正杀掉进程的只有OS!因为OS是进程的管理者
说到底,我们语言上调用exit(),但是在其底层,还是要调用_exit()系统调用来退出进程!即语言没用能力退出进程,语言只能调用OS提供的接口来退出进程!
所以exit 封装了_exit

缓冲区应该在那里?或者 一定不再哪里?
一定不是OS内部的缓冲区!我们之前谈及的缓冲区是库缓冲区!C语言提供的缓冲区!

子进程退出,父进程会自动调用waitpid,获取子进程的僵尸信息吗?

答案是:一般情况下,父进程内非主动调用waitpid和wait时,子进程退出后,父进程并不会主动调用waitpid/wait,子进程退出OS会向父进程发生SIGCHLD信号,父进程是否处理这个信号,是否回收僵尸进程,完全由父进程的代码逻辑决定
不用担心父进程不对僵尸进程进行回收!父进程退出后,原僵尸子进程会变成孤儿僵尸进程,被OS收养,收养后自动回收!

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐