信号的概念:
软中断信号(signal,又简称为信号)信号是 Linux 进程间通信的最古老的方式,用来通知进程发生了异步事件,是一种异步通信的方式 。信号可以导致一个正在运行的进程被另一个正在运行的异步进程中断,转而处理某一个突发事件。
信号时发送给进程的,且信号随时产生(异步),当信号来临时,我们不一定立即处理信号。
信号的编号:
Linux 可使用命令:kill -l("l" 为字母),查看相应的信号。
列表中,编号为1 ~ 31的信号为传统UNIX支持的信号,是不可靠信号(非实时的),编号为34 ~ 64的信号是后来扩充的,称做可靠信号(实时信号)。
信号的产生
一.通过键盘产生信号:
终端上按“Ctrl+c”组合键通常产生中断信号 SIGINT, 终端上按“Ctrl+\”键通常产生中断信号 SIGQUIT, 终端上按“Ctrl+z”键通常产生中断信号 SIGSTOP 等。引发终端产生信号。
二.系统调用向目标进程发送信号:
1.kill函数(给指定进程发送指定信号)
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
功能:给指定进程发送指定信号
参数:
pid : 取值有 4 种情况 :
pid > 0: 将信号传送给进程 ID 为pid的进程。
pid = 0 : 将信号传送给当前进程所在进程组中的所有进程。
pid = -1 : 将信号传送给系统内所有的进程。
pid < -1 : 将信号传给指定进程组的所有进程。这个进程组号等于 pid 的绝对值
sig : 信号的编号,这里可以填数字编号,也可以填信号的宏定义
返回值:
成功:0
失败:-1
2.raise函数(给当前进程发送指定信号)
#include <signal.h>
int raise(int sig);
功能:给当前进程发送指定信号,等价于 kill(getpid(), sig)
参数:
sig:信号编号
返回值:
成功:0
失败:非0值
3.abort函数(给自己发送异常终止信号)
#include <stdlib.h>
void abort(void);
功能:给自己发送异常终止信号 6) SIGABRT,并产生core文件,等价于kill(getpid(), SIGABRT);
参数:无
返回值:无
三.用户可用kill命令将信号发送给其他进程
kill 【参数】【进程号】
四.硬件异常将产生信号。
除数为 0,无效的内存访问等。这些情况通常由硬件检测到,并通知内核,然后内核产生适当的信号发送给相应的进程。
五. 软件异常将产生信号。
当检测到某种软件条件已发生(如:定时器alarm),并将其通知有关进程时,产生SIGALRM信号。
如:管道读端关闭,写端一直写时,操作系统会向进程发送SIGPIPE(13)信号,
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
功能:
设置定时器(闹钟)。在指定seconds后,内核会给当前进程发送14)SIGALRM信号。进程收到该信号,默认动作终止。每个进程都有且只有唯一的一个定时器。
取消定时器alarm(0),返回旧闹钟余下秒数。
参数:
seconds:指定的时间,以秒为单位
返回值:
返回0或剩余的秒数
信号的保存
信号是发送给进程的,进程是通过进程的pcb来保存是否收到某些信号。
信号的其他概念:
- 信号执行信号处理动作称为信号递达。
- 信号产生到递达之间的状态称为信号未决。
- 进程可以选择阻塞某个信号,被阻塞的信号产生时将保存在未决状态,直到进程解除对此信号的阻塞,才执行递达。
信号集的概念:
sigset_t:
为了方便对多个信号进行处理,一个用户进程常常需要对多个信号做出处理,在 Linux 系统中引入了信号集,信号集被定义为一种数据类型:
typedef struct {
unsigned long sig[_NSIG_WORDS];
} sigset_t
sigset_t类型对于每种信号用一个bit表示“有效”或“无效”状态。
在PCB中有两个非常重要的信号集。一个称之为“阻塞信号集”,另一个称之为“未决信号集”。还有一个函数指针表示处理动作。
- 未决信号集,记录着信号的产生。当信号产生时,将其位置1表示该信号的产生,直达信号被递达才清除该标志。
- 未决信号集,记录着被阻塞的信号,当某个进程被阻塞,将其位置1表示该信号被阻塞,直到进程解除对此信号的阻塞,才执行递达。
- 当pending 位为1,black位为0时,才执行信号的抵达处理动作。
这两个信号集都是内核使用位图机制来实现的。但操作系统不允许我们直接对其进行位操作。而需自定义另外一个集合,借助信号集操作函数来对PCB中的这两个信号集进行修改。
信号集操作函数
自定义信号集函数
#include <signal.h>
int sigemptyset(sigset_t *set); //将set集合置空
int sigfillset(sigset_t *set); //将所有信号加入set集合
int sigaddset(sigset_t *set, int signo); //将signo信号加入到set集合
int sigdelset(sigset_t *set, int signo); //从set集合中移除signo信号
int sigismember(const sigset_t *set, int signo); //判断信号是否存在
sigprocmask()修改当前的信号屏蔽集来改变信号的阻塞情况。
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
功能:
检查或修改信号阻塞集,根据 how 指定的方法对进程的阻塞集合进行修改,新的信号阻塞集由 set 指定,而原先的信号阻塞集合由 oldset 保存。
参数:
how : 信号阻塞集合的修改方法,有 3 种情况:
SIG_BLOCK:向信号阻塞集合中添加 set 信号集,新的信号掩码是set和旧信号掩码的并集。相当于 mask = mask|set。
SIG_UNBLOCK:从信号阻塞集合中删除 set 信号集,从当前信号掩码中去除 set 中的信号。相当于 mask = mask & ~ set。
SIG_SETMASK:将信号阻塞集合设为 set 信号集,相当于原来信号阻塞集的内容清空,然后按照 set 中的信号重新设置信号阻塞集。相当于mask = set。
set : 要操作的信号集地址。
若 set 为 NULL,则不改变信号阻塞集合,函数只把当前信号阻塞集合保存到 oldset 中。
oldset : 保存原先信号阻塞集地址
返回值:
成功:0,
失败:-1,失败时错误代码只可能是 EINVAL,表示参数 how 不合法。
例子:将二号信号添加到信号屏蔽集,终端按ctrl+c 无法响应。
sigpending()读取当前进程的未决信号集
#include <signal.h>
int sigpending(sigset_t *set);
功能:读取当前进程的未决信号集
参数:
set:未决信号集
返回值:
成功:0
失败:-1
例子:将二号信号发送给进程后查看当前进程的未决信号集
信号捕捉
信号处理方式
一个进程收到一个信号的时候,可以用如下方法进行处理:
1)执行系统默认动作
对大多数信号来说,系统默认动作是用来终止该进程。
2)忽略此信号(丢弃)
接收到此信号后没有任何动作。
3)执行自定义信号处理函数(捕获)
用用户定义的信号处理函数处理该信号。
【注意】:SIGKILL 和 SIGSTOP 不能更改信号的处理方式,因为它们向用户提供了一种使进程终止的可靠方法。
内核实现信号捕捉过程:
signal函数
#include <signal.h>
typedef void(*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
功能:
注册信号处理函数(不可用于 SIGKILL、SIGSTOP 信号),
参数:
signum:信号的编号,这里可以填数字编号,也可以填信号的宏定义,
handler: 取值有 3 种情况:
SIG_IGN:忽略该信号.
SIG_DFL:执行系统默认动作.
自定义信号处理函数,函数的定义如下
void func(int signo) {
// signo 为触发的信号,
}
返回值:
成功:第一次返回 NULL,下一次返回此信号上一次注册的信号处理函数的地址。如果需要使用此返回值,必须在前面先声明此函数指针的类型。
失败:返回 SIG_ERR
例子:自定义2信号处理函数
// 信号处理函数
void signal_handler(int signo)
{
cout<<"捕获信号:"<<signo<<endl;
}
int main()
{
// 信号注册函数
signal(SIGINT, signal_handler);
while (1); //不让程序结束
return 0;
}
sigaction
sigaction不仅是一个函数,还有一个结构体
sigaction函数
#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
功能:
检查或修改指定信号的设置(或同时执行这两种操作)。
参数:
signum:要操作的信号。
act: 要设置的对信号的新处理方式(传入参数)。
oldact:原来对信号的处理方式(传出参数)。
如果 act 指针非空,则要改变指定信号的处理方式(设置),如果 oldact 指针非空,则系统将此前指定信号的处理方式存入 oldact。
返回值:
成功:0
失败:-1
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); //已弃用
};
1、 sa_handler、sa_sigaction:信号处理函数指针,和 signal() 里的函数指针用法一样,应根据情况给sa_sigaction、sa_handler 两者之一赋值,其取值如下:
- SIG_IGN:忽略该信号
- SIG_DFL:执行系统默认动作
- 处理函数名:自定义信号处理函数
2、sa_mask:信号阻塞集,在信号处理函数执行过程中,临时屏蔽指定的信号。
3、sa_flags:用于指定信号处理的行为,通常设置为0,表使用默认属性
示例。
void myfunc(int sig)
{
printf("捕获到信号: %d\n", sig);
}
int main()
{
// 注册信号捕捉函数
struct sigaction act;
act.sa_flags = 0;
act.sa_handler = myfunc;
// 设置临时屏蔽的信号
sigemptyset(&act.sa_mask); // 清空
// ctrl + 反斜杠
sigaddset(&act.sa_mask, SIGQUIT);
sigaction(SIGINT, &act, NULL); //注册信号
while (1);
return 0;
}