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;
}
相关推荐
大树881 天前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
摇滚侠1 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
霸道流氓气质1 天前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务
小宇宙Zz1 天前
Maven依赖冲突
java·服务器·maven
Inhand陈工1 天前
基于台达PLC与映翰通IG502的智慧水产养殖精准投喂与远程运维解决方案
运维·人工智能·物联网·阿里云·信息与通信
酣大智1 天前
ARP代理--工作原理
运维·网络·arp·arp代理
shushangyun_1 天前
2026年快消品B2B系统推荐:支持终端门店订货、促销政策自动化的工具?
java·运维·网络·数据库·人工智能·spring·自动化
古城小栈1 天前
Unix 与 Linux 异同小叙
linux·服务器·unix
施努卡机器视觉1 天前
SNK施努卡侧滑门锁上滑轮总成自动化装配线,从零件到组件,全流程精密制造方案
运维·自动化·制造
程序猿阿伟1 天前
《Chrome离线扩展安装的底层逻辑与场景落地指南》
服务器·网络·chrome