linux信号

来源:互联网 发布:水泥凝结时间测定数据 编辑:程序博客网 时间:2024/06/05 09:50

linux信号基本概念

信号又称为软中断信号,顾名思义,它是在在软件层次上是对中断机制的一种模拟,用来通知进程发生了异步事件,要去处理。在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。信号是进程间通信机制中唯一的步通信机制,一个进程不必通过任何操作来等待信号的到达,事实上,进程也不知道信号到底什么时候到达。进程之间可以互相通过系统调用kill发送软中断信号。内核也可以因为内部事件而给进程发送信号,通知进程发生了某个事件。
思考一:信号和中断的异同点?
(1)采用了相同的异步通信方式;
(2)当检测出有信号或中断请求时,都暂停正在执行的程序而转去执行相应的处理程序;
(3)都在处理完毕后返回到原来的断点;
(4)对信号或中断都可进行屏蔽。
信号与中断的区别:
(1)中断有优先级,而信号没有优先级,所有的信号都是平等的;
(2)信号处理程序是在用户态下运行的,而中断处理程序是在核心态下运行;
(3)中断响应是及时的,而信号响应一般会有时间上的延迟。
思考二:那什么是异步事件呢?
通俗的说就是进程执行代码的过程中可以随时被打断,然后去执行处理程序中的中断(信号)和计算机系统中的中断
举例:
1)无中断生活场景
李四看书,厨房在烧水,二人各不打扰
2)有中断的生活场景
张三看书,设置闹钟,厨房烧水,当闹钟响后,通知张三(发送信号),这时张三把书合上(记录是30页),然后去把烧水事件处理好,回来重新打开30页看书。

信号的种类

1常用信号
信号名称 描述
SIGABRT 进程停止运行
SIGALRM 警告钟
SIGFPE 算述运算例外
SIGHUP 系统挂断
SIGILL 非法指令
SIGINT 终端中断
SIGKILL 停止进程(此信号不能被忽略或捕获)
SIGPIPE 向没有读者的管道写入数据
SIGSEGV 无效内存段访问
SIGQUIT 终端退出
SIGTERM 终止
SIGUSR1 用户定义信号1
SIGUSR2 用户定义信号2
SIGCHLD 子进程已经停止或退出
SIGCONT 如果被停止则继续执行
SIGSTOP 停止执行
SIGTSTP 终端停止信号
SIGTOUT 后台进程请求进行写操作
SIGTTIN 后台进程请求进行读操作
2:信号的分类
Linux信号机制基本上是从Unix系统中继承过来的。早期Unix系统中的信号机制相对而言比较简单和原始,只有32中信号,也就是大家在man中用kill -l查询出来的前32种信号,它们都属于不可靠信号,来信号抵达的时候,它们不支持排队,可能会导致信号丢失,在后来的linux中对其进行了改良,增加了32种可靠信号,支持排队,信号都是实时信号,不会丢失。

进程对信号的响应

进程对信号有三种相应
一:忽略信号
不采取任何操作、有两个信号不能被忽略:SIGKILL和SIGSTOP。
思考1:为什么进程不能忽略SIGKILL、SIGSTOP信号。(如果应用程序可以忽略这2个信号,系统管理无法杀死、暂停进程,无法对系统进行管理。)。所以SIGKILL和SIGSTOP信号是不能被捕获的。
二:捕获并处理信号
内核中断正在执行的代码,转去执行先前注册过的处理程序。
三:执行默认操作
默认操作通常是终止进程,这取决于被发送的信号。
信号的默认操作:通过 man 7 signal 进程查看
进程到底会对信号做出什么样的相应,取决于信号处理函数的参数

信号发送

发送信号的函数有kill()、raise()、 sigqueue()、alarm()以及abort()。

1、Int kill(pid_t pid, int siq)kill既可以向自身发送信号,也可以向其他进程发送信号;
参数组合情况解释:
kill(pid_t pid, int siq)
pid>0 将信号sig发给pid进程
pid=0 将信号sig发给同组进程
pid=-1 将信号sig发送给所有进程,调用者进程有权限发送的每一个进程(除了1号进程之外,还有它自身)
pid<-1 将信号sig发送给进程组是pid(绝对值)的每一个进程

2、int raise(int signo)只能向自己发信号
函数功能和kill(getpid(),int sig)一样

3、int sigqueue(pid_t pid, int sig, const union sigval val) 发信号的时候可以带参数
sigqueue的第一个参数是指定接收信号的进程ID,第二个参数确定即将发送的信号,第三个参数是一个联合数据结构union sigval,指定了信号传递的参数。
其联合体的结构为
typedef union sigval {
int sival_int;
void *sival_ptr;
}sigval_t;

4、unsigned int alarm(unsigned int seconds)
专门为SIGALRM信号而设,设置一个闹钟延迟发送信号告诉linux内核n秒中以后,发送SIGALRM信号;

5、void abort(void);
向进程发送SIGABORT信号,默认情况下进程会异常退出,当然可定义自己的信号处理函数。即使SIGABORT被进程设置为阻塞信号(这里涉及信号的阻塞和未决,下面会细说),调用abort()后,SIGABORT仍然能被进程接收。

信号的安装

linux中信号的安装有2个函数,一个signal,这个是早期的函数,不支持信号传递信息,也就是不支持sigqueue,所以说,如果你想在发送信号的时候携带一些信息,就得用改良版的signation函数

signal函数:
作用1:站在应用程序的角度,注册一个信号处理函数。
作用2:忽略信号、设置信号默认处理 信号的安装和恢复
函数原型:__sighandler_t signal(int signum, __sighandler_t handler);
signal第一个参数为准备捕捉的信号(信号为int类型)第二个参数handler像上述所说的3种相应,第一可以是下面两个特殊值:SIG_IGN 屏蔽该信号,SIG_DFL 恢复默认行为,第二可以为一个函数入口点的地址,也就是一个回调函数,这个函数的参数必须有一个int的参数来接收信号,类型为void。

看代码#include <signal.h>  #include <stdio.h>  #include <unistd.h>  void test(int sig)  {      printf(" I got signal %d\n", sig);  }  int main()  {       signal(SIGINT, text);  /*改变终端中断信号SIGINT的默认行为,使之执行test函数 ,异步处理*/   /* signal(SIGINT, SIG_DFL);//恢复终端中断信号SIGINT的默认行为    signal(SIGINT, SIG_IGN);//恢复SIGINT的默认行为*/    while(1)      {          printf("Hello World!\n");         sleep(1);      }      return 0;  }

sigaction函数:
int sigaction(int sig, const struct sigaction *act, struct sigaction *oact);
该函数的第一个参数为信号的值,可以为除SIGKILL及SIGSTOP外的任何一 个特定有效的信号(为这两个信号定义自己的处理函数,将导致信号安装错误)
第二个参数是指向结构sigaction的一个实例的指针,在结构 sigaction的实例中,指定了对特定信号的处理,可以为空,进程会以缺省方式对信号处理
第三个参数oldact指向的对象用来保存原来对相应信号的处理,可指定oldact为NULL。
sigaction结构体
第二个参数最为重要,其中包含了对指定信号的处理、信号所传递的信息、信号处理函数执行过程中应屏蔽掉哪些函数等等
struct sigaction {
void (*sa_handler)(int); //信号处理程序 不接受额外数据
void (sa_sigaction)(int, siginfo_t , void *); //信号处理程序 能接受额外数据,和sigqueue配合使用
sigset_t sa_mask; //通常是用来设置信号集,也就是阻塞和未决
int sa_flags; //信号处理修改器
void (*sa_restorer)(void); //废弃
};
注意:回调函数句柄sa_handler、sa_sigaction只能任选其一。

代码

#include <signal.h>  #include <stdio.h>  #include <unistd.h> void handler(int sig){    printf("recv a sig=%d\n", sig); }__sighandler_t my_signal(int sig, __sighandler_t handler){    struct sigaction act;    struct sigaction oldact;    act.sa_handler = handler;    sigemptyset(&act.sa_mask);    act.sa_flags = 0;    if (sigaction(sig, &act, &oldact) < 0)        return SIG_ERR;    return oldact.sa_handler;}int main(int argc, char *argv[]){             struct sigaction act;    sigset_t sa_mask;    act.sa_handler = handler;//注意sa_handler、sa_sigaction只能任选其一    act.sa_flags = 0;//0表示默认值    sigemptyset(&act.sa_mask);//即所以的信号都设置为非阻塞    //测试信号安装函数    //sigaction(SIGINT, &act, NULL);    //模拟signal函数    my_signal(SIGINT, handler);    for (;;)    {        pause();    }    return 0;}

信号阻塞与信号未决

信号在内核中的表示
执行信号的处理动作称为信号递达(Delivery),信号从产生到递达之间的状态,称为信号未决(Pending)。进程可以选择阻塞(Block)某个信号。被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作。
注意,阻塞和忽略是不同,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作。信号在内核中的表示可以看作是这样的:
这里写图片描述
说明1:PCB进程控制块中函数有信号屏蔽状态字(block)信号未决状态字(pending)还有是否忽略标志;
说明2:信号屏蔽状态字(block),1代表阻塞、0代表不阻塞;信号未决状态字(pending)的1代表未决,0代表信号可以抵达了;
说明3:向进程发送SIGINT,内核首先判断信号屏蔽状态字是否阻塞,信号未决状态字(pending相应位制成1;若阻塞解除,信号未决状态字(pending)相应位制成0;表示信号可以抵达了。
相应的api:
int sigemptyset(sigset_t *set); 把信号集情况64bit/8=8个字节
int sigfillset(sigset_t *set); 把信号集情况1
int sigaddset(sigset_t *set, int signo); 根据signo,把信号集中的对应为置成1
int sigdelset(sigset_t *set, int signo); 根据signo,把信号集中的对应为置成0
int sigismember(const sigset_t *set, int signo);//判断signo是否在信号集中

int sigprocmask(int how, const sigset_t *set, sigset_t *oset);//功能:读取或更改进程的信号屏蔽字
SIG_BLOCK 在进程当前阻塞信号集中添加set指向信号集中的信号
SIG_UNBLOCK 如果进程阻塞信号集中包含set指向信号集中的信号,则解除对该信号的阻塞
SIG_SETMASK 更新进程阻塞信号集为set指向的信号集

实战代码void handler(int sig){    if (sig == SIGINT)        printf("recv a sig=%d\n", sig);    else if (sig == SIGQUIT)    {        sigset_t uset;        sigemptyset(&uset);        sigaddset(&uset, SIGINT);        //ctr + \ 用来接触  SIGINT 信号        //解除阻塞        sigprocmask(SIG_UNBLOCK, &uset, NULL);    }}void printsigset(sigset_t *set){    int i;    for (i=1; i<NSIG; ++i)    {        if (sigismember(set, i))//遍历所有的信号,看是否在信号集中            putchar('1');        else            putchar('0');    }    printf("\n");}//3 连续的按ctrl+c键盘,虽然发送了多个SIGINT信号,但是因为信号是不稳定的,只保留了一个。//不支持排队int main(){    sigset_t pset; //用来打印的信号集    sigset_t bset; //用来设置阻塞的信号集    sigemptyset(&bset);    sigaddset(&bset, SIGINT);//把SIGINT加入到bset信号集    if (signal(SIGINT, handler) == SIG_ERR)        ERR_EXIT("signal error");    if (signal(SIGQUIT, handler) == SIG_ERR)        ERR_EXIT("signal error");    //读取或更改进程的信号屏蔽字 这里用来阻塞ctrl+c信号    //ctrl+c信号被设置成阻塞,即使用户按下ctl+c键盘,也不会抵达    sigprocmask(SIG_BLOCK, &bset, NULL);  while(1)    {        //获取未决 字信息        sigpending(&pset);        //打印信号未决  sigset_t字        printsigset(&pset);        sleep(1);    }    return 0;}

信号处理函数遇上可重入和不可重入函数

试想当一个进程接收到信号的时候去执行关联函数,这时同时也有一个进程来执行这个函数,假设他们都要取钱(钱是一个全局变量),这时候,就可能导致很多种不可预料的结果。
所谓可重入函数是指一个可以被多个任务调用的过程,任务在调用时不必担心数据是否会出错。因为进程在收到信号后,就将跳转到信号处理函数去接着执行。而不可重入就有可能导致上面的情况。
linux有张可重入函数表,大家有兴趣的可以去查阅一番。
总而言之,在信号的关联函数中,要尽量不使用静态的变量malloc出来的变量(可能还没free再次申请内存)和标准io(write和read混乱)

原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 脖子痛得低不了怎么办 六岁的孩孑不吃.饭怎么办 大腿肌肉练废了怎么办 吃鸡里的信誉分太低怎么办she 血压高老是晕怎么办27 我腰疼的厉害怎么办 奥克斯空调外机上霜风扇不转怎么办 燃脂膏辣辣的怎么办 减肥期间暴食了怎么办 健身减脂后腹部皮松怎么办 吃减肥药上火怎么办呢 魔域怀旧版新区进不去怎么办 dnf十周年礼盒打开了怎么办 房地产项目完成后企业员工怎么办啊 韩服lol延迟太高怎么办 LOL等级奖励卡掉怎么办 魔域手机号换了怎么办 买的qq号找回了怎么办 买dnf账号被找回怎么办 微博账号已锁定怎么办 抖音账号封手机怎么办 手机号码绑定被别人占用了怎么办 DNF账号给找回了怎么办 转转上被骗了200怎么办 7彩账号被锁定怎么办 猪不吃食没精神怎么办 cf手游签到没给怎么办 cf说停止运行了怎么办 cf端游永久禁赛怎么办 cf端游爆破怕死怎么办 王者荣耀累计扣分12分怎么办 去医院看病没带身份证怎么办 ps4星战2鬼服怎么办 冒险岛英雄五转怎么办 6儿童视力低常怎么办 腰干活累的酸痛怎么办 狗狗体力很差怎么办啊 脉差总是五十多怎么办 吃过敏药嗜睡乏力怎么办 写字紧张心跳的快手抖怎么办 怀孕食欲差没精神怎么办