建议收藏!信号和信号量,你真的会了吗?

0 阅读5分钟

一句话区别:

  • 信号(Signal):通知进程"发生了一件事"(异步通知机制)。
  • 信号量(Semaphore):控制多个线程/进程"谁可以使用资源"(同步互斥机制)。

一、信号(Signal)

1. 什么是信号?

信号(Signal)是 Linux 内核向进程发送的一种异步通知机制

它用于通知进程:

  • 用户按下 Ctrl+C
  • 子进程退出
  • 定时器超时
  • 非法访问内存
  • 管道断开

可以理解为:

信号 = 中断 + 通知

例如:

Ctrl + C
    │
    ▼
Linux Kernel
    │
发送 SIGINT
    │
    ▼
进程收到信号

Kernel(内核)是计算机操作系统最核心的程序,主要负责管理硬件资源、充当软件与硬件的桥梁以及控制系统安全

2. 信号的特点

  • 异步发生
  • 不携带大量数据(普通信号只有编号)
  • 可以打断程序当前执行流程
  • 每个信号都有编号
  • 大多数信号可以被捕获(signal/sigaction)

3. 常见信号

信号编号含义
SIGHUP1终端断开
SIGINT2Ctrl+C
SIGQUIT3Ctrl+\
SIGILL4非法指令
SIGABRT6abort():非正常退出
SIGFPE8除零异常
SIGKILL9强制结束(不能捕获)
SIGSEGV11段错误
SIGPIPE13管道断开
SIGALRM14alarm() 定时器
SIGTERM15正常结束
SIGCHLD17子进程结束
SIGCONT18继续运行
SIGSTOP19暂停(不能捕获)
SIGTSTP20Ctrl+Z

查看:

kill -l
flowchart TD
    A[程序运行] --> B{收到什么信号?}

    B -->|Ctrl+C| C[SIGINT<br/>退出]
    B -->|Ctrl+\\| D[SIGQUIT<br/>退出+Core]
    B -->|Ctrl+Z| E[SIGTSTP<br/>暂停]
    B -->|kill| F[SIGTERM<br/>正常退出]
    B -->|kill -9| G[SIGKILL<br/>强制退出]
    B -->|kill -STOP| H[SIGSTOP<br/>强制暂停]
    H -->|kill -CONT| I[SIGCONT<br/>继续]
    E -->|fg/bg| I

    B -->|除0| J[SIGFPE]
    B -->|非法内存| K[SIGSEGV]
    B -->|非法指令| L[SIGILL]
    B -->|abort| M[SIGABRT]
    B -->|alarm到时| N[SIGALRM]
    B -->|终端关闭| O[SIGHUP]
    B -->|子进程结束| P[SIGCHLD]
    B -->|写入断开的管道/Socket| Q[SIGPIPE]

4. 信号产生方式

(1)键盘产生

例如:

Ctrl+C

发送:

SIGINT
用户
 │
 │ Ctrl+C
 ▼
终端
 │
 ▼
Kernel
 │
 ▼
进程

(2)kill命令

例如:

kill -15 pid

或者

kill -9 pid

(3)系统调用

例如:

kill(pid,SIGUSR1);

或者

raise(SIGINT); 

当前进程主动给自己发送一个 SIGINT 信号。

(4)硬件异常

例如:

int a=1/0;

产生:

SIGFPE

访问非法地址:

SIGSEGV

(5)软件条件

例如:

alarm(5);

五秒后:

SIGALRM

5. 信号处理方式

收到信号以后,有三种处理方式:

graph TD
    A[收到信号] --> B{查询信号处理动作}
    B -->|SIG_DFL| C[默认处理<br/>Default]
    B -->|SIG_IGN| D[忽略<br/>Ignore]
    B -->|User Handler| E[用户捕获<br/>Catch]

默认处理

例如:

SIGTERM

默认:

退出程序

忽略

例如:

signal(SIGINT,SIG_IGN);

按 Ctrl+C:

没有反应

捕获

例如:

void handler(int sig)
{
    printf("收到信号%d\n",sig);
}

signal(SIGINT,handler);

收到:

SIGINT

执行:

handler()

6. signal()

#include <signal.h>

signal(SIGINT,handler);

作用:

安装一个信号处理函数。

流程:

flowchart TD
    A[收到 SIGINT] --> B[Kernel: 检查处理动作]
    B --> C{是否为自定义 handler?}
    C -- 是 --> D[Kernel: 保存用户态上下文]
    D --> E[Kernel: 切换到信号栈]
    E --> F[执行 handler]
    F --> G[执行 sigreturn 系统调用]
    G --> H[Kernel: 恢复用户态上下文]
    H --> I[返回程序继续执行]
    C -- 否 --> J[执行默认动作/忽略]

7. sigaction()

推荐使用。

比 signal() 更安全。

struct sigaction act;

act.sa_handler=handler;

sigaction(SIGINT,&act,NULL);

优势:

  • 更稳定
  • 可以设置屏蔽信号
  • 可以设置更多属性

8. 信号屏蔽

有时候:

不希望现在处理信号。

可以:

先屏蔽
以后再处理

流程:

flowchart LR
A[收到 SIGINT]
B[加入Pending]
C[解除屏蔽]
D[执行handler]

A --> B --> C --> D

常见函数:

sigprocmask()

**<font style="color:rgb(34, 34, 34);">sigprocmask</font>** 是用于检查或修改当前线程信号屏蔽字(Signal Mask)的系统调用,其核心作用是控制哪些信号在当前时刻被“阻塞”(即暂时不递送给进程),从而实现对信号处理的精确时序控制。

9. 未决信号(Pending)

收到信号:

并不会马上执行

可能:

Pending

等待解除屏蔽。

流程:

产生信号
    │
    ▼
Pending
    │
解除Mask
    ▼
执行Handler

10. 信号生命周期

flowchart LR
A[产生 Signal]
B[Pending]
C[是否屏蔽?]

A --> B
B --> C
C -- 是 --> B
C -- 否 --> D[Delivery]
D --> E[Handler]

二、信号量(Semaphore)

1. 什么是信号量?

信号量(Semaphore)用于:

同步多个线程/进程对共享资源的访问。

它本质上就是:

一个整数

谁拿到资源:

--

释放资源:

++

2. 为什么需要信号量?

例如:

厕所只有3个坑位。

来了10个人。

坑位:3

A ✔
B ✔
C ✔

D 等待
E 等待
...

只有出来一个:

++

等待的人:

--

进入。

3. 信号量分类

二值信号量

只有:

0
1

作用:

类似:

Mutex

计数信号量

例如:

5

表示:

有5个资源

4. P操作 和 V操作

经典名称:

P()

申请资源。

sem_wait()
value--

如果:

value<0

阻塞。

V()

释放资源。

sem_post()
value++

唤醒等待线程。

图解

假设:

Semaphore=3
sequenceDiagram
    participant S as Semaphore (初始值=2)
    participant A as 线程A
    participant B as 线程B
    participant C as 线程C
    participant D as 线程D

    Note over S: 初始值 = 2

    A->>S: P()
    S-->>S: 值: 2 → 1
    Note right of A: ✅ 获取成功,继续执行

    B->>S: P()
    S-->>S: 值: 1 → 0
    Note right of B: ✅ 获取成功,继续执行

    C->>S: P()
    S-->>S: 值: 0 → -1
    Note right of C: ⏳ 信号量 < 0,进入等待队列

    D->>S: P()
    S-->>S: 值: -1 → -2
    Note right of D: ⏳ 信号量 < 0,进入等待队列

    Note over S: 当前状态:2个线程运行,2个线程等待

线程A:

V()

Semaphore=0

↓

线程D 被唤醒

5. Linux POSIX信号量

头文件:

#include <semaphore.h>

创建:

sem_t sem;

初始化:

sem_init(&sem,0,3);

表示:

三个资源

等待:

sem_wait(&sem);

相当于:

P()

释放:

sem_post(&sem);

相当于:

V()

销毁:

sem_destroy(&sem);

6. 工作流程

flowchart TD
A[线程请求资源]
B{Semaphore > 0 ?}
C[sem--]
D[进入临界区]
E[阻塞等待]
F[sem_post]
G[sem++]
H[唤醒等待线程]

A --> B
B -- Yes --> C
C --> D
D --> F
F --> G
G --> H
B -- No --> E
H --> D

7. 信号量实现线程同步

例如:

生产者:

生产一个数据
↓

sem_post()

消费者:

sem_wait()

↓

消费数据

流程:

flowchart LR
Producer --> Buffer
Producer --> sem_post
sem_post --> Semaphore
Consumer --> sem_wait
sem_wait --> Buffer

8. 信号量实现互斥

初始化:

Semaphore=1
线程A

wait()

↓

进入临界区

此时:

Semaphore=0

线程B:

wait()

↓

阻塞

线程A:

post()

线程B:

继续执行

9. 信号量 VS 互斥锁

对比项信号量互斥锁
本质资源计数器加锁对象
可以大于1只能锁/解锁
可控制资源数
谁释放任意线程必须加锁线程
是否适合同步一般
是否适合互斥✔(最佳)

三、信号 与 信号量 区别

项目信号 Signal信号量 Semaphore
中文信号信号量
本质异步通知同步机制
谁发送Kernel程序员
是否携带资源
是否控制资源
是否会中断程序
是否用于线程同步
是否用于进程通信

四、整体关系图

graph TD

A[Linux 进程/线程同步与通信]

A --> B[Signal 信号]
A --> C[Semaphore 信号量]

%% Signal
B --> B1[本质]
B1 --> B11[异步通知]
B1 --> B12[软件中断]

B --> B2[产生方式]
B2 --> B21[kill]
B2 --> B22[raise]
B2 --> B23[alarm]
B2 --> B24[Ctrl+C]

B --> B3[分类]
B3 --> B31[不可靠信号]
B3 --> B32[可靠信号]

B --> B4[处理方式]
B4 --> B41[默认]
B4 --> B42[忽略]
B4 --> B43[捕获]

B --> B5[常用API]
B5 --> B51[signal]
B5 --> B52[sigaction]
B5 --> B53[kill]
B5 --> B54[sigprocmask]

%% Semaphore
C --> C1[本质]
C1 --> C11[同步]
C1 --> C12[互斥]
C1 --> C13[资源计数]

C --> C2[类型]
C2 --> C21[无名信号量]
C2 --> C22[有名信号量]

C --> C3[P/V操作]
C3 --> C31[sem_wait]
C3 --> C32[sem_post]
C3 --> C33[sem_trywait]

C --> C4[常用API]
C4 --> C41[sem_init]
C4 --> C42[sem_destroy]
C4 --> C43[sem_open]
C4 --> C44[sem_close]
C4 --> C45[sem_unlink]

C --> C5[应用]
C5 --> C51[线程同步]
C5 --> C52[进程同步]
C5 --> C53[生产者消费者]
C5 --> C54[资源池]

五、面试高频考点

信号(Signal)

  • 什么是信号?有哪些特点?
  • 常见信号有哪些?(SIGINT、SIGKILL、SIGTERM、SIGCHLD、SIGSEGV)
  • signal() 与 sigaction() 有什么区别?
  • SIGKILL 为什么不能捕获?
  • SIGSTOP 为什么不能忽略?
  • 什么是信号屏蔽(Mask)?
  • 什么是未决信号(Pending)?
  • kill()、raise()、alarm() 有什么作用?

信号量(Semaphore)

  • 什么是 P 操作和 V 操作?
  • sem_wait() 和 sem_post() 的作用?
  • 二值信号量和计数信号量有什么区别?
  • 信号量如何实现线程同步?
  • 信号量如何实现互斥?
  • 信号量和互斥锁有什么区别?
  • 生产者消费者模型为什么适合使用信号量?