Linux 信号机制

作者: 大风qixi | 来源:发表于2018-07-18 13:37 被阅读0次

概述

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

Linux中信号的类别

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


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种信号;

相关文章

  • linux手册翻译——signal(7)

    signal - linux 信号机制概览 linux支持两种信号: POSIX可靠信号(下称标准信号) POSI...

  • Linux0.11缓冲区机制详解

    相关阅读(点击即可阅读哦~):Linux0.11写时复制机制详解LINUX0.11信号机制Linux0.11共享内...

  • Linux 信号机制

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

  • linux信号机制

    进程之间可以通过信号传递信息,信号是一种软中断机制,通过信号用来通知进程发生了异步事件。进程之间可以互相通过系统调...

  • Linux信号机制

    理论部分 简单来说 信号是用来通知进程发生了异步事件 比如,在终端运行程序,按下ctrl+c就产生一个中端信号,大...

  • linux 信号机制

    1. Linux支持的信号列表如下。很多信号是与机器的体系结构相关的 信号值 默认处理动作 发出信号的原因 SIG...

  • Shell学习笔记-Shell 信号

    # Linux信号类型 信号(Signal):信号是在软件层次上对中断机制的一种模拟,通过给一个进程发送信号,执行...

  • 学习笔记5

    Linux多线程同步机制 - 信号量信号量函数定义如下:include int semctl(int sem_i...

  • Linux信号机制与信号处理

    信号(signal)是Linux进程间通信的一种机制,全称为软中断信号,也被称为软中断。信号本质上是在软件层次上对...

  • IPC机制

    2.1 Android IPC机制任何一个操作系统都需要IPC机制,Linux可以通过共享内存,管道,信号量来进行...

网友评论

    本文标题:Linux 信号机制

    本文链接:https://www.haomeiwen.com/subject/gamylftx.html