信号(signal),又称为软中断信号,用于通知进程发生了异步事件,它是Linux系统响应某些条件而产生的一个事件,它是在软件层次上对中断机制的一种模拟,是一种异步通信方式,在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的
信号是进程间通信机制中唯一的异步通信机制,一个进程不必通过任何操作来等待信号的到达,事实上,进程也不知道信号到底什么时候到达
kill命令来查看系统中支持的信号种类:
kill -l
常用信号
这64中信号分为两大类非实时信号(不可靠)和实时信号(可靠)
信号值为 1~31 的信号属性非实时信号,它主要是因为这类信号不支持排队,因此信号可能会丢失。比如发送多次相同的信号,进程只能收到一次,也只会处理一次,因此剩下的信号将被丢弃
前面 31 个信号都有一个特殊的名字,对应一个特殊的事件,比如 信号值为 1 的信号 SIGHUP(Signal Hang UP),这个信号就是通知系统关闭中断的,当系统中的一个控制终端被关闭(即挂断, hang up)时,都会产生这个信号。
实时信号(信号值为 34~64 的信号)则不同,它是支持排队的,发送了多少个信号给进程,进程就会处理多少次
产生信号 (1)硬件
- 执行非法指令
- 访问非法内存
- 驱动程序
- ....
(2)软件
控制台:
ctrl+c:中断信号
ctrl+|:退出信号
ctrl+z:停止信号
kill命令
程序调用kill()函数
信号的处理方式
- 忽略:进程当信号从来没有发生过
- 捕获:进程会调用相应的处理函数,进行相应的处理
- 默认:使用系统默认处理方式来处理信号
捕获信号相关 API 函数
1.signal() signal() 主要是用于捕获信号,可以改变进程中对信号的默认行为
使用 signal() 时,它需要提前设置一个回调函数,即进程接收到信号后将要跳转执行的响应函数,或者设置忽略某个信号,才能改变信号的默认行为,这个过程称为“信号的捕获”。对一个信号的“捕获”可以重复进行,不过 signal() 函数将会返回前一次设置的信号响应函数指针。
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
signum: 指定捕获的信号,如果指定的是一个无效的信号,或者尝试处理的信号是不可捕获或不可忽略的信号(如 SIGKILL), errno 将被设置为 EINVAL
handler :一个函数指针,它的类型是 void(*sighandler_t)(int) 类型,拥有一个 int 类型的参数,这个参数的作用就是传递收到的信号值,返回类型为 void。
handler :
- SIG_IGN:忽略
- SIG_DFL:默认
- void (*sighandler_t)(int):自定义
signal() 函数会返回一个 sighandler_t 类型的函数指针,这是因为调用 signal() 函数修改了信号的行为,需要返回之前的信号处理行为是哪个,以便让应用层知悉,如果修改信号的默认行为识别则返回对应的错误代码 SIG_ERR
注:如果调用处理程序导致信号被阻塞,则从处理程序返回后,信号将被解除阻塞。无法捕获或忽略信号 SIGKILL 和 SIGSTOP
#include
#include
#include
#include
#include
#include
/**信号处理函数*/
void signal_handler(int sig)
{
printf("\nthis signal number is%d\n",sig);
if(sig==SIGINT)
{
printf("IhavegetSIGINT!\n\n");
printf("The signalhasbeenrestored to the default processing mode!");
/**恢复信号为默认情况*/
signal(SIGINT,SIG_DFL);//(4)
}
}
int main(void)
{
printf("\nthis is an singaltestfunction\n\n");
/**设置信号处理的回调函数*/
signal(SIGINT,signal_handler);//(1)
while(1)
{
printf("waiting for the SIGINTsignal,pleaseenter\"ctrl+c\");
sleep(1);
}
exit(0);
}
分析: •(1):使用signal()函数捕获SIGINT信号(这个信号可以通过按下CTRL+C产生),并设置回调函数为signal_handler(),当产生信号的时候就调用该函数去处理这个信号。
•(2):在信号没有到来的时候就打印信息并且休眠。
•(3):signal_handler()是信号处理函数,它传入一个int类型的信号值,在信号传递进来的时候就将对应的信号值打印出来,在此例中我们可以看到,信号处理函数使用了一个单独的整数参数,它就是引起该函数被调用的信号值。如果需要在同一个函数中处理多个信号,这个参数就很有用。
•(4):如果信号是SIGINT,则打印对应的信息,并且调用signal()函数将SIGINT信号的处理恢复默认的处理(SIG_DFL),在下一次接收到SIGINT信号的时候就不会进入这个函数里了。
2.sigaction() 函数的功能与signal()函数是一样的,但是API接口稍微有点不同 一般推荐使用这个
int sigaction(intsignum,conststructsigaction*act,structsigaction␣,oldact);
signum:指定捕获的信号值。 act:是一个结构体
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 是一个函数指针,是捕获信号后的处理函数,它也有一个 int 类型的参
数,传入信号的值,这个函数是标准的信号处理函数
sa_sigaction 则是扩展信号处理函数,它也是一个函数指针
sa_mask 是信号掩码,它指定了在执行信号处理函数期间阻塞的信号的掩码,被设置在
该掩码中的信号,在进程响应信号期间被临时阻塞。除非使用 SA_NODEFER 标志否则
即使是当前正在处理的响应的信号再次到来的时候也会被阻塞。
re_restorer 则是一个已经废弃的成员变量,不要使用
sa_flags 是指定一系列用于修改信号处理过程行为的标志
oldact:返回原有的信号处理参数,一般设置为 NULL 即可
发送信号相关 API 函数前面通过“Ctrl+C”来发送了信号,在代码里,可以通过调用 kill()、 raise()、 alarm()等信号发送函数,
kill [信号或选项] PID(s)
PID(s) 是目标进程的 ID,可以是一个进程也可以是多个进程
如果想发送一个信号给进程,而该进程并不是当前的前台进程,就需要使用 kill 命令。该命令需要有一个可选的信号代码或信号名称和一个接收信号的目标进程的 PID
kill - SIGHUP 666
kill函数 进程可以通过调用 kill() 函数向包括它本身在内的其他进程发送一个信号
int kill(pid_t pid,int sig);
参数:
- pid:进程id
- sig:要发送的信号
返回值:
成功:0
失败:-1
头文件:
#include
#include
raise函数 raise() 函数也是发送信号函数,不过与 kill() 函数所不同的是, raise() 函**数只是进程向自身发送信号的,**而没有向其他进程发送信号,可以说 kill(getpid(),sig) 等同于 raise(sig)。
int raise(int sig);
参数:
sig:发送信号
返回值:
成功:0
失败:非0
头文件:
#include
alarm函数 alarm() 也称为闹钟函数,它可以在进程中设置一个定时器,当定时器指定的时间 seconds 到时,它就向进程发送 SIGALARM 信号。
unsigned int alarm(unsigned int seconds);
如果在 seconds 秒内再次调用了 alarm() 函数设置了新的闹钟,则新的设置将覆盖前面的设置,即之前设置的秒数被新的闹钟时间取代。它的返回值是之前闹钟的剩余秒数,如果之前未设闹钟则返回 0。特别地,如果新的 seconds 为 0,则之前设置的闹钟会被取消,并将剩下的时间返回
信号集处理函数 屏蔽信号集屏蔽某些信号
- 手动
- 自动
信号如果被屏蔽,则记录在未处理信号集中
- 非实时信号(1~31),不排队,只留一个
- 实时信号(34~64),排队,保留全部
-
int sigemptyset(sigset_t *set);
- 将信号集合初始化为0
-
int sigfillset(sigset_t *set);
- 将信号集合初始化为1
-
int sigaddset(sigset_t *set,int signum);
- 将信号集合某一位设置为1
-
int sigdelset(sigset_t *set,int signum);
- 将信号集合某一位设置为0
-
int sigprocmask(int how,const sigset_t *set,sigset_t *oldset);
-
使用设置好的信号集去修改信号屏蔽集
-
参数how:
- SIG_BLOCK:屏蔽某个信号(屏蔽集 | set)
- SIG_UNBLOCK:打开某个信号(屏蔽集 & (~set))
- SIG_SETMASK:屏蔽集 = set
-
参数oldset:保存就的屏蔽集的值,NULL表示不保存
-