cen's blog cen's blog
首页
  • 编程文章

    • Markdown使用
    • C语言程序的运行
  • 学习笔记

    • C++学习
    • C++数据结构
    • MySQL
    • Linux
    • 网络编程
算法
  • Git
  • ProtoBuf
  • 分类
  • 标签
  • 归档
关于
GitHub (opens new window)

cen

十年饮冰,难凉热血
首页
  • 编程文章

    • Markdown使用
    • C语言程序的运行
  • 学习笔记

    • C++学习
    • C++数据结构
    • MySQL
    • Linux
    • 网络编程
算法
  • Git
  • ProtoBuf
  • 分类
  • 标签
  • 归档
关于
GitHub (opens new window)
  • Linux环境搭建
  • 基本指令
  • 权限
  • Linux基础开发工具
  • 进程概念
  • 进程控制
  • 基础IO流
  • 动态库和静态库
  • 进程通信
  • 进程信号
    • 信号基础
    • 产生信号
      • 通过系统函数
      • 核心转储
    • 阻塞信号
      • 信号其他相关常见概念
      • 在内核中的表示
      • 信号阻塞和解除阻塞
    • 捕捉信号
      • 内核空间和用户空间
      • 内核态和用户态
  • 多线程
  • 线程安全
  • 生产者消费者
  • 线程池
  • 高级IO
  • 多路转接epoll
  • Reactor
  • Linux
cen
2025-04-14
目录

进程信号

# 信号基础

在 Linux 系统中,信号(Signal)是一种软件中断,它提供了一种机制,允许进程之间、进程和内核之间传递异步事件通知。这些事件可能是用户从终端输入的一个命令(如 Ctrl+C),也可能是系统检测到的一个硬件错误(如内存访问违规)。信号机制为 Linux 系统提供了丰富的进程间通信手段,并且是实现多任务并发执行和进程管理的重要基础。

使用kill -l命令列出所有信号

[cen@VM-4-9-opencloudos lesson03-进程通信]$ kill -l
 1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL       5) SIGTRAP
 6) SIGABRT      7) SIGBUS       8) SIGFPE       9) SIGKILL     10) SIGUSR1
11) SIGSEGV     12) SIGUSR2     13) SIGPIPE     14) SIGALRM     15) SIGTERM
16) SIGSTKFLT   17) SIGCHLD     18) SIGCONT     19) SIGSTOP     20) SIGTSTP
21) SIGTTIN     22) SIGTTOU     23) SIGURG      24) SIGXCPU     25) SIGXFSZ
26) SIGVTALRM   27) SIGPROF     28) SIGWINCH    29) SIGIO       30) SIGPWR
31) SIGSYS      34) SIGRTMIN    35) SIGRTMIN+1  36) SIGRTMIN+2  37) SIGRTMIN+3
38) SIGRTMIN+4  39) SIGRTMIN+5  40) SIGRTMIN+6  41) SIGRTMIN+7  42) SIGRTMIN+8
43) SIGRTMIN+9  44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9  56) SIGRTMAX-8  57) SIGRTMAX-7
58) SIGRTMAX-6  59) SIGRTMAX-5  60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2
63) SIGRTMAX-1  64) SIGRTMAX
# [1, 31]:普通信号
# [32, 64]:实时信号
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

信号的记录

进程接收到信号时可能有更重要的代码,所以信号不一定被立即处理,并且进程要有对信号的保存能力

【保存在 task_struct 中,用位图来保存,其中比特位的位置:代表信号编号,比特位的内容:是否接收到信号】

例如0000 0000 0000 0000 0000 0000 0000 0000,接收 6 号信号时,修改为0000 0000 0000 0000 0000 0000 0010 0000

信号的产生

  1. 终端按键产生信号:Ctrl + C
  2. 调用系统函数发送信号,kill()
  3. 软件/硬件产生信号,alarm()函数可以设置定时器,当定时器超时时,会向进程发送 SIGALRM 信号;硬件检测到除 0 错误,产生 SIGFPE 信号

当某个事件发生时,系统会为该事件生成一个信号。一个进程收到信号,本质就是该进程内的信号位图被修改了,也就是该进程的数据被修改了,而只有操作系统才有资格修改进程的数据,因为操作系统是进程的管理者。也就是说,信号的产生本质上就是操作系统直接去修改目标进程的 task_struct 中的信号位图,由 OS 完成(调用相关系统接口)

信号的处理

进程在处理信号时有三种动作:默认、自定义(也称捕捉一个信号)和忽略。

  1. 忽略处理:

    大多数信号都可使用这种方式进行处理,但有两种信号却不能被忽略。 它们是:SIGKILL 和 SIGSTOP。这两种信号不能被忽略的原因是:它们向超级用户提供一种使进程终止或停止的可靠方法。另外,如果忽略某些由硬件异常产生的信号(例如非法存储访问或除以 0),则进程的行为是未定义的。为了忽略一个信号,进程可以使用 signal()函数或更现代的 sigaction()函数,并指定一个信号处理程序为 SIG_IGN。

  2. 默认处理:

查看各个信号默认的处理动作man 7 signal:

The entries in the "Action" column of the table below specify the default disposition for each signal, as follows:
Term   Default action is to terminate the process.
Ign    Default action is to ignore the signal.
Core   Default action is to terminate the process and dump core (see core(5)).
Stop   Default action is to stop the process.
Cont   Default action is to continue the process if it is currently stopped.
1
2
3
4
5
6

其中 Core 表示在进程当前工作目录的 core 文件中复制了该进程的存储图像(该文件名为 core)

  1. 自定义处理:

捕捉一个信号,进程使用 signal()或 sigaction()函数,并指定一个信号处理程序函数,由用户自定义。当信号被触发时,操作系统会暂停进程的正常执行,调用相应的处理程序,然后恢复进程的执行。

# 产生信号

# 通过系统函数

  1. kill 命令
#include <signal.h>
int kill(pid_t pid, int sig);
1
2

kill 函数用于向进程 ID 为 pid 的进程发送 sig 号信号,如果信号发送成功,则返回 0,否则返回-1

示例:

mysignal

#include <iostream>
#include <unistd.h>
#include <sys/ipc.h>
#include <signal.h>

void Usage() {
    std::cout << "Usage: ./Mysignal pid signo" << std::endl;
}

int main(int argc, char *argv[]) {
    if(argc != 3) {
        Usage();
        exit(1);
    }
    pid_t pid = atoi(argv[1]);
    int signo = atoi(argv[2]);
    int n = kill(pid, signo);
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

test.cpp

#include <iostream>
#include <unistd.h>
#include <sys/ipc.h>

int main(int argc, char *argv[]) {
    while(true) {
        std::cout << "一段运行的进程" << getpid() << ":print Hello World!" << std::endl;
        sleep(2);
    }
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11

运行 窗口 1:

[cen@VM-4-9-opencloudos mysignal]$ make
g++ -o test test.cpp -std=c++11 -g
g++ -o Mysignal mysignal.cpp -std=c++11
[cen@VM-4-9-opencloudos mysignal]$ ./test
一段运行的进程2018165:print Hello World!
一段运行的进程2018165:print Hello World!
一段运行的进程2018165:print Hello World!
一段运行的进程2018165:print Hello World!
一段运行的进程2018165:print Hello World!
一段运行的进程2018165:print Hello World!
一段运行的进程2018165:print Hello World!
Killed
1
2
3
4
5
6
7
8
9
10
11
12

窗口 2:

[cen@VM-4-9-opencloudos mysignal]$ ./Mysignal
Usage: ./Mysignal pid signo
[cen@VM-4-9-opencloudos mysignal]$ ./Mysignal 2018165 9
[cen@VM-4-9-opencloudos mysignal]$
1
2
3
4
  1. raise 函数

raise 函数可以给当前进程发送指定信号,即自己给自己发送信号,raise 函数的函数原型如下:

int raise(int sig);
1
  1. abort 函数

abort 函数可以给当前进程发送 SIGABRT 信号,使得当前进程异常终止,abort 函数的函数原型如下:

void abort(void);
1

# 核心转储

Action 为 core 时:Core Default action is to terminate the process and dump core[核心转储] (see core(5)).

核心转储是默认被关掉的,我们可以通过使用 ulimit -a 命令查看当前资源限制的设定。 我们可以通过 ulimit -c size 命令来设置 core 文件的大小,从而打开核心存储。

[cen@VM-4-9-opencloudos lesson03-进程通信]$ ulimit -a
real-time non-blocking time  (microseconds, -R) unlimited
core file size              (blocks, -c) unlimited
data seg size               (kbytes, -d) unlimited
scheduling priority                 (-e) 0
file size                   (blocks, -f) unlimited
pending signals                     (-i) 6680
max locked memory           (kbytes, -l) 8192
max memory size             (kbytes, -m) unlimited
open files                          (-n) 524288
pipe size                (512 bytes, -p) 8
POSIX message queues         (bytes, -q) 819200
real-time priority                  (-r) 0
stack size                  (kbytes, -s) 8192
cpu time                   (seconds, -t) unlimited
max user processes                  (-u) 6680
virtual memory              (kbytes, -v) unlimited
file locks                          (-x) unlimited
[cen@VM-4-9-opencloudos lesson03-进程通信]$ ulimit -c 1024
[cen@VM-4-9-opencloudos lesson03-进程通信]$ ulimit -a
real-time non-blocking time  (microseconds, -R) unlimited
core file size              (blocks, -c) 1024
data seg size               (kbytes, -d) unlimited
scheduling priority                 (-e) 0
file size                   (blocks, -f) unlimited
pending signals                     (-i) 6680
max locked memory           (kbytes, -l) 8192
max memory size             (kbytes, -m) unlimited
open files                          (-n) 524288
pipe size                (512 bytes, -p) 8
POSIX message queues         (bytes, -q) 819200
real-time priority                  (-r) 0
stack size                  (kbytes, -s) 8192
cpu time                   (seconds, -t) unlimited
max user processes                  (-u) 6680
virtual memory              (kbytes, -v) unlimited
file locks                          (-x) unlimited
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

使用:

当我们的程序在运行过程中崩溃了,我们一般会通过调试来进行逐步查找程序崩溃的原因。而在某些特殊情况下,我们会用到核心转储,核心转储指的是操作系统在进程收到某些信号而终止运行时,将该进程地址空间的内容以及有关进程状态的其他信息转而存储到一个磁盘文件当中,这个磁盘文件也叫做核心转储文件,一般命名为 core.pid,方便问题的定位。

[cen@VM-4-9-opencloudos mysignal]$ make
g++ -o Mysignal mysignal.cpp -std=c++11
[cen@VM-4-9-opencloudos mysignal]$ ./Mysignal
3728615 Hello World!
3728615 Hello World!
3728615 Hello World!
^\Quit (core dumped)
# Ctrl + \ : SIGQUIT      P1990      Core    Quit from keyboard
[cen@VM-4-9-opencloudos mysignal]$ ll
total 32
-rw-r--r-- 1 cen cen   340 Apr 20 17:44 Makefile
-rwxr-xr-x 1 cen cen 17088 Apr 20 18:01 Mysignal
-rw-r--r-- 1 cen cen   549 Apr 20 17:48 mysignal.cpp
-rw-r--r-- 1 cen cen   250 Apr 16 20:15 test.cpp
[cen@VM-4-9-opencloudos mysignal]$ coredumpctl
Hint: You are currently not seeing messages from other users and the system.
      Users in groups 'adm', 'systemd-journal', 'wheel' can see all messages.
      Pass -q to turn off this notice.
TIME                            PID  UID  GID SIG     COREFILE EXE                                                                                 >                              >
Sun 2025-04-20 18:01:51 CST 3728615 1001 1001 SIGQUIT present  /home/cen/Linux/lesson03-进程通信/mysignal/Mysignal
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 阻塞信号

# 信号其他相关常见概念

  1. 实际执行信号的处理动作,称为信号递达(Delivery)。
  2. 信号从产生到递达之间的状态,称为信号未决(Pending)。
  3. 进程可以选择阻塞(Block)某个信号。
  4. 被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作。
  5. 阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后的一种处理动作。

# 在内核中的表示

每个进程的 task_struct 有如下信号相关数据结构:

struct task_struct {
    // 待处理信号集合(私有挂起信号)
    struct sigpending pending;

    // 信号处理函数表及共享信息
    struct signal_struct *signal;

    // 当前阻塞的信号集
    sigset_t blocked;

    // 实际处理函数表(每个信号一个入口)
    struct sighand_struct *sighand;

    // 挂起时保存的信号掩码
    sigset_t saved_sigmask;
};

struct sighand_struct {
    spinlock_t siglock;                    // 保护信号结构的自旋锁
    struct k_sigaction action[_NSIG];      // 每个信号的处理动作
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
  • 信号集(sigset_t):位图结构,通常 64 或 128 位(取决于架构),每个 bit 代表一个信号(1=有效/阻塞,0=无效/不阻塞)
  • 两张位图pending和block和一个函数指针数组handler表:
  • unsigned int pending 比特位的位置表示信号编号, 比特位的内容用于保存当前进程已经接收到但尚未处理的信号。当信号产生时,内核会在进程控制块(PCB)中的 pending 表中设置相应的位,表示该信号已经产生但尚未处理。这样,即使信号的产生和处理之间存在一定的时间差,操作系统也能确保不会丢失任何信号
  • unsigned int block 比特位的位置表示信号编号, 比特位的内容表示是否阻塞对应信号
  • 函数指针数组 handler 表,数组的下标代表某一个信号,数组的内容代表该信号递达时的处理动作,处理动作包括默认、忽略以及自定义。

# 信号阻塞和解除阻塞

调用函数 sigprocmask 可以读取或更改进程的信号屏蔽字(阻塞信号集),即操作 block 表

#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
// 返回值:若成功则为0,若出错则为-1
1
2
3

sigprocmask 函数的第三个参数(oset)是一个指向 sigset_t 的指针,是输出型参数,用于保存调用 sigprocmask 之前的信号屏蔽字的状态。如果 oset 不为 NULL,则当前的信号屏蔽字会被保存到*oset 中;如果 set 是个空指针,则不改变该进程的信号屏蔽字, how 的值也无意义。

how 参数:

  • SIG_BLOCK:set 参数中指定的所有信号都会被添加到当前阻塞的信号集中,即使这些信号已经存在于当前的信号屏蔽字中。
  • SIG_UNBLOCK:从当前阻塞的信号集中移除 set 参数所指向的信号集中的所有信号。
  • SIG_SETMASK:将当前阻塞的信号集(即当前进程的信号屏蔽字)完全替换为 set 参数所指向的信号集。

# 捕捉信号

# 内核空间和用户空间

信号在产生的时候不会被立即处理而是在合适的时候(从内核态到用户态的时候进行处理),用户态为了访问内核或硬件资源,必须通过系统调用完成访问,每一个进程都有自己的进程地址空间,该进程地址空间由内核空间和用户空间组成:

  • 用户所写的代码和数据位于用户空间,通过用户级页表与物理内存之间建立映射关系。
  • 内核空间存储的实际上是操作系统代码和数据,通过内核级页表与物理内存之间建立映射关系。
  • 内核级页表是一个全局的页表,它用来维护操作系统的代码与进程之间的关系。因此,在每个进程的进程地址空间中,用户空间是属于当前进程的,每个进程看到的代码和数据是完全不同的,但内核空间所存放的都是操作系统的代码和数据,所有进程看到的都是一样的内容。

# 内核态和用户态

内核态与用户态:

内核态通常用来执行操作系统的代码,是一种权限非常高的状态。 用户态是一种用来执行普通用户代码的状态,是一种受监管的普通状态。 进程收到信号之后,并不是立即处理信号,而是在合适的时候,这里所说的合适的时候实际上就是指,从内核态切换回用户态的时候。

  1. 如果捕捉信号是忽略处理:
  • 当用户态的程序(main()函数)执行过程中遇到中断、异常或系统调用时,进程会进入内核态进行处理,内核通过处理异常或系统调用后,可能会发送一个信号给用户态,接着内核调用 do_signal()函数来处理信号。
  • 在内核处理完异常并准备返回用户态之前,内核会检查当前进程中是否有可以递达的信号。这些信号可能是之前被阻塞(阻塞在 pending 位图中)的,或者是在内核处理异常期间产生的。先处理当前进程中可以递达的信号(即尚未被阻塞或忽略的信号),待 do_signal()函数执行完毕。
  • 进程再次返回到用户态,继续执行之前被中断的指令。
  1. 如果捕捉信号是默认处理:

  2. 如果捕捉信号是自定义:

  • 当用户态的程序(main()函数)执行过程中遇到中断、异常或系统调用时,进程会进入内核态进行处理。
  • 内核通过处理异常或系统调用后,可能会发送一个信号给用户态。内核会调用 do_signal()函数来处理信号。由于信号的处理动作是自定义的,又从内核态切换到用户态。执行完用户自定义的处理函数后,用户态会执行 sigreturn(用来通知内核信号自定义处理已完成)来进入内核态,以执行信号处理函数。
  • 信号处理函数执行完毕后,进程会再次返回到用户态,继续执行之前被中断的指令。
上次更新: 2026/04/14, 21:59:44
进程通信
多线程

← 进程通信 多线程→

最近更新
01
位运算
04-21
02
单例模式
04-14
03
分治和归并
04-14
更多文章>
Theme by Vdoing | Copyright © 2024-2026 京ICP备2020044002号-3 京公网安备11010502056119号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式