void func(int arg)
{
printf("这里是信号响应函数 %d \n" , arg );
}
int main(int argc, char const *argv[])
{
int pid = fork();
int i = 0 ;
if (pid > 0 )
{
printf("这里是父进程 \n " );
sleep(3);
printf("猎杀时间到了..。 \n " );
sleep(1);
kill(pid , 4 );
}
else if (pid == 0 )
{
signal( 4 , func);
while(1)
{
printf("这里是子进程 : %d \n ", i++ );
sleep(1);
}
}
return 0;
}
没关系,这段代码正适合用来学 Linux 进程 + 信号 ,我一步一步、按小白视角 给你拆开讲 👍
你现在只需要记住一句话:
父进程用
kill给子进程发信号,子进程用signal接收并处理这个信号
一、这段程序在"干什么"(一句话版)
-
程序一开始 fork 出一个子进程
-
子进程:每秒打印一次"我是子进程"
-
父进程 :等 4 秒后,给子进程发一个 信号 4
-
子进程收到信号 4 后,执行 func 函数
二、先看最简单的:信号响应函数
void func(int arg)
{
printf("这里是信号响应函数 %d \n" , arg );
}
这是干嘛的?
-
这是"信号处理函数"
-
当进程 收到某个信号 时,系统会自动调用它
-
arg就是 信号编号
👉 比如收到信号 4
输出就是:
这里是信号响应函数 4
三、fork():一行代码,变两个进程(重点)
int pid = fork();
这行代码执行后:
| 进程 | pid 的值 |
|---|---|
| 父进程 | 子进程的 PID(>0) |
| 子进程 | 0 |
所以才有下面这个判断:
if (pid > 0) // 父进程
else if (pid==0) // 子进程
四、父进程在干什么(像"杀手")
if (pid > 0 )
{
printf("这里是父进程 \n");
sleep(3);
printf("猎杀时间到了..\n");
sleep(1);
kill(pid , 4 );
}
按时间顺序解释:
1️⃣ 父进程打印:
这里是父进程
2️⃣ 睡 3 秒(啥也不干)
3️⃣ 再打印:
猎杀时间到了..
4️⃣ 再睡 1 秒
5️⃣ 关键一句:
kill(pid, 4);
意思是:
给 pid 这个进程(子进程) 发送 4 号信号
⚠️ 注意:
-
kill不一定杀进程 -
它真正意思是:发信号
五、子进程在干什么(像"被监控的人")
else if (pid == 0 )
{
signal(4 , func);
这一句是重点!!!
告诉系统:如果我收到 4 号信号,就调用 func()
📌 类似中文:
"系统啊,以后谁给我发 4 号信号,你别杀我,帮我执行 func"
子进程的循环
while(1)
{
printf("这里是子进程 : %d \n ", i++ );
sleep(1);
}
意思是:
-
死循环
-
每秒打印一次
-
i++只是计数用
输出类似:
这里是子进程 : 0
这里是子进程 : 1
这里是子进程 : 2
...
六、整个程序"时间线动画版"
⏱ 程序开始
父进程:这里是父进程
子进程:这里是子进程 : 0
子进程:这里是子进程 : 1
子进程:这里是子进程 : 2
⏱ 大约 4 秒后
父进程:
猎杀时间到了..
然后:
kill(pid, 4);
⏱ 子进程收到信号 4
系统自动帮你调用:
func(4);
于是子进程输出:
这里是信号响应函数 4
⚠️ 注意:
-
子进程 不会停
-
执行完
func后,继续 while 循环
七、为什么子进程没被杀死?
因为你写了:
signal(4, func);
如果你 没写这句:
signal(4, func);
那:
kill(pid, 4);
👉 子进程会直接被系统终止
八、一句话总结(给小白版)
1️⃣
fork()产生父子进程2️⃣ 父进程用
kill()发信号3️⃣ 子进程用
signal()接信号4️⃣ 信号来了,自动执行回调函数
#include <stdio.h>
#include <sys/types.h>
#include <signal.h>
void func(int arg)
{
printf("这里是信号响应函数 %d \n" , arg );
}
int main(int argc, char const *argv[])
{
// 设置信号响应的函数
signal( 3 , func );
signal( 4 , func );
// 初始化信号集
sigset_t set ;
sigemptyset( &set ); // 将信号集清空
sigaddset( &set , 3 ); // 将指定的一个信号添加到信号集中
sigaddset( &set , 4 ); // 将指定的一个信号添加到信号集中
// 设置阻塞信号集中的信号
sigprocmask(SIG_SETMASK , &set , NULL ); // 把信号集中的信号设置为阻塞状态
// 给自己发送 3.4 号信号
raise(3);
raise(4);
sleep(2);
// 解除阻塞
sigprocmask(SIG_UNBLOCK , &set , NULL ); // 把信号集中的信号设置为阻塞状态
return 0;
}
好,这段代码比上一段更"系统级"一点 ,但思路其实很清晰。
我继续小白模式 + 类比,你只要跟着走就行 👍
一句话先给你结论
程序先把 3、4 号信号"拦在门外"(阻塞),
再给自己发信号,
最后打开门(解除阻塞),
之前积压的信号一次性被处理。
一、先看你写的信号处理函数(老朋友)
void func(int arg)
{
printf("这里是信号响应函数 %d \n" , arg );
}
意思还是:
收到某个信号 → 自动执行 func → 打印信号编号
二、signal:告诉系统"信号来了我怎么处理"
signal(3, func);
signal(4, func);
含义:
-
收到 3 号信号 → 执行
func(3) -
收到 4 号信号 → 执行
func(4)
⚠️ 注意一句话(很重要):
signal 只是"怎么处理",不等于"什么时候处理"
三、sigset_t:信号口袋(核心概念)
sigset_t set;
你可以把它想象成:
🧺 一个"信号名单",
里面写着:哪些信号要被特殊对待
1️⃣ 清空信号集
sigemptyset(&set);
意思:
把这个"信号口袋"清空,一个信号都没有
2️⃣ 把 3、4 号信号放进去
sigaddset(&set, 3);
sigaddset(&set, 4);
现在这个 set 里有:
{ 3号信号, 4号信号 }
四、sigprocmask:真正的"拦截器"
🚫 阻塞信号
sigprocmask(SIG_SETMASK , &set , NULL);
这句是全程序最关键的一句。
人话翻译:
从现在开始:
如果来了 3 或 4 号信号
先别处理
放进系统的"待处理队列"
📌 类比:
-
signal():你告诉保安"抓到人怎么处理"
-
sigprocmask():你告诉保安"先别让人进来"
五、raise:给自己发信号
raise(3);
raise(4);
意思是:
我给我自己发 3、4 号信号
⚠️ 但注意:
👉 现在 3、4 号信号是"阻塞的"
所以此时:
-
❌ 不会执行
func -
✅ 信号被"攒住"
六、sleep(2):什么都不会发生
sleep(2);
这 2 秒里:
-
程序在睡觉
-
信号已经来了
-
但被挡在门外
👉 屏幕 什么都不打印
七、解除阻塞(开门放人)
sigprocmask(SIG_UNBLOCK , &set , NULL);
人话:
允许 3、4 号信号进来了
这时系统会:
1️⃣ 检查:有没有被挡住的信号
2️⃣ 发现:有 3、4
3️⃣ 立刻调用信号处理函数
于是你会看到:
这里是信号响应函数 3
这里是信号响应函数 4
⚠️ 顺序不保证,但通常是你 raise 的顺序
八、完整"时间线动画"
设置好 signal(3), signal(4)
↓
把 3、4 号信号设为阻塞
↓
raise(3) → 被拦住
raise(4) → 被拦住
↓
sleep(2) → 什么都不发生
↓
解除阻塞
↓
func(3) 执行
func(4) 执行
九、为什么要这么"折腾"?
这是真实操作系统里非常重要的用法👇
典型用途:
-
防止关键代码段被信号打断
-
多个信号统一延迟处理
-
保证代码的原子性
📌 比如:
"我正在改共享数据,这段时间你别来打断我"
十、你现在记住 3 句话就够了
1️⃣ signal():信号来了怎么办
2️⃣ sigprocmask():信号什么时候能来
3️⃣ raise():主动发信号
#include <stdio.h>
#include <signal.h>
#include <strings.h>
#include <sys/types.h>
#include <unistd.h>
void func(int sig, siginfo_t * info , void * arg )
{
printf("sig:%d , info: %s arg:%s \n" , sig , (char * )info->si_ptr , (char*)arg );
}
int main(int argc, char const *argv[])
{
// 定义ACT结构体并设置其信息
struct sigaction act ;
bzero(&act , sizeof(act)); // 清空结构体
act.sa_sigaction = func ;
act.sa_flags |= SA_SIGINFO ;
// 设置捕获的信号响应函数
if( sigaction( 3 , &act, NULL ))
{
perror("设置捕获失败!!");
return -1 ;
}
// 设置好携带的参数
union sigval value;
value.sival_int = 1024 ; // 设置整型数组
value.sival_ptr = "Hello Even"; // 设置一个地址(指针)可以是任意类型的指针
// 发送信号
pid_t pid = getpid( );
if(sigqueue(pid , 3 , value))
{
perror("发送信号失败!!");
return -1 ;
}
printf("发送信号成功!!\n");
sleep(1);
return 0;
}
好,这段代码已经是 Linux 信号里的"进阶版"了 ,但我保证:
👉 按小白视角拆开看,其实不难
👉 你现在是在学 sigaction + sigqueue + 带参数的信号
我一步一步来,不跳步。
一句话先告诉你:这段程序在干嘛
进程给自己发送一个"带参数的信号 3",
信号处理函数用siginfo_t接收到这个参数并打印出来。
一、先看最关键的:信号处理函数(升级版)
void func(int sig, siginfo_t * info , void * arg )
{
printf("sig:%d , info: %s arg:%s \n" ,
sig ,
(char *)info->si_ptr ,
(char*)arg );
}
这个函数为什么有 3 个参数?
因为你用了:
act.sa_flags |= SA_SIGINFO;
👉 这是 sigaction 的高级模式
| 参数 | 含义 |
|---|---|
| sig | 信号编号(这里是 3) |
| info | 内核给你的"信号详细信息" |
| arg | 用户上下文(你现在不用,先忽略) |
⭐ 重点:info->si_ptr
这是哪里来的?
👉 来自 sigqueue() 发送的参数
二、sigaction:signal 的"专业版"
1️⃣ 定义结构体
struct sigaction act;
这是一个"信号配置表"。
2️⃣ 清空它(非常重要)
bzero(&act , sizeof(act));
避免里面有垃圾值。
3️⃣ 指定信号处理函数
act.sa_sigaction = func;
⚠️ 注意:
-
不是
sa_handler -
而是
sa_sigaction -
因为你要用 3 参数版本
4️⃣ 打开高级模式
act.sa_flags |= SA_SIGINFO;
人话翻译:
"系统啊,我要用 带参数的信号处理函数"
5️⃣ 注册信号 3
sigaction(3, &act, NULL);
意思是:
当进程收到 3 号信号时,用 act 里的规则处理
三、union sigval:信号携带的"快递包裹"
union sigval value;
这是一个 联合体(union),只能二选一:
int sival_int;
void *sival_ptr;
你这里写的是:
value.sival_int = 1024;
value.sival_ptr = "Hello Even";
⚠️ 重要提醒(小白一定要记住):
union 同一时间只能保存一个值
后写的会覆盖前写的
👉 所以最终真正传出去的是:
"Hello Even"
1024 已经没了 ❌
四、sigqueue:真正"带参数"的发信号
pid_t pid = getpid();
sigqueue(pid, 3, value);
逐个翻译:
| 代码 | 含义 |
|---|---|
| getpid() | 获取当前进程 ID |
| pid | 给自己发信号 |
| 3 | 信号编号 |
| value | 携带的数据 |
👉 相当于:
"我给我自己发一个 3 号信号,并带一句话"
五、信号是怎么"走到 func 的"
系统内部发生的事(理解版)
1️⃣ sigqueue() 把信号 + 参数交给内核
2️⃣ 内核发现信号 3 有 sigaction
3️⃣ 因为你设置了 SA_SIGINFO
4️⃣ 内核调用:
func(3, &siginfo, context);
5️⃣ info->si_ptr = "Hello Even"
六、程序最终输出什么?
你会看到类似:
sig:3 , info: Hello Even arg:(null)
发送信号成功!!
⚠️ arg 是上下文指针,你没用它,打印出来通常是 (null) 或乱码(这是正常的)
七、为什么要用 sigaction + sigqueue?
这是真正工业级用法👇
signal 的缺点
-
只能知道"信号来了"
-
不能带数据
-
不稳定(旧接口)
sigaction + sigqueue 的优点
✅ 能带数据
✅ 能知道是谁发的
✅ 行为可控
✅ Linux 推荐用法
📌 多进程通信、守护进程、事件通知,全靠它
八、你现在必须记住的 5 个点(考试级)
1️⃣ signal() 是简化版
2️⃣ sigaction() 是专业版
3️⃣ SA_SIGINFO → 三参数回调
4️⃣ sigqueue() → 带参数信号
5️⃣ siginfo_t → 信号的"身份证"
九、⚠️ 小白常犯的 3 个错误(你差一点就踩)
❌ 1. union 同时用 int 和 ptr
value.sival_int = 1024;
value.sival_ptr = ...
👉 只能留一个
❌ 2. sa_sigaction 却没设 SA_SIGINFO
👉 会直接崩
❌ 3. 在信号里做复杂事情
信号函数里 只能 printf / 设置标志位