Linux操作系统~进程崩溃的原理是什么?信号的产生方式有哪些?

28 篇文章 0 订阅
17 篇文章 0 订阅

目录

1.信号的概念

2.signal函数的使用

kill -l

自定义信号处理函数signal函数

3.进程异常/崩溃的原理

(1).进程为什么会崩溃?

(2).如何知道进程崩溃/异常的原因

(3).core dump的作用——可以知道在哪一行崩溃的

4.信号的产生方式

系统调用产生信号(kill,raise,abort函数)

软件条件产生信号(alarm函数)


1.信号的概念

        1.信号的产生->信号是给进程发的->进程要在合适的时候,要执行对应的动作

        2.进程在没有收到信号的时候,进程已经知道应该如何识别是哪一个信号以及如何处理它,曾经的操作系统工程师在写进程源代码的时候,就已经设置好了。

进程具有识别信号并处理信号的能力,远远早于信号的产生的

        3.信号随时都可能产生(异步),(异步信号发出后不需要等待其响应返回,发出者可以直接去做别的事情,所以收到信号的人可以先不作出响应,继续做自己更重要的事情),但是我当前可能做着更重要的事情!

进程收到信号之后,需要先将信号保存起来,以供在合适的时候处理!

        4.保存在哪里呢?进程的PCB也就是task_struct里面

信号本质也是:数据!

信号的发送->往进程task_struct内写入信号数据!

        5.无论我们的信号任何发送,本质都是在底层通过OS发送的

信号的整个生命周期:

        信号产生前(信号产生的各种方式)——信号产生中(信号保存的方式)——信号产生后(信号处理的方式)


2.signal函数的使用

kill -l

kill -l可以查看系统支持的信号

键盘Ctrl+C的时候本质上是向进程发送2号信号

前31个是普通信号,后31个是实时信号

[zebra@VM-8-12-centos cpp]$ kill -l
 1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL       5) SIGTRAP
 6) SIGABRT      7) SIGBUS       8) SIGFPE       9) SIGKILL     10) SIGUSR1
11) SIGSEGV     12) SIGUSR2     13) SIGPIPE     14) SIGALRM     15) SIGTERM
16) SIGSTKFLT   17) SIGCHLD     18) SIGCONT     19) SIGSTOP     20) SIGTSTP
21) SIGTTIN     22) SIGTTOU     23) SIGURG      24) SIGXCPU     25) SIGXFSZ
26) SIGVTALRM   27) SIGPROF     28) SIGWINCH    29) SIGIO       30) SIGPWR
31) SIGSYS      34) SIGRTMIN    35) SIGRTMIN+1  36) SIGRTMIN+2  37) SIGRTMIN+3
38) SIGRTMIN+4  39) SIGRTMIN+5  40) SIGRTMIN+6  41) SIGRTMIN+7  42) SIGRTMIN+8
43) SIGRTMIN+9  44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9  56) SIGRTMAX-8  57) SIGRTMAX-7
58) SIGRTMAX-6  59) SIGRTMAX-5  60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2
63) SIGRTMAX-1  64) SIGRTMAX
[zebra@VM-8-12-centos cpp]$ 

自定义信号处理函数signal函数

signal函数:修改进程对信号的默认处理动作

  • signum:表示要修改几号信号
  • handler表示要做出的处理动作函数

e.g.:

siganl.cc:

#include <iostream>
#include <signal.h>
#include <unistd.h>

using namespace std;
void handler(int signo)
//传入的信号表示要对几号信号进行自定义处理
{
    printf("get a signal no: %d, pid: %d\n",signo, getpid());  //打印执行的是几号信号的处理函数,打印执行
}

int main()
{
    signal(2,handler);  //通过signal注册2号信号的处理动作,改成自定义处理动作
    while(1)
    {
        cout << "zebra, pid :" << getpid() << endl << endl;
        sleep(1);
    }
    return 0;
}

        当我们使用kill -2向对应进程发送2号信号,或者使用Ctrl+C发送2号信号的时候,会回调handler方法,执行我们自己定义的对2号信号的处理动作

        信号的产生方式其中一种就是通过键盘产生键盘产生的信号只能用来终止前台进程

        如果我们运行进程的时候加上一个&,让进程在后台运行,此时我们用Ctrl+C就无法终止前台的进程。只有使用kill -9 目标进程pid,才能杀掉后台进程。

        一般而言,进程收到信号的处理方案有3中情况

1.默认动作-- 一部分是终止自己,暂停等

2.忽略动作--是一种信号处理的方式,只不过动作就是什么也不干

3.(信号的捕捉)自定义动作--我们刚刚用signal方法,就是在修改信号的处理动作由:默认-〉自定义动作

——9号信号无法被自定义(捕捉)


3.进程异常/崩溃的原理

(1).进程为什么会崩溃?

        在win or Linux下进程崩溃的本质,是进程收到了对应的信号,然后进程执行信号的默认处理动作(杀死进程)

        根本原因:你的操作影响到了硬件或者其他软件,操作系统检测到了,就发送对应的信号终止了进程。

        所有的异常都是操作系统给进程发送信号导致的。自定义异常就很像自定义信号处理。

1.出现段错误(segmentation fault)

原因:访问越界或者使用野指针(实际上也是访问越界)

野指针的情况:

1.指针没有被初始化

2.指针指向的空间被释放,指针没有置空

3.指针变量进行了错误的运算

4.进行了错误的指针类型转换

为什么会崩溃,因为收到了11号信号 SIGSEGV seg fault

2.浮点数异常(除0错误),实际上是因为发送了8号信号 SIGFPE float point e

(2).如何知道进程崩溃/异常的原因

        进程崩溃/异常退出的时候,进程会收到信号,我们waitpid得到的status里面。低7位就是终止信号,我们可以根据终止信号来判断进程崩溃的原因


(3).core dump的作用——可以知道在哪一行崩溃的

core dump——核心转储

        除此之外,进程如果异常的时候,使用了core dump的话,该位置会被设置为1

        注:进程异常终止的时候,不一定所有的信号都会产生core dump文件(由core dump这一位决定),但是低7位的终止信号肯定会被设置

可以用以下方式检验:用位运算哪一位都可以给你取出来

  printf("exit code: %d, exit sig: %d, core dump flag: %d\n", (status>>8)&0xFF, status&0x7F, (status>>7) & 1);
  1. 在Linux中,当一个进程退出的时候,它的退出码和退出信号都会被设置(正常情况)
  2. 当一个进程异常的时候,进程的退出信号会被设置,表明当前进程退出的原因
  3. 如果必要,OS会设置退出信息中的core dump标志位为1,并将进程在内存中的数据转储到磁盘当中,方便我们后期调试

         在一般的云服务器中,core dump的相关功能默认是关闭的,我们可以使用以下指令打开这个功能:

ulimit -c 10240

        打开以后,我们在运行可执行文件的时候,后面会跟上core dumped,表示当前进程文件被core dumped了,而且在当前目录下会多一个文件,也就是内存中的一些数据,保存了出现异常时的一些数据,比如是在哪一行代码出现的异常。

        此时我们对要调试的可执行文件进行调试,进入调试模式后,可以使用core-file命令把core文件加载进来,此时我们可以知道代码崩溃在哪一行。这就是事后调试。        


4.信号的产生方式

1. 键盘产生(比如Ctrl+C发出2号信号,只能对前台进程发信号)

2. 进程异常,也能产生信号(本质上是因为比如I/0异常,非法访问等异常会导致硬件异常,见上节)

3. 通过系统调用,产生信号(比如kill指令或者kill函数)

4. 软件条件,也能产生信号

        信号产生的方式种类虽然非常多,但是无论产生信号的方式千差万别,但是最终,一定都是通过OS向目标进程发送的信号

系统调用产生信号(kill,raise,abort函数)

        采用系统调用向目标进程发起信号,在代码里面可以调用kill函数

 kill函数:采用系统调用向目标进程发起信号

 raise函数(C库中的函数):自己给自己发信号

 

 kill和raise函数都是成功返回0,失败返回-1

abort函数:给自己发一个6号信号

就像exit函数一样,abort函数总是会成功的,所以没有返回值。


软件条件产生信号(alarm函数)

        通过某种软件(OS),来触发信号的发送,系统层面设置定时器,或者某种操作而导致条件不就绪等这样的场景下,触发的信号发送。

        e.g.:进程间通信:当读端不光不读,而且还关闭了读fd,写端一直在写,最终写进程会受到sigpipe (13),就是一种典型的软件条件触发的信号发送

alarm函数:

         调用alarm函数可以设定一个闹钟,也就是告诉内核在seconds秒之后给当前进程发SIGALRM信号(14号信号), 该信号的默认处理动作是终止当前进程。

        这个函数的返回值是0或者是以前设定的闹钟时间还余下的秒数。如果seconds值为0,表示取消以前设定的闹钟,函数的返回值仍然是以前设定的闹钟时间还余下的秒数

        alarm(0)表示关闭闹钟,返回值就是剩余的秒数

e.g.:尝试统计如果count每次加1,服务器1秒钟能把count加到多少

alarm.test:

#include <iostream>
#include <unistd.h>
#include <signal.h>
using namespace std;
int count = 0;
void handler(int signo)
//自定义函数,发送SIGALRM信号以后,先打印count的值,然后再调用exit函数退出进程
{
    cout << "count: " << count << endl;
    exit(1);
}

int main()
{
    signal(SIGALRM,handler);
    alarm(1);
    while(true)
    {
        count++;
        // cout << "count: " << count << endl;
    }
    return 0;
}

如果每一次循环都将count的值打印一遍,则可以加到9万多 

 

        如果每次循环不打印,等到1s过后,当对SIGALRM14号信号进行处理的时候再打印,则可以加到更大的数,原因是因为IO太慢了,除了打印的是IO,服务器和主机交互的网络通信也算是IO,所以不要每次循环都打印的话,可以有更多的时间给到CPU去计算。

 

 

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值