IPC:信号

使用场景:1、为了并发,中断处理其它事件,1、进程间通信
1、中断
中止(注意不是终止)当前正在执行的程序,
转而执行其它任务。

硬件中断:来自硬件设备的中断。
软件中断:来自其它程序的中断。

2、信号是一种软件中断
信号提供了一种以异步方式执行任务的机制。

3、常见的信号
SIGHUP(1):连接断开信号
如果终端接口检测一个连接断开,则将此信号发送给与该终端相关的控制进程。
SIGINT :Ctrl+C
SIGQUIT: Ctrl+
SIGKILL:kill -9
SIGTERM: kill
SIGCHLD:

4、不可靠信号(又叫非实时信号)
1、那些建立在早期机制伤的信号被称为“不可靠信号”。
小于SIGRTMIN(34)的信号都是不可靠信号。
2、不支持排队,可能会丢失。同一个信号产生多次,
进程可能只收到一次该信号。
3、进程每次处理完这些信号后,对相应信号的响应被自动恢复为默认动作,
除非显式地通过signal函数重新设置一次信号处理机制。

5、可靠信号(实时信号,排队信号)
2、支持排队,不会丢失
3、无论可靠信号还是不可靠信号
都可以通过sigqueue/qigaction函数发送/安装
以获得比其早期版本kill/signal函数更可靠的使用效果

6、信号的来源
1、硬件异常:除0、无效的内存访问等。
这些异常通常被硬件(驱动)检测到,并通知系统内核。
系统内核再向引发这些异常的进程递送相应的信号。
2、软件异常:通过kill/raise/alarm/setitimer/sigqueue函数产生的信号。

7、信号处理
1、忽略
2、终止进程。
3、终止进程同时产生core文件
4、捕获并处理。当信号发生时,内核会调用一个事先注册好的用户函数(信号处理函数)

二、signal
|#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal (int signum,
sighandler_t handler);

signum - 信号码,也可以使用系统预定义的常量宏,如SIGINT等。
handler - 信号处理函数指针或以下常量;
SIG_IGN:忽略该信号;
SIG_DFL:默认处理;
成功返回原来的信号处理函数指针或SIG_IGN/SIG_DFL常量,失败返回SIG_ERR。

1、在某些unix系统上,
通过signal函数注册的信号处理函数只一次有效,
即内核每次调用该信号处理函数前,
会将对该信号的处理自动恢复为默认方式。
为了获得更持久有效的信号处理,可以在信号处理函数中再次调用signal函数,
重新注册一次。
例如:
void sigint (int signum){
...
signal(SIGINT, sigint);
}

int main(void){
...
signal(SIGINT, sigint);
}

三、子进程的信号处理
1、子进程会继承父进程的信号处理方式,只到子进程调用exec函数。
2、子进程调用exec函数后
exec函数将被父进程设置为捕获的信号恢复至默认处理,其余保持不变。

四、发送信号
1、键盘
Ctrl+C SIGINT 终端中断
Ctrl+\ SIGQUIT 终端退出
Ctrl+Z SIGTSTP 终端暂停
2、错误
除0 SIGFPE 算术异常
非法内存访问 SIGSEGV 段错误
硬件故障 SIGBUS 总线错误
3、命令
kill -信号 进程号
4、函数
|#include <signal.h>
int kill(pid_t pid, int sig) //不等待信号处理完就返回,异步处理
成功返回0 ,失败返回-1
pid >0 -向pid进程发送sig信号
pid=0 - 向同进程组的所有进程发送信号
pid = -1 -向所有进程发送信号,前提是有权限
pid < -1 -向绝对值等于pid的进程组发送信号
0信号为空信号。
若sig取0,则kill函数仍会执行错误检查,但并不实际发送信号。常被用来确定一个
进程是否存在。
向一个不存在的进程发送信号,会返回-1,且errno为ESRCH。

     int raise (int sig)
     向调用进程自身发送sig信号。成功返回0,失败返回-1。kill函数也可以实现此功能
     |#include <signal.h>
     |#include <stdio.h>
     |#include <stdlib.h>
     |#include <unistd.h>
     void sigint (int signum){
          printf("%u进程,永别了!\n", getpid());
          exit(0);
          //raise(signum);   这里不是递归调用,raise返回,sigint也返回,函数栈并不一致增长,会有波动,所以程序不会爆掉。
     }
     int main(void){
          if(signal(SIGINT, sigint) == SIG_ERR){
                perror("signal");
                return -1;
          }
          printf("%u进程,我要自杀。。。\n", getpid());
          if(raise(SIGINT) == -1){
               perror("raise");
               return -1;
           };
     }

五、pause 暂停函数
int pause(void);
1、使调用进程进入睡眠状态,不再占有cpu,直到有信号终止该进程或被捕获。
2、只有调用了信号处理函数并从中返回以后,该函数才会返回-1;
3、该函数要么不返回(未捕获到信号),要么返回-1(被信号中断),errno为EINTR。
4、相当于没有时间限制的sleep函数。
|#include <stdio.h>
|#include <signal.h>
|#include <unistd.h>
|#include <sys/wait.h>
void sigint (int signum){
printf("%u进程,收到SIGINT信号!\n", getpid());
}
int main(void){
pid_t pid = fork();
if(pid == -1){
perror("fork");
return -1;
}
if (pid == 0)
{
if (signal(SIGINT, sigint) == -1){ //没信号不阻塞
perror("signal");
return -1;
}
printf("%u进程:我是子进程 , 大睡ing....\n",getpid());
pause();
printf("%u进程:我是子进程 , 我被唤醒了......\n",getpid());
return 0 ;
}
sleep(1);
printf("%u进程:我是父进程 , 向%u发送信号......\n",getpid(), pid);
if(kill(pid, SIGINT) == -1){
perror("kill");
return -1;
}
if((pid = wait(0) == -1){ //回收僵尸,子进程退出,wait返回
perror("wait");
return -1;
}
printf("%u进程:%u进程已退出。\n", getpid(), pid);
return 0;
}


图片发自简书App

六、sleep 跟pause函数功能差不多
|#include <unistd.h>
unsigned int sleep(unsigned int seconds);
1、使调用进程睡眠seconds秒,除非有信号终止该进程或被捕获。
2、只有睡够seconds秒,或调用了信号处理函数并从中返回以后,该函数才会返回。
3、该函数要么返回0(睡够),
要么返回剩余秒数(被信号中断)。
4、相当于有时间限制的pause函数。
|#include <stdio.h>
|#include <signal.h>
|#include <unistd.h>
|#include <sys/wait.h>
void sigint (int signum){
printf("%u进程,收到SIGINT信号!\n", getpid());
}
int main(void){
pid_t pid = fork();
if(pid == -1){
perror("fork");
return -1;
}
if (pid == 0)
{
if (signal(SIGINT, sigint) == -1){ //没信号不阻塞
perror("signal");
return -1;
}
printf("%u进程:我是子进程 , 小睡10秒....\n",getpid());
unsigned int left = sleep(10);
printf("%u进程:我是子进程 , 还剩%u秒没睡,即将退出......\n",getpid(),left);
return 0 ;
}
sleep(3);
printf("%u进程:我是父进程 , 向%u发送信号......\n",getpid(), pid);
if(kill(pid, SIGINT) == -1){
perror("kill");
return -1;
}
if((pid = wait(0) == -1){ //回收僵尸,子进程退出,wait返回
perror("wait");
return -1;
}
printf("%u进程:%u进程已退出。\n", getpid(), pid);
return 0;
}

七、usleep 以微秒为单位
int usleep (useconds_t usec);
使调用进程睡眠usec微秒,除非有信号终止该进程或被捕获。
成功返回0,失败返回-1.

七、alarm 等于设置一个定时器
|#include <unistd.h>
unsigned int alarm(unsigned int seconds);
1、使内核在seconds秒之后,向调用进程发送SIGALRM(14)闹钟信号。
2、SIGALRM信号的默认处理是终止进程。
3、若之前已设过定时且尚未超时,则调用该函数会重新设置定时,并返回之前定时的剩余时间。
4、seconds取0表示取消之前设过且尚未超时的定时。

|#include <stdio.h>
|#include <time.h>
|#incude <signal.h>
void sigalrm(int signum){
time_t t = time(NULL);
struct tm* lt = localtime(&t);
printf("\r%02d:%02d:%02d", lt->tm_hour, lt->tm_min, lt->tm_sec);
//'\r' 回车,回到当前行的行首,而不会换到下一行,如果接着输出的话,本行以前的内容会被逐一覆盖;
alarm(1);
}

int main(void){
setbuf(stdout, 0);
if(signal(SIGALRM,sigalrm) == -1){
perror("signal");
return 0;
}
sigalrm(SIGALRM);
FOR (;;){
}
return 0;
}

结果:每秒每秒更新时间

unsigned int remain = alarm(10);
sleep(2);
remain = alarm(5); //此时remain = 8
remain = alarm(0); //取消定时器

八、信号集与信号阻塞
1、信号集
1)多个信号的捷类型:
sigset_t,128个二进制位(实际预编译查看是128字节),每个位代表一个信号。
2)相关函数
|#include <signal.h>
//将信号集中的全部信号位置1
int sigfillset(sigset_t* set);
//将信号集中的全部信号位清0
int sigemptyset(sigset_t* set);
//将信号集set中的signum信号对应位置1
int sigaddset(sigset_t* set, int signum);
//将信号集set中的signum信号对应位清0
int sigdelset(sigset_t* set, int signum);
成功返回0,失败返回-1

         //判断信号集set中与signum对应的位是否为1,是1则返回1,否则返回0
         int sigismember(const sigset_t* set, int signum);
   |#include <stdio.h>
   |#include <signal.h>
   int main(viod){
       sigset_t set;    //预编译后看到是一个unsigned long int的32个元素数组128个字节
       print("%u\n", sizeof(set));
       sigfillset(&set);
       sigemptyset(&set);
       sigaddset(&set, SIGINT);
       sigdelset(&set, SIGINT);
       if(sigismember(&set, SIGINT))
            printf("有\n");
        else
            printf("没有\n");
       return 0;
   }

2、信号屏蔽(信号阻塞)
1)、当信号产生时,系统内核会在其所维护的进程表中,为特定的进程设置一个
与该信号相对应的标志位,这个过程称为递送。
2)、信号从产生到完成递送之间存在一定的时间间隔。
处于这段时间间隔中的信号状态,称为未决。
3)、每一个进程都有一个信号掩码(signal mask)
它实际上是一个信号集,
其中包括了所有需要被屏蔽的信号。 所以这些信号就变成了未决状态的信号了。
4)、可以通过sigprocmask函数,
检测和修改调用进程的信号掩码。
也可以通过sigpending函数,
获取调用进程当前处于未决状态的信号集。
5)、当进程执行诸如更新数据库等敏感任务时,
可能不希望被某些信号中断。
这时可以暂时屏蔽(注意不是忽略)这些信号,
使其停留在未决状态,
待任务完成以后,再回过头来处理这些信号。
6)、在信号处理函数的执行过程中,
这个正在被处理的信号总是处于信号掩码中。
这就避免了同样的信号同时再调用同一个函数。
|#include <signal.h>

int sigprocmask(int how, const sigset_t* set, sigset_t* oldset);
成功返回0 ,失败返回-1

how - 修改信号掩码的方式,可取以下值
SIG_BLOCK: 新掩码是当前掩码和set的并集(将set加入信号掩码)
SIG_UNBLOCK: 新掩码是当前掩码和set的补集的交集(将set从信号掩码移除)
SIG_SETMASK: 新掩码是set(将信号掩码设为set)

set: NULL则忽略
oldset:备份以前的信号掩码, NULL则不备份

int sigpending(sigset_t set);
set - 输出,调用进程当前处于未决状态的信号集。
成功返回0 ,失败返回-1

注意:对于不可靠信号,
通过sigprocmask函数设置信号掩码以后,
相同的被屏蔽信号只会屏蔽第一个,
并在恢复信号掩码后被递送,其余的则直接忽略掉。
而对于可靠信号,
则会在信号屏蔽时按其产生的先后顺序排队,
一旦恢复信号掩码,这些信号会依次被信号处理函数处理。

|#include <stdio.h>
|#include <unistd.h>
|#include <signal.h>
|#include <stdlib.h>
void sighand(int signum){
printf("%u进程:收到%d信号!\n", getpid(), signum);
}
void updatedb (void){
int i;
for(i=0;i<5;++i){
printf("%u进程:更新第%d记录!\n", getpid(), i+1);
sleep(1);
}
}
int main(void){
if(signal(SIGINT, sighand) == SIG_ERR){
perror("signal");
return -1;
}
if(signal(SIGINT, sighand) == SIG_ERR){
perror("signal");
return -1;
}
pid_t pid = fork();
if(pid == -1){
perror("fork");
return -1;
}
if(pid == 0){
pid_t pid = getppid();
int i ;
for(i=0; i<5; ++i){
printf("%u进程:向%u进程发送%d信号... \n", getpid(), pid, 50);
kill(pid, 50);
}
return 0;
}
updatedb();
return 0;
}

更新数据库操作频繁被打断,如下图。


图片发自简书App

做一个修正。

|#include <stdio.h>
|#include <unistd.h>
|#include <signal.h>
|#include <stdlib.h>
void sighand(int signum){
printf("%u进程:收到%d信号!\n", getpid(), signum);
}
void updatedb (void){
int i;
for(i=0;i<5;++i){
printf("%u进程:更新第%d记录!\n", getpid(), i+1);
sleep(1);
}
}
int main(void){
if(signal(SIGINT, sighand) == SIG_ERR){
perror("signal");
return -1;
}
if(signal(SIGINT, sighand) == SIG_ERR){
perror("signal");
return -1;
}
printf("%u进程:屏蔽%d信号和%d信号... \n", getpid(), SIGINT, 50);
sigset_t new;
if(sigemptyset(&new) == -1){
perror("sigemptyset");
return -1;
}
if(sigaddset(&new, SIGINT) == -1){
perror("sigaddset");
return -1;
}
if(sigaddset(&new, 50) == -1){
perror("sigaddset");
return -1;
}
sigset_t old;
if(sigprocmask(SIG_SETMASK, &new, &old) == -1){
perror("sigprocmask");
return -1;
}
pid_t pid = fork();
if(pid == -1){
perror("fork");
return -1;
}
if(pid == 0){
pid_t pid = getppid();
int i ;
for(i=0; i<5; ++i){
printf("%u进程:向%u进程发送%d信号... \n", getpid(), pid, 50);
kill(pid, 50);
}
return 0;
}
updatedb();
//将信号集从掩码集中移除,恢复处理之前未决的信号
sigset_t pend;
if(sigpending(&pend) == -1){
perror("sigpending");
return -1;
}
if(sigismember(&pend, SIGINT)) //执行过程中按Ctrl+C
printf("%u进程:发现%d信号未决... \n", getpid(), SIGINT);
if(sigismember(&pend, 50))
printf("%u进程:发现%d信号未决... \n", getpid(), 50);
//取消SIGINT ,50 的屏蔽
if(sigprocmask(SIG_SETMASK, &old, NULL) == -1){
perror("sigprocmask");
return -1;
}
return 0;
}

  • 此时不会再次打断进程。上传结果图片,此时最后可以收到5个50信号,1个2(不可靠)信号:


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

推荐阅读更多精彩内容

  • 信号的基本概念 信号被认为是一种软件中断(区别于硬件中断),信号机制提供了一种在单进程/线程下处理异步事件的方法。...
    小叶大孟阅读 1,907评论 0 1
  • 一、Linux系统概述 不加引号可理解为宏,直接替换,单引号中特殊字符会被解释为普通字符,双引号中$,,'还是特殊...
    赤果_b4a7阅读 1,500评论 0 2
  • 信号信号是linux操作系统进程间通信的一种方式,一个应用进程可以接受、发送信号给另一个进程,当进程捕获到某个信号...
    zhile_doing阅读 465评论 0 0
  • 基本规则 信号可以由系统内核程序发出,也能由某些进程发送,但是大部分时候都是由内核程序发出. 当一个信号正在被处理...
    wangxn阅读 954评论 0 0
  • 今天要和大家分享的这本书,名叫《胡适谈读书》。其中摘录了胡适一些演讲内容和已发表的文章。 在我看来胡适口中的“读书...
    Ciciy阅读 3,948评论 2 9