进程信号~信号的产生

💬之前学习的进程间通信的管道、共享内存等机制,它们主要用于进程间的数据传输。但有没有一种更轻量级的通信方式,可以让一个进程"通知"另一个进程发生了某个事件呢?答案是信号!信号是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闹钟信号。

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

相关推荐
Emtronix英创4 小时前
RK3568 CAN驱动测试及使用说明
linux·arm开发·rk3568·全国产主板
行走的陀螺仪4 小时前
JavaScript 算法详解:10大经典算法,通俗易懂,从入门到精通
开发语言·javascript·算法
vortex54 小时前
CentOS 系包管理器完全指南:从 dnf 到 rpm
linux·运维·centos
小当家.1054 小时前
Codex + SSH 远程运维实战:让 AI 管你的云服务器
运维·服务器·人工智能·ssh·codex·ai-coding
SZ放sai哑滋4 小时前
工控机刷Linux、Qt教程
linux·运维·服务器
努力成为AK大王4 小时前
Java并发线程核心知识(一)
java·开发语言·面试
MY_TEUCK4 小时前
【2026最新Linux本地部署Ollama】Ollama Linux 安装全流程(含离线 / 开机自启 / 远程访问)
linux·运维·服务器
可依软件crf2864 小时前
几个笔记软件的优缺点
笔记
t-think4 小时前
深入理解指针(2)
c语言·开发语言