系统api-信号

常见信号及其产生条件

(1). SIGABRT
abort产生
(2). SIGALRM
alarm设置的时间到达
(3). SIGCHLD
子进程状态改变(终止/停止).
这里的停止可以理解为暂停的意思.暂停即为不继续执行,但仍有恢复执行可能(通过继续).
这里的终止可理解为停止的意思.停止了则意味着执行结束(正常或异常).没有恢复执行可能(重新启动不算恢复).
(4). SIGCONT
子进程由暂停变为继续
(5). SIGHUP
控制终端断开时,发此信号给终端所关联的会话首进程(可能是后台进程,也可能为前台进程).
正常下,终端产生的信号,总是传递给关联到此终端的前台进程.
会话首进程终止时,也产生SIGHUP,但将其送给会话的前台进程组内所有进程.
守护进程不关联终端,也不是前台进程,绝对不可能收到此消息.
但可人为向守护进程发此消息,让其做自定义工作.
(6). SIGINT
终端中断
(7). SIGIO
异步I/O
(8). SIGKILL
用于杀死进程,其响应方式不可更改
(9). SIGPIPE
管道没有进程以读方式打开时,对其执行写产生.SOCK_STREAM套接字来自对端的RST已经收到,后续本地对起执行write时,产生.
(10). SIGQUIT
终端退出(终端上按退出键).
(11). SIGSEGV
无效内存引用.
(12). SIGSTOP
停止一个进程,这里的停止可以理解为暂停的意思.暂停即为不继续执行,但仍有恢复执行可能(通过继续).
(13). SIGSYS
无效系统调用
(14). SIGTERM
让进程有机会在被终止前做好清理工作,系统关机时,init进程通常给所有进程发SIGTERM信号(默认是终止进程),隔一段固定时间后,给所有仍在运行的进程发SIGKILL
(15). SIGTSTP
终端停止符(终端上按挂起键).

(16). SIGTTIN
后台进程读其控制终端时
(17). SIGTTOU
后台进程写控制终端时
(18). SIGURG
套接字的紧急情况
(19). SIGUSR1
用户自定义
(20). SIGUSR2
用户自定义

exec

exec运行的进程中,exec执行前进程的信号处理设置失效.
fork产生新进程,新进程继承父进程对信号的处理设置.

异步信号安全/可重入函数

进程在执行指令序列中,每个CPU指令执行完毕时,都有可能被信号中断,转而执行信号处理函数的指令序列.
在信号处理函数指令序列执行中,每个CPU指令执行完毕时,也可能再次产生新的信号,转而执行对应的信号处理函数的指令序列.

一个信号处理函数执行完毕,会返回被其打断的上一层次指令序列打断处/打断处下一指令接着执行.

如果程序正在执行malloc的指令序列,这时被信号打断,执行信号的处理,信号处理中也执行了malloc我们直到malloc是基于一个进程全局资源来维护的.在上一个malloc执行到一半时,又执行一个malloc,会对全局资源的维护造成混乱和错误.所以,称这样的函数malloc即为不可重入的.对这样的函数一般不再信号处理函数中进行调用,若调用则可能存在上述调用时序下让程序发生混乱的可能.

但也有一些函数,比如执行一些基于函数内本地变量的计算返回结果等,这些函数即使正常执行被打断,在插入的处理函数中再次执行,因为函数并不操作全局数据,每次函数操作的都是栈数据,所以,这样的调用不会有让程序发生混乱和错误的风险.称这样的函数为可重入的/异步线程安全.

一般,在信号处理函数中若要调用函数,我们应调用可重入的函数.来保证我们的信号处理函数在任何执行序列下均不会有让程序混乱,出错的可能.另外一个值得注意的是,一般系统调用出错均设置出错码,而出错码又是一个全局变量.针对此特殊情况,采取的策略一般是:信号处理函数开始时,用本地变量记录此时的出错码.信号处理函数返回时,恢复进入时的出错码.

以下整理了一些可重入的系统调用,可以安全的在信号处理函数中调用这些函数:
在这里插入图片描述

信号

信号产生时,内核记录信号类型,目标进程.
若目标进程未阻塞此信号,则设置目标进程的信号标记.目标进程将下接下来中断返回时执行相应的信号处理.
若目标进程阻塞了此信号,则设置目标进程的信号标记,但在解除对其阻塞前,目标进程不会在中断返回时执行相应的信号处理.
对非实时信号,若内核在已经记录了某(信号,进程)信息下,又收到重叠的(信号,进程)消息,则仍然只对此(信号,进程)维持一个记录(不排队).

kill,raise

// pid:
// > 0 	进程ID为pid的进程
// = 0	和调用进程同组的所有进程
// -1	所有进程
// < 0	属于进程组|pid|的所有进程
// 不发给系统进程集中进程,需对目标进程有发送权限

// 调用进程是否可向目标进程发送成功,需要权限检查通过
int kill(pid_t pid, int signo);

alarm,pause

// 指定时间后给进程发SIGALRM
unsigned int alarm(unsigned int seconds);
int pause();

信号集

int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set, int signo);
int sigdelset(sigset_t *set, int signo);
// 若真,返回1。若假,返回0。
int sigismember(const sigset_t *set, int signo);

sigprocmask

// SIG_BLOCK		增加集合位
// SIG_UNBLOCK		减少集合位
// SIG_SETMASK		设置集合位
int sigprocmask(int how, const sigset_t *restrict set, sigset_t *restrict oset);

sigpending

// set中获取进程此时所有阻塞信号构成的信号集合
int sigpending(sigset_t *set);

sigaction

int sigaction(int signo, const struct sigaction *restrict act, struct sigaction *restrict oact);

struct sigaction
{
	// fun addr/SIG_IGN/SIG_DFL
	void (*sa_handler)(int);
	// signals to block
	sigset_t sa_mask;
	// 设置了SA_SIGINFO标志时的处理函数
	void (*sa_sigaction)(int, siginfo_t*, void*);
	int sa_flags;
};

可靠信号语义:
(1). 信号中断产生,让进程执行信号处理程序期间.此中断信号自动被屏蔽.
(2). sigaction中,信号处理程序为sa_handler.信号处理期间sa_mask与中断信号构成的集合被屏蔽.信号处理结束,恢复信号屏蔽为中断前状态.
(3). 信号处理一旦用sigaction进行设置,此后一直有效.
(4). 关于信号是否会中断系统调用,即使设置了SA_RESTART有些系统也仍然会中断.可统一按会中断系统调用处理.

typedef void(*Sigfunc)(int);
Sigfunc signal(int signo, Sigfunc func)
{
	struct sigaction act, oact;
	act.sa_handler = func;
	sigemptyset(&act.sa_mask);
	act.sa_flags = 0;
#ifdef SA_INTERRUPT
	ct.sa_flags |= SA_INTERRUPT;
#endif
	if(sigaction(signo, &act, &oact) < 0)
	{
		return SIG_ERR;
	}
	return (oact.sa_handler);
}

sigsetjmp,siglongjmp

// 参数2非0时,首次调sigsetjmp会存储进程信号屏蔽字到env,供后续siglongjmp恢复
int sigsetjmp(sigjmp_buf env, int savemask);
int siglongjmp(sigjmp_buf env, int val);

引入背景:
setjmp,longjmp也用来实现非局部转移.但longjmp在信号处理中实现非局部转移时,有的实现在转移后,恢复对信号处理关联信号的阻塞为处理前,有的实现不处理.sigsetjmp, siglongjmp允许显式指定在env中记录进程的信号屏蔽字,用于siglongjmp时恢复

sig_atomic_tISO C定义的变量类型,特点是写此类型变量不会被中断.volatile用于高速编译器变量在内存中,每次获取变量都要从内存读其值,而不要将其优化到寄存器.两者一般联合起来使用.

sigsuspend

int sigsuspend(const sigset_t *sigmask);

以原子方式[执行不会被信号中断]执行
(1). 设置进程的信号屏蔽字为sigmask
(2). 进程挂起

进程继续条件:
捕捉到一个信号,且从该信号的信号处理返回,则sigsuspend返回-1,进程继续

引入背景:
若以单独语句实现
(1). 设置进程信号屏蔽字为sigmask
(2). 进程挂起
则存在以下可能:在(1)执行前进程阻塞了信号X,且(1)执行时,针对此进程的信号X已经产生,但由于进程阻塞了它,故未发给进程.
执行(1)后,如sigmask中解除了对X的阻塞.则在执行2前,信号X被进程接收,执行其处理.
执行(2)时,进程阻塞等待被信号中断.但此前处理的X,此时,不会再引起(2)中阻塞的中断.

如用sigsuspend.(1),(2)以原子方式执行.可以保证不会产生上述现象.

信号的应用:用于实现父子进程间的同步

利用sigsuspend实现阻塞.利用kill实现解除阻塞.

另一种父子同步是,父亲用wait阻塞.子进程终止通过发SIGCHLD给父亲,解除阻塞.(父进程需忽略SIGCHLD信号处理,如不忽略,此信号默认处理将终止父进程.不用显式处理是因为父进程需用wait阻塞等待同步.而不是异步信号来时,执行一次wait).
终端控制信号会发给前台进程组所有进程,如只想让特定信号发给指定进程,则需对前台进程组进行相关信号的阻塞等额外处理.

abort

void abort(void);

SIGABRT给调用进程

作业控制信号

(1). SIGCHLD
子进程停止/终止时,给父进程发.
(2). SIGCONT
发给停止的子进程,让其继续.
(3). SIGSTOP
不可被捕捉或忽略
(4). SIGTSTP
(5). SIGTTIN
后台进程组成员读控制终端
(6). SIGTTOU
后台进程组成员写控制终端

strsignal

char* strsignal(int signo);

信号处理范例

#include "apue.h"
static void sig_alrm(int signo)
{
}

// 通过alarm实现sleep--alarm + 处理SIGALRM
unsigned int sleep(unsigned int seconds)
{
	struct sigaction	newact, oldact;
	sigset_t			newmask, oldmask, suspmask;
	unsigned int		unslept;
	// 设置好对SIGALRM处理方式
	newact.sa_handler = sig_alrm;
	sigemptyset(&newact.sa_mask);
	newact.sa_flags = 0;
	sigaction(SIGALRM, &newact, &oldact);
	
	// 为何要提前阻塞SIGALRM
	// 这样可以避免在alarm后,进入阻塞等待SIGALRM打断这一时间段内收到SIGALRM
	// 造成后续阻塞无法被此SIGALRM所打断
	sigemptyset(&newmask);
	sigaddset(&newmask, SIGALRM);
	sigprocmask(SIG_BLOCK, &newmask, &oldmask);
	
	alarm(seconds);
	suspmask = oldmask;
	sigdelset(&suspmask, SIGALRM);
	// 以原子方式[整个执行序列不会被信号打断]
	// 设置可接收SIGALRM+进入阻塞等待
	sigsuspend(&suspmask);
	// 剩余时间获取
	unslept = alarm(0);

	// 恢复对SIGALRM处理,恢复信号阻塞
	sigaction(SIGALRM, &oldact, NULL);
	sigprocmask(SIG_SETMASK, &oldmask, NULL);
	return(unslept);
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

raindayinrain

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值