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效率会很低

相关推荐
Chenyu_3103 小时前
DPDK、TC 和 XDP:Linux 高性能网络到底在优化什么
linux·服务器·网络·缓存
HPtiger4 小时前
数据结构——选择排序
数据结构·算法·排序算法
康实训4 小时前
老年评估仿真实训平台架构设计与老年人能力评估实训室教学落地实践
大数据·人工智能·算法·实训室·实训室建设
DongQiShanRen5 小时前
六根硬件映射(四):归属澄清、两处不一致与卷十一总表
开发语言·人工智能·算法·rust·哈希算法·集成学习
CCblandy5 小时前
雪花 ID 四个边界实测:时钟回拨重号 500 个,前端 JSON.parse 直接丢精度
后端·python·算法·性能优化·架构
陈让然5 小时前
WSL 桥接设置静态IP(快速)
linux·网络·ubuntu
丑梨数据5 小时前
信创环境里跑文档工具:格式兼容与本地合规的几个坑
数据结构·人工智能·算法·决策树·排序算法
乔旭伟5 小时前
Linux 服务器备份实战:一致性快照、日志归档与恢复演练
linux·运维·服务器
把我格子衫拿来5 小时前
VTK 9.5 与 Qt 6 联合开发:从嵌入三维窗口到可维护的桌面架构
c++·python·qt·vtk·三维可视化
乔旭伟5 小时前
Linux 服务器安全加固实战:SSH、防火墙、补丁与入侵排查
linux·服务器·安全