Linux 高并发学习笔记 - 信号通信、发送信号、捕捉信号、计时器信号

本文介绍了Linux系统中的信号机制,它是进程间通信的一种方式,用于通知进程发生了特定事件。信号包括实时信号和系统信号,有不同的默认动作,如终止进程、忽略或记录核心。信号集的概念被用来管理信号的阻塞和未决状态。此外,文章还讨论了如何发送信号、设置计时器以及使用signal和sigaction函数来捕获和处理信号。
摘要由CSDN通过智能技术生成
2.3.4 信号

Linux 高并发学习笔记

信号概述
  • 信号Linux进程间通信最古老的方式之一,是事件发生时的通知机制,有时也称为软件中断,一种异步通信模式。相比于管道的主动获取,信号由接收进程被动获取,可以中断进程用于完成一个突发事件。信号具有简单高优先级无法携带大量信息的特点。
  • 信号具有三种状态:产生未决递达。信号未决时被阻塞在内核区,等待信号阻塞解除;信号递达,被进程处理或忽略。
  • 信号分为主要两种:实时信号系统信号实时信号不具有特定意义,可以由用户指定;系统信号通常具有特定含义。
  • 信号递达时,进程会中断当前的程序,转而处理与信号相关联的程序,当信号处理完后继续执行当前进程。
  • 信号阻塞后释放,多个信号将同时从未决状态进入递达状态。该过程进程处理不同信号拥有不同优先级。默认动作高于自定义动作是首要优先级;在实时信号中,高值信号优先级高于低值信号;在系统信号中,以及实时信号系统信号间,则具有复杂的优先级关系。
  • 多个相同信号同时递达实时信号会被多次处理,系统信号只处理一次。

(注:接下来在常用信号中将介绍实时信号系统信号以及默认动作。在信号集章节中,将涉及信号阻塞;在捕捉信号章节中,将涉及自定义动作。)

常用信号
  • 查看信号列表$ kill -l1~31为常规信号,其余为实时信号。
  • 查看信号说明$ man 7 signal
信号宏信号值事件说明默认动作
S I G I N T SIGINT SIGINT 2 2 2< C t r l Ctrl Ctrl+ C C C> 终端向运行中子进程发出此信号 T e r m Term Term
S I G Q U I T SIGQUIT SIGQUIT 3 3 3< C t r l Ctrl Ctrl+ \ \backslash \> 终端向运行中子进程发出此信号 T e r m Term Term
S I G K I L L SIGKILL SIGKILL 9 9 9强制终止进程 T e r m Term Term
S I G S E G V SIGSEGV SIGSEGV 11 11 11访问无效内存(Segment Fault段错误 C o r e Core Core
S I G P I P E SIGPIPE SIGPIPE 13 13 13向无读端管道写数据(Broken Pipe管道破裂 T e r m Term Term
S I G A L R M SIGALRM SIGALRM 14 14 14计时器结束向自己发出此信号 T e r m Term Term
S I G C H L D SIGCHLD SIGCHLD 17 17 17子进程结束,父进程收到此信号 I g n Ign Ign
S I G C O N T SIGCONT SIGCONT 18 18 18如果进程暂停,继续进程 C o n t Cont Cont
S I G S T O P SIGSTOP SIGSTOP 19 19 19暂停进程 S t o p Stop Stop
→ S I G M A X      S I G M I N ^{~~~~SIGMIN}_{\to SIGMAX} SIGMAX    SIGMIN 34 → 64 34 \to 64 3464Linux实时信号,用户自定义意义 T e r m Term Term
  • 默认动作:

    • Term:终止进程。
    • Ign:忽略。
    • Core:终止进程并记录。
    • Stop:中断进程。
    • Cont:继续进程。
  • SIGKILLSIGSTOP信号不能被产生、阻塞与忽略。

信号集
信号集概述
  • 信号具有三种状态产生未决递达。信号是否未决并阻塞在进程内核区是由阻塞信号集决定的。
  • 信号集是一个64bit整数,标志1~64信号。信号集分为未决信号集阻塞信号集自定义信号集
    • 阻塞信号集:位于内核区,其中为1的标志位表示阻塞该信号。用户可以通过Linux函数操作阻塞信号集
    • 未决信号集:位于内核区,其中为1的标志位表示该信号未决。在阻塞后试图递达的信号将未决,并在未决信号集标记;在阻塞解除后该标记位立即置0,信号递达
    • 自定义信号集:位于用户区。事实上任何64bit整数都可以是自定义信号集自定义信号集仅是拥有信号集意义的整数,在操作阻塞信号集时作为参数传递。
信号集编码
  • 信号集操作通过位运算进行,对于x号信号,其对应的标志位为x-1。例如:一个包含279信号的信号集的值为0b 0001 0100 0010
  • 标准C库提供了一系列信号集运算函数。但事实上我们不必记忆这些函数,理解原理后使用位运算即可解决。
#include <signal.h>
// remove all the signums from the set
int sigemptyset(sigset_t *set);						// set = 0; return 0;
// add all the signums into the set
int sigfillset(sigset_t *set);						// set = -1ull; return 0;
// add the signum into set
int sigaddset(sigset_t *set, int signum);			// set = set | 1 << signum - 1; return 0;
// remove the signum from the set
int sigdelset(sigset_t *set, int signum);			// set = set & ~(1 << signum - 1); return 0;
// examine if the signum is in the set
int sigismember(const sigset_t *set, int signum);	// return set >> signum - 1 & 1;
操作阻塞信号集
  • sigprocmask,用户无法直接操作阻塞信号集,必须使用内核提供的函数完成操作。
  • 注意:SIGKILLSIGSTOP信号在操作中会被过滤。
#include <signal.h>
// 	examine and change the block signal set
// 		how: 
// 			SIG_BLOCK:
// 				add the signals with 1 flag in the given set into the block signal set
// 			SIG_UNBLOCK:
// 				remove the signals with 1 flag in the given set from the block signal set
// 			SIG_SETMASK:
// 				set the block sognal set to the given set
// 		set:
// 			Operating signal set whose actual meaning is depending on "how".
// 		oldset:
// 			to receive the former block signal set, and can be NULL
// 		return value:
// 			return 0 for success, -1 for error
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

// About more:
// $ man 2 sigprocmask
  • 只获取oldset如下:
const unsigned long long zero = 0;
unsigned long long oldset;
sigprocmask(SIG_BLOCK, &zero, &oldset);
发送信号
  • kill用于向任意进程或任意进程组发射信号。

  • pid:分为下述情况:

    • < -1:将信号发送到所有组ID-pidpid的绝对值)的进程。
    • -1:将信号发送到所有允许发送到的进程。
    • 0:将信号发送到所有组ID与调用进程组ID相同的进程。
    • > 0:将信号发送到PIDpid的进程。
  • 返回值:成功返回0,失败返回-1

#include <sys/types.h>
#include <signal.h>
// send a signal to a process
int kill(pid_t pid, int sig);
// $ man 2 kill
  • raise向当前进程发送信号,以下是等价的。(但是kill的返回值是0/1raise的返回值是0/nonzero
#include <signal.h>
// send a signal to the caller
int raise(int sig);
// $ man 3 raise
kill(getpid(), sig);
  • abort向当前进程发送SIGABRT信号,以下是等价的。(但是kill的返回值是0/1abort无返回值)
#include <stdlib.h>
// send SIGABRT to the caller
void abort(void);
// $ man 3 abort
kill(getpid(), SIGABRT);
计时器信号
alarm 计时器
  • alarm计时器,执行一次,非阻塞,执行结束触发SIGABRT信号。每执行alarm函数将刷新计时器,计时器时间设置为seconds
  • seconds:计时器时间。需要注意的是,此操作会直接重置计时器,而之前的未触发的信号也不会触发;如果设置为0,则计时器置为不可用,不会触发信号。
  • 返回值:返回当前计时器时间(seconds设置前),初始为0
#include <unistd.h>
// set an alarm clock for delivery of a signal
unsigned int alarm(unsigned int seconds);
// $ man 2 alarm
itimer 计时器
  • itimer计时器,循环执行,非阻塞,每轮结束触发响应信号(由which指定)。每执行setitimer函数将刷新计时器,计时器实时参数重置为new_value,并获取当前计时器实时参数(new_value设置前),初始为{{0, 0}, {0, 0}}。执行getitimer函数可获取当前计时器实时参数到curr_value而不重置计时器。
  • which:(下列选择一个)
    • ITIMER_REAL:真实时间,触发SIGALRM信号。
    • ITIMER_VIRTUAL:用户时间,触发SIGVTALRM信号。
    • ITIMER_PROF:用户与内核时间,触发SIGPROF信号。
    • 真实事件 = 用户时间 + 切换时间 + 内核时间 真实事件 = 用户时间 + 切换时间 + 内核时间 真实事件=用户时间+切换时间+内核时间
    • 无特殊需求通常选用ITIMER_REAL
    • getitimer中需与setitimer使用相同的which参数。
  • new_valueold_valuecurr_value原型如下:
// struct itimerval, in sys/time.h
struct itimerval {
    struct timeval it_interval; // 时间间隔, 如为 {0, 0} 计时器不循环
    struct timeval it_value;    // 距离下次触发时间, 初始状态可以大于 it_interval; 如设置为 {0, 0} 计时器不可用
};
struct timeval {
    time_t      tv_sec;         // 毫秒
    suseconds_t tv_usec;        // 微秒
};
  • new_value:设置新参数。
  • old_value:接收旧参数,可为NULL
  • curr_value:接收当前参数。
  • 返回值:成功返回0,失败返回-1
#include <sys/time.h>
// 	get value of an interval timer
int getitimer(int which, struct itimerval *curr_value);
//  $ man 2 getitimer

// 	set value of an interval timer
int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);
// 	$ man 2 setitimer
  • 以下两者是等价的:
setitimer(ITIMER_REAL, new_value, old_value);
getitimer(ITIMER_REAL, old_value);
setitimer(ITIMER_REAL, NULL);
捕捉信号
signal 捕捉信号
  • signal函数系统兼容性较差,不推荐使用。但相较于sigaction函数,signal函数更为简单。
#include <signal.h>
typedef void (*sighandler_t)(int);
// 	ANSI C signal handling
// 		signum:
// 			signal number
// 		handler:
// 			one of the following:
// 				SIG_DFL for using the default action
// 				SIG_IGN for ignore the signal
// 				callback function pointer
// 		return value:
// 			Return the last handler, or SIG_ERR for error. The inital handler is SIG_DFL
sighandler_t signal(int signum, sighandler_t handler);

// About more
// $ man 2 signal

示例:

#include <signal.h>
#include <stdio.h>

void func(int sig) {
	printf("Get a signal %d\n", sig);
}

int main() {
    signal(SIGINT, func);
  	raise(SIGINT);  // 向自己发射 SIGINT 信号
    while (1);  // 发现尝试使用 Ctrl+C 退出进程将失败
	return 0;
}
sigaction 捕捉信号
  • sigaction函数具有更高的系统兼容性,并且支持更丰富的功能,尽管它们很少被用到。
#include <signal.h>
struct sigaction {
    void     (*sa_handler)(int);	// handler 1, same to "signal"
    void     (*sa_sigaction)(int, siginfo_t *, void *);	// handler 2, more options, see "About more"
    sigset_t   sa_mask;				// temporary block signal mask which will rollback after calling
    int        sa_flags;
    // usual 0 (handler 1), SA_SIGINFO for choosing handler 2, more flags see "About more"
    void     (*sa_restorer)(void);	// deprecated
};
// 	change signal action
// 		signum:
// 			signal number
// 		act:
// 			the structure describe callback function, see "struct sigaction"
// 		oldact:
// 			to receive the last action
// 		return value:
// 			return 0 for success, -1 for error
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

// About more
// $ man 2 sigaction

示例:(与上个示例等价)

#include <signal.h>
#include <stdio.h>

void func(int sig) {
	printf("Get a signal %d\n", sig);
}

int main() {
    struct sigaction action = {func};
    sigaction(SIGINT, &action, NULL);
  	raise(SIGINT);  // 向自己发射 SIGINT 信号
    while (1);  // 发现尝试使用 Ctrl+C 退出进程将失败
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值