linux信号原理

基本概念

信号的目的通信

一般是进程向另一个进程发送信号,下面列举一些情况
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 : 

	
		
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值