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

最新文章

  1. .NET不可变集合已经正式发布
  2. zabbix_proxy安装[yum mysql5.6]
  3. PHP魔术方法在框架中的应用
  4. SQLServer多表连接查询
  5. nuget.exe the application could not be started
  6. python 第三方模块 转 https://github.com/masterpy/zwpy_lst
  7. 学习JQuery中文文档之map()函数和get()函数
  8. iOS开发——动画编程Swift篇&amp;(一)UIView基本动画
  9. Tableau学习笔记之四
  10. HDU 5754 Life Winner Bo (找规律and博弈)
  11. append与after
  12. SLF4J warning or error messages and their meanings(转)
  13. Ubuntu环境下SSH的安装及使用
  14. Jmeter4.0版本实现背景色切换
  15. Python四步实现决策树ID3算法,参考机器学习实战
  16. Java多线程:向线程传递参数的三种方法
  17. CSS之实现垂直时间线展示相关内容效果
  18. NSJSONSerialization 反序列化失败 NSCocoaErrorDomain Code=3840
  19. centos7/RHEL7下快速搭建DNS域名解析服务器
  20. Linux 实时查看tomcat 日志--less命令

热门文章

  1. SpringBoot(一) - SpringBoot 初识
  2. vue路由守卫用于登录验证权限拦截
  3. map集合类型/实体类类型的参数
  4. Linux基础_6_文本编辑
  5. JavaScript中通过按回车键进行数据的录入
  6. Django之同时新增数据到两个数据库表与同时返回两个表的数据(插拔式)
  7. postman一些你不常用的实用技巧,竟然还能这么玩
  8. 前端无法渲染CSS文件
  9. Java单例模式,看这一篇就够了
  10. Datatable 数据源