目录
[三、调用系统命令向进程发信号 kill](#三、调用系统命令向进程发信号 kill)
一、预备知识
1.信号!=信号量。两者没有任何关系
2.什么是信号?
定义一:它是一种信号的异步通知机制
定义二:信号是一种给进程发送的、用来进行事件异步通知的机制
注:信号的产生与进程的运行是异步的。
3.基本结论
<1>信号的处理:进程在信号还未产生的时候就知道该如何处理信号
<2>信号的处理不是立即处理的,而是可以等一会儿在合适的时候进行信号处理
<3>进程能识别信号,这是因为它提前被"教育"过的。进程早已内置了对信号的识别和处理方式
<4>信号源是非常多的
4.键盘产生的信号只能发给前台进程(前台进程介绍看下方)
二、信号的产生
一、键盘产生信号
1.例子
ctrl+c:给目标进程发送信号
注:相当一部分的信号处理动作是让自己终止
2.信号有哪些?
<1>kill -l:查看全部的信号列表
<2>信号列表中的1-31是普通信号,可以不立即处理;34-64是实时信号,需要立即处理
3.查看信号处理的过程
查看更改进程的默认信号处理动作函数:signal()
函数:sighandler_t signal(int signum, sighandler_t handler);
头文件:<signal.h>
参数:
signum
:信号编号,表示要处理的信号类型(如 SIGINT
、SIGTERM
等),就是信号列表中的宏
handler
:指向信号处理函数的指针。如果为 SIG_DFL
,表示使用默认处理方式;如果为 SIG_IGN
,表示忽略信号;如果为自定义函数,则调用该函数处理信号
返回值:返回先前的信号处理函数指针。如果出错,返回 SIG_ERR。
4.什么是目标进程?
<1>前台进程:前台进程是用户当前直接交互的进程,通常占据用户的注意力焦点
本质:从键盘上获取数据
例子:命令行的shell进程
<2>后台进程:后台进程是用户未直接交互的进程,通常在后台执行任务
例子:孤儿进程
注:后台进程不对ctrl+c做处理,只能用 kill -9 目标进程id号 命令行来杀掉目标进程
特性 | 前台进程 | 后台进程 |
---|---|---|
用户交互 | 直接交互,实时响应 | 间接交互,自动运行 |
优先级 | 高优先级,资源优先分配 | 低优先级,资源受限 |
可见性 | 窗口或界面始终显示 | 无界面或隐藏在系统托盘中 |
典型场景 | 文本编辑、浏览器、游戏 | 系统服务、下载管理、自动更新 |
控制方式 | 用户直接操作(如关闭窗口) | 用户通过设置或通知栏管理 |
<3>前台进程和后台进程的相互转换
1)前台进程到后台进程:用户通过最小化窗口、切换任务(如Alt+Tab)或后台运行命令(如nohup
)实现
cpp
./xxx #执行前台进程
./xxx & #将进程切换成后台进程
2)后台进程到前台进程:用户通过任务管理器、通知栏或特定命令(如fg
)恢复前台
cpp
jobs #查看所有后台进程
fg 任务号 #将特定进程提到前台
ctrl+z #进程切换到后台
bg 任务号 #让后台进程恢复运行
<4>前台进程只能有一个,但是后台进程可以有多个
5.什么叫给进程发送信号?
<1>发送信号的本质是向目标进程写信号,修改位图
<2>不管信号怎么产生,发送信号在底层有OS发送,OS提供发送信号的系统调用函数kill()
函数:int kill(pid_t pid, int sig);
参数:
pid
:目标进程的进程 ID (PID)
sig
:要发送的信号编号
头文件:<signal.h>
功能:向指定进程或进程组发送信号
返回值:成功返回0;失败时返回 -1
,并设置 errno
以指示错误类型
<3>信号产生后并不是立即处理,而是将信号记录下来在合适的时候处理。将信号记录在一个结构体里

<4>处理信号的三种做法
1)默认处理路径
2)自定义信号处理动作,但是9号和19号信号无法自定义捕捉
3)忽略处理
二、系统调用
1.int kill(pid_t pid, int sig);
参数:
pid
:目标进程的进程 ID (PID)
sig
:要发送的信号编号
头文件:<signal.h>
功能:向指定进程或进程组发送信号
返回值:成功返回0;失败时返回 -1
,并设置 errno
以指示错误类型
2.int raise(int sig);
参数:sig
:要发送的信号编号
头文件:<signal.h>
功能:在程序内部向当前进程发送信号
返回值:成功返回0;失败时返回 非零值,并设置 errno
以指示错误类型
3.void abort(void);
头文件:<stdlib.h>
功能:用于异常终止当前进程
三、调用系统命令向进程发信号 kill
四、硬件异常
1.程序崩溃的原因是硬件异常

2.除0:SIGFPE
3.野指针:SIGSEGV 段错误
五、软件条件
1.例子:进程1具有写权限,通过管道向进程2写数据,进程2的读权限关闭,此时会报错SIGPIPE,这个信号就是软件条件
2.unsigned int alarm(unsigned int seconds);
功能:为发送信号设置一个闹钟,该信号的默认处理动作为终止进程
3.int pause(void);
功能:等待信号
4.小结:
程序主动触发 :如 raise
、kill
、abort
。
异常或错误:如非法内存访问、除以零。
定时器或异步事件 :如 alarm
。
用户交互:如终端信号。
进程间通信 :如 kill
系统调用。
库函数或系统调用错误 :如 exec
失败。
自定义信号 :如 SIGRTMIN
系列信号
三、信号的保存
1.预备知识
<1>信号递达:实际执行信号的处理动作(默认、自定义、忽略)
<2>信号未决:信号从产生到递达之间的状态,即信号还在位图中,还未来得及处理
<3>阻塞/屏蔽信号:信号被阻塞后就不会递达
<4>三张表:让进程识别信号
1)pending表:保存收到信号的位图
比特位的位置:表示是第几个信号
比特位的内容:是否收到
2)block表:也是位图
比特位的位置:表示的是第几个信号
比特位的内容:是否阻塞
3)handler表:sighandler_t handler[31],函数指针数组,数组下标是信号编号
2.Linux提供信号的操作
<1>int sigprocmask(int how, const sigset_t* set, sigset_t* oldest);
功能:操作进程信号屏蔽字
参数:
how
:- 指定如何修改信号屏蔽字。
- 取值可以是以下之一:
SIG_BLOCK
:将set
中的信号添加到当前信号屏蔽字中(阻塞这些信号)。SIG_UNBLOCK
:从当前信号屏蔽字中移除set
中的信号(解除阻塞这些信号)。SIG_SETMASK
:用set
中的信号完全替换当前信号屏蔽字。
set
:- 指向一个
sigset_t
类型的信号集,指定需要阻塞或解除阻塞的信号。 - 如果为
NULL
,则不对信号屏蔽字进行修改。
- 指向一个
oldset
:- 指向一个
sigset_t
类型的信号集,用于存储修改前的信号屏蔽字。 - 如果为
NULL
,则不保存修改前的信号屏蔽字。
- 指向一个
头文件:<signal.h>
返回值:成功返回0;失败时返回 -1
,并设置 errno
以指示错误类型
<2>int sigpending(sigset_t* set);
功能:获取当前进程的未决信号集
参数:set:指向一个 sigset_t
类型的信号集,用于存储当前进程的未决信号集。调用成功后,set
将包含所有未决信号的集合。
头文件:<signal.h>
返回值:成功返回0;失败时返回 -1
,并设置 errno
以指示错误类型
3.Core VS Term
1>Core:会在当前路径下形成一个文件,进程退出时进程会在内存中的核心数据从内存拷贝到磁盘形成一个文件,即核心转储
注:核心转储是为了支持Debug
2>Term:进程退出
四、信号的处理
1.在合适的时候处理信号,合适的时候是什么时候?
进程从内核态返回到用户态的时候进行信号检查
2.如果信号处理操作是忽略呢?
将pending表由1->0,返回用户层
3.重谈捕捉过程
<1>在执行自定义方法时,OS必须以用户身份执行
<2>流程(重点,需要牢记)

<3>硬件中断:由外部设备触发的,导致系统停止运行
注1:信号的本质就是用软件模拟实现硬件中断
注2:操作系统在硬件时钟终端的驱动下进行调度,操作系统是基于中断进行工作的软件
<4>软件中断:由软件原因触发中断
例如:缺页中断、内存碎片处理、除零错误、野指针错误都会被转化成CPU内部的软件中断
4.用户和内核都在同一个[0,4GB]的地址空间上
<1>用户态:以用户身份只能返回自己的[0,3GB]
<2>内核态:以内核身份运行,通过系统调用的方式访问OS的[3,4GB]
5.系统调用的过程是在进程地址空间上进行的,所用的函数调用都是地址空间之间的跳转
注:OS只提供系统调用号,不提供任何系统调用接口
6.可重入函数
<1>定义:可以被多个任务(或线程)安全调用,且在调用过程中不会破坏函数内部或外部数据的函数
<2>特点:在函数未执行完毕时,允许再次被调用,且不会导致数据竞争或程序崩溃。
<3>特性:
1)不使用全局或静态变量
2)不依赖外部不可重入函数
3)使用局部变量
4)不使用动态内存分配
5)不使用I/O操作
特性 | 可重入函数 | 不可重入函数 |
---|---|---|
全局变量使用 | 不使用 | 使用全局或静态变量 |
线程安全性 | 安全 | 不安全 |
调用方式 | 可被多次中断并重新调用 | 调用时可能破坏数据 |
示例 | int add(int a, int b) { return a + b; } |
int counter = 0; void increment() { counter++; } |