6.1 前言

本章简单描述信号。信号是Linux系统中,内核和进程通信的一种方式。如果内核希望打断进程的顺序执行,那么内核会在进程的PCB中记录信号。而当这个进程被分配到CPU,进入执行状态时,首先会检查是否有信号,如果有信号,那么进程会先尝试执行信号处理函数。

内核需要打断进程运行的时机:

  • 进程无法继续了

int* p = NULL;
*p = 100;
// 此时代码无法再继续运行,内核会发送SIGSEGV信号给进程,这是我们常见的段错误
int a = 0;
int b = 1/a;
// 除0操作,发送SIGFPE给进程
  • 按下ctrl+c
    ctrl+c其实是bash向前台进程组发送SIGINT

int main()
{
    fork();
    fork(); // 四个进程
    while(1)
    {
        sleep(1);
    }
}

运行该程序后,再按Ctrl+c,结果是四个进程全部退出

int main()
{
    signal(SIGINT, SIG_IGN);
    fork();
    fork(); // 四个进程
    while(1)
    {
        sleep(1);
    }
}

有了signal的处理之后,ctrl+c发送的SIGINT不会导致进程退出。

6.2 信号类型

通过kill -l命令可以看到系统定义的信号类型,信号值小于32的是传统的信号,称之为非实时信号,而大于32的称之为实时信号。这里只讨论非实时信号。

6.3 信号的处理

可以通过signal函数,注册信号处理函数。如果没有注册信号处理函数,那么按照默认方式处理。
也可以通过signal设置忽略信号。

信号 默认处理动作 发出信号的原因
SIGHUP A 进程session leader退出时,同session的其他进程会收到这个信号
SIGINT A Ctrl+C
SIGQUIT C Ctrl+D
SIGILL C 非法指令
SIGABRT C 调用abort函数产生的信号
SIGFPE C 浮点异常
SIGKILL AEF Kill信号
SIGSEGV C 无效的内存引用
SIGPIPE A 管道破裂: 写一个没有读端口的管道
SIGALRM A 由alarm(2)发出的信号
SIGTERM A 终止信号
SIGUSR1 A 用户自定义信号1
SIGUSR2 A 用户自定义信号2
SIGCHLD B 子进程状态变化会给父进程发送SIGCHLD信号
SIGCONT   进程继续(曾被停止的进程)
SIGSTOP DEF 暂停进程
SIGTSTP D 控制终端(tty)上按下停止键
SIGTTIN D 后台进程企图从控制终端读
SIGTTOU D 后台进程企图从控制终端写

A 缺省的动作是终止进程
B 缺省的动作是忽略此信号
C 缺省的动作是终止进程并进行内核映像转储(dump core)
D 缺省的动作是停止进程
E 信号不能被捕获
F 信号不能被忽略

因为SIGKILL不能被捕获,那么以下代码是不正常
signal(SIGKILL, handle) //xxxx
#include <stdio.h>
#include <signal.h>
#include <stdlib.h> void signal_handle(int a)
{
if(a == SIGINT)
printf("signal_handle\n");
else if(a == SIGABRT)
printf("abrt\n");
else if(a == SIGALRM)
printf("alarm\n");
else if(a == SIGCHLD)
printf("child\n");
else if(a == SIGUSR1)
printf("usr1 signal\n");
} int main()
{
// SIGINT 2
signal(SIGINT, signal_handle);
signal(SIGABRT, signal_handle);
signal(SIGALRM, signal_handle);
signal(SIGCHLD, signal_handle);
signal(SIGUSR1, signal_handle); pid_t pid = fork();
if(pid == 0)
return 0; // 给自己发送一个abrt信号
//abort();
alarm(1); while(1)
{
sleep(1);
}
}

6.4 不可靠信号

信号值小于32的都是不可靠信号,假如进程收到一个信号来不及处理,这时候又收到一个同样的信号,那么这两个信号会合并成一个信号,这个原因是因为进程保存该信号的值只有1位。

6.5 中断系统调用(中断阻塞)

假如一个进程调用了某系统调用导致该进行处于挂起状态,而此时该进程接收到一个信号,那么该系统调用被唤醒。通常该系统调用会返回-1,错误码是EINTR

也有些系统调用,可以设置打断后重启,这样就不会被信号打断,具体参考man 7 signal

如果使用signal函数注册信号处理函数,默认被中断的系统调用是自动重启的。

// 阻塞
int ret = read(0, buf, sizeof(buf));
printf("read data\n");
#include <signal.h>
#include <stdio.h>
#include <errno.h>
void handle(int v){
printf("ctrl+c\n");
} int main()
{
signal(SIGINT, handle); char buf;
int ret = read(0, buf, sizeof(buf)); // read被中断打断了
printf("ret = %d, errno=%d, EINTR=%d\n", ret, errno, EINTR); // EINTR
}

6.6 可重入问题

信号会导致可重入问题,比如一个全局链表。

std::vector<int> v;
void handler(int sig)
{
  v.push_back(0);
}
int main()
{
  signal(SIGINT, handler);
    signal(SIGUSR1, handler);
  while(1)
  {
        // 先屏蔽所有信号
    v.push_back(0);
        // 再去掉屏蔽
  }
}

以上代码在一定情况下会崩溃,在main函数中不停调用push_back,如果在push_back执行一半时,被中断打断,然后去执行中断处理函数时,那么该中断处理函数的push_back会崩溃。

有些系统调用本身带有局部静态变量,因此那些函数不能在信号处理函数中调用,比如strtokreaddir等,对应的可重入的函数是strtok_rreaddir_r

6.7 发送信号

可以通过kill函数发送信号。

调用kill
发送信号
进程1
内核
进程2

kill也可以进程组发送信号

#include <sys/types.h>
#include <signal.h> int main()
{
kill(27054, SIGUSR1);
}

补充:掩盖信号

//掩盖不可靠信号
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
// 掩盖SIGINT
// 掩盖一个可靠信号
void handle(int v)
{
printf("sigint \n");
} int main()
{
signal(SIGINT, handle);
sigset_t set; // 将set集合设置为空
sigemptyset(&set);
// 将SIGINT信号加入集合
sigaddset(&set, SIGINT);
// 把这个集合代表的信号,加入信号掩码
sigprocmask(SIG_BLOCK, &set, NULL); // 从此,该进程收到SIGINT,不会被处理 sleep(5);
printf("remove SIGINT from mask\n"); // 去掉SIGINT的掩码
sigprocmask(SIG_UNBLOCK, &set, NULL); while(1)
{
sleep(1);
}
}
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
// 掩盖34
// 掩盖一个可靠信号
void handle(int v)
{
printf("hahahaha \n");
} int main()
{
signal(34, handle);
sigset_t set; // 将set集合设置为空
sigemptyset(&set);
// 将34信号加入集合
sigaddset(&set, 34);
// 把这个集合代表的信号,加入信号掩码
sigprocmask(SIG_BLOCK, &set, NULL); // 从此,该进程收到34,不会被处理 kill(getpid(), 34);
kill(getpid(), 34);
kill(getpid(), 34);
kill(getpid(), 34);
kill(getpid(), 34); sleep(5);
printf("remove 34 from mask\n"); // 去掉34的掩码
sigprocmask(SIG_UNBLOCK, &set, NULL); while(1)
{
sleep(1);
}
}

6.8 忽略信号

signal(SIGPIPE, SIG_IGN);
#include <signal.h>

void handle(int v)
{ } int main()
{
// 表示忽略SIGINT
// 忽略信号和掩盖信号是有区别:
//
// 未决的信号:已经发出但是没有被处理的信号叫未决的信号
signal(SIGINT, SIG_IGN); // 从这里开始就不忽略
signal(SIGINT, handle); // 设置一个处理函数
signal(SIGINT, SIG_DFL); // 恢复成默认处理
while(1)
{
sleep(1);
}
}

以上例子,忽略SIGPIPE信号,那么进程收到SIGPIPE后,不会有任何反应。

6.9 屏蔽信号

屏蔽和忽略不同,忽略意味着在忽略期间,接收的信号就被忽略了。而屏蔽的意思,是暂时屏蔽,屏蔽期间收到的信号依旧在,如果某一时刻该信号不再忽略时,该信号的处理程序会被调用。

设置屏蔽集合,使用sigprocmask

6.10 SIGCHLD

SIGCHLD信号产生于子进程退出和状态变化,父进程通常在该信号的处理函数中,调用wait来回收子进程的PCB,这样可以避免阻塞。

#include <signal.h>

void chld_handle(int v)
{
// 有子进程退出了
// wait(NULL);
//
while(1) // 使用while(1)是避免有多个子进程同时退出,由于SIGCHLD是不可靠信号,函数只会调用一次
{
int ret = waitpid(-1, NULL, WNOHANG); // 每次回收都应该用非阻塞方式去回收
if(ret == -1) // 没有僵尸进程了,之前僵尸进程已经被回收了
break;
}
} int main()
{
// 等待子进程的结束,问题是:ddddd
// 它阻塞主进程的执行,影响效率
// wait(NULL);
//
signal(SIGCHLD, chld_handle); pid_t pid = fork();
if(pid == 0) return 0; while(1)
{
sleep(1);
}
}

6.11 sigaction和sigqueue

sigaction和signal一样用来注册信号处理函数,siqqueue和kill一样,用来发送信号,但是sigaction比signal功能强大,signal比较简单。

强大:

  1. 可以传递参数

  2. 可以获得发送信号的进程信息

  3. 可以设置SA_RESTART

    #include <signal.h>
    #include <stdio.h>
    #include <errno.h>
    //void handle(int v){}
    //
    // 新的信号处理函数
    void handle(int v, siginfo_t* info, void* p)
    {
    printf("ctrl+c\n");
    }
    int main()
    {
    // 默认的signal已经有SA_RESTART这个flag了
    //signal(SIGINT, handle); struct sigaction sig;
    sig.sa_handler = NULL;
    sig.sa_sigaction = handle;
    sigemptyset(&sig.sa_mask);
    // sig.sa_flags = 0;
    sig.sa_flags = SA_RESTART; // 让阻塞的系统调用,被这个信号打断之后,要重启
    sig.sa_restorer = NULL; // 在Linux下没用,直接填NULL就可以了
    sigaction(SIGINT, &sig, NULL); char buf;
    int ret = read(0, buf, sizeof(buf)); // read被中断打断了
    printf("ret = %d, errno=%d, EINTR=%d\n", ret, errno, EINTR); // EINTR
    }
    #include <stdio.h>
    #include <stdlib.h>
    #include <signal.h> char buf[1024]; void handle_data()
    {
    printf("user input is %s\n", buf);
    } void handle_chld1(int v)
    {
    while(1)
    {
    int ret = waitpid(-1, NULL, WNOHANG);
    if(ret < 0) break;
    }
    } void handle_chld(int v, siginfo_t* info, void* p)
    {
    handle_chld1(v);
    } int main()
    {
    // signal(SIGCHLD, handle_chld); struct sigaction act;
    act.sa_handler = NULL;
    act.sa_sigaction = handle_chld;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    act.sa_restorer = NULL;
    sigaction(SIGCHLD, &act, NULL); while(1)
    {
    // char* ret = fgets(buf, sizeof(buf), stdin);
    // if(ret == NULL) break; int ret = read(0, buf, sizeof(buf));
    if(ret < 0)
    {
    // 说明read出错,不是真正的出错,而是被中断打扰了
    // 那此时,应该重新调用read函数,去获取信息
    if(errno == EINTR)
    {
    continue;
    }
    // 如果是其他错误原因,就break
    break;
    } pid_t pid = fork();
    if(pid == 0)
    {
    handle_data(); // 创建一个子进程去处理数据
    exit(0);
    }
    }
    }
    #include <signal.h>
    #include <stdio.h>
    void handle(int v, siginfo_t* info, void* p)
    {
    printf("recv SIGINT, arg=%d\n", info->si_value.sival_int);
    } int main()
    {
    struct sigaction act;
    act.sa_handler = NULL;
    act.sa_sigaction = handle;
    sigemptyset(&act.sa_mask);
    act.sa_flags = SA_SIGINFO|SA_RESTART;
    act.sa_restorer = NULL; // 注册信号处理函数
    sigaction(SIGINT, &act, NULL); union sigval v;
    v.sival_int = 99;
    // 相当于kill,但是它可以传递一个参数
    sigqueue(getpid(), SIGINT, v); getchar();
    }
    #include <signal.h>
    #include <stdio.h> int main()
    {
    char* p = malloc(100);
    union sigval v;
    // v.sival_int = 98;
    v.sival_ptr = p;
    // 相当于kill,但是它可以传递一个参数
    sigqueue(28360, SIGINT, v); getchar();
    }

6.12 函数命令

signal:注册信号处理函数
kill:发送信号
sigprocmask:设置信号掩码
sigemptyset:清空信号集
sigfillset:设满信号集
sigaddset:往信号集增加一个信号
sigdelset:从信号集删除一个信号
sigismember:判断信号否则在信号集

sigaction:注册更加强大的处理函数
sigqueue:发送信号

abort
alarm
pause

(已转)Linux基础第六章 信号的更多相关文章

  1. Linux 笔记 - 第六章 Linux 磁盘管理

    博客地址:http://www.moonxy.com 一.前言 1.1 硬盘 硬盘一般分为 IDE 硬盘.SCSI 硬盘和 SATA 硬盘.在 Linux 中,IDE 接口的设备被称为 hd,SCSI ...

  2. Linux基础入门 第二章 Linux终端和shell

    Linux终端 进入编辑IP地址命令:vi /etc/sysconfig/network-scripts/ifcfg-eth0 按键“i”:进行编辑 按键“ESC”:退出编辑  按键“:”:输入wq, ...

  3. linux基础-第六单元 用户、群组和权限

    用户及passwd文件 /etc/passwd文件的功能 /etc/passwd文件每个字段的具体含义 shadow文件 /etc/shadow文件的功能 /etc/shadow文件每个字段的具体含义 ...

  4. java基础(六章)

    一.for循环的使用场合 l  while循环--先判断,再循环 while(1.条件表达式){      //2.循环操作      //3.更改循环条件表达式 } l  do-while--先循环 ...

  5. Linux基础入门 第一章:Linux环境搭建——Redhat 6.4图文安装教程

    1.创建新的虚拟机 2.选择自定义 3.选择Workstation 10.0 4.选择稍后安装操作系统 5.选择Red Hat 6 64位 6.对虚拟机命名和选择安装位置 7.选择处理器配置 8.选择 ...

  6. linux基础(六)

    今天我们来看一下Samba服务和nginx服务. Samba服务 1.samba的功能 samba是一个网络服务器,用于Linux和Windows之间共享文件. 2.samba服务的启动.停止.重启  ...

  7. java基础 第六章课后习题

    1.说明循环结构中break语句和continue语句的区别. 在循环结构中 break语句 是结束程序运行. continue语句是结束本句程序  不是结束程序. 2.使用for循环结构实现,从键盘 ...

  8. Linux基础第六课——grep|awk|sort|uniq

    管道符 | 前面的输出作为后面的输入 grep 可以理解为正则表达式 grep [参数] 文件名 -c 打印符合要求的行数 -v 打印不符合要求的行 -n 在输出符合要求的行的同时连同行号一起输出 - ...

  9. C语言基础-第六章

    数组和字符串 1.一维数组 数组当中最简单的数据 声明: 类型说明符 数组名[常量表达式] int a[3];说明a的长度为3,那么给a赋值的语句是:a={1,2,3}; 2.多维数组 2.1 二维数 ...

  10. Linux基础篇六:Linux文件属性和类型

    -:代表文件 s: sorket文件 b:block块设备 (磁盘,光驱等) c:字符设备 l:连接文件 p:管道文件 d:代表目录文件 为了更加区分- (文件的具体类型),系统提供了file命令更加 ...

随机推荐

  1. 媒介查询兼容各种端口的响应式范围取值(移动端、PC端、ipad、移动端侧屏)

    !!!(chrome作者亲测)!!!数据仅供参考 /*ipad*/@media screen and (min-width:760px) and (max-width:1000px) /*移动端*/@ ...

  2. Pytorch及Yolov5环境配置及踩坑

    Pytorch及Yolov5环境配置及踩坑 1.何为Yolov5 yolo是计算机视觉方面用来进行目标检测的一个十分出名的开源框架,我搜不到官方的对此概括性的定义,但实际上也没什么必要,更重要的是会使 ...

  3. PHP全栈开发(八):CSS Ⅸ dispaly & visibility

    display用来设置一个元素如何显示: visibility用来设置一个元素可见还是隐藏. visibility:hidden: 这个语句会使元素在HTML页面中不可见.但是这个元素仍然会占用HTM ...

  4. 【博学谷学习记录】超强总结,用心分享|Linux修改文件权限方法总结

    一.介绍 linux中"一切皆文件".每个文件都设定了针对不同用户的访问权限. 文件权限主要针对以下三种对象: 属主:拥有者 属组:所属的组 其他人:不属于上述两类 二.文件权限 ...

  5. CAN随机度测试

    在车联网安全测试场景中必不可少的就是对于CAN总线的测试,而CAN总线测试中27服务又是一项必不可少的测试! 当我们进入编程会话请求种子后必定会对种子进行破解,当种子随机度足够安全时就会加大破解难度. ...

  6. 小白转行入门STM32----手机蓝牙控制STM32单片机点亮LED

    @ 目录 引言导读 一.通信基础知识 1.1 通信到底传输的是什么? 1.2 比特率和波特率 习题 1.1 双工和单工 习题 1.2 串行和并行 1.3 异同通信和同步通信 习题 二.连接STM32单 ...

  7. java 新特性之 Stream API

    强大的 Stream API 一.Stream API 的概述 Stream到底是什么呢? 是数据渠道,用于操作数据源(集合.数组等)所生成的元素序列. "集合讲的是数据,Stream讲的是 ...

  8. 【YOLOv5】手把手教你使用LabVIEW ONNX Runtime部署 TensorRT加速,实现YOLOv5实时物体识别(含源码)

    前言 上一篇博客给大家介绍了LabVIEW开放神经网络交互工具包[ONNX],今天我们就一起来看一下如何使用LabVIEW开放神经网络交互工具包实现TensorRT加速YOLOv5. 以下是YOLOv ...

  9. 河北首家城商行传统核心业务国产化,TDSQL突破三“最”为秦皇岛银行保驾护航

    11 月 1 日,秦皇岛银行新一代分布式核心系统成功投产并稳定安全运行超过三个月,标志着秦皇岛银行数字化转型应用和服务水平登上了一个新台阶. 这是秦皇岛银行有史以来规模最大.范围最广.难度最高的一次系 ...

  10. CSS动画-transition/animation

    HTML系列: 人人都懂的HTML基础知识-HTML教程(1) HTML元素大全(1) HTML元素大全(2)-表单 CSS系列: CSS基础知识筑基 常用CSS样式属性 CSS选择器大全48式 CS ...