Linux 信号

Linux 信号。

中断的概念和分类

中断

  • 中断是系统对于异步事件的响应
  • 中断信号
  • 中断源
  • 现场信息
  • 中断处理程序
  • 中断向量表

中断分类

  • 硬件中断(外部中断)
    • 外部中断是指由外部设备通过硬件请求的方式产生的中断,也称为硬件中断
  • 软件中断(内部中断)
    • 内部中断是由CPU运行程序错误或执行内部程序调用引起的一种中断,也称为软件中断

信号

  • 信号是UNIX系统响应某些状况而产生的事件,进程在接收到信号时会采取相应的行动
  • 信号是因为某些错误条件而产生的,比如内存段冲突、浮点处理器错误或者非法指令等
  • 信号是在软件层次上对中断的一种模拟,所以通常把它称为是软中断

信号与中断

信号与中断的相似点:
(1)采用了相同的异步通信方式;
(2)当检测出有信号或中断请求时,都暂停正在执行的程序而转去执行相应的处理程序;
(3)都在处理完毕后返回到原来的断点;
(4)对信号或中断都可进行屏蔽。
信号与中断的区别:
(1)中断有优先级,而信号没有优先级,所有的信号都是平等的;
(2)信号处理程序是在用户态下运行的,而中断处理程序是在核心态下运行;
(3)中断响应是及时的,而信号响应通常都有较大的时间延迟。


进程对信号的三种响应

  • 忽略信号
    • 不采取任何操作、有两个信号不能被忽略:SIGKILL和SIGSTOP。
  • 捕获并处理信号
    • 内核中断正在执行的代码,转去执行先前注册过的处理程序。
  • 执行默认操作
    • 默认操作通常是终止进程,这取决于被发送的信号。

signal函数

  • 功能:安装信号
  • 原型:

    1
    2
    3
    4
    5
    typedef void (*__sighandler_t) (int);
    #define SIG_ERR ((__sighandler_t) -1)
    #define SIG_DFL ((__sighandler_t) 0)
    #define SIG_IGN ((__sighandler_t) 1)
    __sighandler_t signal(int signum, __sighandler_t handler);
  • 参数:

    • signum:准备捕捉或屏蔽的信号
    • handler:接收到指定信号时将要调用的函数
    • handler也可以是下面两个特殊值:
      • SIG_IGN 屏蔽该信号
      • SIG_DFL 恢复默认行为
  • 返回值:成功返回上次信号处理函数,失败返回-1

不可靠信号

linux信号机制基本上是从unix系统中继承过来的。早期unix系统中的信号机制比较简单和原始,后来在实践中暴露出一些问题,它的主要问题是:

  • 进程每次处理信号后,就将对信号的响应设置为默认动作。在某些情况下,将导致对信号的错误处理;因此,用户如果不希望这样的操作,那么就要在信号处理函数结尾再一次调用signal(),重新安装该信号。
  • 早期unix下的不可靠信号主要指的是进程可能对信号做出错误的反应以及信号可能丢失。
  • linux支持不可靠信号,但是对不可靠信号机制做了改进:在调用完信号处理函数后,不必重新调用该信号的安装函数(信号安装函数是在可靠机制上的实现)。因此,linux下的不可靠信号问题主要指的是信号可能丢失。

可靠型号

  • 随着时间的发展,实践证明了有必要对信号的原始机制加以改进和扩充。所以,后来出现的各种unix版本分别在这方面进行了研究,力图实现”可靠信 号”。由于原来定义的信号已有许多应用,不好再做改动,最终只好又新增加了一些信号,并在一开始就把它们定义为可靠信号,这些信号支持排队,不会丢失。
  • 同时,信号的发送和安装也出现了新版本:信号发送函数sigqueue()及信号安装函数sigaction()。

实时信号

  • 早期Unix系统只定义了32种信号,Ret hat7.2支持64种信号,编号0-63(SIGRTMIN=31,SIGRTMAX=63),将来可能进一步增加,这需要得到内核的支持。前32种信号已经有了预定义值,每个信号有了确定的用途及含义,并且每种信号都有各自的缺省动作。如按键盘的CTRL ^C时,会产生SIGINT信号,对该信号的默认反应就是进程终止。后32个信号表示实时信号,等同于前面阐述的可靠信号。这保证了发送的多个实时信号都被接收。实时信号是POSIX标准的一部分,可用于应用进程。
  • 非实时信号都不支持排队,都是不可靠信号;实时信号都支持排队,都是可靠信号。

可重入函数

  • 为了增强程序的稳定性,在信号处理函数中应使用可重入函数。
  • 所谓可重入函数是指一个可以被多个任务调用的过程,任务在调用时不必担心数据是否会出错。因为进程在收到信号后,就将跳转到信号处理函数去接着执行。如果信号处理函数中使用了不可重入函数,那么信号处理函数可能会修改原来进程中不应该被修改的数据,这样进程从信号处理函数中返回接着执行时,可能会出现不可预料的后果。不可再入函数在信号处理函数中被视为不安全函数。
  • 满足下列条件的函数多数是不可再入的:(1)使用静态的数据结构,如getlogin(),gmtime(),getgrgid(),getgrnam(),getpwuid()以及getpwnam()等等;(2)函数实现时,调用了malloc()或者free()函数;(3)实现时使用了标准I/O函数。

信号在内核中的表示

  • 执行信号的处理动作称为信号递达(Delivery),信号从产生到递达之间的状态,称为信号未决(Pending)。进程可以选择阻塞(Block)某个信号。被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作。注意,阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作。信号在内核中的表示可以看作是这样的:

kill函数

  • 功能:发送信号到指定的进程
  • 原型:

    1
    int kill(pid_t pid, int sig);
  • 参数:

    • pid:
      • pid > 0 :信号sig发送到进程号等于pid的进程
      • pid = 0:信号sig将被发送给调用者所在组中的每一个进程
      • pid = -1:信号sig将被发送给调用者进程有权限发送的每一个进程,除了1号进程与自身之外
      • pid < -1:信号sig将发送给进程组等于pid绝对值中的每一个进程
  • 参数:成功返回0,失败返回-1

raise函数

  • 功能:向自己发送信号
  • 原型:
    1
    int raise(int sig);

pause函数

  • 将进程置为可中断睡眠状态。然后它调用schedule(),使linux进程调度器找到另一个进程来运行。
  • pause使调用者进程挂起,直到一个信号被捕获

信号集操作函数

1
2
3
4
5
6
7
#include <signal.h>
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set, int signo);
int sigdelset(sigset_t *set, int signo);
int sigismember(const sigset_t *set, int signo);

sigprocmask函数

  • 功能:读取或更改进程的信号屏蔽字。
  • 原型:

    1
    int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
  • 返回值:若成功则为0,若出错则为-1

  • 注意:如果oset是非空指针,则读取进程的当前信号屏蔽字通过oset参数传出。如果set是非空指针,则更改进程的信号屏蔽字,参数how指示如何更改。如果oset和set都是非空指针,则先将原来的信号屏蔽字备份到oset里,然后根据set和how参数更改信号屏蔽字。假设当前的信号屏蔽字为mask,下表说明了how参数的可选值。
参数 说明
SIG_BLOCK set包含了我们希望添加到当前信号屏蔽字的信号,相当于 mask = mask \ set
SIG_UNBLOCK set包含了我们希望添加到当前信号屏蔽字的信号,相当于 mask = mask & ~set
SIG_SET 设置当前屏蔽字为set所指向的值,相当于 mask = set

实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
void hander(int arg) {
if (arg == SIGINT) {
printf("recv a sig=%d\n", arg);
} else if (arg == SIGQUIT) {
sigset_t uset;
sigemptyset(&uset);
sigaddset(&uset, SIGINT);
sigprocmask(SIG_UNBLOCK, &uset, NULL);
}
}
void printsigset(sigset_t *set) {
int i;
for (i=1; i<NSIG; ++i) {
if (sigismember(set, i)) {
putchar('1');
} else {
putchar('0');
}
}
printf("\n");
}
int main(int argc, char *argv[]) {
sigset_t pset;
sigset_t bset;
sigemptyset(&bset);
sigaddset(&bset, SIGINT);
if (signal(SIGINT, hander) == SIG_ERR) {
ERR_EXIT("signal");
}
if (signal(SIGQUIT, hander) == SIG_ERR) {
ERR_EXIT("signal");
}
sigprocmask(SIG_BLOCK, &bset, NULL);
for(;;) {
sigpending(&pset);
printsigset(&pset);
sleep(1);
}
return 0;
}


sigaction函数

  • 功能:sigaction函数用于改变进程接收到特定信号后的行为。
  • 原型:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    int sigaction(int signum,const struct sigaction *act,const struct sigaction *old);
    struct sigaction {
    void (*sa_handler)(int);
    void (*sa_sigaction)(int, siginfo_t *, void *);
    sigset_t sa_mask;
    int sa_flags;
    void (*sa_restorer)(void);
    };
  • 参数:

    • signum:信号的值,可以为除SIGKILL及SIGSTOP外的任何一 个特定有效的信号(为这两个信号定义自己的处理函数,将导致信号安装错误)
    • act:结构sigaction的一个实例的指针,在结构 sigaction的实例中,指定了对特定信号的处理,可以为空,进程会以缺省方式对信号处理
    • old:用来保存原来对相应信号的处理,可指定oldact为NULL。
  • 返回值:函数成功返回0,失败返回-1

用sigaction实现signal:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
void handler(int arg) {
printf("receive %d signal\n", arg);
}
__sighandler_t my_signal(int sig, __sighandler_t handler) {
struct sigaction act;
struct sigaction old;
act.sa_handler = handler;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
if (sigaction(sig, &act, &old) < 0) {
return SIG_ERR;
}
return old.sa_handler;
}
int main(int argc, char *argv[]) {
if (my_signal(SIGINT, handler) == SIG_ERR) {
ERR_EXIT("my_signal");
}
for (;;) {
pause();
}
return 0;
}


sigqueue函数

  • 功能:新的发送信号系统调用,主要是针对实时信号提出的支持信号带有参数,与函数sigaction()配合使用。
  • 原型:

    1
    int sigqueue(pid_t pid, int sig, const union sigval value);
  • 参数:

    • pid:定接收信号的进程id
    • sig:即将发送的信号,
    • value:信号传递的参数,即通常所说的4字节值。
  • 返回值:成功返回0,失败返回-1

实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
int main(int argc, char *argv[]) {
if (argc != 2) {
fprintf(stderr, "Usage: %s pid\n", argv[0]);
exit(EXIT_FAILURE);
}
pid_t pid = atoi(argv[1]);
union sigval v;
v.sival_int = 100;
sigqueue(pid, SIGINT, v);
return 0;
}


三种不同精度的睡眠

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
unsigned int sleep(unsigned int seconds);
int usleep(useconds_t usec);
int nanosleep(const struct timespec *req, struct timespec *rem);
time_t
struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* microseconds */
};
struct timespec {
time_t tv_sec; /* seconds */
long tv_nsec; /* nanoseconds */
};

setitimer函数

  • 功能:比alarm功能强大,支持3种类型的定时器
  • 原型:

    1
    int setitimer(int which, const struct itimerval *value, struct itimerval *ovalue));
  • 参数:

  • which: 指定定时器类型
  • value: 结构itimerval形式
  • ovalue:可不做处理。
  • 返回值:成功返回0,失败返回-1

which取值:

  • ITIMER_REAL:经过指定的时间后,内核将发送SIGALRM信号给本进程
  • ITIMER_VIRTUAL :程序在用户空间执行指定的时间后,内核将发送SIGVTALRM信号给本进程
  • ITIMER_PROF :进程在内核空间中执行时,时间计数会减少,通常与ITIMER_VIRTUAL共用,代表进程在用户空间与内核空间中运行指定时间后,内核将发送SIGPROF信号给本进程。