linux 信号传入进程,Linux 信号机制

概述

Linux 在进程间通信时,有时候需要用到异步通讯方式,而信号机制是Linux系统本身提供的一种异步通讯.

Linux中信号的类别

Linux信号在系统中总数是有限的,信号种类如下所示:

e4ce1f6488af

Linux支持的信号.png

这些信号在Linux系统中各自有不同的用途.同时在Unix和Linux的不断发展中出现了两种信号,或者说由于历史遗留问题出现了两种信号:可靠信号和不可靠信号.不可靠信号是从早期的Unix继承而来,而可靠信号是后来定义的信号.

信号的处理

一个进程对信号的响应可以分为三种情况,分别为:

忽略信号

捕捉信号

执行系统默认操作

忽略信号

忽略信号是指在代码中进行设置后,进程不会对信号进行响应,值得注意的是有两个信号是不能忽略的这两个信号为SIGKILL和SIGSTOP.

捕捉信号

捕捉信号是指在代码中设置函数,当指定的信号发生时,调用已经设置好的处理函数,使得进程可以按照自己的意愿对信号发生时所代表的时间进行处理.

执行系统默认操作

Linux操作系统规定了很多对于信号的默认操作,这些可以通过查询获取到,但是对于实时信号来说,器系统的默认操作都是进程终止.

信号的使用

在使用信号时首先需要确认使用何种信号.然后需要进程去产生信号.

信号的产生

信号可以通过六个函数产生:

kill函数

raise函数

sigqueue函数

alarm函数

setitimer函数

abort函数

kill函数

kill函数原型如下:

int kill(pid_t pid,int signo)

kill函数中的pid参数可以设置为如下方式:

pid > 0: 将信号发送给指定进程ID为pid的进程

pid == 0: 将信号发送给与发送进程在同一进程组的所有进程

pid < 0: 将信号发送给进程组ID等于pid绝对值的进程,如果pid==-1,那么就将信 号发送给有权限发送信号的系统上的所有进程.

kill函数中的signo参数也可以设置为如下方式:

signo == 0:发送一个空信号,实际上不发送任何值给目标进程,但是可以检测目标进程是否存在,同时是否有权限向目标进程发送该信号.

signo != 0:向目标进程发送指定的信号

raise函数

raise函数原型如下:

int raise(int sig);

raise函数在实质上等价于kill(get_pid(),signo);因此raise函数只可以向自身进程发送信号其中signo参数的设置和kill函数相同.

sigqueue函数

sigqueue函数原型如下:

int sigqueue(pid_t pid, int sig, const union sigval value);

sigqueue函数的pid参数和sig参数和kill函数相同其功能也和kill函数类似.但是和kill函数不同sigqueue函数是较新的发送信号的函数,支持后面出现的实时信号,在发送信号的时候,也支持参数的传递,比kill函数多了一些信号的附加信息.

sigqueue函数比kill函数更加优越的地方主要在于第三个参数的使用上.第三个参数定义如下:

typedef union sigval {

int sival_int;

void *sival_ptr;

}sigval_t;

可以注意到这是一个联合体,在联合体中可以指定信号传输的参数,要么是一个四字节值,要么是一个指针.使用这个联合体时,信号的目标进程也需要使用新的信号捕捉函数sigaction,否则该参数无效,具体的内容参照下面关于sigaction函数的叙述.

alarm函数

alarm函数的原型如下所示:

unsigned int alarm(unsigned int seconds);

信号中有一个专门用来定时的信号SIGALRM信号,alarm函数就是为使用这个信号专门设计的一个函数,在alarm函数中的senconds参数中传入具体的秒数,在相应的时间到达时,就会向函数所在进程发送一个SIGALRM信号.

需要注意的一点是,每个进程只能拥有一个闹钟时间,如果一个进程已经设置过闹钟时间,且时间还未达到时,再次调用alarm函数设置闹钟时间,那么之前的值将会被新值替代,同时将闹钟时间的余留值返回.所以当新设置的闹钟时间为0时,就会取消原有的闹钟时间.

setitimer函数

setitimer函数原型如下:

int setitimer(int which, const struct itimerval *new_value,struct itimerval *old_value);

setitimer函数是对alarm函数功能的扩充,同时setitimer函数还有一个配套的查询函数:

int getitimer(int which, struct itimerval *curr_value);

setitimer函数支持三种定时器,这个选择由which参数指定,这三个定时器分别为:

ITIMER_REAL:设定绝对时间,当设定的时间到达时内核将发送SIGALRM信号给本进程

ITIMER_VIRTUAL :设定程序的执行时间(指程序在用户层运行的时间),当程序的执行时间到达时,内核将发送SIGALRM信号给本进程

ITIMER_PROF :设定进程执行以及内核因本进程而消耗的时间总和,内核将发送ITIMER_VIRTUAL信号给本进程

setitimer的第二个参数是指定运行的时间,这个参数的结构体原型如下所示:

struct itimerval

{

struct timeval it_interval; /* Interval for periodic timer */

struct timeval it_value; /* Time until next expiration */

};

struct timeval

{

time_t tv_sec; /* seconds */

suseconds_t tv_usec; /* microseconds */

};

其中itimerval结构体是传入的参数,这个结构体包含了两个timerval结构体变量,这两个变量用来设定时间.

其中it_interval指定的是发送信号的周期时间,it_value中保存的是到下一次发送信号的时间.

在setitimer的第三个参数时返回之前设定的时间周期值.

abort函数

abort函数原型如下

void abort(void);

该函数向进程发送SIGABORT信号,默认情况下进程会异常退出,即使SIGABORT被进程设置为阻塞信号,调用abort()后,SIGABORT仍然能被进程接收。该函数无返回值。

信号的捕捉和处理函数

目前在Linux中信号的捕捉处理函数有两个:

signal函数

sigaction函数

signal函数

signal函数的原型如下所示i:

typedef void (*sighandler_t)(int);

sighandler_t signal(int signum, sighandler_t handler);

第一个参数signum负责设定相应要捕捉的信号

第二个参数是一个函数指针,这个参数可以被指定为3个值:

SIG_IGN:忽略该信号

SIG_DFL:系统默认方式处理信号

函数指针:负责设定捕捉到信号时应采取的操作,函数原型为typedef指定的格式.

sigaction函数

sigaction函数在功能上已经彻底取代了signal函数,该函数的原型为:

int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);

第一个参数signum负责指定要捕捉的信号.

第二个参数和第三个参数都是一个sigaction结构体,其中第二个为设定新值,第一个为返回原有的设定值.该结构体定义如下:

struct sigaction

{

void (*sa_handler)(int);

void (*sa_sigaction)(int, siginfo_t *, void *);

sigset_t sa_mask;

int sa_flags;

void (*sa_restorer)(void);

};

注意,在使用过程中,第一个元素sa_handler和第二个元素sa_sigaction不能同时指定.

这个结构体中的元素如下:

第一个元素sa_handler是函数指针:(可以参照signal函数中的函数指针)

负责指定关于信号的信号处理函数,但是该函数只能传入一个对应信号的信号值

第二个参数sa_sigaction也是一个函数指针:(可以参照signal函数中的函数指针)

负责指定相应信号的处理函数,但是该函数可以传入三个参数,第一个为信号值,第二个参数为一个siginfo_t结构体用以说明本次信号处理的各种详细信息,第三个参数现行标准中现在还没有做相关规定

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) */

}

第三个元素是一个信号集:设定了在信号处理期间应该屏蔽的信号.

第四个元素:设定了修改信号行为的标识.例如当自身子进程状态改变时,不接受子进程状态改变信号等.这个flag的设定值可以通过查询获取到.

第五个参数也是一个函数指针:POSIX标准不再使用.

信号处理中的一些问题

1 信号SIGKILL和SIGSTOP不能被忽略,如果采取忽略这两个信号,那么程序会报错

2 对某个进程发送信号时,如果该进程是多线程的,就会出现问题.由于信号在设计时没有多线程概念,所以在设计之初就没有考虑信号和多线程一起使用的情况,虽然在后面对标准进行了重新修订,单一些较老的信号依然存在一些问题.

在信号和多线程一起使用时,信号只会被进程中的一个线程处理,其他线程是无法收到信号的.而且处理信号的线程是未知的(在ubuntu下,测试发现信号首先被主线程处理),而如果指定的信号没有被处理信号的线程重新定义操作函数,那么会采用系统默认操作,导致程序运行出现问题.最好的解决办法就是,其他线程都屏蔽要捕捉的线程,只有信号处理线程不进行屏蔽,那么该信号就会达到目标线程进行处理.

对信号阻塞进行处理的函数如下所示:

int sigprocmask(int how, const sigset_t *set, sigset_t *oset);

其中how共有三个参数:

how的取值为:

SIG_BLOCK 把参数set中的信号添加到信号屏蔽字中

SIG_SETMASK 把信号屏蔽字设置为参数set中的信号

SIG_UNBLOCK 从信号屏蔽字中删除参数set中的信号

而在使用sigset之前要确保先调用int sigemptyset(sigset_t *set)或者int sigfillset(sigset_t *set); 对信号集进行初始化,否则该参数初值是未知的.

sigemptyset(sigset_t *set)初始化由set指定的信号集,信号集里面的所有信号被清空;

int sigfillset(sigset_t *set);

调用该函数后,set指向的信号集中将包含linux支持的64种信号;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值