首页 > 技术文章 > linux学习之信号篇(一)

rainbow1122 2017-11-13 22:16 原文

信号

1.信号的概念

信号编号
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
//32号之后的叫实时信号,当做一些驱动编程的时候会用到
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

 

2.信号产生种类

ctl+c SIGINT
//以下两个信号不允许阻塞,捕获和忽略,留给内核管理用 ctl
+z SIGTSTP ctl+\ SIGQUIT

终端特殊按键

发送给进程,可以练习编写一个程序,运行练习。

note:jobs显示进程,ps aux 详细显示,fg %编号 将后台信号切换到前台显示。


硬件异常
* 除0操作
* 访问非法内存

#include<stdio.h>
 
int main(void)
{
    int a=0;
    
    char *str="hello";//段错误,11:SIGSEGV  非法操作内存
    *str='H';
     printf("%d\n",3/a);//浮点错误,8:SIGFPE cpu不能除0
     while(1);
     return 0;
}

 

kill函数或kill命令

int kill(pid_t pid, int sig)
pid > 0
sig发送给ID为pid的进程
pid == 0
sig发送给与发送进程同组的所有进程
pid < 0
sig发送给组ID为|-pid|的进程,并且发送进程具有向其发送信号的权限
pid == -1
sig发送给发送进程有权限向他们发送信号的系统上的所有进程
sig为0时,用于检测,特定为pid进程是否存在,如不存在,返回-1

不过,kill向调用者返回测试结果时,原来存在的被测试进程可能刚终

int raise(int sig)//自己向自己发信号
void abort(void)

 

某种软件条件已发生 定时器alarm到时,每个进程只有一个定时器

 

 

unsigned int alarm(unsigned int seconds)

例:

#include <unistd.h>
#include <stdio.h>
int main(void)
{
    int counter;
   alarm(1);//sleep(1)是停止不执行一秒,而alarm下面的代码仍会执行,不会阻塞
   for(counter=0; 1; counter++)
   printf("counter=%d ", counter);
   return 0;
}
//可以测试1秒中,counter++了多少次

管道读端关闭,写端写数据

 

3.信号产生原因

1) SIGHUP:当用户退出shell时,由该shell启动的所有进程将收到这个信号,默认动作为终止进程
2)SIGINT:当用户按下了<Ctrl+C>组合键时,用户终端向正在运行中的由该终端启动的程序发出此信号。默认动作为终止进程。
3)SIGQUIT:当用户按下<ctrl+\>组合键时产生该信号,用户终端向正在运行中的由该终端启动的程序发出些信号。默认动作为终止进程。
4)SIGILL:CPU检测到某进程执行了非法指令。默认动作为终止进程并产生core文件
5)SIGTRAP:该信号由断点指令或其他 trap指令产生。默认动作为终止里程 并产生core文件。
6 ) SIGABRT:调用abort函数时产生该信号。默认动作为终止进程并产生core文件。
7)SIGBUS:非法访问内存地址,包括内存对齐出错,默认动作为终止进程并产生core文件。
8)SIGFPE:在发生致命的运算错误时发出。不仅包括浮点运算错误,还包括溢出及除数为0等所有的算法错误。默认动作为终止进程并产生core文件。
9)SIGKILL:无条件终止进程。本信号不能被忽略,处理和阻塞。默认动作为终止进程。它向系统管理员提供了可以杀死任何进程的方法。
10)SIGUSE1:用户定义 的信号。即程序员可以在程序中定义并使用该信号。默认动作为终止进程。
11)SIGSEGV:指示进程进行了无效内存访问。默认动作为终止进程并产生core文件。
12)SIGUSR2:这是另外一个用户自定义信号 ,程序员可以在程序中定义 并使用该信号。默认动作为终止进程。1
13)SIGPIPE:Broken pipe向一个没有读端的管道写数据。默认动作为终止进程。
14) SIGALRM:定时器超时,超时的时间 由系统调用alarm设置。默认动作为终止进程。
15)SIGTERM:程序结束信号,与SIGKILL不同的是,该信号可以被阻塞和终止。通常用来要示程序正常退出。执行shell命令Kill时,缺省产生这个信号。默认动作为终止进程。
16)SIGCHLD:子进程结束时,父进程会收到这个信号。默认动作为忽略这个信号。
17)SIGCONT:停止进程的执行。信号不能被忽略,处理和阻塞。默认动作为终止进程。
18)SIGTTIN:后台进程读终端控制台。默认动作为暂停进程。
19)SIGTSTP:停止进程的运行。按下<ctrl+z>组合键时发出这个信号。默认动作为暂停进程。
21)SIGTTOU:该信号类似于SIGTTIN,在后台进程要向终端输出数据时发生。默认动作为暂停进程。
22)SIGURG:套接字上有紧急数据时,向当前正在运行的进程发出些信号,报告有紧急数据到达。如网络带外数据到达,默认动作为忽略该信号。
23)SIGXFSZ:进程执行时间超过了分配给该进程的CPU时间 ,系统产生该信号并发送给该进程。默认动作为终止进程。
24)SIGXFSZ:超过文件的最大长度设置。默认动作为终止进程。
25)SIGVTALRM:虚拟时钟超时时产生该信号。类似于SIGALRM,但是该信号只计算该进程占用CPU的使用时间。默认动作为终止进程。
26)SGIPROF:类似于SIGVTALRM,它不公包括该进程占用CPU时间还包括执行系统调用时间。默认动作为终止进程。
27)SIGWINCH:窗口变化大小时发出。默认动作为忽略该信号。
28)SIGIO:此信号向进程指示发出了一个异步IO事件。默认动作为忽略。
29)SIGPWR:关机。默认动作为终止进程。
30)SIGSYS:无效的系统调用。默认动作为终止进程并产生core文件。
31)SIGRTMIN~(64)SIGRTMAX:LINUX的实时信号,它们没有固定的含义(可以由用户自定义)。所有的实时信号的默认动作都为终止进程。

 

4.进程处理信号行为

man  page  里信号3种处理方式:

SIG_IGN
SIG_DFL
a signal handling function

进程处理信号的行为:
(1)默认处理动作

term    //终止进程
core    //终止此个进程,并生成core文件。进程死之前的内存情况,死后验尸。
gcc -g file.c
ulimit -c 1024
gdb a.out core

ign    //忽略掉此信号
stop   //暂停此进程
cont   //继续一个进程的执行

(2).忽略
(3)捕捉(用户自定义信号处理函数)

5.信号集处理函数

sigset_t为信号集,可sizeof(sigset_t)察看
//参考下面的图,先构造出sigset_t,通过注册的方式和阻塞信号集联系起来。
int sigemptyset(sigset_t *set)//全部置0; int sigfillset(sigset_t *set)//全部置1;

int sigaddset(sigset_t *set, int signo)//部分置1;
int sigdelset(sigset_t *set, int signo)//部分清零
int sigismember(const sigset_t *set, int signo)//查看

6.PCB的信号集

信号在内核中的表示示意图

  PCB里边有未决信号集合和阻塞信息集合。未决信号集放的是信号产生了但还没有被响应的信号。对应的信号为置1,表示该位的信号产生了。(未决态),信号产生并被响应称之为递达态,未决信号集对应位自动翻转。用户可以自动设置阻塞信息集。每个信号都对应一个默认的动作。前32位信号不支持排队,同一个信号产生多次,只记录一次。

  如果在进程解除对某信号的阻塞之前这种信号产生过多次,将如何处理?POSIX.1允许系统递送该信号一次或多次。Linux是这样实现的:常规信号在递达之前产生多次只计一次,而实时信号在递达之前产生多次可以依次放在一个队列里。本章不讨论实时信号。从上图来看,每个信号只有一个bit的未决标志,非0即1,不记录该信号产生了多少次,阻塞标志也是这样表示的。因此,未决和阻塞标志可以用相同的数据类型sigset_t来存储,sigset_t称为信号集,这个类型可以表示每个信号的“有效”或“无效”状态,在阻塞信号集中“有效”和“无效”,“无效”的含义是该信号是否处于未决状态。效”的含义是该信号是否被阻塞,而在未决信号集中“有阻塞信号集也叫做当前进程的信号屏蔽字(Signal Mask),这里的“屏蔽”应该理解为阻塞而不是忽略。

sigprocmask

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

#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
返回值:若成功则为0,若出错则为-1

  how参数的含义

SIG_BLOCK set包含了我们希望添加到当前信号屏蔽字的信号,相当于mask=mask|set
SIG_UNBLOCK set包含了我们希望从当前信号屏蔽字中解除阻塞的信号,相当于mask=mask&~set
SIG_SETMASK 设置当前信号屏蔽字为set所指向的值,相当于mask=set

  如果调用sigprocmask解除了对当前若干个未决信号的阻塞,则在sigprocmask返回前,至少将其中一个信号递达。

sigpending

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

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


示例:

#include <signal.h>
#include <stdio.h>
void printsigset(const sigset_t *set)
{
    int i;
    for (i = 1; i < 32; i++)
    if (sigismember(set, i) == 1)
        putchar('1');
    else
         putchar('0');
     puts("");
}
int main(void)
{
       sigset_t s, p;
       sigemptyset(&s);
       sigaddset(&s, SIGINT);
       sigprocmask(SIG_BLOCK, &s, NULL);
       while (1) 
       {
             sigpending(&p);
             printsigset(&p);
             sleep(1);
       }
       return 0;
}
程序运行时,每秒钟把各信号的未决状态打印一遍,由于我们阻塞了SIGINT信号,按Ctrl-C将会使SIGINT信号处于未决状态,按Ctrl-\仍然可以终止程序,因为SIGQUIT信号没有阻塞。
......@ubuntu:~$ ./a.out
0000000000000000000000000000000
0000000000000000000000000000000(这时按Ctrl-C)
0100000000000000000000000000000
0100000000000000000000000000000(这时按Ctrl-\)
Quit (core dumped)

 

推荐阅读