进程终止

进程退出场景

先确定进程退出是否异常,无异常再查看运行结果是否正确(关心退出码)。

	代码运行完毕,结果正确

​	代码运行完毕,结果不正确

​	代码异常终止

代码异常终止,本质可能就是代码没有跑完。

异常:退出码没有意义!!所以我们不关心退出码了。

要关心为什么异常了,以及发生了什么异常?

  1 #include <stdio.h>  
  2 #include <unistd.h>  
  3 #include <stdlib.h>  
  4 #include <string.h>  
  5 #include <errno.h>  
  6 int main()
  7 {
  8     char *p=NULL;
  9     *p=100;
 10     return 0;
 11 }

image-20250311223529831

以上是:野指针异常。对于0号地址进行解引用,修改0号地址的内容。

  1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <stdlib.h>
  4 #include <string.h>
  5 #include <errno.h>
  6 int main()
  7 {
  8     int a=10;
  9     a/=0;                                        
 12     return 0;
 13 }

image-20250311223659350

以上是:浮点数异常(浮点数溢出)。

进程出现了异常,本质是进程收到了对应的信号!

野指针(在虚拟地址空间的页表上没有映射关系或者权限为只读)

/除零(寄存器出现溢出)等一定会发生一些硬件上的错误的,

都会转换成硬件问题,这些硬件问题都会被操作系统识别,

进而操作系统向目标进程发信号来完成的。

当进程一旦出问题,操作系统可以给它发信号kill -9,杀掉这个进程。

image-20250312130656130

浮点数报错:kill-8

段错误:kill-11

当我们的程序一旦出异常,会被系统转化为信号的方式,发送给进程,从而让进程直接退出。

  1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <stdlib.h>
  4 #include <string.h>
  5 #include <errno.h>
  6 
  7 int main()
  8 {
  9     while(1)
 10     {
 11         printf("Hello Linux!pid:%d\n",getpid());
 12         sleep(1);
 13     }
 14     return 0;
 15 }

image-20250312131919966

结论:进程出现异常的本质就是收到了对应的信号!

父进程要关心子进程的退出,要关心以下的点:

进程退出时有没有收到对应的信号

(没有收到信号,说明没有异常;

代码跑完了,再查对应的退出结果(退出码),退出码为0表示成功,非0表示错误,并且有错误的原因。)

进程常见退出方法

正常终止(可以通过 echo $? 查看进程退出码):

  1. 从main返回
  2. 调用exit
  3. _exit

异常退出:

ctrl + c,信号终止

return退出

return是一种更常见的退出进程方法。

执行return n等同于执行exit(n),

因为调用main的运行时函数会将main的返回值当做 exit的参数。

  1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <stdlib.h>
  4 
  5 int main()
  6 {
  7     printf("模拟一个逻辑实现\n");
  8 
  9     return 0;//进程的退出码,表征进程的运行结果是否正确 0->success
 10 } 

return 0->告诉我们 1.代码跑完了 2.结果是正确的

image-20250310214101067

进程中,谁会关心我的运行情况呢?

一般而言,是父进程要关心!

父进程要关心的是代码跑完了,但是结果是不正确的。

运行结果是否正确统一用进程的退出码(即main函数的返回值)进行判定。


父进程为什么要关心?why?

子进程创建出来是要完成任务的,我们需要通过退出码来查看子进程任务完成得怎么样。

实际上是用户关心,当指令执行失败后,

用户根据执行失败的原因,来调整自己执行程序的方式重新运行程序。

要查看用户创建的子进程运行的结果如何,就要通过父进程把退出信息转交给用户,

从而让用户根据退出结果,进行下一阶段的执行决策。


可以用return的不同的返回值数字,表征不同的出错原因 — 退出码

main函数的返回值,本质表示:进程运行完成时是否是正确的结果,

如果不是,可以用不同的数字表示不同的出错原因!

  1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <stdlib.h>
  4 
  5 int main()
  6 {
  7     printf("模拟一个逻辑实现\n");
  8 
  9     return 11;//进程的退出码,表征进程的运行结果是否正确 0->success
 10 } 

image-20250310215046385

echo用来获得最近一个程序的退出码 把最近一次程序的退出码保存到 ‘$?’ 中

第二次,最近的命令就变成了echo命令,所以我们的退出码就变成0了

有些程序运行往往不向显示器打印任何信息,我们想要知道代码进程任务是否完成,就要查看退出码。

退出码是给计算机看的,所以,strerror就是把错误码(退出码)转换成错误码描述呈现给我们看。

image-20250310224856146

  1 #include <stdio.h>  
  2 #include <unistd.h>  
  3 #include <stdlib.h>  
  4 #include <string.h>  
  5 int main()  
  6 {  
  7     int i=0;
  8     for(;i<200;i++)  
  9     {  
 10         printf("%d:%s\n",i,strerror(i));  
 11     }                                                    
 13     return 0;                                                                  
 14 }

image-20250310225527424

以上就称为 - 错误码表述。(系统提供的默认的错误码以及错误码描述)

系统提供的错误码和错误码描述是有对应关系的。

image-20250310225958016

这个错误码描述对应错误码2。

ls是一条指令,也是运行起来的进程,

ls执行完之后,检测到文件不存在,出错了所以返回2(退出码2),并且把2转化为错误描述打印出来了。


也可以自己设计一套退出码体系!

const char* errstring[]={
	"success",
    "error 1",
    "error 2",
    "error 3",
    "error 4",
    "error 5",
    "error 6"
 };   

返回值就可以直接返回你自己设计的对应的错误码体系的下标。


    1 #include <stdio.h>
    2 #include <unistd.h>
    3 #include <stdlib.h>
    4 #include <string.h>
    5 
    6 int main()
    7 {
    8     int ret=0;
    9     char *p=(char*)malloc(1000*1000*1000*4);
   10     if(p==NULL)
   11     {
   12         printf("malloc error\n");
   13         ret=1;
   14     }
   15     else
   16     {
   17         printf("malloc success\n");
   18     }
   19     return ret;
   20 }

image-20250311134015420

malloc error了 如果我们想知道是什么原因可以查看退出码(我们自己设置的malloc不成功就返回1)。


errno是c语言给我们提供的全局变量,记录了最近一次执行的错误码。

我们调用库函数时是有可能是失败的,c语言会把对应的全局变量errno设置为数字,

表示调用该函数出错时的错误码。

image-20250311134402490

    1 #include <stdio.h>
    2 #include <unistd.h>
    3 #include <stdlib.h>
    4 #include <string.h>
    5 #include <errno.h>
    6 int main()
    7 {
    8     int ret=0;
    9     char *p=(char*)malloc(1000*1000*1000*4);
   10     if(p==NULL)
   11     {
   12         printf("malloc error,%d:%s\n",errno,strerror(errno));
   13         ret=errno;
   14     }
   15     else
   16     {
   17         printf("malloc success\n");
   18     }
   19     return ret;
   20 }

image-20250311135312080

这样既可以知道错误码是什么,又可以知道对应的错误原因是什么,

并且还可以把错误码转化成进程的退出码让父进程知道。

所以如果是纯c的编程就可以这么写,但是我们后面的学习用到C和C++,

所以我们不会采用c标准库给我们提供的出错体系,我们自己定义。

exit函数

exit:终止一个进程。其参数是退出码。

image-20250312132744150

exit最后也会调用exit, 但在调用exit之前,还做了其他工作:

  1. 执行用户通过 atexit或on_exit定义的清理函数。
  2. 关闭所有打开的流,所有的缓存数据均被写入
  3. 调用_exit

实例:

  1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <stdlib.h>
  4 #include <string.h>
  5 #include <errno.h>
  6 
  7 int main()
  8 {
  9     printf("Hello Linux!\n");
 10     exit(12);
 11     //return 12;
 12 }  

运行结果:

exit运行结果:

image-20250312133101629

return运行结果:

image-20250312133354411

exit和return在main函数里是等价的。

区别:

  1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <stdlib.h>
  4 #include <string.h>
  5 #include <errno.h>
  6 void show()
  7 {
  8     printf("hello show!\n");
  9     printf("hello show!\n");
 10     printf("hello show!\n");
 11     printf("hello show!\n");
 12     printf("hello show!\n");
 13     printf("hello show!\n");
 14     exit(13);
 15     printf("end show!\n");
 16     printf("end show!\n");
 17     printf("end show!\n");
 18     printf("end show!\n");
 19     printf("end show!\n");
 20 }
 21 int main()
 22 {
 23     show();
 24     printf("Hello Linux!\n");
 25     //exit(12);
 26     return 12;
 27 }

image-20250312133717348

exit在任意地方被调用,都表示调用进程直接退出。

  1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <stdlib.h>
  4 #include <string.h>
  5 #include <errno.h>
  6 void show()
  7 {
  8     printf("hello show!\n");
  9     printf("hello show!\n");
 10     printf("hello show!\n");
 11     printf("hello show!\n");
 12     printf("hello show!\n");
 13     printf("hello show!\n");
 14     //exit(13);
 15     return;
 16     printf("end show!\n");                              
 17     printf("end show!\n");                              
 18     printf("end show!\n");                              
 19     printf("end show!\n");
 20     printf("end show!\n");
 21 }                    
 22 int main()           
 23 {                    
 24     show();                  
 25     printf("Hello Linux!\n");
 26     //exit(12);      
 27     return 12;       
 28 } 

image-20250312134003553

return只表示当前函数返回。show函数return到了main函数继续执行。

return在其他函数当中代表函数结束,在main函数当中代表进程退出。

exit在任意地方都代表进程退出。

_exit函数

_exit是系统调用。

image-20250312134302959

#include <unistd.h>

void _exit(int status);

参数:status 定义了进程的终止状态,父进程通过wait来获取该值,也是退出码。

说明:虽然status是int,但是仅有低8位可以被父进程所用。

所以_exit(-1)时,在终端执行$?发现返回值是255。

exit和_exit都可以终止进程

  1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <stdlib.h>
  4 #include <string.h>
  5 #include <errno.h>
  6 void show()
  7 {
  8     printf("hello show!\n");
  9     printf("hello show!\n");
 10     printf("hello show!\n");
 11     printf("hello show!\n");
 12     printf("hello show!\n");
 13     printf("hello show!\n");
 14     //exit(13);
 15     //retfurn;
 16     _exit(14);
 17     printf("end show!\n");                                                 
 18     printf("end show!\n");                                                 
 19     printf("end show!\n");                                                 
 20     printf("end show!\n");                                                 
 21     printf("end show!\n");                                                 
 22 }                                                                          
 23 int main()                                                                 
 24 {                                                                          
 25     show();                                                                
 26     printf("Hello Linux!\n");                                              
 27     //exit(12);                                                            
 28     return 12;                                                             
 29 }

image-20250312134715706


exit和_exit的区别

  1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <stdlib.h>
  4 #include <string.h>
  5 #include <errno.h>
  6 int main()
  7 {
  8     printf("Hello Linux!");
  9     sleep(1);
 10     exit(11);
 11 }

image-20250312135048617

一开始,打印信息不显示,因为printf没有+’\n’,然后打印信息就会保存在缓冲区,

1秒之后,进程退出,系统才会把缓冲区的数据刷新到显示器上。

  1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <stdlib.h>
  4 #include <string.h>
  5 #include <errno.h>
  6 int main()
  7 {
  8     printf("Hello Linux!");
  9     sleep(1);
 10     _exit(11);
 11 }

image-20250312135500208

没有打印出缓冲区的数据。

_exit是系统调用,在操作系统内部会直接终止进程,缓冲区的数据不做刷新。

exit是库函数,在终止之前,要先冲刷缓冲区,关闭流等,所以会让我们看到刷新后的结果。

image-20250311231158841

_exit是系统调用接口,正常程序直接调用操作系统接口,直接在进程层面上把进程终止了。

exit将会把曾经打开的流进行刷新,然后再调_exit。(所以是先把把数据刷新了,再结束进程的)。

printf一定是先把数据写到缓冲区中的,等到合适的时机(遇到\n,或者进程退出的时候)再进行刷新。

image-20250312143017166

缓冲区绝对不在内核空间中!!

如果在操作系统内部,无论是_exit还是exit在终止的时候,

必然会将在操作系统内部的对应区域也进行刷新,

因为是操作系统内部的数据,操作系统不做任何浪费时间和空间的事情,

最终都要调用_exit,如果都不把缓冲区的数据刷新,

为什么要维护这块缓冲区,将数据暂时保存起来呢?

所以操作系统维护缓冲区就一定要刷新数据。

缓冲区在用户空间。

_exit在系统调用中,看不到用户空间的缓冲区,所以不能对缓冲区做刷新,

exit在用户空间,所以可以看到缓冲区,可以对数据进行刷新。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐