Linux进程控制(二)之进程终止
Linux进程终止:进场退出的场景(三种),常见的退出方法(return,exit,_exit),main函数的返回值-退出码(0代表正确),获得退出码的方法,自己设计退出码体系,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 char *p=NULL;
9 *p=100;
10 return 0;
11 }

以上是:野指针异常。对于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 }

以上是:浮点数异常(浮点数溢出)。
进程出现了异常,本质是进程收到了对应的信号!
野指针(在虚拟地址空间的页表上没有映射关系或者权限为只读)
/除零(寄存器出现溢出)等一定会发生一些硬件上的错误的,
都会转换成硬件问题,这些硬件问题都会被操作系统识别,
进而操作系统向目标进程发信号来完成的。
当进程一旦出问题,操作系统可以给它发信号kill -9,杀掉这个进程。

浮点数报错: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 }

结论:进程出现异常的本质就是收到了对应的信号!
父进程要关心子进程的退出,要关心以下的点:
进程退出时有没有收到对应的信号
(没有收到信号,说明没有异常;
代码跑完了,再查对应的退出结果(退出码),退出码为0表示成功,非0表示错误,并且有错误的原因。)
进程常见退出方法
正常终止(可以通过 echo $? 查看进程退出码):
- 从main返回
- 调用exit
- _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.结果是正确的

进程中,谁会关心我的运行情况呢?
一般而言,是父进程要关心!
父进程要关心的是代码跑完了,但是结果是不正确的。
运行结果是否正确统一用进程的退出码(即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 }

echo用来获得最近一个程序的退出码 把最近一次程序的退出码保存到 ‘$?’ 中
第二次,最近的命令就变成了echo命令,所以我们的退出码就变成0了
有些程序运行往往不向显示器打印任何信息,我们想要知道代码进程任务是否完成,就要查看退出码。
退出码是给计算机看的,所以,strerror就是把错误码(退出码)转换成错误码描述呈现给我们看。

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 }

以上就称为 - 错误码表述。(系统提供的默认的错误码以及错误码描述)
系统提供的错误码和错误码描述是有对应关系的。

这个错误码描述对应错误码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 }

malloc error了 如果我们想知道是什么原因可以查看退出码(我们自己设置的malloc不成功就返回1)。
errno是c语言给我们提供的全局变量,记录了最近一次执行的错误码。
我们调用库函数时是有可能是失败的,c语言会把对应的全局变量errno设置为数字,
表示调用该函数出错时的错误码。

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 }

这样既可以知道错误码是什么,又可以知道对应的错误原因是什么,
并且还可以把错误码转化成进程的退出码让父进程知道。
所以如果是纯c的编程就可以这么写,但是我们后面的学习用到C和C++,
所以我们不会采用c标准库给我们提供的出错体系,我们自己定义。
exit函数
exit:终止一个进程。其参数是退出码。

exit最后也会调用exit, 但在调用exit之前,还做了其他工作:
- 执行用户通过 atexit或on_exit定义的清理函数。
- 关闭所有打开的流,所有的缓存数据均被写入
- 调用_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运行结果:

return运行结果:

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 }

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 }

return只表示当前函数返回。show函数return到了main函数继续执行。
return在其他函数当中代表函数结束,在main函数当中代表进程退出。
exit在任意地方都代表进程退出。
_exit函数
_exit是系统调用。

#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 }

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 }

一开始,打印信息不显示,因为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 }

没有打印出缓冲区的数据。
_exit是系统调用,在操作系统内部会直接终止进程,缓冲区的数据不做刷新。
exit是库函数,在终止之前,要先冲刷缓冲区,关闭流等,所以会让我们看到刷新后的结果。

_exit是系统调用接口,正常程序直接调用操作系统接口,直接在进程层面上把进程终止了。
exit将会把曾经打开的流进行刷新,然后再调_exit。(所以是先把把数据刷新了,再结束进程的)。
printf一定是先把数据写到缓冲区中的,等到合适的时机(遇到\n,或者进程退出的时候)再进行刷新。

缓冲区绝对不在内核空间中!!
如果在操作系统内部,无论是_exit还是exit在终止的时候,
必然会将在操作系统内部的对应区域也进行刷新,
因为是操作系统内部的数据,操作系统不做任何浪费时间和空间的事情,
最终都要调用_exit,如果都不把缓冲区的数据刷新,
为什么要维护这块缓冲区,将数据暂时保存起来呢?
所以操作系统维护缓冲区就一定要刷新数据。
缓冲区在用户空间。
_exit在系统调用中,看不到用户空间的缓冲区,所以不能对缓冲区做刷新,
exit在用户空间,所以可以看到缓冲区,可以对数据进行刷新。
更多推荐

所有评论(0)