Linux 信号(上)

来源:互联网 发布:文字美图软件 编辑:程序博客网 时间:2024/05/16 07:24


kill -l

[bozi@localhost test_20160723]$ kill -l

 1) SIGHUP     2) SIGINT 【进程中断】    3) SIGQUIT     4) SIGILL     5) SIGTRAP

 6) SIGABRT     7) SIGBUS     8) SIGFPE 【除0异常】    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 

【--这没有那两个32 和33(共62个)   前面的1-31为普通信号  后面的34以上实时信号--】  

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  

信号在内核中的表示

以上我们讨论了信号产生 (Generation )的各种原因 ,而 实际执行信号的处理动作称为信号递

(Delivery), 信号从产生到递达之间的状态 ,称为信号未决(Pending)。进程可以选择阻塞

(Block )某个信号。被阻塞的信号产生时将保持在未决状态 ,直到进程解除对此信号的阻塞 ,

才 执行递达的动作。 注意, 阻塞和忽略是不同的 ,只要信号被阻塞就不会递达 ,而忽略是在递

达之后 可选的一种处理动作。信号在内核中的表示可以看作是这样的 :

wKiom1eYtXjCp_OUAACsPKsxduU158.png

函数:        typedef void (*sighandler_t)(int);  【给对用的信号 注册接收到对用信号时的处理函数 注意9号信号不能起效 接收到9号信号 直接进程终止】       sighandler_t signal(int signum, sighandler_t handler); 注册用户自定义函数handler到相应的信号 实现捕捉过程       int kill(pid_t pid, int sig); 该某个pid进程发送sig信号       int raise(int sig);   自己给自己发信号           void abort(void); 使得自己接收到一个SIGABRT(流产)信号 而 异常终止

发信号:

ctrl+2 给当前进程pid发送2号中断信号

kill -2 对应的pid   同样也是向进程号为pid的进程发送2号中断信号

进程报错过程:

     进程出发异常(如除以2、越界访问等), 操作系统捕捉到这个异常给进程发送相应的信号,进程相应相应的信号,这样就出现相应的错误提示 如 段错误

进程收到一个信号后,进程对信号的处理不一定立即的,先保存到PCB中

进程收到信号大部分是立即退出

进程处理信号的可选动作有3种:

1 忽略此信号

2 执行该信号的,默认处理动作

提供一个信号处理函数 ,要求内核在处理该信号时切换到用户态执行这个处理函数 ,这种方 式称为捕捉 (Catch)一个信号。


信号的产生(4种):

1 键盘

2 系统函数

      kill函数

      abort函数使当前进程接收到SIGABRT信号而异常终止。

      raise函数可 以给当前进程发送指定的信号 (自给自发信号 )

          alarm函数 设置闹钟

3 操作系统硬件异常

4 kill 命令行命令 


练习程序:

(1) 命令行kill发信号

#include<stdio.h>#include <sys/types.h>#include <unistd.h>int main(){        printf("pid is :%d\n", getpid());        while (1);        return 0;}
执行命令行 [root@localhost test_20160723]# kill 2 30467 给pid为30467的进程发送2号信号[bozi@localhost signal_test]$ ./signal_testpid is :30467已终止

(2)kill函数发信号

#include<stdio.h>#include <sys/types.h>#include <unistd.h>#include <signal.h>int main(){        printf("pid is :%d\n", getpid());        sleep(3);        kill(getpid(),8);        while (1);        return 0;}
运行结果:[bozi@localhost signal_test]$ ./signal_testpid is :30602浮点数例外 (core dumped)

(3)abort()

#include<stdio.h>#include <sys/types.h>#include <unistd.h>#include <signal.h>#include <stdlib.h>int main(){        printf("pid is :%d\n", getpid());        sleep(3);        //kill(getpid(),8);        abort();        while (1);        return 0;}
运行结果:[bozi@localhost signal_test]$ ./signal_testpid is :30653已放弃 (core dumped)

(4)raise()

#include<stdio.h>#include <sys/types.h>#include <unistd.h>#include <signal.h>#include <stdlib.h>int main(){        printf("pid is :%d\n", getpid());        sleep(3);        //kill(getpid(),8);//        abort();        raise(9);        while (1);        return 0;}
运行结果:[bozi@localhost signal_test]$ ./signal_testpid is :30697已杀死

(5)alarm()

#include<stdio.h>#include <sys/types.h>#include <unistd.h>#include <signal.h>#include <stdlib.h>int main(){        printf("pid is :%d\n", getpid());        //sleep(3);        //kill(getpid(),8);//        abort();        //raise(9);        alarm(3);        while (1)        {            printf("1\n");            usleep(423100);        }        return 0;}
运行结果:[bozi@localhost signal_test]$ ./signal_testpid is :3080111111111闹钟

(6) sighandler_t signal(int signum, sighandler_t handler); 注册用户自定义函数handler到相应的信号 实现捕捉过程

#include<stdio.h>#include <sys/types.h>#include <unistd.h>#include <signal.h>#include <stdlib.h>int count = 0;void sigalrm_fun(int sig){    printf("catch you sig :%d,  count: %d\n", sig, count);    alarm(0); // 取消以前设定的闹铃}int main(){       signal(SIGALRM, sigalrm_fun);    alarm(2);    while (1)    {        count++;    }        return 0;}
运行结果:[bozi@localhost signal_test]$ ./signal_testcatch you sig :14,  count: 341103803还可以再次设置闹铃void sigalrm_fun(int sig){    printf("catch you sig :%d,  count: %d\n", sig, count);    int lastseconds = alarm(2); // 设定的闹铃2seconds lastseconds 记录上一次闹铃的剩余时间     printf("last seconds: %d\n", lastseconds);}运行:(走走停停效果)[bozi@localhost signal_test]$ ./signal_testcatch you sig :14,  count: 367417988last seconds: 0catch you sig :14,  count: 736827166last seconds: 0catch you sig :14,  count: 1101348640last seconds: 0catch you sig :14,  count: 1467117036last seconds: 0

进程异常退出的状态 叫做core dump信息

首先解释什么是 Core Dump。当一个进程要异常终止时 ,可以选择把进程的用户空间内存数据全部 保存到磁盘上 ,文件名通常是 core, 这叫做Core Dump【内存转储。进程异常终止通常是因为有Bug,比如非法内存访问导致段错误 ,事后可以用调试器检查 core文件以查清错误原因 ,这叫做Post-mortem Debug。一个进程允许产生多大的 core文件取决于进程的 Resource Limit(个信息保存 在 PCB )。默认是不允许产生 core文件的 ,因为 core文件中可能包含用户密码等敏感信息 ,不安全。在开发调试阶段可以用 ulimit命令改变这个限制,允许产生 core件。

首先用 ulimit命令改变Shell 进程的Resource Limit,允许 core文件最大为 1024K:

$ ulimit -c 1024ulimit -a 显示所有[bozi@localhost signal_test]$ vim Makefile[bozi@localhost signal_test]$ vim signal_test.c[bozi@localhost signal_test]$ ulimit -acore file size          (blocks, -c) 1024data seg size           (kbytes, -d) unlimitedscheduling priority             (-e) 0file size               (blocks, -f) unlimitedpending signals                 (-i) 7922max locked memory       (kbytes, -l) 64max memory size         (kbytes, -m) unlimitedopen files                      (-n) 1024pipe size            (512 bytes, -p) 8POSIX message queues     (bytes, -q) 819200real-time priority              (-r) 0stack size              (kbytes, -s) 10240cpu time               (seconds, -t) unlimitedmax user processes              (-u) 1024virtual memory          (kbytes, -v) unlimitedfile locks                      (-x) unlimited
#include<stdio.h>#include <sys/types.h>#include <unistd.h>int main(){        printf("pid is :%d\n", getpid());        while (1);        return 0;}
[bozi@localhost signal_test]$ ./signal_testpid is :24463^\退出 (core dumped)[bozi@localhost signal_test]$ ls -al总用量 96drwxrwxr-x. 2 bozi bozi   4096 7月  26 13:21 .drwxrwxr-x. 3 bozi bozi   4096 7月  26 13:02 ..-rw-------. 1 bozi bozi 159744 7月  26 13:21 core.24463-rw-rw-r--. 1 bozi bozi     80 7月  26 13:20 Makefile-rwxrwxr-x. 1 bozi bozi   4765 7月  26 13:20 signal_test-rw-rw-r--. 1 bozi bozi    429 7月  26 13:19 signal_test.c

进程号 和 core.24463一样

gdb调试

[bozi@localhost signal_test]$ gdb(gdb) core-file core.24463 Missing separate debuginfo for the main executable fileTry: yum --disablerepo='*' --enablerepo='*-debug*' install /usr/lib/debug/.build-id/5a/f6fa6e34efab3e72d76ed7ec227347690fc681[New Thread 24463]Core was generated by `./signal_test'.Program terminated with signal 3, Quit.   【signal3   就是 ctrl + \】#0  0x08048413 in ?? ()(gdb) where#0  0x08048413 in ?? ()#1  0x00778d26 in ?? ()#2  0x00000001 in ?? ()#3  0xbfd54f34 in ?? ()#4  0x08048361 in ?? ()

--------------------------------------------------------------------------------------------------------

小插曲:用gdb调试core dump文件

转载自:http://blog.chinaunix.net/u2/83905/showart_2134570.html

在Unix系统下,应用程序崩溃,一般会产生core文件,如何根据core文件查找问题的所在,并做相应的分析和调试,是非常重要的。

什么是Core Dump?
Core的意思是内存, Dump的意思是扔出来, 堆出来.开发和使用Unix程序时, 有时程序莫名其妙的down了, 却没有任何的提示(有时候会提示core dumped). 这时候可以查看一下有没有形如core.进程号的文件生成, 这个文件便是操作系统把程序down掉时的内存内容扔出来生成的, 它可以做为调试程序的参考.
core dump又叫核心转储, 当程序运行过程中发生异常, 程序异常退出时, 由操作系统把程序当前的内存状况存储在一个core文件中, 叫core dump.

为什么没有core文件生成呢?

有时候程序down了, 但是core文件却没有生成. core文件的生成跟你当前系统的环境设置有关系, 可以用下面的语句设置一下, 然后再运行程序便成生成core文件.
ulimit -c unlimited
core文件生成的位置一般于运行程序的路径相同, 文件名一般为core.进程号


当获得了core文件以后,就可以利用命令gdb进行查找,参数一是应用程序的名称,参数二是core文件
如: gdb [...]xmsd [...]/xmsd_PID1065_SIG11.core

然后输入bt或者where找到错误发生的位置和相应的堆栈信息。就可知道发生错误时的函数调用关系,然后可以使用up或者down查看上一条和下一条具体详细信息。这样便能对问题进行大概定位,然后看源代码,进行分析。

eg:

#0 0x0804ff8c in memory_error (self=0x818ca10, ptr=0x82e5a94, msg=0x813efb2 "memory_free") at /aston/h_debit/XMS/bin/XMS_1_15_11/src/xms/lib/memory.c:140#1 0x080504cd in memory_free (ptr=0x82e5a94) at /aston/h_debit/XMS/bin/XMS_1_15_11/src/xms/lib/memory.c:275#2 0x080505ac in xml_free (ptr=0x82e5a94) at /aston/h_debit/XMS/bin/XMS_1_15_11/src/xms/lib/memory.c:316#3 0xb7edb589 in xmlFreeNode () from /home/zhenbo/workstation/DEVSUITE-HEAD/debug/lib/libxml2.so#4 0x082b2228 in ?? ()#5 0x08097031 in interfacehandler_set (self=0x82bd74c, correlator=-4, conf=0x0, cb=0x82b0aac) at /aston/h_debit/XMS/bin/XMS_1_15_11/src/interfaces/interfacehandler.c:150#6 0x0805e897 in configurable_set (self=0x82bd74c, correlator=-4, conf=0x0, cb=0x82b0aac) at

如果之前不能准确定位错误的位置,则从以上信息可以知道,可从函数interfacehandler_set入手进行分析,其中的参数和行号都已经给出了。 

read this for an example: How to Debug Using GDB

--------------------------------------------------------------------------------------------------------

阻塞信号:区阻塞 和 忽略不同 忽略是处理了,只是处理的动作是忽略  而阻塞 是还没有处理

信号递达:实际处理信号的处理动作

信号未决: 信号从产生到递达之间的状态

信号是可以阻塞的

信号的产生 和 阻塞是没有关系的(信号在未产生之前也可以 阻塞, 阻塞后等到 信号产生后就保持在 未决状态)



信号集操作函数

#include <signal.h>int sigemptyset(sigset_t *set);

函数sigemptyset初始化 set所指向的信号集 ,使其中所有信号的对应 bit清零【置0】 ,表示该信号集

不包含 任何有效信号。

int sigfillset(sigset_t *set);

函数sigfillset初始化 set所指向的信号集 ,使其中所有信号的对

bit置位【置1】 ,表示 该信号集的有效信号包括系统支持的所有信号。

int sigaddset(sigset_t *set, int signo);int sigdelset(sigset_t *set, int signo);

注意,在使用 sigset_t类型

的变量之前 ,一定要调 用 sigemptysetsigfillset 做初始化,使信号集处于确定的状

态。初始化 sigset_t变量之后就可以 在调用 sigaddsetsigdelset 在该信号集中添加

或删除某种有效信号。

int sigismember(const sigset_t *set, int signo);

sigismember是一个布尔

函数,用于判断一个信号集的有效信号中是否包含某种 信号 ,若包含则返回 1,不包含则返回 0,

出错返回-1


调用函数sigprocmask可以读取或更改进程的信号屏蔽字(阻塞信号集)。

#include <signal.h>int sigprocmask(int how, const sigset_t *set, sigset_t *oset);

返回值:若成功则为0,若出错则为-1

wKioL1eYuCOioTsaAAD3NLj96rU420.png

练习代码

#include<stdio.h>#include <signal.h>#include <stdlib.h>void show_pending(sigset_t *pending){    int  i = 1;    for (; i <= 31; i++)    {        if (sigismember(pending, i))        {            printf("1");        }        else        {            printf("0");        }    }    printf("\n");}void handler(int sig){    printf("catch a sig :%d\n", sig);    //exit(3);}int main(){    signal(2, handler);    sigset_t block, oldblock, pending;    sigemptyset(&block);    sigemptyset(&oldblock);    sigemptyset(&pending);    sigaddset(&block, SIGINT); // 添加SIGINT信号    sigprocmask(SIG_SETMASK, &block, &oldblock);    int count = 0;    while (1)    {        sigpending(&pending);        show_pending(&pending);        sleep(1);        count++;        if (count > 10)        {            printf("recover old sig block set\n");            sleep(2);    sigdelset(&block, SIGINT); // 删除SIGINT信号            sigprocmask(SIG_SETMASK, &block, &oldblock);        }    }}
执行结果:[bozi@localhost test_20160723]$ makegcc -o pending pending.c[bozi@localhost test_20160723]$ ./pending00000000000000000000000000000000000000000000000000000000000000^C0100000000000000000000000000000 // ctrl+c 产生SIGINT信号01000000000000000000000000000000100000000000000000000000000000010000000000000000000000000000001000000000000000000000000000000100000000000000000000000000000010000000000000000000000000000001000000000000000000000000000000100000000000000000000000000000recover old sig block setcatch a sig :20000000000000000000000000000000recover old sig block set0000000000000000000000000000000recover old sig block set0000000000000000000000000000000【先把sigint信号对应的信号位 置位   然后再置空】

内核态 用户态 转换


经常 由于系统调用、中断、异常   从 用户态转化为 内核态

内核态 权限最高 随之而来 危险系数也最高

收到一个信号 在恰当的时候处理 ,恰当的时候是有内核态转到用户态时【恰当的时候 是下面的2】

信号捕捉的过程(一次信号捕捉进行4次权限切换):

1 、用户态main函数中,产生中断或异常 进入内核态,处理中断 

2、【恰当的时候 】 内核处理完中断等,由内核态转用户态时,捎带着进行相应信号的检测,

【(1)信号捕捉的情况】如果这时有用户自定义函数, 执行用户自定义函数sighandler,[内核决定返回用户态后不是恢复 main函数的上下文继续执行 ,而是执行 sighandler,sighandler main函数使用不同的堆栈空间 ,它们之间不存在调用和被调用的关系 ,是 两个独立的控制流程,可以并发执行]

wKiom1eYuK2hvC58AABr5duiI9Y515.png

【(2)其他情况】如果是 【默认动作】,直接终止,不返回用户态,权限转换1次

wKioL1eYuK7CWeunAAA2pE9RPgU368.png

【(3)其他情况】如果是  【忽略】  pending表修改后 直接返回用户态,权限转换2次

wKiom1eYuK-Do5oTAABMMH1susU619.png

3、 用户自定义函数执行完成后, 再次返回内核态

4、 如果没有新的信号要递达,返回用户态时,恢复main函数的上下文继续执行。

---------------------------------------------------------------------------------------------

参考:

A. 内核如何实现信号的捕捉

如果信号的处理动作是用户自定义函数 ,在信号递达时就调用这个函数 ,这称为捕捉信号。由

于信号处理函数的代码是在用户空间的 ,处理过程比较复杂 ,举例如下 :

1. 用户程序注册了SIGQUIT信号的处理函数 sighandler

2. 当前正在执行main函数 ,这时发生中断或异常切换到内核态。

3. 在中断处理完毕后要返回用户态的 main函数之前检查到有信号 SIGQUIT递达。

4. 内核决定返回用户态后不是恢复 main函数的上下文继续执行 ,而是执行 sighandler

,sighandler main 函数使用不同的堆栈空间 ,它们之间不存在调用和被调用的关系 ,

是 两个独立的控制流程。

5. sighandler函数返回后自动执行特殊的系统调用 sigreturn再次进入内核态。

6. 如果没有新的信号要递达 ,这次再返回用户态就是恢复 main函数的上下文继续执行了。

---------------------------------------------------------------------------------------------

#include <signal.h>int sigpending(sigset_t *set);

sigpending读取当前进程的 未决信号集 ,通过 set参数传出。调用成功则返回 0,出错则返回

-1。

#include <signal.h>

int sigaction(int signo, const struct sigaction *act, struct

sigaction *oact);

sigaction函数可以读取和修改与指定信号相关联的处理动作。调用成功则返回 0,出错则

返回- 1 signo是指定信号的编号。若 act指针非空 ,则根据 act修改该信号的处理动作。

oact指针非空 ,则通过 oact传出该信号原来的处理动作。 act oact指向 sigaction

构体:

                struct sigaction {

               void     (*sa_handler)(int);

               void     (*sa_sigaction)(int, siginfo_t *, void *);

               sigset_t   sa_mask;

               int        sa_flags;

               void     (*sa_restorer)(void);

           };

sa_handler赋值为常数 SIG_IGN传给sigaction 表忽略信号 ,赋值为常数 SIG_DFL

表执系统默认动作 ,赋值为一个函数指针表自定义函数捕捉信号 ,或者说向内核注册

了一个信号处理函 数 ,该函数返回值为 void, 可以带一个 int 参数 ,通过参数可以得知当前信

号的编号 , 这样就可以用同一个函数处理多种信号。 显然, 这也是一个回调函数 ,不是被 main

函数调用 ,而是被系统所调用。

sa_mask字段说明这些需要额外屏蔽的信号 ,当信号处理函数返回时自动恢复原来的信号

屏蔽字。

sa_flags字段包含一些选项,这里都把 sa_flags设为0, sa_sigaction是实时信号的处理函 数这里不讨论

练习代码:#include<stdio.h>#include <signal.h>void catch(int sig){    printf("catch sig :%d \n", sig );}int main(){    struct sigaction act, oldaction;    act.sa_handler = catch;    sigemptyset(&act.sa_mask);    act.sa_flags = 0;    sigaction(2, &act, &oldaction);    while (1)    {        printf("1111111111111111111\n");        sleep(1);    }    return 0;}
运行:[bozi@localhost test_20160726]$ ./sigaction111111111111111111111111111111111111111111111111111111111^Ccatch sig :21111111111111111111

用SIGALRM信号处理 模拟 sleep

练习代码:

#include<stdio.h>#include <signal.h>#include <stdlib.h>void catch(int sig){ //        什么都不做 只是防止alarm默认的 终止进程}int my_sleep(int timeout){    signal(SIGALRM, catch); // 用catch 是为了让pause出错返回    //signal(SIGALRM, SIG_IGN); // 用catch 是为了让pause出错返回    //signal(SIGALRM, SIG_DFL); // 用catch 是为了让pause出错返回    alarm(timeout); //    pause();       //  alarm pause 不是安全的 可以用sigsuspend    int ret = alarm(0);    signal(SIGALRM, SIG_DFL); // 回复以前的行为 方便别人用时 和自己调用之前是一样的 而不是 变成catch信号后 调用用户自定义函数    return ret;}int main(){    while (1)    {        printf("hello word\n");        my_sleep(2);    }    return 0;}
运行:[bozi@localhost test_20160726]$ ./pause_testhello wordhello wordhello wordhello wordhello word


信号捕捉过程函数 31个全block 看哪个 可以捕捉 有的直接就让程序挂掉le

不进入捕捉,直接挂掉的信号有9和19号

 

 9) SIGKILL   19) SIGSTOP
测试代码:#include<stdio.h>#include <signal.h>void catch(int sig){    printf("catch sig:%d\n", sig);}int main(){    struct sigaction act, oldaction;    act.sa_handler = catch;    sigemptyset(&act.sa_mask);    act.sa_flags = 0;    int i = 1;    for (; i <= 31; i++)    {        sigaction(i, &act, &oldaction);    }    while(1)    {        printf("------------------\n");        sleep(1);    }    return 0;}

 

部分运行结果:

[bozi@localhost test_20160726]$ kill -22 1293[bozi@localhost test_20160726]$ kill -23 1293[bozi@localhost test_20160726]$ kill -24 1293[bozi@localhost test_20160726]$ kill -25 1293[bozi@localhost test_20160726]$ kill -26 1293[bozi@localhost test_20160726]$ kill -27 129317964  1216  1216 17964 pts/1    17964 T      500   0:00 ./sigaction_catch31 // T 是stop状态  19) SIGSTOP信号的效果------------------catch sig:20------------------------------------------------------------------------------------------catch sig:21------------------


本文出自 “城市猎人” 博客,请务必保留此出处http://alick.blog.51cto.com/10786574/1830892

0 0
原创粉丝点击