Linux 信号机制

概述

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342

推荐阅读更多精彩内容

  • 信号的基本概念 信号被认为是一种软件中断(区别于硬件中断),信号机制提供了一种在单进程/线程下处理异步事件的方法。...
    小叶大孟阅读 1,880评论 0 1
  • 进程之间可以通过信号传递信息,信号是一种软中断机制,通过信号用来通知进程发生了异步事件。进程之间可以互相通过系统调...
    lintong阅读 407评论 0 2
  • 理论部分 简单来说 信号是用来通知进程发生了异步事件 比如,在终端运行程序,按下ctrl+c就产生一个中端信号,大...
    捉虫大师阅读 1,731评论 0 2
  • Linux进程间通信的概念 linux下进程间通信的几种主要手段简介: 管道(Pipe)及有名管道(named p...
    wewarriors阅读 927评论 0 6
  • 又近一年暑假,我早早地就安排好了孩子暑期的各个课程,又急吼吼地等待着学校的特训名单。下午拿到成绩时,看到...
    逸妈阅读 217评论 4 1