LINUX程序员

Linux 信号机制

2018-07-18  本文已影响0人  大风qixi

概述

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

Linux中信号的类别

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


Linux支持的信号.png

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

信号的处理

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

忽略信号

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

捕捉信号

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

执行系统默认操作

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

信号的使用

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

信号的产生

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

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函数

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不能同时指定.

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

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

信号处理中的一些问题

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

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

上一篇下一篇

猜你喜欢

热点阅读