linux 进程信号(上)

1.进程的概念

信号是进程之间事件异步通知的一种方式,属于软中断。

1.1 查看信号

查看信号的命令是kill -l

bash 复制代码
jcm@iZwz9c6v6ajdocnv9xkh8rZ:~/linux/9-7$ 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	

每个信号都有⼀个编号和⼀个宏定义名称,这些宏定义可以在signal.h中找到,例如其中有定义

Core Dump(核心转储):当进程因为收到某些异常信号而崩溃时,操作系统把这个进程崩溃那一刻完整内存快照保存到磁盘上,生成一个 core 文件,相当于程序崩溃的「事故现场照片」,用来事后排查崩溃原因。

1.2 信号处理

1.2.1 忽略信号

cpp 复制代码
#include <iostream>
#include <signal.h>

int main()
{

    signal(SIGINT, SIG_IGN);
    // signal(SIGINT,hander);
    while (true)
    {
        std::cout << "我是一个进程" << getpid() << std::endl;
        sleep(1);
    }
    return 0;
}

当信号被改为SIG_IGN时,就会忽略,我发送2号信号就不能终止进程了。

什么信号不能被忽略(列如九号信号)

就算把九号信号做忽略处理,kill给进程发送九号信号,进程也会终止。(不然有进程有病毒,忽略全部信号一直不能被杀死)

cpp 复制代码
#include <iostream>
#include <signal.h>

int main()
{
    signal(SIGKILL, SIG_IGN);
    //signal(SIGINT, SIG_IGN);
    // signal(SIGINT,hander);
    while (true)
    {
        std::cout << "我是一个进程" << getpid() << std::endl;
        sleep(1);
    }
    return 0;
}

1.2.2 自定义信号(就是把信号的默认动作修改我们自定义的函数)

提供一个信号处理函数,要求内核在处理该信号时切换到用户态执行这个处理函数,这种方式称为自定义捕捉(Catch)一个信号 (也叫捕捉信号)

cpp 复制代码
#include <iostream>
#include <signal.h>

void hander(int sig)
{

    std::cout << "这是我的自定义方法" << sig << std::endl;
}

int main()
{
    // signal(SIGKILL, SIG_IGN);
    // signal(SIGINT, SIG_IGN);
    signal(SIGINT, hander);
    while (true)
    {
        std::cout << "我是一个进程" << getpid() << std::endl;
        sleep(1);
    }
    return 0;
}

我们crtl+c的时候或者发送2号信号的时候,就不会终止,而是打印自定义的方法

1.2.3 默认动作

默认动作就是signal(,SIG_DFL)

1.3 signal函数

2 产生信号

2.1 键盘产生 crtl+z(进程暂停,进程从前台进程变成后台进程)

我们进程,前台进程必须只有一个,后台进程有很多个

开始的时候,命令行shell是前台进程组,我们./运行一个程序的时候,相当于产生一个子进程变成前台进程组,所以shell变成后台进程组。

fg 加序号:恢复前台进程组

bg 加序号:执行(因为ctrl+z变成后台。暂停了进程)

jobs:查看后台进程组。

2.2 函数产生信号

2.2.1 kill函数产生信号

  • pid:目标进程 PID(正数);进程组 ID(负数,发送给组内所有进程,发给进程组id,假如进程组里面有三个进程就发给三个进程);0(发送给当前进程组);-1(发送给所有有权限的进程);
  • sig:信号编号,(0 表示检测进程是否存在,不发送信号)

成功返回0 ,不成功返回-1;

cpp 复制代码
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        printf("失败");
        return 1;
    }
    // stoi函数将字符串转换为整数
    int number = std::stoi(argv[1] + 1);
    pid_t id = std::stoi(argv[2]);
    int n = kill(id, number);
    if (n < 0)
    {
        printf("kill失败");
    }
    return 0;
}

2.2.2 raise给当前进程发送指定信号

成功返回0,不成功返回其他数字

cpp 复制代码
void hander(int number)
{

    std::cout << "信号捕捉" << number << std::endl;
}

int main()
{

    signal(SIGINT, hander);
    while (true)
    {
        sleep(1);
        int n = raise(SIGINT);
        if (n == 0)
        {
            std::cout << "raise成功" << std::endl;
        }
    }
}

2.2.3 abort函数

  1. 作用:向当前进程发送 SIGABRT(6 号信号),强制异常终止,不可被忽略或自定义捕捉;但是他能执行自定义的操作只不过最后依旧会终止
  2. 无返回值(必然终止进程)
cpp 复制代码
void hander(int number){
    std::cout<<"信号是"<<number<<std::endl;

}

int main(){
    signal(SIGABRT,hander);
    while(true){
        sleep(1);
        abort();

    }

    return 0;

}

2.3 硬件产生信号

2.3.1 除零错误

复制代码
// 硬件产生的错误
// 除零错误

void hander(int number)
{
    std::cout << "发生硬件错误" << number << std::endl;
    //exit(12);
}

int main()

{
    signal(SIGFPE, hander);
    int a = 1;
    a = a / 0;

    return 0;
}

如果hander里面不退出的话。会一直打印这个语句,为什么了?

复制代码
; 对应C++ a = a / 0
mov eax, 1
mov ebx, 0
div ebx        ; ← 这一条除法指令!执行到这里直接硬件报错,这条指令没有结束!
               ; 触发异常,跳去跑信号处理函数 hander
; ---- hander return回来以后 ----
; CPU恢复寄存器,又回到 div ebx,再次执行这条没做完的除法!

CPU 取到除法指令,准备运行

执行的时候发现除数是 0,硬件抛出异常,这条指令中途被打断,没有完成

CPU 停止当前代码,进入内核,内核发送 SIGFPE 信号,切换去跑你的hander信号处理函数

当你的handerreturn 返回时,内核要恢复 CPU,回到刚才被打断、还没有执行完的那条指令,而不是下一行指令

2.3.2 野指针

复制代码
void handler(int number)
{
    std::cout << "野指针造成的内存问题" << std::endl;
    exit(1);
}

int main()
{
    // 11号信号
    signal(SIGSEGV, handler);
    int *p = nullptr;
    *p = 4;
    return 0;
}

因为 0 是最典型、最容易复现的非法地址:

空指针解引用是初学者最常见的 bug,nullptr / NULL 的值就是 0。

操作系统故意不映射 0 页,所以用 0 举例,几乎必然触发 fault,演示效果最干净。

0 地址查页表"没有条目"这个说法最直观,好画图。

我们int*p=nullptr就是虚拟地址是零号地址。

  1. 代码里有 int* p = nullptr;,然后 *p = 10;(向 0 号地址写 10)。
  2. CPU 执行写指令,把虚拟地址 0 送给 MMU 去翻译。
  3. MMU 查页表,发现虚拟地址 0 这一项没有有效映射条目(present 位无效)。
  4. MMU 作为硬件单元,无法完成地址翻译,直接向 CPU 发出异常信号(page fault)。
  5. 这个异常是硬件层面产生的,所以叫"硬件报错"。
  6. 后续:CPU 陷入内核,OS 的异常处理程序接手 → 判定非法访问 → 给进程发 SIGSEGV → 进程崩溃。

2.4 软中断

2.4.1 管道破裂

当向无读端的管道写入数据时,内核会向写进程发送 SIGPIPE(13 号信号),默认动作是终止进程

cpp 复制代码
// 软中断
// 管道
#include <iostream>
#include <unistd.h>
#include <sys/wait.h>

void hander(int number)
{

    std::cout << "错误" << std::endl;
}

int main()
{
    signal(SIGPIPE, hander);
    int fd[2];
    char buf[264];
    // 创建匿名管道。
    pipe(fd);
    pid_t id;
    // fork之后,子进程复制了父进程的文件描述表。所以他们才能通信
    id = fork();
    if (id == 0)
    {
        // 读端
        close(fd[1]); // 关闭自己的写端
        close(fd[0]); // 关闭自己的读端
        // ssize_t n = read(fd[0], buf, sizeof(buf) - 1);
        // buf[n] = '\0';
        // std::cout<<buf<<std::endl;
    }
    else
    {
        // 为什么要关闭两个读端,因为靠的是计数器,当读端计算器为0,才能写入发生软件错误。
        close(fd[0]);
        // 必须先等子进程退出来,不然计算器可能不是0,可能子进程还没有执行完
        wait(NULL);
        std::string s = "hello world";
        ssize_t n = write(fd[1], s.c_str(), s.size()); // string对象c_str转换为字符串的地址。
        if (n > 0)
        {
            std::cout << "成功" << std::endl;
        }
    }
    return 0;
}

2.4.2 alarm设置闹钟触发软中断

alarm(0)取消设置的闹钟

cpp 复制代码
#include<iostream>
#include<unistd.h>
#include<signal.h>
int main(){

    alarm(1);
    signal(SIGALRM,SIG_DFL);
    int count=0;
    while(true){
        std::cout<<count<<"次数"<<std::endl;
        count++;
    }


return 0;

}
cpp 复制代码
#include <iostream>
#include <unistd.h>
#include <signal.h>

int count = 0;
void hander(int number)
{

    std::cout << count << "次数" << number << std::endl;
}

int main()
{
    alarm(1);
    signal(SIGALRM, hander);
    while (true)
    {

        count++;
    }

    return 0;
}

有io效率会很低

相关推荐
罗西的思考1 小时前
[Agent Memory / 强化学习] MemPO源码学习笔记 ---(5)--- GRPO
人工智能·算法·机器学习
richard_yuu2 小时前
动态规划:强化学习的「数学基础」,从 MDP 到值迭代
深度学习·神经网络·算法·yolo·机器学习·动态规划
niuTaylor2 小时前
飞腾 FT-2000/4 Ubuntu 内存卡 / NVMe SSD 系统制作完整指南
linux·服务器·人工智能·驱动开发·单片机
mmmmath_32 小时前
LeetCode.225.用队列实现栈
算法
Elaine3362 小时前
数据结构与算法-程序
数据结构·python·算法·计算机基础·编程基础
2601_965742222 小时前
布局GEO AI本地营销,我重点看这几个细节
大数据·人工智能·算法·ai·新媒体运营
EatFan2 小时前
AI Agent 上生产前先加三道闸门:审批、限权、可回放的工程实践
人工智能·python·算法·多智能体·ai agent·mcp·harness
思茂信息3 小时前
CST软件BCI仿真模型及仿真案例
开发语言·单片机·嵌入式硬件·算法·emc
海上小飞龙3 小时前
改一个数,右边全得重算,这题怎么扛住两万次查询
java·c++·python