Linux信号管理

Linux信号管理

一、信号介绍

1、什么是信号:

​ 信号是UNIX、类UNIX以及其他POSIX兼容的操作系统中,进程间通讯的一种有限制的方式。它是一种异步的通知机制,用来提醒进程一个事件已经发生。

​ 当一个信号发送给一个进程,操作系统中断了进程正常的执行、控制流程,此时,任何非原子操作都将被中断。如果进程定义了信号的处理函数,那么它将被执行,否则就执行默认动作。

# 执行kill -l命令可以显示出当前系统可能产生的所有信号
 1) SIGHUP	 	2) SIGINT	 	3) SIGQUIT	 	4) SIGILL	 	5) SIGTRAP
 6) SIGABRT	 	7) SIGBUS	 	8) SIGFPE	 	9) SIGKILL		10) SIGUSR1
11) SIGSEGV		12) SIGUSR2		13) SIGPIPE		14) SIGALRM		15) SIGTERM
16) SIGSTKFLT	17) SIGCHLD		18) SIGCONT		19) SIGSTOP		20) SIGTSTP
21) SIGTTIN		22) SIGTTOU		23) SIGURG		24) SIGXCPU		25) SIGXFSZ
26) SIGVTALRM	27) SIGPROF		28) SIGWINCH	29) SIGIO		30) SIGPWR
31) SIGSYS		34) SIGRTMIN	35) SIGRTMIN+1	36) SIGRTMIN+2	37) SIGRTMIN+3
38) SIGRTMIN+4	39) SIGRTMIN+5	40) SIGRTMIN+6	41) SIGRTMIN+7	42) SIGRTMIN+8
43) SIGRTMIN+9	44) SIGRTMIN+10	45) SIGRTMIN+11	46) SIGRTMIN+12	47) SIGRTMIN+13
48) SIGRTMIN+14	49) SIGRTMIN+15	50) SIGRTMAX-14	51) SIGRTMAX-13	52) SIGRTMAX-12
53) SIGRTMAX-11	54) SIGRTMAX-10	55) SIGRTMAX-9	56) SIGRTMAX-8	57) SIGRTMAX-7
58) SIGRTMAX-6	59) SIGRTMAX-5	60) SIGRTMAX-4	61) SIGRTMAX-3	62) SIGRTMAX-2
63) SIGRTMAX-1	64) SIGRTMAX	
2、不可靠信号与可靠信号:
不可靠信号
  • 小于SIGRTMIN(34)的信号都是不可靠信号。
  • 非实时信号,不支持排队,可能会丢失,同一个信号产生多次,进程可能只收到一次该信号。
可靠信号
  • 位于[SIGRTMIN(34),SIGRTMAX(64)]区间的信号都是可靠信号。
  • 实时信号,支持排队,信号每产生一次就处理一次,因此可靠信号不会丢失。
  • 无论可靠信号还是不可靠信号,都可以通过sigqueue/sigaction函数发送/安装,以获得比其早期版本kill/signal函数更可靠的使用效果。
3、信号来源和默认动作:
信号进程的默认动作产生条件
SIGHUP(1)终止终端挂起或者控制进程终止产生此信号
SIGINT(2)终止用户按中断键(Ctrl+C),产生此信号,并送至前台进程组的所有进程
SIGQUIT(3)终止+core用户按退出键(Ctrl+\),产生此信号,并送至前台进程组的所有进程
SIGILL(4)终止+core进程执行了一条非法硬件指令,通常是因为可执行文件本身出现错误
SIGTRAP(5)终止+core可以实现自定义的硬件故障,常用于gdb调试时产生,如函数没返回值
SIGABRT(6)终止+core调用abort函数,产生此信号
SIGBUS(7)终止+core总线错误,一般产生的原因是错误的内存访问
SIGFPE(8)终止+core表示一个算术运算异常,例如除以0、浮点溢出等
SIGKILL(9)终止必须死信号,不能被捕获或忽略,常用于杀死进程
SIGUSR1(10)终止用户自定义信号,用于进程间通信
SIGSEGV(11)终止+core段错误信号,试图访问未分配的内存,或向没有写权限的内存写入数据
SIGUSR2(12)终止用户自定义信号,用于进程间通信
SIGPIPE(13)终止写管道时读进程已终止,或SOCK_STREAM类型套接字时连接已断开,均产生此信号
SIGALRM(14)终止alarm函数设置的计时器到期,setitimer函数设置的时间到期,产生此信号
SIGTERM(15)终止程序结束信号, 与SIGKILL不同的是该信号可以被阻塞和处理,通常用来要求程序自己正常退出
SIGSTKFLT(16)终止表示数学协处理器发生栈故障
SIGCHLD(17)忽略在一个进程终止或停止时,将此信号发送给其父进程
SIGCONT(18)继续/忽略向处于停止状态的进程发送此信号,令其继续运行
SIGSTOP(19)停止进程不能被捕获或忽略,停止一个进程
SIGTSTP(20)停止进程用户按停止键(Ctrl+Z),产生此信号,并送至前台进程组的所有进程(前台->后台)(后台->前台 “fg”命令)
SIGTTIN(21)停止后台进程组中的进程试图从控制终端读数据,产生此信号
SIGTTOU(22)停止后台进程组中的进程试图往控制终端写数据,产生此信号
SIGURG(23)忽略从网络上接收到紧急带外数据,产生此信号
SIGXCPU(24)终止+core当进程的运行时间超过了其CPU时间限制,产生此信号
SIGXFSZ(25)终止+core当进程企图扩大文件以至于超过文件大小资源限制,产生此信号
SIGVTALRM(26)终止以setitimer函数设置的虚拟间隔时间到期,产生此信号
SIGPROF(27)终止以setitimer函数设置的虚拟梗概统计间隔时间到期,产生此信号
SIGWINCH(28)忽略终端窗口大小改变,产生此信号
SIGIO(29)终止表示出现一个异步I/O事件
SIGPWR(30)终止电源失效,关机时产生此信号
SIGSYS(31)终止+core指示一个无效的系统调用【嵌】

二、捕获信号

#include <signal.h>

typedef void (*sighandler_t) (int);
sighandler_t signal(int signum,sighandler_t handler);
功能:注册信号处理函数
signum  - 信号码,也可使用系统预定义的常量宏,如SIGINT等。
handler - 信号处理函数指针或以下常量:
    sighandler_t 收到信号时执行该函数
    SIG_IGN: 忽略该信号
    SIG_DFL: 默认处理
返回值:成功返回原来的信号处理函数指针或SIG_IGN/SIG_DFL常量,失败返回SIG_ERR。

​ 在某些UNIX系统上,通过signal函数注册的信号处理函数只一次有效,即内核每次调用信号处理函数后,会将对该信号的处理自动恢复为默认方式,为了获得持久有效的信号处理,可以在信号处理函数中再次调用signal函数,重新注册一次。

void sigint (int signum) 
{
    ...
    signal (SIGINT, sigint);
}

int main (void) 
{
    ...
    signal (SIGINT, sigint);
    fllush(stdout);
    ...
}

​ SIGKILL/SIGSTOP信号不能被忽略,也不能被捕获,普通用户只能给自己的进程发送信号,root用户可以给任何进程发送信号。

三、发送信号的方式

1、键盘:

​ 给当前终端下的进程发送信号:

​ Ctrl+C 终端中断SIGINT(2)
​ Ctrl+\ 终端退出SIGQUIT(3)
​ Ctrl+Z 终端暂停SIGTSTP(20)

2、命令:

​ 使用命令向指定的进程发送信号:

kill <-信号> 进程号
killall <-信号> 进程名
killall -9 bash 可以关闭所有终端
注意:如果省略了信号,则默认发送SIGTERM(15)信号
3、事件:

​ 当进程执行某些非法操作时,被操作系统发现,操作系统就会向进程发送相关信号,如果:段错误SIGSEGV(11)、浮点数例外SIGFPE(8)、总线错误SIGBUS(7)。

4、函数:
#include <signal.h>

int kill (pid_t pid, int sig);
功能:向指定的进程发送sig信号
pid > 0 - 向pid进程发送sig信号。
pid = 0 - 向同进程组的所有进程发送信号。
pid =-1 - 向所有进程发送信号,前提是调用进程有向其发送信号的权限(子进程)。
pid <-1 - 向绝对值等于pid的进程组发信号。
    
int raise (int sig);
功能:向调用进程自身发送sig信号。
    
void abort(void);
功能:向调用进程向自身发送SIGABRT信号
    
unsigned int alarm (unsigned int seconds);
功能:使内核在seconds秒之后,向调用进程发送SIGALRM(14)闹钟信号,SIGALRM信号的默认处理是终止进程。

四、暂停与休眠

#include <unistd.h>
int pause (void);
功能:使调用进程暂停执行,进入睡眠状态,直到有信号终止该进程或被有信号被捕获,相当于没有时间限制的sleep函数。
ps aux//查看进程
#include <unistd.h>
unsigned int sleep (unsigned int seconds);
功能:使调用进程睡眠seconds秒,除非有信号终止该进程或被捕获,相当于有时间限制的pause函数。

int usleep (useconds_t usec);
功能:使调用进程睡眠usec微秒,除非有信号终止该进程或被捕获。

五、信号集与信号屏蔽

1、什么是信号集:

​ 一种专门存储信号的数据类型sigset_t,有128个字节,每个字节代表一个信号。

2、操作信号集的相关函数:
#include <signal.h>

int sigfillset (sigset_t* set);
功能:将信号集set中的全部信号位置1

int sigemptyset (sigset_t* set);
功能:将信号集set中的全部信号位置0

int sigaddset (sigset_t* set, int signum);
功能:将信号集set中与signum对应的位 置1

int sigdelset (sigset_t* set, int signum);
功能:将信号集set中与signum对应的位 清0

int sigismember (const sigset_t* set, int signum);
功能:若信号集set中与signum对应的位为1,则返回1,否则返回0
3、信号的递送与未决:
  • 当信号产生时,系统内核会在其所维护的进程表中,为特定的进程设置一个与该信号相对应的标志位,这个过程称为递送(delivery),(递送后的信号被内核立即处理)。
  • 信号从产生到完成递送【递达】之间存在一定的时间间隔,处于这段时间间隔中的信号状态,称为未决(pending)(该状态主要受阻塞【屏蔽】影响)。
4、信号屏蔽:
  • 每个进程都有一个信号掩码(signal mask,它实际上是一个信号集【信号屏蔽集合】),其中包括了所有需要被屏蔽的信号。【异步】
  • 当进程执行诸如更新数据库等敏感任务时,可能不希望被某些信号中断,这时可以暂时屏蔽(阻塞)(注意不是忽略)这些信号,使其滞留在未决状态,待任务完成以后,再回过头来处理这些信号。
  • 在信号处理函数的执行过程中,这个正在被处理的信号总是处于信号掩码中。
  • 可以通过sigprocmask函数,检测和修改调用进程的信号掩码,也可以通过sigpending函数,获取调用进程当前处于未决状态的信号集。
5、可靠与不可靠信号屏蔽的区别:
  • 对于不可靠信号,通过sigprocmask函数设置信号掩码以后,相同的被屏蔽信号只会屏蔽第一个,并在恢复信号掩码后被递送,其余的则直接忽略掉(无论在屏蔽期间被屏蔽多少次,取消屏蔽后只会递送一次)。
  • 而对于可靠信号,则会在信号屏蔽时按其产生的先后顺序排队,一旦恢复信号掩码,这些信号会依次被信号处理函数处理。
#include <signal.h>

int sigprocmask (int how, const sigset_t* set,
    sigset_t* oldset);
功能:修改当前进程的信号掩码
how  - 修改信号掩码的方式,可取以下值:
    SIG_BLOCK: 新掩码是当前掩码和set的并集(将set加入信号掩码);
    SIG_UNBLOCK: 新掩码是当前掩码和set补集的交集(从信号掩码中删除set);
    SIG_SETMASK: 新掩码即set(将信号掩码设为set)。
set  -NULL则忽略。
oldset - 备份以前的信号掩码,NULL则不备份。

int sigpending (sigset_t* set);
功能:获取当前进程处于未决状态的信号集。

六、带参的信号发送与捕获

#include <signal.h>

int sigaction (int signum, const struct sigaction* act,struct sigaction* oldact);
功能:设置信号的处理方案
signum:信号码
act:信号处理方式
oldact:原信号处理方式,可为NULL

struct sigaction {
    void (*sa_handler)(int); // 信号处理函数指针1
    void (*sa_sigaction)(int,siginfo_t*,void*); // 信号处理函数指针2
    sigset_t sa_mask; // 信号掩码
    int sa_flags; // 信号处理标志
    void (*sa_restorer)(void); // 保留,NULL
};

sa_flags可为以下值的位或:
	SA_ONESHOT/SA_RESETHAND 执行完一次信号处理后,即对此信号的处理恢复为默认,这也是老版本signal函数的缺省行为。
	SA_NODEFER/SA_NOMASK 在信号处理函数的执行过程中,不屏蔽这个正在被处理的信号。
	SA_NOCLDSTOP 若signum参数取SIGCHLD,则当子进程暂停时,不通知父进程。
	SA_RESTART 系统调用一旦被signum参数所表示的信号中断,会自行重启。
	SA_SIGINFO 使用信号处理函数指针2,通过该函数的第二个参数,提供更多信息。

typedef struct siginfo {
    pid_t si_pid;   // 发送信号的PID
    sigval_t si_value; // 信号附加值(需要配合sigqueue函数)
    ...
}siginfo_t;

typedef union sigval {
    int sival_int;
    void* sival_ptr;
}sigval_t;

int sigqueue (pid_t pid, int sig,const union sigval value);
功能:向pid进程发送sig信号,附加value值(整数或指针)

七、计时器

1、系统为进程维护三个计时器:
  • 真实计时器:程序运行的实际时间。

  • 虚拟计时器:程序运行在用户态所消耗的时间。

  • 实用计时器:程序运行在用户态和内核态所消耗的时间之和。

    实际时间(真实计时器) = 用户时间(虚拟计时器) + 内核时间 + 睡眠时间,用指定的初始间隔和重复间隔为进程设定好计时器后,该计时器就会定时地向进程发送时钟信号。

2、获取/设置计时器:
#include <sys/time.h>

int getitimer (int which,struct itimerval* curr_value);
功能:获取当前进程的计时器设置。

int setitimer (int which,const struct itimerval* new_value,struct itimerval* old_value);
功能:设置计时器。
which      - 指定哪个计时器,取值:
    ITIMER_REAL: 真实计时器;
    ITIMER_VIRTUAL: 虚拟计时器;
    ITIMER_PROF: 实用计时器。
new_value  - 新的设置。
old_value  - 旧的设置(可为NULL)struct itimerval {
    struct timeval it_interval;
    // 重复间隔(每两个时钟信号的时间间隔),取0将使计时器在发送第一个信号后停止
    struct timeval it_value;
    // 初始间隔(从调用setitimer函数到第一次发送时钟信号的时间间隔),取0将立即停止计时器
};

struct timeval {
    long tv_sec;   / 秒数
    long tv_usec; // 微秒数,不能超过1000000
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值