信号量
本质是一个计数器,用来表示系统资源中,资源数量多少的问题。
公共资源:能被多个进程同时访问的资源。
访问没有被保护的资源,可能会出现数据不一致问题。
让不同进程看到同一个资源的目的是想通信。
为了解决进程具有独立性无法 ,让进程看到同一个资源,解决过程中,又引入了数据不一致问题。
未来被保护起来的公共资源,一般是临近资源。是共享的。
其他的进程中申请的资源大部分是独立资源。
资源是要被使用的,有进程对应的代码会访问这部分资源。
访问临近资源的代码是临界区,不访问临近资源的代码是非临界区。
要么不做,要做就做完:原子性。
为什么要信号量
想买电影票的座位号,当想要某种资源时,可以进行预订。
共享资源:作为一个整体使用,划分成为一个一个资源的子部分。
进程申请信号量成功 相当于预订了共享资源的一部分,可以访问这部分资源,否则就不可以,就可以实现保护共享资源的目的。
当信号量是1 代表对共享资源整体访问,实现互斥功能。
所有IPC资源的第一个字段都是ipc_perm的结构,可以定义一个指针数组,不同对象,可以用相同指针数组存储。
信号:
信号不一定立即被处理,需要时间窗口。
共识:信号是给进程发的。
进程如何识别信号?认识+动作。
进程是程序员编写的属性和逻辑的集合,是程序员编码完成的。
进程本身要有对信号的保存能力。
进程处理信号称为信号被捕捉。
进程保存信号,保存在进程task_struct 结构体中。
发送信号本质是修改进程pcb中的信号位图。
PCB是内核维护的数据结构对象。
PCB管理者是OS。
发送信号的方式都是通过OS向进程发送的信号。
kill 命令底层一定调用了对应的系统调用。
自定义信号操作:
cpp
#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。
异常捕捉:
cpp
#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
函数会一直被调用,形成无限循环。
软件条件:类似与管道通信,读端关闭,写端也关闭。
软件条件闹钟:
cpp
#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;
}
}
cpp
#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闹钟。
cpp
#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 。核心转储。
核心转储目的是支持调试。
直接定位到出错地方。
cpp
#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信号集一般叫信号屏蔽字。
默认情况,所有信号都是不被阻塞的,如果一个信号被屏蔽了,该信号不会被递达。
cpp
#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号信号,并屏蔽。
用数组选择要屏蔽的信号。
cpp
#include<iostream>
#include<signal.h>
#include<unistd.h>
#include<vector>
using namespace std;
#define BLOCK_SIGNAL 2
#define MAX_SIGNUM 31
static 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);
}
}
恢复屏蔽:
cpp
#include<iostream>
#include<signal.h>
#include<unistd.h>
#include<vector>
using namespace std;
#define BLOCK_SIGNAL 2
#define MAX_SIGNUM 31
static 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号信号,验证恢复抵达成功。
cpp
#include<iostream>
#include<signal.h>
#include<unistd.h>
#include<vector>
using namespace std;
#define BLOCK_SIGNAL 2
#define MAX_SIGNUM 31
static 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:
cpp
#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;
}
当信号处理时间很长的情况。
cpp
#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个信号。
上一个信号处理完,会自动解除对上一个信号和当前设置的信号的屏蔽。
可重入函数
cpp
#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;
}