【Linux后端服务器开发】信号量与信号

news2024/11/14 19:32:59

目录

一、信号量概述

二、信号概述

三、信号产生

1、终端按键产生信号

2、调用系统函数产生信号

3、硬件异常产生信号

4、软件条件

四、信号保存

1、信号阻塞

2、信号捕捉流程

五、信号递达


一、信号量概述

  • 信号量:一个计数器,通常用来表示公共资源中,资源数量的多少
  • 公共资源:能够被多个进程同时访问的资源
  • 访问没有被保护的公共资源:数据不一致
  • 被保护的公共资源:临界资源
  • 资源(内存、文件、网络......)是要被使用的,一定有该进程对应的代码来访问这部分临界资源(临界区),不访问临界资源的代码是非临界区
  • 如何保护公共资源:互斥 && 同步
  • 所有进程在访问公共资源之前,都需要先申请sem信号量 ---> 信号量本身就是一个公共资源
  • 公共资源的使用:1. 作为一个整体使用;2. 划分为一个个子资源使用
  • 进程预定公共资源,信号量sem--(P操作);进程释放公共资源,信号量sem++(V操作);若信号量sem == 0,进程无法预定公共资源
  • 信号量的 --、++ 操作是原子性的(要么不执行,要么执行完)

申请信号量

控制信号量

设置信号量(PV操作)

二、信号概述

信号与信号量的关系,就如同老婆和老婆饼的关系,没有任何关系。

[1, 31]是普通信号,[34, 64]是实时信号

  • 进程本身是被程序员编写的属性和逻辑的集合 ------ 程序员编码完成
  • 当进程收到信号的时候,进程可能正在执行更重要的代码,信号不一定会被立即处理
  • 进程本身要有对应信号的保存能力
  • 进程对信号的三种处理方式:默认、自定义、忽视 ------ 信号被捕捉
  • 进程对信号的保存:task_struct中用位图结构保存
  • 发送信号的本质:修改pcb中的信号位图
  • pcb是内核维护的数据结构对象,由os管理
  • 所有发送信号的方式,本质都是os向目标进程发信号: os向用户提供发送信号的系统调用

三、信号产生

1、终端按键产生信号

#include <iostream>
#include <unistd.h>
using namespace std;

int main() 
{
    while (1) 
    {
        cout << "this is a process, pid = " << getpid() << endl;
        sleep(1);
    }
    return 0;
}

键盘按键 ctrl C ---> os将ctrl+C解释为2号信号(SIGINT) ---> 进程终止

自定义捕捉方法

signal函数的调用,并不是自定义捕捉方法函数的直接调用

仅仅设置了对目标信号的捕捉方法,该方法并不一定会被调用

这个自定义捕捉方法只有收到了捕捉信号的时候才会被调用

#include <iostream>
#include <unistd.h>
#include <signal.h>
using namespace std;

void Handler(int signo) 
{
    cout << "进程捕捉到了一个信号,信号编号是:" << signo << endl;
}

int main() 
{
    signal(2, Handler);

    while (1) 
    {
        cout << "this is a process, pid = " << getpid() << endl;
        sleep(1);
    }
    return 0;
}

当自定义2号信号(SIGINT)的捕捉方法之后,进程收到2号信号并不会终止,而是执行自定义捕捉方法的打印函数

2、调用系统函数产生信号

  • kill() 可以向任意进程发送任意信号
  • raise() 可以向自身进程发送任意信号 ----- kill(getpid(), signal)
  • abort() 给自己发送指定的信号SIGABRT ----- kill(getpid(), SIGABRT)

mykill.cc

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <cstdio>
using namespace std;

static void Usage(const string& proc) 
{
    cout << "\nUsage: " << proc << "pid signo\n" << endl;
}

void Handler(int signo) 
{
    cout << "进程捕捉到了一个信号,信号编号是:" << signo << endl;
}

int main(int argc, char* argv[]) 
{
    if (argc != 3) 
    {
        Usage(argv[0]);
        exit(1);
    }
    pid_t pid = atoi(argv[1]);
    int signo = atoi(argv[2]);
    int n = kill(pid, signo);
    if (n != 0) 
        perror("kill");

    return 0;
}

test.cc 一个死循环进程

#include <iostream>
#include <sys/types.h>
#include <unistd.h>
using namespace std;

int main() 
{
    while (true) 
    {
        cout << "我是一个正在运行的进程,pid:" << getpid() << endl;
        sleep(1);
    }
    return 0;
}

myraise.cc

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <cstdio>
using namespace std;

int main() 
{
    int cnt = 0;
    while (true) 
    {
        printf("cnt: %d\n", cnt++);
        sleep(1);
        if (cnt >= 5) 
        {
            raise(9);
            //abort();
        }
    }
    return 0;
}

5s后进程被自己发送的信号杀死(或放弃)

3、硬件异常产生信号

SIGFPE

#include <iostream>
#include <signal.h>
#include <unistd.h>
#include <cstdio>
using namespace std;

int main() 
{
    while (true) 
    {
        cout << "我在运行中..." << endl;
        sleep(1);
        int a = 10;
        a /= 0;         
    }

    return 0;
}

除0 浮点数错误SIGFPE

获取信号编号

#include <iostream>
#include <signal.h>
#include <unistd.h>
#include <cstdio>
using namespace std;

void CatchSig(int signo) 
{
    cout << "获取一个信号,信号编号是:" << signo << endl;
    sleep(1);
}

int main() 
{
    signal(SIGFPE, CatchSig);

    while (true) 
    {
        cout << "我在运行中..." << endl;
        sleep(1);
        int a = 10;
        a /= 0;         //除0 浮点数报错
    }

    return 0;
}

  • os通过状态寄存器当中的溢出标记位判断cpu是否发生运算异常,若产生异常,os发送信号
  • 进程收到信号,不一定会立即执行
  • cpu每次切换进程时进行上下文保存和恢复,os都会检测一次异常

4、软件条件

设置闹钟,进程1s之后结束,统计cnt累加次数

#include <iostream>
#include <signal.h>
#include <unistd.h>
#include <cstdio>
using namespace std;

int main() 
{
    alarm(1);
    int cnt = 0;
    while (1) 
    {
        cout << "cnt: " << cnt++ << endl;
    }

    return 0;
}

将cnt定义为全局重新,用捕捉信号重新定义

#include <iostream>
#include <signal.h>
#include <unistd.h>
#include <cstdio>
#include <cstdlib>
using namespace std;

int cnt = 0;

void CatchSig(int signo) 
{
    cout << "cnt: " << cnt << endl;
}

int main(int argc, char* argv[]) 
{
    signal(SIGALRM, CatchSig);
    alarm(1);
    while (1) 
    {
        cnt++;
    }

    return 0;
}

由此可见计算机将数据从内存打印到外设时间损耗巨大,IO效率很低

闹钟是一次性闹钟,执行之后不再执行

#include <iostream>
#include <signal.h>
#include <unistd.h>
#include <cstdio>
#include <cstdlib>
using namespace std;

static int cnt = 0;

void CatchSig(int signo) 
{
    cout << "cnt: " << cnt << endl;
    alarm(1);
}

int main(int argc, char* argv[]) 
{
    signal(SIGALRM, CatchSig);
    alarm(1);
    while (1) 
        cnt++;

    return 0;
}

任何一个进程都可以通过alarm()系统调用设置闹钟,操作系统如何管理闹钟?先描述,再组织

核心转储

部分服务器默认关闭了核心转储

#include <iostream>
using namespace std;

int main(int argc, char* argv[]) 
{
    //核心转储
    while (1) 
    {
        int a[10];
        a[100000] = 100;
    }

    return 0;
}

越界访问,不仅报了段错误,还生成了core.30202文件

以Term退出的没有核心转储

核心转储:当进程出现异常的时候,将进程在对应的时刻,在内存中的数据转储到磁盘中

核心转储意义:支持调试(事后调试)

对所有信号做自定义捕捉测试

#include <iostream>
#include <signal.h>
#include <unistd.h>
#include <cstdio>
#include <cstdlib>
#include <sys/types.h>
using namespace std;

void CatchSig(int signo) 
{
    cout << "收到一个信号,信号编号是:" << signo << endl;
}

int main(int argc, char* argv[]) 
{
    for (int signo = 31; signo >= 1; --signo) 
        signal(signo, CatchSig);

    while (1) 
    {
        cout << "我在运行, pid = " << getpid() << endl;
        sleep(1);
    }
    return 0;
}

只有9号信号可杀死进程(9号进程由os直接控制,禁止对9号信号做捕捉)

四、信号保存

  • 执行信号的处理动作称之为信号递达
  • 信号从产生到递达之间的状态叫做信号未决
  • 进程可以选择阻塞某个信号
  • 被阻塞的信号永远不会递达,除非进程接触阻塞
  • 阻塞和忽视是有不同的,忽视是递达之后的一种处理动作

1、信号阻塞

每个信号都有两个标志位表示阻塞(block)未决(pending),还有一个函数指针表示处理动作

如果一个信号没有产生,可以预先将它设为阻塞状态

2、信号捕捉流程

信号产生的时候,不会被立即处理,而是在从内核态返回用户态的时候进行处理

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

CPU中存在CR3表态寄存器,表示当前运行的运行级别:0表示内核态,3表示用户态

每一个进程都有自己的地址空间(用户空间独占)、内核空间(被映射到每一个进程的3~4G)

进程要访问OS的接口,只需要在自己的地址空间上进行跳转

每一个进程都会共享一个内核级页表,无论进程如何切换,不会更改内核空间

系统调用接口,起始位置会将我们的用户权限更改为内核态(陷入内核)

特定的身份调用特定的代码,内核态也无法直接调用用户态状态

信号捕捉流程图

五、信号递达

sigprocmask读取或更改进程的信号屏蔽字

sigpending获取进程的pending位图

#include <iostream>
#include <signal.h>
#include <unistd.h>
#include <cstdio>
#include <cstdlib>
#include <sys/types.h>
#include <vector>
using namespace std;

static vector<int> sigarr = {2, 3};

void ShowPending(const sigset_t& pending) 
{
    for (int signo = 31; signo >= 1; signo--) {
        if (sigismember(&pending, signo)) 
            cout << "1";
        else 
            cout << "0";
    }
    cout << endl;
}

void MyHandler(int signo) 
{
    cout << signo << " 号信号已经被递达\n" << endl;
}

int main(int argc, char* argv[]) 
{
    for (const auto& e : sigarr) 
        signal(e, MyHandler);

    // 1. 屏蔽指定的信号
    sigset_t block, oblock, pending;
    // 1.1 初始化
    sigemptyset(&block);
    sigemptyset(&oblock);
    // 1.2 添加要屏蔽的信号
    for (const auto& e : sigarr) 
        sigaddset(&block, e);   // 2号信号
    // 1.3 开始屏蔽
    sigprocmask(SIG_SETMASK, &block, &oblock);

    // 2. 遍历打印pending信号集
    int cnt = 10;
    while (1) 
    {
        sigemptyset(&pending);
        sigpending(&pending);
        ShowPending(pending);
        sleep(1);
        if (cnt-- == 0) 
        {
            cout << "恢复对信号的屏蔽\n" << endl;
            sigprocmask(SIG_SETMASK, &oblock, &block);
        }
    }

    return 0;
}

将2号、3号信号屏蔽

ctrl C + ctrl \ 发送2号信号和3号信号,信号未决,pending位图2号和3号bit位置1

10s后重置对信号的屏蔽,os直接将重置的信号递达,捕捉到2号、3号信号

sigaction

#include <iostream>
#include <signal.h>
#include <unistd.h>
#include <vector>
using namespace std;

void ShowPending(const sigset_t& pending) 
{
    for (int signo = 31; signo >= 1; signo--) 
    {
        if (sigismember(&pending, signo)) 
            cout << "1";
        else 
            cout << "0";
    }
    cout << endl;
}

void MyHandler(int signo) 
{
    cout << "get a signo: " << signo << endl;
}

int main(int argc, char* argv[]) 
{
    struct sigaction act, oact;
    act.sa_handler = MyHandler;
    act.sa_flags = 0;
    sigemptyset(&act.sa_mask);
    sigaction(SIGINT, &act, &oact);

    while (true);

    return 0;
}

进程串行处理同类信号

#include <iostream>
#include <signal.h>
#include <unistd.h>
#include <cstdio>
#include <cstdlib>
using namespace std;

void Count(int cnt) 
{
    while (cnt) 
    {
        printf("cnt: %d\n", cnt);
        fflush(stdout);
        cnt--;
        sleep(1);
    }
}

void MyHandler(int signo) 
{
    cout << "get a signo: " << signo << endl;
    Count(10);
}

int main(int argc, char* argv[]) 
{
    struct sigaction act, oact;
    act.sa_handler = MyHandler;
    act.sa_flags = 0;
    sigemptyset(&act.sa_mask);
    sigaddset(&act.sa_mask, 3);     //在2号信号捕捉期间,屏蔽3号信号
    sigaction(SIGINT, &act, &oact);

    while (true) sleep(1);

    return 0;
}

  • 当进程正在递达某一个信号期间,同类信号无法被递达
  • 当当前信号正在被捕捉,系统自动将当前信号加入到进程的信号屏蔽字,当捕捉完成,系统又自动解除对该信号的屏蔽
  • 一般一个信号被解除屏蔽的话,会自动递达当前屏蔽信号
  • 进程处理信号的原则是串行处理同类信号,不允许递归处理

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/739730.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【档案专题】二、电子档案管理

导读&#xff1a;主要针对电子档案管理相关内容介绍。对从事电子档案管理信息化的职业而言&#xff0c;不断夯实电子档案管理相关理论基础是十分重要。只有通过不断梳理相关知识体系和在实际工作当中应用实践&#xff0c;才能走出一条专业化加职业化的道路&#xff0c;从而增强…

《黑马头条》 ElectricSearch 分词器 联想词 MangoDB day08-平台管理[实战]作业

07 app端文章搜索 1) 今日内容介绍 1.1)App端搜索-效果图 1.2)今日内容 2) 搭建ElasticSearch环境 2.1) 拉取镜像 docker pull elasticsearch:7.4.0 2.2) 创建容器 docker run -id --name elasticsearch -d --restartalways -p 9200:9200 -p 9300:9300 -v /usr/share/elasticse…

Android 查看ANR和Crash日志(adb bugreport)

今天测试那儿出了个ANR&#xff0c;我自己手机没问题&#xff0c;很烦&#xff0c;定位不了位置。 于是还是得用ADB连接来看一下&#xff0c;之前用&#xff0c;但是老是会忘记&#xff0c;今天总结一下。 ADB命令查看应用包名_adb查看包名命令_&岁月不待人&的博客-C…

基于matlab使用部分或较低分辨率图像快速处理阻塞图像(附源码)

一、前言 此示例展示了如何使用两种策略快速处理阻塞图像&#xff0c;这两种策略可以对高分辨率图像的较小代表性样本进行计算。 处理被阻止的图像可能非常耗时&#xff0c;这使得算法的迭代开发成本过高。有两种常见的方法可以缩短反馈周期&#xff1a;迭代较低分辨率的图像…

【Tensorflow2.x】tensorflow-gpu 在 Ubuntu 上的安装

好几次遇到问为什么安装的 tensorflow 不能调用GPU&#xff0c;之前搞定过几次&#xff0c;前两天又有人问&#xff0c;又捣鼓了很久才搞定&#xff0c;这里简单记录一下我遇到的问题&#xff0c;以及解决方案。 一、安装方法 &#xff08;一&#xff09;安装并更新 conda 1…

C++STL:顺序容器之list

文章目录 1. 概述2. 成员函数3. list容器的创建4. 迭代器5. 访问元素6. 添加/插入元素list insert()成员方法list splice()成员方法 7. 删除元素 1. 概述 STL list 容器&#xff0c;又称双向链表容器&#xff0c;即该容器的底层是以双向链表的形式实现的。这意味着&#xff0c…

RTOS学习笔记

前言 进程&#xff1f;线程&#xff1f;并发&#xff1f;并行&#xff1f;主线程&#xff1f;子线程&#xff1f;主线程中创建子线程&#xff1f;每个线程就是一个死循环&#xff1f; 进程 多个线程&#xff0c;每个线程可以写一个死循环处理一个需要循环执行的代码块&#x…

leetcode-203.移除链表元素

leetcode-203.移除链表元素 文章目录 leetcode-203.移除链表元素题目描述代码提交 题目描述 代码提交 代码 class Solution { public:ListNode* removeElements(ListNode* head, int val) {ListNode *dummyhead new ListNode(0); // 设置一个虚拟头结点,堆上dummyhead->ne…

SOLIDWORKS、UG、Proe三款三维绘图软件哪个好?

提到制图&#xff0c;很多人可能会先想到AutoCAD&#xff0c;但它现在主要会被用来进行二维的平面制图。3DMAX是一款被广泛应用的三维制图软件。Proe也是一种比较好用的三维建模软件。而SW也就是SolidWorks更为知名&#xff0c;它是世界上第一个专为Windows系统开发的三维CAD建…

解决小程序 scroll-view 里面的image有间距、小程序里面的图片之间有空隙的问题。

1&#xff09;小程序 image跟view标签上下会有间隙&#xff0c;解决方法如下&#xff1a; 在image那里设置vertical-align:top/bottom/text-top/text-bottom 原因&#xff1a;图片文字等inline元素默许是跟父级元素的baseline对齐&#xff0c;而baseline又和父级底边有必定间距…

web 前端 Day 3

伪类选择器 <title>伪类选择器</title> </head> <style>a:link {color: beige;} a:visited {color: aquamarine; } a:hover { 鼠标悬停cursor: cell; 鼠标样式font-size: 80px; } a:active {font-size: 70px; } div{width: 300px;height: 400…

813. 打印矩阵

链接&#xff1a; 打印矩阵 题目&#xff1a; 给定一个 rowcolrowcol 的二维数组 aa&#xff0c;请你编写一个函数&#xff0c;void print2D(int a[][N], int row, int col)&#xff0c;打印数组构成的 rowrow 行&#xff0c;colcol 列的矩阵。 注意&#xff0c;每打印完一整行…

JPA的saveAndFlush

#Stable Diffusion 美图活动一期# 关于MyBatis与JPA&#xff1a; 笔者初次接触这两个持久层框架的时候&#xff0c;那还是得从iBatis、Hibernate开始说起。那时候知道的一个很浅显、但最明显的区别就是&#xff1a;iBatis是半自动化的ORM框架&#xff0c;适用于表关联关系复杂的…

浅谈利用树莓派卡片电脑进行图像识别学习和研发

利用树莓派进行图像识别学习和研发是一个非常有前景和潜力的领域。树莓派是一款小巧且功能强大的单板计算机&#xff0c;具备较高的处理能力和丰富的接口&#xff0c;非常适合用于图像识别的应用开发。 在图像识别方面&#xff0c;树莓派可以利用其强大的计算能力和丰富的软件…

react知识点汇总四--react router 和 redux

react-router React-Router 是一个用于在 React 应用中实现页面导航和路由管理的库。它提供了一种方式来创建单页应用&#xff08;Single-Page Application&#xff0c;SPA&#xff09;&#xff0c;其中页面的切换是在客户端进行的&#xff0c;而不需要每次跳转都向服务器请求…

Mac上绿色软件怎么长期保存

1、找到想长期保存的绿色软件&#xff0c;右键拷贝 2、来到「应用程序」&#xff0c;点工具栏-操作-粘贴项目 3、这样绿色软件就长期保留下来了

华纳云:一台香港多IP服务器如何设置多个IP?

在一台香港多IP服务器上设置多个IP的步骤如下&#xff1a; 1.确认服务器支持多个IP地址&#xff1a;首先&#xff0c;确保你的服务器有多个网卡接口或虚拟网卡接口&#xff0c;以支持多个IP地址。 2.查看当前IP配置&#xff1a;运行以下命令来查看当前的IP配置信息&#xff1a;…

深度学习——神经网络参数的保存和提取

代码与详细注释&#xff1a; Talk is cheap. Show you the code&#xff01; import torch import matplotlib.pyplot as plt# 造数据 x torch.unsqueeze(torch.linspace(-1, 1, 100), dim1) # x data (tensor), shape(100, 1) y x.pow(2) 0.2*torch.rand(x.size()) # n…

unity 调用高德SDK

unity 2022.2.20f1c1 一、准备工作&#xff1a; 方式一&#xff1a;Unity打包arr 导入AndroidStudio &#xff0c;AndroidStudio打包 方式二&#xff1a;Unity通过MainActivity.java调用SDK &#xff0c;MainActivity.java 放入到Android Studio中编写代码 二、打包环境…

数字化时代,企业的数据指标体系

在社会节奏越来越快&#xff0c;处理的信息量越来越大的今天&#xff0c;传统的经营管理模式已经适应不了当下的环境。而由经验、情感组成的业务调整以及决策能力不再能正确指导企业走在正确的方向上&#xff0c;所以数据就成为了企业新的业务优化调整和支撑企业高层管理进行决…