基本概念
信号的目的通信
一般是进程向另一个进程发送信号,下面列举一些情况
a.硬件发生异常
b.终端下输入如: CTRL + C
c.进程调用 kill()系统调用可将任意信号发送给另一个进程或进程组 :接收信号的进程和发送信号的进程的所有者必须相同
d.发生了软件事件:如进程所设置的定时器已经超时、进程执行的 CPU 时间超限
信号由谁处理、怎么处理
a.忽略信号
b.捕获信号:执行预先绑定好的信号处理函数
c.执行系统默认操作
信号的分类
Linux一共有62种信号
Linux 系统下可对信号从两个不同的角度进行分类
非实时信号=不可靠信号=Linux前31个信号
实时信号=可靠信号=后面32 个信号
非实时信号
这类信号不支持排队,因此信号可能会丢失
发送多次相同的信号,进程只能收到一次, 也只会处理一次,因此剩下的信号将被丢弃
实时信号
些信号到来了,进程也不一定会立即去处理它,系统一般都会选择在内核态切换回用户态的时候处理信号
这些信号都在排队,所以把信号储存在进程唯一的PCB(进程控制块)当中。
常见前31信号大概什么意思
进程怎么处理信号
最简单的调用signal()
#include <signal.h>
typedef void (*sig_t)(int);
sig_t signal(int signum, sig_t handler);
@description : 最简单的处理信号函数
@param - int signum : 收到的信号值(最好用宏定义)
@param - sig_t handler : 用户自定义,有信号就会调用
1.用户自定义,有信号就会调用
2.忽略: SIG_IGN
3.系统默认操作: SIG_DFL
@return : sig_t*
sucess: 指向在此之前的信号处理函数
err : 返回 SIG_ERR,并会设置 errno
专业调用sigaction()
sigaction()获取信号处理函数而不是设置函数
#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
@description : 最简单的处理信号函数
@param - const struct sigaction *act : act 参数是一个 struct sigaction 类型指针,不为NULL时候,表示要设置新的处理方式
@param - struct sigaction *oldact : 如果参数oldact 不为 NULL,则会将信号之前的处理方式等信息通过参数 oldact 返回出来
@return : sig_t*
sucess: 返回0
err : 返回-1,设置 errno
// 参数结构体详解
struct sigaction {
void (*sa_handler)(int); //指定信号处理函数,与 signal()函数的 handler 参数相同
void (*sa_sigaction)(int, siginfo_t *, void *);//增强版,提供了更多的参数
sigset_t sa_mask; //设置可以防止其他信号打断
int sa_flags; //设置标志,控制信号处理
void (*sa_restorer)(void); //过时
};
sa_handler:指定信号处理函数,与 signal()函数的 handler 参数相同。
sa_sigaction:也用于指定信号处理函数,这是一个替代的信号处理函数,他提供了更多的参数,可以通
过该函数获取到更多信息,这些信号通过 siginfo_t 参数获取,稍后介绍该数据结构;sa_handler 和
sa_sigaction 是互斥的,不能同时设置,对于标准信号来说,使用 sa_handler 就可以了,可通过 SA_SIGINFO 标志进行选择。
sa_mask:参数 sa_mask 定义了一组信号,当进程在执行由 sa_handler 所定义的信号处理函数之前,会
先将这组信号添加到进程的信号掩码字段中,当进程执行完处理函数之后再恢复信号掩码,将这组信号
从信号掩码字段中删除。当进程在执行信号处理函数期间,可能又收到了同样的信号或其它信号,从而
打断当前信号处理函数的执行,这就好点像中断嵌套;通常我们在执行信号处理函数期间不希望被另一
个信号所打断,那么怎么做呢?那么就是通过信号掩码来实现,如果进程接收到了信号掩码中的这些信
号,那么这个信号将会被阻塞暂时不能得到处理,直到这些信号从进程的信号掩码中移除。在信号处理
函数调用时,进程会自动将当前处理的信号添加到信号掩码字段中,这样保证了在处理一个给定的信号
时,如果此信号再次发生,那么它将会被阻塞。如果用户还需要在阻塞其它的信号,则可以通过设置参
数 sa_mask 来完成
sa_flags:参数 sa_flags 指定了一组标志,这些标志用于控制信号的处理过程,可设置为如下这些标志
(多个标志使用位或" | "组合):
SA_NOCLDSTOP
如果signum为SIGCHLD,则子进程停止时(即当它们接收到SIGSTOP、SIGTSTP、SIGTTIN或SIGTTOU
中的一种时)或恢复(即它们接收到 SIGCONT)时不会收到 SIGCHLD 信号。
SA_NOCLDWAIT
如果 signum 是 SIGCHLD,则在子进程终止时不要将其转变为僵尸进程。
SA_NODEFER
不要阻塞从某个信号自身的信号处理函数中接收此信号。也就是说当进程此时正在执行某个信号的处
理函数,默认情况下,进程会自动将该信号添加到进程的信号掩码字段中,从而在执行信号处理函数期间阻
塞该信号,默认情况下,我们期望进程在处理一个信号时阻塞同种信号,否则引起一些竞态条件;如果设置
了 SA_NODEFER 标志,则表示不对它进行阻塞。
SA_RESETHAND
执行完信号处理函数之后,将信号的处理方式设置为系统默认操作。
SA_RESTART
被信号中断的系统调用,在信号处理完成之后将自动重新发起。
SA_SIGINFO
如果设置了该标志,则表示使用 sa_sigaction 作为信号处理函数、而不是 sa_handler,关于 sa_sigaction
信号处理函数的参数信息。
//最后补充一下siginfo_t 结构体
siginfo_t {
int si_signo; /* Signal number */
int si_errno; /* An errno value */
int si_code; /* Signal code */
int si_trapno; /* Trap number that caused hardware-generated signal(unused on most
architectures) */
pid_t si_pid; /* Sending process ID */
uid_t si_uid; /* Real user ID of sending process */
int si_status; /* Exit value or signal */
clock_t si_utime; /* User time consumed */
clock_t si_stime; /* System time consumed */
sigval_t si_value; /* Signal value */
int si_int; /* POSIX.1b signal */
void *si_ptr; /* POSIX.1b signal */
int si_overrun; /* Timer overrun count; POSIX.1b timers */
int si_timerid; /* Timer ID; POSIX.1b timers */
void *si_addr; /* Memory location which caused fault */
long si_band; /* Band event (was int in glibc 2.3.2 and earlier) */
int si_fd; /* File descriptor */
short si_addr_lsb; /* Least significant bit of address(since Linux 2.6.32) */
void *si_call_addr; /* Address of system call instruction(since Linux 3.5) */
int si_syscall; /* Number of attempted system call(since Linux 3.5) */
unsigned int si_arch; /* Architecture of attempted system call(since Linux 3.5) */
}
向进程发送信号
kill()函数
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
@description : 简单的用于对应用发送信号
@param - pid_t pid : 表示接受信号进程的pig
@param - int sig : 参数 sig 指定需要发送的信号值
参数填0,可以用来检测这个进程在不在
参数填-1,发送 这个运行这个函数的进程 ,有权发送到的每一个进程
参数小于-1,发送到ID为-pid 的进程组中的每个进程。
@return :
sucess: 返回0
err : 返回-1,设置 errno
raise()向自身发送信号
#include <signal.h>
int raise(int sig); //需要发送的信号值
alarm()定时发送信号函数
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
@description : 设置一个定时器,时间到的时候,内核会向进程发送 SIGALRM信号
每个进程只能设置一个闹钟
@param - unsigned int seconds : 参数大于0,设置定时时间,以秒为单位
参数等于0,取消之前的alarm闹钟
@return :
sucess: 0
如果这个进程之前还有闹钟没有触发,返回闹钟剩余值
pause()暂停当前进程
让这个进程进入休眠状态,直到这个进程捕捉到了其他信号
#include <unistd.h>
int pause(void);
信号集(表示多种信号的数据类型)
想一次传递多种信号
信号集就是 sigset_t 类型数据结构
# define _SIGSET_NWORDS (1024 / (8 * sizeof (unsigned long int)))
typedef struct
{
unsigned long int __val[_SIGSET_NWORDS];
} sigset_t;
使用这个结构体可以表示一组信号,将多个信号添加到该数据结构中
下面写一些关于这个结构体的api
初始化信号集
下面两个函数可以初始化信号集
#include <signal.h>
int sigemptyset(sigset_t *set);//初始化的结构体不包含任何信号
int sigfillset(sigset_t *set)//初始化的结构体包含所有信号
@description : 初始化一个信号量结构体
@param - sigset_t *set : 要设置初始化信号量的地址
@return :
sucess: 返回0
err : 返回-1,设置 errno
向信号量里增加/删除信号
#include <signal.h>
int sigaddset(sigset_t *set, int signum);
int sigdelset(sigset_t *set, int signum);
@description : 对信号量中增加或删除信号
@param - sigset_t *set : 指向信号量
@param - int signum : 增加或删除的信号
@return :
sucess: 返回0
err : 返回-1,设置 errno
想知道这个信号在不在信号量中
#include <signal.h>
int sigismember(const sigset_t *set, int signum);
@description : 判断信号在不在 sigset_t 里面
@param - sigset_t *set : 指向信号量
@param - int signum : 需要查询的信号
@return :
sucess: 返回1,表示信号在当前的信号量中
返回0,表示信号不在当前的信号量中
err : 返回-1,设置 errno
知道某个信号宏定义是做什么的(查表更块)
系统给前面的信号很多初始定义
如果不通过查表,也可以用程序的方法进行打印
sys_siglist[SIGINT]来获取对 SIGINT 信号的描述
比如说可以使用打印上面数组返回的字符串
来表示SIGINT 的意思
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
int main(void) {
printf("SIGINT 描述信息: %s\n", sys_siglist[SIGINT]);
printf("SIGQUIT 描述信息: %s\n", sys_siglist[SIGQUIT]);
printf("SIGBUS 描述信息: %s\n", sys_siglist[SIGBUS]);
exit(0);
}
strsignal()函数更加快
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void) {
printf("SIGINT 描述信息: %s\n", strsignal(SIGINT));
printf("SIGQUIT 描述信息: %s\n", strsignal(SIGQUIT));
printf("SIGBUS 描述信息: %s\n", strsignal(SIGBUS));
printf("编号为 1000 的描述信息: %s\n", strsignal(1000));
exit(0);
}
信号掩码(阻塞信号传递)
在信号掩码中增加这个信号,感觉可以先收到信号,放入队列,等信号掩码里面移除这个信号,这个信号就马上生效
每个进程有一组信号掩码,收到这组掩码里的信号,将不做处理,先进行阻塞
直到这个信号从掩码中去除,才会马上响应
不仅可以自己对信号掩码增加,同时下面的例子也会对信号掩码中增加信号
同时调用信号处理函数 sig_t handler 也会把响应的信号放入信号掩码中
差不多意思为: 信号发来,触发了sig_t handler 在这时,会把这个信号放入信号掩码中,(这时候看是阻塞,还是忽略了)
阻塞的话,有两个信号执行两次,忽略的话,两个信号执行一次(!!!这里还需要查一下,可能不准)
使用 sigprocmask()增加移除信号
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
@description : 对信号掩码中加入或删除信号
@param - int how : 指定调用函数的行为
1. SIG_BLOCK : 参数 set 所指向的信号集内的所有信号添加到进程的信号掩码中
2. SIG_UNBLOCK : 将参数 set 指向的信号集内的所有信号从进程信号掩码中移除
3. SIG_SETMASK : 进程信号掩码直接设置为参数 set 指向的信号集。
@param - const sigset_t *set : 表示对该信号移除或增加
@param - sigset_t *oldset : 用户自定义,有信号就会调用
1.用户自定义,有信号就会调用
2.忽略: SIG_IGN
3.系统默认操作: SIG_DFL
@return :
sucess: 返回0
error : 返回-1
阻塞等待最好使用的原子操作 sigsuspend()
对一个信号阻塞恢复的时候,有小概率收不到
举个例子
下面有受到保护的代码 不想被SIGIN给打断,先给它屏蔽
恢复后使用pause进行休眠,等待SIGIN进行唤醒
sigset_t new_set, old_set;
/* 信号集初始化 */
sigemptyset(&new_set);
sigaddset(&new_set, SIGINT);
/* 向信号掩码中添加信号 */
if (-1 == sigprocmask(SIG_BLOCK, &new_set, &old_set))
exit(-1);
/* 受保护的关键代码段 */
......
/**********************/
/* 恢复信号掩码 */
if (-1 == sigprocmask(SIG_SETMASK, &old_set, NULL))
exit(-1);
//这个地方可能需要增加原子操作
//不然刚恢复可能收完信号才执行paruse(),就会让代码一直中断
pause();/* 等待信号唤醒 */
虽然几率很小,但可以使用sigsuspend()进行原子操作
上面的代码改为
sigprocmask(SIG_SETMASK, &mask, &old_mask);
pause();
sigprocmask(SIG_SETMASK, &old_mask, NULL);
sigprocmask() 用法如下
#include <signal.h>
int sigsuspend(const sigset_t *mask);
@description : 替换了pause(),挂起进程睡眠进行等待,直到有信号来唤醒
@param - const sigset_t *mask : mask 指向一个信号集
@return :