1

信号的概念与分类

问题:按下 Ctrl + C 后,命令行中的前台进程会被中止。为什么???

什么是信号?

  • 信号是一种 “软件中断”, 用来处理异步事件

    • 内核发送信号到某个进程,通知进程事件的发生
    • 时间可能来自硬件,可能来自用户输入,可能来自除零错误
  • 信号是一种类型的进程间通讯方式(一个进程向另一个进程发送信号)

    • A 进程发生事件 T, 向 B 进程发送信号, B 进程执行动作响应事件
    • 进程可以对接收到的不同信号进行不同动作响应(信号 → 处理)

信号的分类

  • 硬件异常

    • 内核检测到硬件错误,发送相应信号给相关进程
  • 终端信号

    • 在终端输入 “特殊字符” 等价于向前台进程组发送相应信号
  • 软件信号

    • 在软件层面(进程代码中)触发的信号(发送给自身或其它进程)

硬件异常信号

信号说明
SIGBUS7总线错误,进程发生了内存访问错误(如,访问的内存地址未对齐)
SIGFPE8算数错误,FPE标识浮点异常
SIGILL9指令错误,进程尝试执行非法指令
SIGSEGV11段错误,进程访问了非法内存区域(如,进程写了受保护的内存)

终端相关信号

  • SIGINT (Ctrl + C)

    • 程序中止信号,用于通知前台进程组终止进程
  • SIGOUIT (Ctrl + )

    • 与 SIGINT 类似,进程收到该信号退出时可产生 coredump 文件
  • SIGTSTP (Ctrl + Z)

    • 进程收到该信号后停止运行(状态发生转换),后续可恢复运行状态
    • 停止进程的运行,进程收到该信号后可以选择处理或忽略

信号发送与处理

软件相关信号

  • 子进程退出:父进程收到 SIGCHLD 信号
  • 父进程退出:子进程可能收到信号(什么信号?)
  • 定时器到期: alarm(). ualarm(). timer_create(), ...
  • 主动发送信号:kill(), raise() ...
  • 。。。

内核与信号

image.png

紫色剑头:

进程通过系统调用申请内核发送信号 (比如 timer)

蓝色箭头:

内核监测到硬件错误,给进程 B 发送信号

黄色箭头:

进程间通讯,进程 A 发送信号给进程 B

信号的默认处理

默认处理方式说明示例
ignore进程丢弃信号不会产生任何影响SIGCHLD, SIGURG(TCP带外数据)
terminate中止进程SIGKILL, SIGHUP
coredump SIGQUIT, SIGILL
stop / continue停止进程执行 / 恢复进程执行SIGSTOP, SIGCONT

知识加油站 : System V vs BSD (仅操作风格上存在不同)

  • System V

    • 也被称为 AT&T System V, 是 Unix 操作系统众多版本中的一支
  • BSD

    • 加州大学伯克利分校开创,Unix 衍生系统,代表由此派生出的各种套件集合
Linux 之所以被称为类 Unix 操作系统 (Unix Like), 部分原因就是 Linux 的操作风格介于上述二者纸间,且不同厂商为了照顾不同的用户,其发行版的操作风格存在差异。
Sytem VBSD
Root脚本位置/etc/init.d/etc/rc.d
默认ShellBshellCshell
文件系统数据/etc/mnttab/etc/mtab
内核位置/UNIX/vmUnix
打印机设备lprlp
字符串函数memcopybcopy
终端初始化设置文件/etc/initab/etc/ttys
终端控制termiotermios

自定义信号处理

  • #include <sys/types.h>
  • #include <signal.h>
  • typedef void (*sighandler_t)(int);
  • sighandler_t signal(int signum. sighandler_t handler); → 默认风格
  • sighandler_t sysv_signal(int signum. sighandler_t handler); → Sytem V 风格
  • sighandler_t bsd_signal(int signum. sighandler_t handler); → BSD 风格

image.png

信号处理示例

#include <signal.h>

void signal_handler(int sig)
{
    printf("sig = %d\n", sig);
}

int main(int argc, char *argv)
{
    int i =0;

    signal(SIGINT, signal_handler);
    // sysv_signal(SIGINT, signal_handler);
    // bsd_signal(SIGINT, signal_handler);

    while (1) {
        sleep(1);
    }

    return 0;
}

自定义信号发送

  • #include <sys/types.h>
  • #include <signal.h>
  • int kill(pid_t pid, int sig);

    • pid > 0,指定信号发送给对应进程
    • pid == 0,指定信号发送给当前进程组中的每一个进程
    • pid < -1, 如 -1000, 那么发送指定信号给 PGID 为 1000 的进程组
  • int raise(int sig); // 发送指定信号给当前进程(自己发自己),信号处理完毕才返回

image.png

标准信号是 Unix 系统的信号,编号范围从 1 - 31。
实时信息是 Linux 独有的信号,编号范围从 31 到 64。

信号发送示例

int main(int argc, char *argv[])
{
    int pid = atoi(argv[1]);
    int sig = atoi(argv[2]);

    printf("send sig(%d) to process(%d)\n", sig, pid);

    kill(pid, sig); // 给指定进程发执行信号
    raice(SIGINT);  // 终止退出

    while (1) {
        printf("while ... \n");
        sleep(1);
    }
}

信号发送与处理实验

信号的接收

//#define _GNU_SOURCE

//#define _XOPEN_SOURCE    600
//#define _POSIX_C_SOURCE  200800L

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>

void signal_handler(int sig)
{
    printf("handler : sig = %d\n", sig);
}

int main(void)
{
    signal(SIGINT, signal_handler);
    // sysv_signal(SIGINT, signal_handler);
    // bsd_signal(SIGINT, signal_handler);

    while (1) {
        sleep(1);
    }

    return 0;
}

输出:

^Chandler : sig = 2    // ctrl + c
^Chandler : sig = 2    // ctrl + c
^Chandler : sig = 2    // ctrl + c
^Chandler : sig = 2    // ctrl + c
^Chandler : sig = 2    // ctrl + c
^\Quit (core dumped)   // ctrl + \

信号的发送

app1.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>

void signal_handler(int sig)
{
    printf("handler : sig = %d\n", sig);
}

int main(void)
{
    signal(40, signal_handler);

    while (1) {
        sleep(1);
    }

    return 0;
}
app2.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>

int main(int argc, char *argv[])
{
    int pid = atoi(argv[1]);
    int sig = atoi(argv[2]);

    printf("send sig(%d) to process (%d)...\n", sig, pid);

    kill(pid, sig);

    raise(SIGINT);

    while (1) {
        printf("while...\n");
        sleep(1);
    }

    return 0;
}
输出:
wu_tiansong@ubuntu-server:~/test$ ./app1 &             // 信号接收
[1] 3871089

wu_tiansong@ubuntu-server:~/test$ ./app2 3871089 40    // 信号发送
send sig(40) to process (3871089)...
handler : sig = 40

思考

三种自定义信号处理函数有什么区别?


TianSong
734 声望138 粉丝

阿里山神木的种子在3000年前已经埋下,今天不过是看到当年注定的结果,为了未来的自己,今天就埋下一颗好种子吧