进程信号~信号的产生

💬之前学习的进程间通信的管道、共享内存等机制,它们主要用于进程间的数据传输。但有没有一种更轻量级的通信方式,可以让一个进程"通知"另一个进程发生了某个事件呢?答案是信号!信号是Linux中最古老、最经典的进程间通信方式,也是理解操作系统运行机制的重要窗口。

文章目录

  • 信号的概念
  • 信号产生的方式:
    • [1. 键盘产生信号](#1. 键盘产生信号)
    • [2. 系统调用(也可以向目标进程发送信号)](#2. 系统调用(也可以向目标进程发送信号))
    • [3. 通过异常产生信号(硬件异常)](#3. 通过异常产生信号(硬件异常))
    • [4. 软件条件](#4. 软件条件)

信号的概念

首先,我们要明确:信号和信号量毫无关系。

接下来的顺序:信号的产生(4个方式)、保存(3张表)、处理

信号产生的方式:

1. 键盘产生信号

信号编号确实是用宏定义的,比如#define SIGINT 2

  • 进程收到信号之后,在合适的时候会处理,有3种处理方式:

    1.默认的处理动作;

    2.自定义信号处理动作;又叫做信号捕捉

    3.忽略处理

  • 大部分进程收到信号都会默认终止自身(即相当一部分进程都会采用默认处理的动作:让自己终止,执行信号),只有少数信号默认动作是忽略、暂停、继续、转储核心等。

  • 可以使用该函数,更改默认处理动作

这一个过程又叫做:自定义捕捉

  • 前台进程&后台进程
  • 查看所有的后台进程:jobs(可以看到后台进程的编号)

  • 把(后台特定进程)提到(前台):fg 任务号

  • 一个进程一旦被ctrl + z,就被暂停了,提到后台

  • 让暂停的后台进程恢复运行:bg 任务号

  • 1~31信号产生之后,并不是立即处理,所以进程需要把信号记录下来。但是记录到哪里?如何记录?发送信号的本质又是什么?

  • 该信号是发给进程的,所以会记录到PCB描述结构体(struct task_struct{})中,用整数unsigned int sigs的位图结构来记录信号,毕竟信号不止一个

struct task_struct{}是操作系统内的数据结构对象,只能由操作系统来修改位图(本质是修改内核的数据),所以发送信号(修改位图),只能由OS发送!!

为了能够支撑我们向目标进程发送信号,OS需要提供发送信号的系统调用(kill就是C语言写的程序,底层调用了系统调用的接口,来完成发送信号的任务)

发送信号 = 内核修改目标进程的信号位图(发送信号的本质:其实是让OS修改位图)

流程:

1.内核找到目标进程

2.把进程信号位图里对应比特位 置为1

3.进程下次从内核态切回用户态时,扫描位图、执行信号处理函数

  • 信号 vs 通信IPC
    通信是进程间的通信,传递数据:在进程和进程之间,通知信息:从一个用户到另一个用户
    信号机制本质是OS和进程之间的关系,真正的写数据交互是OS给目标进程写的,写数据交给写到数据结构里(不是缓冲区)

信号是人通过OS给进程发信号。IPC是进程和进程之间。

广义来说,信号也可以理解为通信范畴,信号也是某种事件的通知机制(不是以传递数据为目的)

  • 有一个信号,禁止被自定义捕捉:signal kill,9号和19号信号,无法被自定义捕捉
  • 如果除了kill,剩下都被自定义捕捉,杀掉进程,首先知道进程的id(ps ajx | grep 可执行程序名字),然后再kill -9 pid

2. 系统调用(也可以向目标进程发送信号)

用户进程通过系统调用请求内核代为发送信号

  1. 给任意进程发送任意信号:kill)

    接口:int kill(pid_t pid, int sig);给指定 PID 的进程发送信号

  2. 系统调用(自己给自己发信号:raise

    接口:int raise(int sig);
    raise(sig); 等价于kill(getpid(), sig);

    调用内核接口,修改当前进程自身的信号位图,对应信号位直接置 1。

  3. abort() 是强制终止当前进程的函数,它会发送 6 号信号:SIGABRT给当前进程。执行时会先撤销该信号所有自定义捕捉函数、恢复为默认处理方式,要求进程必须处理这个信号,SIGABRT 不能被忽略!不能被阻塞!必须处理!

3. 通过异常产生信号(硬件异常)

  1. 异常 ->崩掉的常见情况:除零,野指针
  2. 一旦程序(运行就是进程)产生异常,发生错误,进程就会收到信号,进而崩掉
  3. 一个进程收到信号的本质:是OS修改进程PCB中的位图结构,所以发信号永远是OS(代码犯错,OS发)

信号,都是由操作系统发送的。(程序出错误了--->OS发现进程出错了--->出错的类型--->根据类型发送信号)

  1. 还有一个问题,OS如何发现进程出错?

    以除零为例,除零操作在CPU上进行,CPU上有各种寄存器,有一种状态寄存器(标志寄存器EFLAGS),是32/64个比特位,其有一个比特位是用来记录,CPU在当前计算时,数据是否溢出

  2. 那OS怎么知道计算出问题了?

    CPU是硬件,OS是软硬件资源的管理者,它能识别到硬件出错,然后发现计算是溢出的。而寄存器保存的是进程上下文以及struct_task,然后识别到哪一个进程出错,然后发现你是数据溢出,转而给进程发送8号信号

  3. 那OS如何发现野指针这个错误的呢?

    拿着野指针访问0号地址,但是在页表中并没有对它的映射关系,无法映射到内存当中,CPU拿到的都是虚拟地址,CPU中存在一个CR3寄存器,保存当前进程对应的页表的物理地址,在CPU中集成了一种硬件单元MMU。

寻址:将虚拟地址交给MMU,讲CR3里的内容也交给MMU,接着做地址转换。如果转化失败,MMU硬件报错,OS作为软硬件资源的管理者,给当前进程发送11号信号

4. 软件条件

调用alarm函数可以设定一个闹钟,也就是告诉内核在seconds秒之后给当前进程发SIGALRM信号(默认处理动作是终止当前进程,如果显式设置了处理函数,内核才会转而执行你写的回调函数;不设置就直接忽略)

cpp 复制代码
unsigned int alarm(unsigned int seconds);
  1. 有一种软件条件是alarm系统调用(闹钟超时之后,向目标进程发送信号的方式,成为软件条件)

闹钟alarm()设定秒数,时间到内核主动发送SIGALRM信号,这种靠软件计时触发发信号的形式,就叫做软件条件产生信号。

  1. 返回值:是0或者是以前设定的闹钟时间还余下的秒数
  2. 如果seconds值为0,表示取消以前设定的闹钟,
    函数的返回值仍然是以前设定的闹钟时间还余下的秒数

两个进程基于管道进行通信,写端在写,但读端关闭,再写进程就被OS终止了。管道是文件,是缓冲区,它们是软件概念,当软件条件不具备,OS会发送SIGPIPE信号

简单快速理解系统闹钟

  1. 进程是被OS调度的,那谁调度OS呢?

外部刺激让OS不断去运行,拿掉外部刺激OS就会一直pause暂停

进程由操作系统调度运行,操作系统本身是陷入暂停停滞状态(pause),依靠硬件时钟中断这个外部刺激持续运转,触发时钟中断才会去执行任务。没有任务,则pause暂停

系统中每个进程都能调用alarm设置专属闹钟,操作系统遵循先描述、后组织的内核管理思想,用专属内核数据结构来描述闹钟信息,再统一排队组织管理,创建闹钟本质就是在内核中新建对应的闹钟结构体对象。

  1. 时钟带来的中断刺激节奏固定,进程闹钟设定的时长、进程调度使用的时间片,底层本质都是内核维护的软件计数器,依靠数值递减完成计时。(刺激固定,时间固定,时间片本质是一个计数器)

系统闹钟从计时管理到超时判定,全程都由内核软件逻辑实现,属于纯软件层面的计时条件;当计时计数器归零、闹钟达到设定时长触发超时条件后,操作系统主动向对应进程发送SIGALRM闹钟信号。

这种依靠软件计时条件满足而产生、触发发送的信号,就定义为软件条件产生信号。

相关推荐
努力的小雨7 小时前
我用 QClaw 做了个 Web3 陪学助手,专治 Java 程序员的“概念劝退”
经验分享·ai智能
RainCity20 小时前
Java Swing 自定义组件库分享(十二)
java·笔记·后端
AlfredZhao2 天前
生产环境里,为什么不建议把普通端口直接暴露到公网?
linux·https·443·80
戴为沐3 天前
Linux内存扩容指南
linux
zylyehuo3 天前
Linux 彻底且安全地删除文件
linux
用户805533698034 天前
主线 U-Boot 上 RK3506:和闭源 rkbin 拔河的三个隐性契约
linux·嵌入式
用户034095297914 天前
linux fcitx 5 雾凇拼音 设置在中文输入法下仍然输入英文标点
linux
Web3探索者6 天前
可视化服务器管理和传统命令行区别是什么?新手教程:Linux 运维到底该用图形界面还是 SSH 命令行?
linux·ssh
zylyehuo6 天前
Linux系统中网线与USB网络共享冲突
linux
Sokach10157 天前
Linux Shell 脚本从零到能用:一个新手的一天学习总结
linux