Linux进程信号

信号量

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

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

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

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

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

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

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

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

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

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

为什么要信号量

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

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

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

当信号量是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;
}
相关推荐
摸鱼也很难27 分钟前
Docker 镜像加速和配置的分享 && 云服务器搭建beef-xss
运维·docker·容器
woshilys1 小时前
sql server 查询对象的修改时间
运维·数据库·sqlserver
疯狂飙车的蜗牛2 小时前
从零玩转CanMV-K230(4)-小核Linux驱动开发参考
linux·运维·驱动开发
恩爸编程2 小时前
探索 Nginx:Web 世界的幕后英雄
运维·nginx·nginx反向代理·nginx是什么·nginx静态资源服务器·nginx服务器·nginx解决哪些问题
Michaelwubo4 小时前
Docker dockerfile镜像编码 centos7
运维·docker·容器
远游客07134 小时前
centos stream 8下载安装遇到的坑
linux·服务器·centos
好像是个likun4 小时前
使用docker拉取镜像很慢或者总是超时的问题
运维·docker·容器
LIKEYYLL6 小时前
GNU Octave:特性、使用案例、工具箱、环境与界面
服务器·gnu
云云3216 小时前
搭建云手机平台的技术要求?
服务器·线性代数·安全·智能手机·矩阵
云云3216 小时前
云手机有哪些用途?云手机选择推荐
服务器·线性代数·安全·智能手机·矩阵