Linux进程信号

ops/2024/12/1 8:50:34/

信号量

本质是一个计数器,用来表示系统资源中,资源数量多少的问题。

公共资源:能被多个进程同时访问的资源。

访问没有被保护的资源,可能会出现数据不一致问题。

让不同进程看到同一个资源的目的是想通信。

为了解决进程具有独立性无法 ,让进程看到同一个资源,解决过程中,又引入了数据不一致问题。

未来被保护起来的公共资源,一般是临近资源。是共享的。

其他的进程中申请的资源大部分是独立资源。

资源是要被使用的,有进程对应的代码会访问这部分资源。

访问临近资源的代码是临界区,不访问临近资源的代码是非临界区。

要么不做,要做就做完:原子性。

为什么要信号量

想买电影票的座位号,当想要某种资源时,可以进行预订。

共享资源:作为一个整体使用,划分成为一个一个资源的子部分。

进程申请信号量成功 相当于预订了共享资源的一部分,可以访问这部分资源,否则就不可以,就可以实现保护共享资源的目的。

 

 当信号量是1 代表对共享资源整体访问,实现互斥功能。

所有IPC资源的第一个字段都是ipc_perm的结构,可以定义一个指针数组,不同对象,可以用相同指针数组存储。

 信号:

 

 信号不一定立即被处理,需要时间窗口。

 共识:信号是给进程发的。

进程如何识别信号?认识+动作。

进程是程序员编写的属性和逻辑的集合,是程序员编码完成的。

进程本身要有对信号的保存能力。

进程处理信号称为信号被捕捉。

 进程保存信号,保存在进程task_struct 结构体中。

发送信号本质是修改进程pcb中的信号位图。

 PCB是内核维护的数据结构对象。

PCB管理者是OS。

发送信号的方式都是通过OS向进程发送的信号。

kill 命令底层一定调用了对应的系统调用。

 

自定义信号操作: 

#include<iostream>
#include<unistd.h>
#include<signal.h>
using namespace std;void handler(int signo)
{cout<<"进程捕捉到一个信号,该信号是:"<<signo<<endl;
}
int main()
{signal(2,handler);while(true){cout<<"我是一个进程"<<getpid()<<endl;sleep(2);}
}

 

利用系统调用接口,模拟实现kill。

raise,给自己发任意的信号。 相当于kill(getpid(),3)。

 

 abort():给自己发送指定的信号。信号SIGABRT。

 异常捕捉:

#include<iostream>
#include<signal.h>
#include<unistd.h>
using namespace std;
void catchSig(int signo)
{cout<<"获取到一个信号,信号编号是"<<signo<<endl;
}
int main(int argc,char* argv[])
{int cnt=10;signal(SIGFPE,catchSig);while(cnt--){cout<<"cnt:"<<cnt<<endl;sleep(1);int a=10;a/=0;}
}

收到信号不一定引起进程退出,进程没有退出就有可能还被调度。

CPU内部寄存器只有一份,但是寄存器中内容属于进程上下文。 

状态寄存器cpu自己维护,不能更改它。

进程被切换时,就有无数次状态寄存器被保存和恢复的过程。 

每次恢复时,就让操作系统识别到了CPU内部状态寄存器的溢出标志位。

OS将 硬件问题转换为软件问题,向目标进程发信号,导致异常。

当程序捕获到 SIGFPE 信号并调用 catchSig 后,程序会恢复到触发信号的那一行(a/=0;),导致再次触发 SIGFPE 信号。因此 catchSig 函数会一直被调用,形成无限循环。

软件条件:类似与管道通信,读端关闭,写端也关闭。

软件条件闹钟:

#include<iostream>
#include<signal.h>
#include<unistd.h>
using namespace std;int main(int argc,char* argv[])
{alarm(1);int cnt=10;while(true){cout<<"cnt:"<<cnt++<<endl;}   
}

#include<iostream>
#include<signal.h>
#include<unistd.h>
using namespace std;
int cnt=0;
void catchSig(int signo)
{cout<<"获取到一个信号,信号编号是:"<<cnt<<endl;
}
int main(int argc,char* argv[])
{signal(SIGALRM,catchSig);alarm(1);while(true){// cout<<"cnt:"<<cnt++<<endl;cnt++;}   
}

一次性闹钟信号。 

catchSig函数中加入alarm闹钟。 

#include<iostream>
#include<signal.h>
#include<unistd.h>
using namespace std;
int cnt=0;
void catchSig(int signo)
{cout<<"获取到一个信号,信号编号是:"<<cnt<<endl;alarm(1);
}
int main(int argc,char* argv[])
{signal(SIGALRM,catchSig);alarm(1);while(true){// cout<<"cnt:"<<cnt++<<endl;cnt++;}   
}

 闹钟其实是用软件实现的

OS周期性检查闹钟,超时后,发送SIGALARM信号。

3:记录在进程PCB中

4: 系统程序员默认写好

5:更改目标进程PCB信号位图

核心转储

段错误是执行动作是Core 。核心转储。

 

 核心转储目的是支持调试。

直接定位到出错地方。 

#include<iostream>
#include<signal.h>
#include<unistd.h>
using namespace std;
int cnt=0;
void catchSig(int signo)
{cout<<"捕捉到一个异常,编号是:"<<signo<<endl;
}
int main(int argc,char* argv[])
{for(int i=1;i<=31;i++){signal(i,catchSig);}while(1){cout<<"这是一个进程,pid:"<<getpid()<<endl;sleep(1);}}

  kill -9 9号信号不能被捕捉。

实际执行信号处理动作叫信号递达。

信号从产生到递达之间的状态,交信号未决。

进程可以阻塞某个信号。

2个位图,一个数组分别代表未决,阻塞,处理状态。

 一个信号没有产生,并不妨碍它可以被阻塞。

信号产生时候不会被立即处理,而是在合适的时候,从内核态转为用户态时处理。

用户为了访问内核或硬件资源,必须通过系统调用完成访问。

实际执行系统调用的人是进程,身份其实是内核。

系统调用较费时间。尽量避免频繁进行系统调用。

凡是和当前进程相关的数据,都是当前进程的上下文数据。

CPU对应的CR3寄存器,为0是内核态,为3为用户态。
跳转到内核态,为了进行系统调用。

进程要执行系统调用,就从用户空间跳到内核空间后,调用内核级页表,执行系统调用。还需要从用户态变到内核态。

 

不会更改内核空间:

 pending为1代表收到,handler是处理方法。

handler处理信号方式,包括默认,忽略和自定义。

 所有语言 直接间接访问系统资源,IO,访问硬件 一定经过操作系统,经过操作系统一定有系统调用,有系统调用一定会切到系统内核。

 执行handler中的自定义处理方法,需要转为用户态。防止用户态程序利用内核态身份非法操作。

在用户态执行完handler函数后,需要返回到内核态,获取进程程序位置后,再返回到用户态进程继续执行。

自定义信号处理过程:

sigset_t

 信号集包括pending信号集和block信号集,block信号集一般叫信号屏蔽字。

默认情况,所有信号都是不被阻塞的,如果一个信号被屏蔽了,该信号不会被递达。

#include<iostream>
#include<signal.h>
#include<unistd.h>
using namespace std;#define BLOCK_SIGNAL 2
#define MAX_SIGNUM 31
static void show_pending(const sigset_t &pending)
{for(int signo=1;signo<=MAX_SIGNUM;signo++){if(sigismember(&pending,signo)){cout<<"1";}else cout<<"0";}cout<<endl;
} 
int main()
{sigset_t block,oblock,pending;//初始化sigemptyset(&block);sigemptyset(&oblock);sigemptyset(&pending);//添加要屏蔽的信号sigaddset(&block,BLOCK_SIGNAL);//开始屏蔽,设置进内核sigprocmask(SIG_SETMASK,&block,&oblock);//遍历打印pending信号集while(true){//初始化sigemptyset(&pending);//获取sigpending(&pending);//打印show_pending(pending);sleep(1);}
}

捕捉2号信号,并屏蔽。

用数组选择要屏蔽的信号。

#include<iostream>
#include<signal.h>
#include<unistd.h>
#include<vector>
using namespace std;#define BLOCK_SIGNAL 2
#define MAX_SIGNUM 31static vector<int> sigarr={2,4};
static void show_pending(const sigset_t &pending)
{for(int signo=1;signo<=MAX_SIGNUM;signo++){if(sigismember(&pending,signo)){cout<<"1";}else cout<<"0";}cout<<endl;
} 
int main()
{sigset_t block,oblock,pending;//初始化sigemptyset(&block);sigemptyset(&oblock);sigemptyset(&pending);//添加要屏蔽的信号sigaddset(&block,BLOCK_SIGNAL);//开始屏蔽,设置进内核for(const auto& sig:sigarr ){sigaddset(&block,sig);}sigprocmask(SIG_SETMASK,&block,&oblock);//遍历打印pending信号集while(true){//初始化sigemptyset(&pending);//获取sigpending(&pending);//打印show_pending(pending);cout<<"pid:"<<getpid()<<endl;sleep(1);}
}

恢复屏蔽:

#include<iostream>
#include<signal.h>
#include<unistd.h>
#include<vector>
using namespace std;#define BLOCK_SIGNAL 2
#define MAX_SIGNUM 31static vector<int> sigarr={2,4};
static void show_pending(const sigset_t &pending)
{for(int signo=1;signo<=MAX_SIGNUM;signo++){if(sigismember(&pending,signo)){cout<<"1";}else cout<<"0";}cout<<endl;
} 
int main()
{sigset_t block,oblock,pending;//初始化sigemptyset(&block);sigemptyset(&oblock);sigemptyset(&pending);//添加要屏蔽的信号sigaddset(&block,BLOCK_SIGNAL);//开始屏蔽,设置进内核for(const auto& sig:sigarr ){sigaddset(&block,sig);}sigprocmask(SIG_SETMASK,&block,&oblock);int cnt=15;//遍历打印pending信号集while(true){//初始化sigemptyset(&pending);//获取sigpending(&pending);//打印show_pending(pending);cout<<"pid:"<<getpid()<<endl;sleep(1);if(cnt--<=0){sigprocmask(SIG_SETMASK,&oblock,&block);cout<<"恢复对信号的屏蔽,不屏蔽任何信号"<<endl;}}
}

屏蔽恢复成功,但没有打印,因为是2信号,恢复后,进入内核态进程中止,进程不会返回用户态 

 自定义捕捉2号信号,验证恢复抵达成功。

#include<iostream>
#include<signal.h>
#include<unistd.h>
#include<vector>
using namespace std;#define BLOCK_SIGNAL 2
#define MAX_SIGNUM 31static vector<int> sigarr={2,4};
static void show_pending(const sigset_t &pending)
{for(int signo=1;signo<=MAX_SIGNUM;signo++){if(sigismember(&pending,signo)){cout<<"1";}else cout<<"0";}cout<<endl;
} 
void myhandler(int signo)
{cout<<signo<<"号信号已经被抵达"<<endl;
}
int main()
{sigset_t block,oblock,pending;//初始化sigemptyset(&block);sigemptyset(&oblock);sigemptyset(&pending);//添加要屏蔽的信号sigaddset(&block,BLOCK_SIGNAL);//开始屏蔽,设置进内核for(const auto& sig:sigarr ){   signal(sig,myhandler);sigaddset(&block,sig);}sigprocmask(SIG_SETMASK,&block,&oblock);int cnt=15;//遍历打印pending信号集while(true){//初始化sigemptyset(&pending);//获取sigpending(&pending);//打印show_pending(pending);cout<<"pid:"<<getpid()<<endl;sleep(1);if(cnt--<=0){sigprocmask(SIG_SETMASK,&oblock,&block);cout<<"恢复对信号的屏蔽,不屏蔽任何信号"<<endl;}}
}

 sigaction:

#include<iostream>
#include<signal.h>
#include<unistd.h>
#include<vector>
using namespace std;void handler(int signo)
{cout<<"get a signo"<<signo<<endl;
}
int main()
{struct sigaction act,oact;act.sa_handler=handler;act.sa_flags=0;sigemptyset(&act.sa_mask);sigaction(SIGINT,&act,&oact);while(true)sleep(1);return 0;
}

 当信号处理时间很长的情况。

#include<iostream>
#include<signal.h>
#include<unistd.h>
#include<vector>
using namespace std;void handler(int signo)
{cout<<"get a signo"<<signo<<"正在处理"<<"pid:"<<getpid()<<endl;sleep(15);
}
int main()
{struct sigaction act,oact;act.sa_handler=handler;act.sa_flags=0;sigemptyset(&act.sa_mask);sigaction(SIGINT,&act,&oact);while(true)sleep(1);return 0;
}

最终只处理了2个信号。 

 

 

 

 

上一个信号处理完,会自动解除对上一个信号和当前设置的信号的屏蔽。

 

 可重入函数

 

#include<iostream>
#include<signal.h>
#include<unistd.h>
#include<vector>
using namespace std;int quit=0;
void handler(int signo)
{cout<<signo<<"信号正在被捕捉"<<endl;cout<<"quit:"<<quit;quit=1;cout<<"->"<<quit<<endl;
}
int main()
{signal(2,handler);while(!quit)sleep(1);cout<<"我是正常退出的"<<endl;return 0;
}


http://www.ppmy.cn/ops/138151.html

相关文章

2411C++,CXImage简单使用

介绍 CxImage是一个可非常简单快速的加载,保存,显示和转换图像的C类. 文件格式和链接的C库 Cximage对象基本上是加了一些成员变量来保存有用信息的一个位图: class CxImage{...protected:void* pDib; //包含标题,调色板,像素BITMAPINFOHEADER head; //标准头文件CXIMAGEINFO…

shell编程之sed

一、sed工作原理 sed是一种流编程器&#xff0c;配合正则表达式使用来处理文本。 原理&#xff1a;首先&#xff0c;把当前处理的行存储在临时缓冲区&#xff08;模式空间&#xff09;&#xff0c;接着用sed命令处理缓冲区中的内容&#xff0c;处理完成之后&#xff0c;把缓冲…

scala的名次排名

1.准备一个空的List 2.读取文件—按行读取 3.添加学生到List 对有序的列表&#xff0c;从前开始向后一一比较 (1).如果当前的分数不等于预设分数&#xff0c;则名次1&#xff0c;更新预设分数 (2).如果当前的分数等于预设分数&#xff0c;则名次不变 4.排名 实例操作代码如…

JavaScript 进阶教程:深入理解函数、事件和模块化

在上一节中&#xff0c;我们学习了 JavaScript 的基础语法&#xff0c;并通过一个简单计数器案例感受了它的实际应用。这一节将进一步深入学习 JavaScript 中的重要概念&#xff0c;如函数的高级用法、事件机制以及模块化开发&#xff0c;为更复杂的项目开发打下基础。 一、深入…

android将pcm byte[]通过Librtmp进行rtmp推流

需求 我们这边做的功能是智能戒指&#xff0c;戒指可以录音&#xff0c;然后app通过蓝牙连接&#xff0c;将音频的byte[]进行rtmp推流 技术 因为我们不涉及直播&#xff0c;也不涉及视频&#xff0c;工期也比较短&#xff0c;只是音频推流&#xff0c;所以没用更复杂的ffmpe…

【MySQL-6】MySQL的复合查询

目录 1. 整体学习的思维导图 2. 回顾基本查询 3. 多表查询 4. 自连接 5. 子查询 5.1 单行子查询 5.2 多行子查询 5.3 多列子查询 5.4 在from子句中使用子查询 6. 合并查询 1. 整体学习的思维导图 2. 回顾基本查询 使用scott数据库中的表&#xff0c;完成以下查询&am…

Ansible自动化一键部署单节点集群架构

自动化部署利器&#xff1a;Ansible 一键部署脚本 在现代IT基础设施管理中&#xff0c;Ansible以其简洁、强大的自动化能力脱颖而出。以下是精心打造的Ansible自动化一键部署脚本&#xff0c;旨在简化部署流程&#xff0c;提升效率&#xff0c;确保一致性和可靠性。 通过这个…

模拟器快速上手,助力HarmonyOS应用/服务高效开发

文章目录 1 创建模拟器1&#xff09;打开设备管理界面2&#xff09;设置本地模拟器实例存储路径3&#xff09;创建一个模拟器&#xff08;1&#xff09;选择模拟器设备&#xff08;2&#xff09;创建模拟器&#xff08;3&#xff09;启动模拟器&#xff08;4&#xff09;关闭模…