【linux进程信号】————产生信号:signal自定义信号处理动作(自定义捕捉)、前后台进程、产生信号的方式(函数、软条件、硬件异常)....等等

目录

1.信号

[1.1 预备](#1.1 预备)

[1.2 基本结论:](#1.2 基本结论:)

[2. 信号的产生](#2. 信号的产生)

[2.1 处理信号的三种动作:](#2.1 处理信号的三种动作:)

[2.2 a.信号都有哪些?](#2.2 a.信号都有哪些?)

[2.3 查看所有信号详情的命令:](#2.3 查看所有信号详情的命令:)

[3. signal 函数](#3. signal 函数)

[3.1 无法被自定义的信号:](#3.1 无法被自定义的信号:)

[3.2 signal函数的返回值的作用:](#3.2 signal函数的返回值的作用:)

[4. c.目标进程??前台进程和后台进程](#4. c.目标进程??前台进程和后台进程)

[4.1 补充一部分命令,前后台移动](#4.1 补充一部分命令,前后台移动)

jobs命令:

[fg 命令,将特定的进程提到前台](#fg 命令,将特定的进程提到前台)

ctrl+z,将进程暂停+切换到后台

bg命令,让后台进程恢复运行

[5. d.什么叫做给进程发送信号?](#5. d.什么叫做给进程发送信号?)

[6. 产生信号的方式](#6. 产生信号的方式)

[7. 使用函数产生信号](#7. 使用函数产生信号)

[kill 函数](#kill 函数)

[raise 函数](#raise 函数)

[abort 函数](#abort 函数)

[8. 硬件异常产生信号](#8. 硬件异常产生信号)

[9. 由软件条件产生信号](#9. 由软件条件产生信号)

概念

[alarm 函数](#alarm 函数)

[pause 函数](#pause 函数)

快速理解系统闹钟

[10. 总结](#10. 总结)


1.信号

1.1 预备

信号vs信号量=老婆:老婆饼 ->没有任何关系!

1.信号:闹钟、红绿灯、上课铃声、狼烟、电话铃声、肚子叫、敲门声、脸色不好......

2.什么叫做信号:中断我们人正在做的事情,是一种事件的异步通知机制

  • 信号是一种给进程发送的,用来进行事件异步通知的机制!
  • 信号的产生,相对于进程的运行,是异步的!
  • 信号是发给进程的!

同步与异步示例:老师让张三取物品

同步场景:

全班自习等待张三返回后再继续上课

异步场景:

课程继续进行,张三独立完成取物任务

本质区别:

  • 同步:并发进程相互依赖(一方需等待另一方)
  • 异步:并发进程互不干扰

1.2 基本结论:

  • 信号处理:进程在信号还没有产生的时候,早就知道信号该如何处理了,且进程必须把要信号记录下来。
  • 信号的处理,不是立即处理,而是可以等一会再处理,合适的时候,进程会进行信号的处理。
  • 人能识别信号,是提前被 "教育" 过的,进程也是如此。OS程序员设计的进程,进程早已经内组织了对于信号的识别和处理方式!
  • 信号源非常多 -> 给进程产生信号的,信号源,也非常多!

2. 信号的产生

当前阶段:

信号的产生方式非常多:

1.键盘产生信号

ctrl+c 是给目标进程(前台进程)发送信号的,相当一部分信号的处理动作,就是让在自己终止!

收到信号,处理信号,进程收到信号治好后,合适的时候,处理信号。

2.1 处理信号的三种动作:

  • 默认处理动作
  • 自定义信号处理动作(自定义捕捉)
  • 忽略处理

2.2 a.信号都有哪些?

  • 1 ~ 31 号信号是普通信号,剩余的是实时信号。
  • ctrl + c就是给进程发信号,发哪一个信号?------2号信号(SIGINT)

2.3 查看所有信号详情的命令:

bash 复制代码
man 7 signal

b.你怎么证明?

我想看到信号处理的过程,我们尝试着更改进程的默认信号处理动作。

用到的函数:

3. signal 函数

  • signal 函数用于自定义信号处理动作。
  • signum 是信号,2号信号就传 SIGINT,handler 是一个 void (int) 函数,以后进程接收到 SIGINT 信号就会跳到 handler() 函数中执行,而不是中止进程了,我也是我们说的自定义信号处理动作。

3.1 无法被自定义的信号:

  • SIGKILL(9号信号):强制杀死进程。
  • SIGSTOP(19号信号):强制暂停进程。

3.2 signal函数的返回值的作用:

  • 保存旧的处理方式 :返回值是修改前的信号处理函数指针(handler,void (int))。你可以保存它,以便在临时修改后恢复原来的行为。
  • 判断是否设置成功:如果返回 SIG_ERR,说明设置失败(例如试图捕获不可捕获的信号)。
cpp 复制代码
#include <signal.h>
typedef void (*sighandler_t)(int);// 函数指针类型sighandler_t

sighandler_t signal(int signum, sighandler_t handler);
cpp 复制代码
#include <iostream>
#include <unistd.h>
#include <signal.h>

typedef void (*sighandler_t)(int); // 函数指针类型

void handlerSigal(int sig)
{
    std::cout << "获得一个信号: " << sig << std::endl;
}

int main()
{
    sighandler_t sig = signal(SIGINT, handlerSigal); // handlerSigal(SIGINT)

    int cnt = 0;
    while (true)
    {
        std::cout << "hello world!,cnt: " << cnt++ << std::endl;
        sleep(1);
    }
    return 0;
}

结果:

我们看见,本来能够终止进程的 ctrl+c ,现在无法终止进程了,变成了 "获得一个信号: 2",想要终止进程,我们可以 ctrl+

bash 复制代码
[wsj@iZgw05f0yp422tzyebhkoaZ lesson26]$ ./t
bash: ./t: No such file or directory
[wsj@iZgw05f0yp422tzyebhkoaZ lesson26]$ ./TestSignal 
hello world!,cnt: 0
hello world!,cnt: 1
hello world!,cnt: 2
^C获得一个信号: 2
hello world!,cnt: 3
^C获得一个信号: 2
hello world!,cnt: 4
^C获得一个信号: 2
hello world!,cnt: 5
^C获得一个信号: 2
hello world!,cnt: 6
hello world!,cnt: 7
hello world!,cnt: 8
^\Quit
[wsj@iZgw05f0yp422tzyebhkoaZ lesson26]$ 

4. c.目标进程??前台进程和后台进程

  • 直接执行一个可执行文件,它就是前台进程,后面加上 '&' 它就是后台进程。
bash 复制代码
./xxx //前台进程
./xxx & // 后台进程
  • 命令行shel进程是一个前台进程
  • 键盘产生的信号,只能发给前台进程(组合键,也是键盘输入!)
  • 当一个进程是后台进程,使用 ctrl+c 想要终止这个进程,进程不做处理,因为它收不到键盘产生的信号。
bash 复制代码
[wsj@iZgw05f0yp422tzyebhkoaZ lesson26]$ ./testSignal &
[1] 59188
[wsj@iZgw05f0yp422tzyebhkoaZ lesson26]$ hello world!, 进程id: 59188
hello world!, 进程id: 59188
hello world!, 进程id: 59188
hello world!, 进程id: 59188
^C
[wsj@iZgw05f0yp422tzyebhkoaZ lesson26]$ hello world!, 进程id: 59188
^C
[wsj@iZgw05f0yp422tzyebhkoaZ lesson26]$ hello world!, 进程id: 59188
hello world!, 进程id: 59188
hello world!, 进程id: 59188
hello world!, 进程id: 59188
hello world!, 进程id: 59188
hello world!, 进程id: 59188
hello world!, 进程id: 59188
hello world!, 进程id: 59188
hello world!, 进程id: 59188
hello world!, 进程id: 59188

[1]+  Killed                  ./testSignal
[wsj@iZgw05f0yp422tzyebhkoaZ lesson26]$ 

最后是使用 kill -9 进程号杀掉这个进程的。

谈谈前后台问题:

  • 后台进程,无法从标准输入中获取内容;前台进程,能从键盘获取标准输入;但是都可以向标准输出上打印。
  • 为什么后台进程无法从标准输入中获取内容?因为键盘只有一个,输入数据一定是给一个确定的进程的!
  • 前台进程必须只有一个!!后台进程可以有多个。
  • 前台进程的本质,就是要从键盘获取数据的!

比如使用 fork() 创建了子进程,但父进程作为前台进程却先退出了,此时子进程被 1号 进程接管,也就是自动提到后台,此时 ctrl+c 杀不掉这个子进程了。

4.1 补充一部分命令,前后台移动

jobs命令:

jobs 命令只能查看当前 Shell 会话中,并且仍在运行或处于暂停状态的后台任务。

所以如果你开一个 shell 运行一个后台任务,另一个 shell 使用 jobs 命令查的话是查不到的。

bash 复制代码
jobs //查看当前shell的所有后台任务
bash 复制代码
[wsj@iZgw05f0yp422tzyebhkoaZ lesson26]$ ./testSignal &
[1] 60069
hello world!, 进程id: 60069
[wsj@iZgw05f0yp422tzyebhkoaZ lesson26]$ hello world!, 进程id: 60069
jobs // 我在这里使用了jobs命令
hello world!, 进程id: 60069

[1]+  Running                 ./testSignal &  // 这就是当前shell的所有后台任务
[wsj@iZgw05f0yp422tzyebhkoaZ lesson26]$ hello world!, 进程id: 60069
hello world!, 进程id: 60069
hello world!, 进程id: 60069
hello world!, 进程id: 60069
hello world!, 进程id: 60069
hello world!, 进程id: 60069

fg 命令,将特定的进程提到前台

  • fg 命令是 Ctrl + Z 的"好搭档"。如果说 Ctrl + Z 是把任务"藏"到后台,那么 fg 就是把它"抓"回前台。
  • 它的全称是 Foreground(前台)。
bash 复制代码
fg 任务号
[1]+  Running                 ./testSignal & // 这个后台进程的任务号就是1
也就是可以这样用:fg 1
bash 复制代码
[wsj@iZgw05f0yp422tzyebhkoaZ lesson26]$ ./testSignal &
[1] 60310
hello world!, 进程id: 60310
// 后台进程,使用 ctrl+c 无反应
[wsj@iZgw05f0yp422tzyebhkoaZ lesson26]$ hello world!, 进程id: 60310
^C
[wsj@iZgw05f0yp422tzyebhkoaZ lesson26]$ hello world!, 进程id: 60310
hello world!, 进程id: 60310
^C
[wsj@iZgw05f0yp422tzyebhkoaZ lesson26]$ hello world!, 进程id: 60310
hello world!, 进程id: 60310
// 使用 fg 将其提到前台
fg 1
./testSignal
hello world!, 进程id: 60310
hello world!, 进程id: 60310
// ctrl+c 有反应了,也可以使用ctrl+\结束进程
^C获得一个信号: 2
hello world!, 进程id: 60310
hello world!, 进程id: 60310
^\Quit
[wsj@iZgw05f0yp422tzyebhkoaZ lesson26]$ 

ctrl+z,将进程暂停+切换到后台

Ctrl + Z 的作用正是:将当前正在前台运行的进程"暂停"(挂起),并将其放入后台。

bg命令,让后台进程恢复运行

  • bg 是 background 的缩写,它是 fg 的"兄弟",也是 Ctrl + Z 的最佳拍档。
  • 如果说 Ctrl + Z 是把任务按下了"暂停键",那么 bg 就是帮你在后台按下了"播放键"。

5. d.什么叫做给进程发送信号?

  • 信号产生后,并不是立即处理的,所以要求,进程必须把信号记录下来!!(合适的时候处理!)
  • 记录在哪里?记录的本质就是修改位图,接收到信号就将对应 bite 位置1
  • 而 task_struct 结构体,属于操作系统内的数据结构!
  • 修改位图,本质:修改内核的数据!!
  • 不管信号怎么产生,发送信号,在底层,必须让OS发送!!!
  • 所以操作系统自己,会提供发送信号的系统调用!!------kill
cpp 复制代码
#include <sys/types.h>
#include <signal.h>

int kill(pid_t pid, int sig);// 进程id+信号编号

发送信号,本质是什么??

  • 向目标进程写信号
  • 修改位图!
  • 通过进程的pid,给进程发送信号的编号

6. 产生信号的方式

产生信号的方式:

  • 调用系统命令向进程发信号 ------kill
  • **[硬件]**异常,如:除0,野指针,会产生问题:崩掉!

7. 使用函数产生信号

kill 函数

kill 命令是调⽤ kill 函数实现的。 kill 函数可以给⼀个指定的进程发送指定的信号。

cpp 复制代码
#include <sys/types.h>
#include <signal.h>

int kill(pid_t pid, int sig);// 进程id+信号编号

raise 函数

raise 函数可以给当前进程发送指定的信号(自己给自己发信号)。

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

int raise(int sig);// sig信号

abort 函数

  • abort 函数使当前进程接收到信号而异常终止。
  • abort 函数的设计机制决定了它"必须"杀死进程,即使你注册了信号处理函数。
  • abort 函数发出的是 6 号信号SIGABRT
cpp 复制代码
#include <stdlib.h>

void abort(void);
cpp 复制代码
typedef void(*signal_t)(int);

void Abort(int sig)
{
    std::cout<<"abort 发出的信号:"<<sig<<std::endl;
}

int main()
{
    signal_t sig=signal(6,Abort);
    abort();
    return 0;
}
bash 复制代码
[wsj@iZgw05f0yp422tzyebhkoaZ lesson26]$ ./testSignal 
abort 发出的信号:6
Aborted
[wsj@iZgw05f0yp422tzyebhkoaZ lesson26]$ 

8. 硬件异常产生信号

OS怎么知道硬件异常了?

  • 因为产生了信号,除0会产生 8 号信号(SIGFPE ,浮点数异常);野指针会产生 11 号信号(SIGSEGV,段错误)。

操作系统怎么知道犯错了?

9. 由软件条件产生信号

概念

"软件条件"其实就是操作系统内核根据程序运行的逻辑状态或资源情况,主动判定"出事了"而发送的信号。 它不是硬件坏了(如除零、段错误),也不是人按了键,而是内核作为"管理员"发现不符合规则了。

最典型的三个"软条件":

  • 路断了 (SIGPIPE):你往管道写数据,但读的那头已经挂了(关闭了),内核觉得你白忙活,就发信号终止你。
  • 时间到了 (SIGALRM):你设了闹钟(alarm),时间一到,内核就发信号提醒你。
  • 孩子变了 (SIGCHLD):子进程结束了,内核发信号通知父进程去"收尸"(回收资源)。

alarm 函数

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

unsigned int alarm(unsigned int seconds);
  • 调⽤ alarm 函数可以设定⼀个闹钟,也就是告诉内核在 seconds 秒之后给当前进程发 SIGALRM 信号(14 号信号),该信号的默认处理动作是终⽌当前进程
  • 这个函数的返回值是0或者是以前设定的闹钟时间还余下的秒数。比如:

先设置 alarm(5) ,返回值为0。

过了 3 秒,再设置 alarm(10) ,返回值就是 2 ,意思是上次的闹钟还有 2 秒。

cpp 复制代码
int main()
{
    alarm(5);
    int cnt =1;
    while (true)
    {
        printf("second: %d\n",cnt++);
        sleep(1);
    }
    return 0;
}
bash 复制代码
[wsj@iZgw05f0yp422tzyebhkoaZ lesson26]$ ./testSignal 
second: 1
second: 2
second: 3
second: 4
second: 5
Alarm clock
[wsj@iZgw05f0yp422tzyebhkoaZ lesson26]$

pause 函数

pause() 函数的主要作用是让当前进程挂起(暂停执行),直到它接收到一个信号为止。

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

int pause(void);

快速理解系统闹钟

  • 假如现在的时间戳是 1000 ,调用 alarm(5) 后,就会在 1005 时给当前进程发 SIGALRM 信号。

OS会不会同时存在很多的闹钟?要不要对闹钟进程管理?

  • 所以我们需要先描述,再组织。
  • 在底层就可以使用小堆这种数据,将闹钟都插入,然后将当前的时间戳不断与堆顶闹钟比较。

10. 总结

  • 所有信号产生,最终都要有OS来进行执行,为什么?OS是进程的管理者
  • 信号的处理是否是立即处理的?在合适的时候
相关推荐
Gauss松鼠会1 小时前
效率起飞!GaussDB 管理平台(TPOPS)升级指南
服务器·数据库·性能优化·gaussdb·经验总结
芝士就是力量啊 ೄ೨1 小时前
Windows11使用Edge切屏后,会卡屏的解决方案
前端·edge
晚风予卿云月1 小时前
【linux】僵尸进程与孤儿进程
linux·运维·服务器
hhb_6181 小时前
Tcl脚本自动化运维实操落地案例详解
运维·网络·自动化
故事还在继续吗1 小时前
Linux cgroup 使用指南:从原理到实践
linux·运维·服务器
csdn2015_2 小时前
lambdaQuery 加 or
java·linux·服务器
尘世壹俗人2 小时前
前端如何自适应宽高
前端
源图客2 小时前
Linux(CentOS9)服务器部署gitlab-ce-18.11.1-ce.0.el9.x86_64.rpm
linux·服务器·gitlab
invicinble2 小时前
对于docker相关的理解
运维·docker·容器