linux 进程学习之信号

复制代码
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 / 设置标志位


相关推荐
若风的雨1 小时前
linux Page Table 和 TLB 操作总结
linux
AlenTech1 小时前
如何解决Ubuntu中使用系统pip报错的问题,error: externally-managed-environment
linux·ubuntu·pip
被遗忘的旋律.2 小时前
Linux驱动开发笔记(二十四)——(上)IIO + icm20608驱动
linux·驱动开发·笔记
Y1rong2 小时前
刷机与系统启动
linux
zhangrelay2 小时前
thinkpad等笔记本电脑在ubuntu等linux系统下电池校准如何做?
笔记·学习
zandy10112 小时前
衡石科技Agentic BI实战:基于自然语言查询与自动化分析的新一代智能系统
运维·科技·自动化·agentic bi
南梦浅2 小时前
[特殊字符]️ Docker 镜像加速器完整配置流程下面是在 CentOS 7 系统上配置 Docker 镜像加速器的完整步骤
linux·docker·centos
AiTEN_Robot2 小时前
机器人叉车的技术落地与效率挖掘:仓储自动化的效能提升方案
运维·机器人·自动化
卓应米老师2 小时前
【网络配置实战】堆叠的配置
运维·服务器·网络·华为认证