1: 信号
信号(signal)是一种软件中断,它提供了一种处理异步事件的方法,也是进程间惟一的异步通信方式。
2:信号名和编号
每个信号都有一个名字和编号,这些名字都以“SIG”开头。
信号定义在signal.h
头文件中,信号名都定义为正整数。
3:查看linux所有的信号
命令: kill -l
4:常见的信号解释
SIGINT:用户按下<Ctrl + C>组合键时。默认动作为终止进程。
SIGKILL:无条件终止进程。
SIGUSR1:用户定义的信号,即程序可以在程序中定义并使用该信号。默认动作为终止进程。
SIGSEGV:指示进程进行了无效的内存访问(段错误)。默认动作为终止进程并使用该信号。
SIGUSR2:这是另外一个用户定义信号,程序员可以在程序中定义并使用该信号。默认动作为终止进程。
5:信号的处理
信号的处理有三种方法,分别是:忽略、捕捉和默认动作
- 忽略信号:大多数信号可以使用这个方式来处理。
- 有两种信号不能被忽略(分别是
SIGKILL
和SIGSTOP
)。因为他们向内核和超级用户提供了进程终止和停止的可靠方法。
- 有两种信号不能被忽略(分别是
- 捕捉信号:需要告诉内核,用户希望如何处理某一种信号,说白了就是写一个信号处理函数,当该信号产生时,调用用户自定义的函数,以此来实现某种信号的处理。
- 系统默认动作:对于每个信号来说,系统都对应由默认的处理动作,当发生了该信号,系统会自动执行。
6:各种信号的默认处理情况
7:常用的信号处理函数: https://blog.csdn.net/FallingU/article/details/52751091
(1)signal
#include typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
signal的第1个参数signum表示要捕捉的信号,第2个参数是个函数指针,表示要对该信号进行捕捉的函数。函数也可以是SIG_IGN(表示忽略掉该信号而不做任何处理,就会执行这个信号的默认处理动作)。signal如果调用成功,返回以前该信 号的处理函数的地址,否则返回SIG_ERR。
(2)sigaction (为了应对signal函数对于信号处理阻塞中遇到的问题)
#include int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
参数signum为需要捕捉的信号;参数 act是一个结构体,里面包含信号处理函数地址、处理方式等信息。参数oldact是一个传出参数,sigaction函数调用成功后,oldact里面包含以前对signum的处理方式的信息。如果函数调用成功,将返回0,否则返回-1
结构体 struct sigaction(注意名称与函数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和sa_sigaction只应该有一个生效,如果想采用老的信号处理机制,就应该让sa_handler指向正确的信号处 理函数;否则应该让sa_sigaction指向正确的信号处理函数,并且让字段sa_flags包含SA_SIGINFO选项。
8:针对sigset_t结构体,信号集操作函数
int sigemptyset(sigset_t *set); //清空信号集合set
int sigfillset(sigset_t *set); //将所有信号填充进set中
int sigaddset(sigset_t *set, int signum); //往set中添加信号signum
int sigdelset(sigset_t *set, int signum); //从set中移除信号signum
int sigismember(const sigset_t *set, int signum); //判断signnum是不是包含在set中
9:当一个进程调动了 fork 函数,那么子进程会继承父进程的信号处理方式。
10: 信号的发送函数
kill 的函数原型
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
正如我之前所说的,信号的处理需要有接受者,显然发送者必须要知道发给谁,根据 kill 函数的远行可以看到,pid 就是接受者的 pid,sig 则是发送的信号的类型
11:linux多线程中的信号处理
在Linux的多线程中使用信号机制,与在进程中使用信号机制有着根本的区别,可以说是完全不同。在进程环境中,对信号的处理是,先注册信号处理函数,当信号异步发生时,调用处理函数来处理信号。它完全是异步的(我们完全不知到信号会在进程的那个执行点到来!)。
多线程中处理信号的原则却完全不同,它的基本原则是:将对信号的异步处理,转换成同步处理,也就是说用一个线程专门的来“同步等待”信号的到来,而其它的线程可以完全不被该信号中断/打断(interrupt)。这样就在相当程度上简化了在多线程环境中对信号的处理。而且可以保证其它的线程不受信号的影响。这样我们对信号就可以完全预测,因为它不再是异步的,而是同步的。
一些关于线程级的信号函数使用见如下: https://blog.csdn.net/sjin_1314/article/details/83050668
(1): pthread_sigmask函数:
每个线均有自己的信号屏蔽集(信号掩码),可以使用pthread_sigmask函数来屏蔽某个线程对某些信号的响应处理,仅留下需要 处理该信号的线程来处理指定的信号。实现方式是:利用线程信号屏蔽集的继承关系(在主进程中对sigmask进行设置后,主进程创建出来的线程将继承主进程的掩码)
(2):pthread_kill函数:
在多线程程序中,一个线程可以使用pthread_kill对同一个进程中指定的线程(包括自己)发送信号。注意在多线程中 一般不使用kill函数发送信号,因为kill是对进程发送信号,结果是:正在运行的线程会处理该信号,如果该线程没有注册信号处理函数,那么会导致整个进程退出
(3): sigwait或者sigwaitinfo或者sigtimedwait等函数
sigwait 监听信号集set中所包含的信号,sigwait()函数暂停调用线程的执行,直到信号集中指定的信号之一被传递为止。在多线程代码中,总是使用sigwait或者sigwaitinfo或者sigtimedwait等函数来处理信号。而不是signal或者sigaction等函数。因为在一个线程中调用signal或者sigaction等函数会改变所有线程中的信号处理函数。而不是仅仅改变调用signal/sigaction的那个线程的信号处理函数。
注意:调用sigwait同步等待的信号必须在调用线程中被屏蔽,并且通常应该在所有的线程中被屏蔽(这样可以保证信号绝不会被送到除了调用sigwait的任何其它线程),这是通过利用信号掩码的继承关系来达到的。