Linux进程信号(信号的产生)

目录

什么是信号?

信号的产生

信号产生方式1:键盘

前台进程

后台进程

查看信号

signal系统调用

案例

理解进程记录信号

软件层面

硬件层面

信号产生方式2:指令

信号产生方式3:系统调用

kill系统调用

案例

其他产生信号的函数调用

1.raise

 2.abort

信号产生方式4:软件条件

什么是软件条件?

alarm系统调用

使用

1.

2.

3.

信号的产生5:异常

示例1:

示例2:

那么os如何知道进程内部错误?

理解一下野指针的异常

Term和Core的区别


什么是信号?

信号!=信号量

信号:是一种用户,OS,其他进程向目标进程发送异步事件的一种方式。

同步和异步:

1.怎么识别信号?

识别信号是内置的。即进程识别信号时程序的内置属性。可以类比一下语言的内置数据类型(int,double)

2.信号产生后,进程知道怎么处理吗?知道  。如果没产生,进程知道怎么处理吗?也知道,因为信号的处理方法在产生之前就已经准备好了。

3.信号会被进程立即处理吗?

不一定,如果不,那么什么时候处理呢?进程会在一个合适的时候处理。

信号到来 --> <进程不立即处理,记录信号>  --> 合适的时候处理信号

4.处理信号的方法: 1.默认行为 2.忽略信号3.自定义动作

信号的产生

信号产生方式1:键盘

这有一段死循环的程序

#include <iostream>
#include <unistd.h>
int main()
{
    while (true)
    {
        std::cout << "hello world" << std::endl;
        sleep(1);
    }
    return 0;
}

从键盘中输入 ctrl+c 可以前台进程杀掉前台进程。

前台进程

命令 :       ./程序名     

运行起来的是前台进程也可以用ctrl+c杀掉进程

后台进程

命令 :  ./进程名&

后台进程无法用 ctrl+c 杀死

杀掉后台进程的方法:1. fg命令将后台进程切换到前台,在ctrl+c杀掉进程。

                                    2. kill -9 pid(对应进程的pid)

方法一:

方法二: 

查看信号

kill -l  

 

man 7 signal//中可以找到相应信号的描述

signal系统调用

信号的自定义捕捉

signal的作用是捕捉signum号信号,捕捉之后,执行handler指向的函数

signum: 要捕捉的信号

#define SIGQUIT 3

handler:处理函数

案例

ctrl+\  默认终止进程

#include <iostream>
#include <unistd.h>
#include <signal.h>
void handler(int signo)
{
    std::cout << "signo: " << signo << std::endl;
}
int main()
{
    //捕捉  3)SIGQUIT信号
    signal(SIGQUIT, handler);
    while (true)
    {
        std::cout << "hello world" << std::endl;
        sleep(1);
    }
    return 0;
}

 

ctrl+\,被捕捉不再执行默认行为(默认终止),而是将这个信号传给signo并调用自定义函数 

根据上面的案例是不是我们捕捉所有信号,那么这个死循环程序就不会被杀死了呢?

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <functional>
using func_t = std::function<void(int)>;
void handler(int signo)
{
    std::cout << "signo: " << signo << std::endl;
}
int main()
{

    //signal(SIGQUIT, handler);
    //捕捉信号
    for(int i = 1;i<=31;i++)
    {
        func_t f = signal(i,handler);
        std::cout<<"捕捉:"<<i<<std::endl;
    }
    while (true)
    {
        std::cout << "hello world" << std::endl;
        sleep(1);
    }
    return 0;
}

从结果上可以知道ctrl+c   ctrl+\因为被捕捉了,所以杀不掉进程,但kill -9 可以 杀掉进程,也就说明了,9)SIGKILL 不能被捕捉。

理解进程记录信号

软件层面

键盘(比如ctrl+c) -> os  -> 进程 --> 记录信号

因为os管理外设,所以键盘将信号传给os,os也管理进程,os在将信号传给进程,之后进程记录信号。

进程如何记录信号?

总结:

发送流程:键盘中的信号发送给os,os在发送给进程,修改进程对应的信号位图和其维护处理方法的函数指针数组。

 无论什么信号都是由os发送的。

os是task_struct的唯一管理者,所以只有 os有权利修改信号位图。

硬件层面

os是如何知道键盘上有数据?

轮徇(不可以,os的任务太多了)

硬件中断

冯诺依曼体系结构

在数据层面上,我们知道cpu不与外设直接打交道。而是通过存储器

但在cpu和键盘是连接的,键盘可以直接将数据交给cpu

硬件中断 --> cpu告诉os,键盘上有数据  --> 这样硬件和os就可以并行执行了

信号vs信号中断

信号是纯软甲的

信号中断是纯硬件的

信号产生方式2:指令

指令:

kill -信号 相应进程的pid

信号产生方式3:系统调用

kill系统调用

kill的作用:就是OS向(pid)进程 发送sig信号

使用代码:

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <functional>
#include <sys/types.h>
int main()
{
    int cnt= 5;
    while(true)
    {
        std::cout<<cnt<<std::endl;
        if(cnt<=0)
            kill(getpid(),SIGQUIT);
        sleep(1);
        cnt--;
    }
    return 0;
}

执行结果:

 kill()系统调用与kill命令的关系

kill命令行就是封装的kill()系统调用。

案例

简单实现下自己的kill

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <functional>
#include <sys/types.h>


void Usage(char* argv)
{
    std::cout<<"Usage error: "<<argv<<" signo"<<" pid"<<std::endl;
    exit(1);
}
//./kill 9 pid


int main(int argc,char* argv[])
{
    if(argc!=3)
    {
        //正确使用提醒
        Usage(argv[0]);
    }

    int signo = std::stoi(argv[1]);
    pid_t pid = std::stoi(argv[2]);
    int ret = kill(pid,signo);
    
    if(ret<0)
    {
        perror("kill");
        exit(2);
    }
    return 0;
    
}

执行结果: 

其他产生信号的函数调用

1.raise

作用:让OS向调用该函数的进程返送sig信号

 2.abort

作用:让OS向调用该函数的进程返送6号信号(该进程被干掉)

 这两个函数比较简单

这两个函数都是由kill封装的,libc库函数

kill(getpid(),sig);//raise
kill(getpid(),6);//abort

信号产生方式4:软件条件

什么是软件条件?

软件条件产生信号,通常指的是在软件应用程序中,根据某些特定的条件或逻辑判断来触发或生成信号

比如:管道中,读端关闭,但写端没有关闭   这时,os回想进程发送  13)SIGPIPE信号

alarm系统调用

作用:设置一个seconds秒的闹钟,时间一到就会给调用闹钟的进程发送 14)SIGALRM信号

 alarm(条件);

里面设置的时间就相当于条件,满足条件就发送信号。

OS中维护的定时器的大概内核数据结构

 

使用

1.

#include <iostream>
#include <unistd.h>


int main()
{
    alarm(1);
    int cnt = 0;
    while(true)
    {
        //一秒打印多少次
        std::cout<<cnt<<std::endl;
        cnt++;
    }
    return 0;
}

结果:

打印了11万多次,快只能说很慢。 

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <functional>
#include <sys/types.h>



int cnt = 0;
void handler(int signo)
{
    std::cout<<cnt<<std::endl;
    exit(0);
}
int main()
{
    alarm(1);
    signal(SIGALRM,handler);
    while(true)
    {
        //std::cout<<cnt<<std::endl;
        cnt++;
    }
    return 0;
}

结果:计算了将近16亿次 

从上面两个程序可以看出,IO影响计算速度。

2.

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <functional>
#include <sys/types.h>



int main()
{
    alarm(5);
    sleep(1);
    int n = alarm(0);//alarm(0)取消闹钟   n上一个闹钟剩余的时间
    std::cout<<n<<std::endl;
    return 0;
}

 3.

pause

作用:等待信号

 闹钟是一次性的发送完信号后就被取消。

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <functional>
#include <sys/types.h>
#include <vector>
using func_t = std::function<void()>;

int cnt = 0;
std::vector<func_t> v;//任务

void handler(int signo)
{
    for (auto &e : v)
    {
        e();
    }
    alarm(2);
    cnt++;
}
int main()
{
    v.push_back([]()
                { std::cout << "我是一个日志任务" << std::endl; });
    v.push_back([]()
                { std::cout << "我是一个下载任务" << std::endl; });
    v.push_back([]()
                { std::cout << "我是一个mysql任务" << std::endl; });
    alarm(2);//一次性闹钟,超时后就会被取消
    signal(SIGALRM, handler);
    while (true)
    {
        pause();//等待信号
        std::cout << "我醒来了..." << std::endl;
        std::cout << "cnt: " << cnt << std::endl;
    }
}

运行结果:

将上述逻辑中的信号变为硬件中断,就是OS的运行逻辑。

OS本质上就是一个中断处理程序 

信号的产生5:异常

示例1:

野指针

int main()
{
    int *p = nullptr;
    *p = 0;//野指针
    return 0;
}

Segmentation fault  段错误       

11)SIGEGV 

示例2:

int main()
{
    int a = 10;
    a/=0;
    return 0;
}

Floating point exception   浮点异常

8)SIGFPE

 现在我们可以理解一下为什么程序会崩溃?
出现错误时,OS会向进程发送相应的信号,从而进程被干掉。

那么os如何知道进程内部错误?

比如:div 0

cpu中有状态寄存器 Eflag,Eflag中有个core dump(溢出标记为)标记为,1.cpu计算错误

                                                                                               0.无错误

验证:

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <functional>
#include <sys/types.h>
#include <wait.h>

int main()
{
    pid_t pid = fork();
    if(pid==0)
    {
        //子进程
        int a = 10;
        a/=0;
    }
    else
    {
        int status;
        waitpid(pid,&status,0);
        std::cout<<"core dump: "<<((status>>7)&1)<<std::endl;
        std::cout<<"signo: "<<(status&0x7F)<<std::endl;
    }
}

 结果:

 正如所料:core dump被设为1,并向子进程发送8)SIGFPE信号

 --------------------------------------------------------------------------------------------------------------------------------

void handler(int signo)
{
    std::cout<<"signo: "<<signo<<std::endl;
}
int main()
{
    pid_t pid = fork();
    if(pid==0)
    {
        //子进程
        int a = 10;
        a/=0;
    }
    else
    {
        int status;
        waitpid(pid,&status,0);
        std::cout<<"core dump: "<<((status>>7)&1)<<std::endl;
        std::cout<<"signo: "<<(status&0x7F)<<std::endl;
    }
}

差不多的程序如果捕获了8号信号,

结果:

os一直发送8号信号。

总结:

cpu错误 --> 谁弄坏了cpu,os发送信号杀死进程   (--> 如果捕捉了8号信号--->进程没退出--->还要调度-->还要切换执行等 -->  os不会修复Eflag中core dump(1)-->一直触发信号)

理解一下野指针的异常

 野指针的错误是由cpu中MMU错误引起的

Term和Core的区别

Trem:正常终止,不需要debug

Core:核心转储--->在当前目录下形成core文件-->进程崩溃,将进程在内存中的部分信息保存起来,方便以后调试-->一般被云服务器关闭(如果崩溃一次形成一个新的core,那么就可能把磁盘空间占满)

但当前目录下没有啊 

如何打开?

ulimit -a//查看

可以看到core file size为0;

ulimit -c 大小//改变core file size

 

readelf -S  ./sig  //查看程序elf格式

 

运行程序就可已生成core.pid的文件

如果无法生成core文件的话

在root下,输入下面的命令就可以了

echo core > /proc/sys/kernel/core_pattern
core-file core.pid//方便调试

  

如果是子进程异常呢?core的生成条件?

core dump--> 退出信号的终止动作是core && 云服务器开启core 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

7.qsqlquerymodel 与 qtableview使用

目录 qtableview 委托QStyledItemDelegateQAbstractItemDelegateCheckBoxItemDelegate使用qtableview控制列宽&#xff0c;行高&#xff0c;隐藏拖拽行列 qtableview 委托 //设置单元格委托 void setItemDelegate(QAbstractItemDelegate *delegate); QAbstractItemDelegate *it…

利用 Avalonia UI 构建 Blazor 混合应用程序

Blazor 是一个 .NET 前端框架&#xff0c;用于仅使用 .NET 技术构建 Web 应用程序。2021 年&#xff0c;Blazor 扩展到桌面端&#xff0c;推出了 Blazor Hybrid&#xff08;混合&#xff09;&#xff0c;使开发者可以在桌面平台上使用已有的技能。 Blazor 混合应用程序是传统的…

MFC中Excel的导入以及使用步骤

参考地址 在需要对EXCEL表进行操作的类中添加以下头文件&#xff1a;若出现大量错误将其放入stdafx.h中 #include "resource.h" // 主符号 #include "CWorkbook.h" //单个工作簿 #include "CRange.h" //区域类&#xff0c;对Excel大…

【深入浅出】Linux进程(三)

&#x1f4c3;博客主页&#xff1a; 小镇敲码人 &#x1f49a;代码仓库&#xff0c;欢迎访问 &#x1f680; 欢迎关注&#xff1a;&#x1f44d;点赞 &#x1f442;&#x1f3fd;留言 &#x1f60d;收藏 &#x1f30f; 任尔江湖满血骨&#xff0c;我自踏雪寻梅香。 万千浮云遮碧…

2024 第五次周赛

A: 直接遍历即可 #include<bits/stdc.h> using namespace std;typedef long long ll; typedef pair<ll, ll>PII; const int N 2e6 10; const int MOD 998244353; const int INF 0X3F3F3F3F;int n, m; int main() {cin >> n;int cnt 0;for(int i 0; i …

gitlab无法创建合并请求是所有分支都不显示

点击Merge Requests ------> New merge request 创建新的合并请求时&#xff0c;在Source branch和Target branch中一个分支都不显示 排查思路&#xff1a; 1.怀疑是权限问题。 发现只有我的一个账号出现&#xff0c;检查了账号的权限&#xff0c;尝试了master、develop角色…

Linux中给普通账户一次性提权

我在以前文章中Linux常见指令大全&#xff08;必要知识点&#xff09;-CSDN博客 写过sudo的概念与用法。其实本质就是提权用的但是在某些场景下就算提权了也不能使用。 例如&#xff1a;打开主工作目录 他不相信你这个用户&#xff0c;虽然你是erman 解决方法 使用root账号打开…

【C++】—掌握STL string类:string的模拟实现

文章目录 &#x1f49e;1.浅拷贝&#x1f49e;2.深拷贝&#x1f49e;3. string类的模拟实现&#x1f49e;3.1 string的构造函数&#x1f49e;3.2 string的析构函数&#x1f49e;3.3 string的拷贝构造&#x1f49e;3.4 string的size&#x1f49e;3.5 string的operator[]&#x1…

详解基于C#开发Windows API的SendMessage方法的鼠标键盘消息发送

在C#中&#xff0c;SendMessage方法是一个强大的工具&#xff0c;它允许我们与Windows API交互&#xff0c;模拟键盘和鼠标事件。本文将详细介绍如何使用SendMessage方法来发送鼠标和键盘消息。 1. SendMessage方法概述 SendMessage是Windows API中的一个函数&#xff0c;它用…

15.UE5等级、经验、血条,魔法恢复和消耗制作

2-17 等级、经验、血条、魔法消耗_哔哩哔哩_bilibili 目录 1.制作UI&#xff0c;等级&#xff0c;经验&#xff0c;血条 ​2.为属性面板绑定角色真实的属性&#xff0c;实现动态更新 3.魔法的消耗和恢复 1.制作UI&#xff0c;等级&#xff0c;经验&#xff0c;血条 创建控…

<项目代码>YOLOv8 玉米地杂草识别<目标检测>

YOLOv8是一种单阶段&#xff08;one-stage&#xff09;检测算法&#xff0c;它将目标检测问题转化为一个回归问题&#xff0c;能够在一次前向传播过程中同时完成目标的分类和定位任务。相较于两阶段检测算法&#xff08;如Faster R-CNN&#xff09;&#xff0c;YOLOv8具有更高的…

现场工程师日记-MSYS2迅速部署PostgreSQL主从备份数据库

文章目录 一、概要二、整体架构流程1. 安装 MSYS2 环境2. 安装postgresql 三、技术名词解释1.MSYS22.postgresql 四、技术细节1. 创建主数据库2.添加从数据库复制权限3. 按需修改参数&#xff08;1&#xff09;WAL保留空间&#xff08;2&#xff09;监听地址 4. 启动主服务器5.…

堆排序与链式二叉树:数据结构与排序算法的双重探索

大家好&#xff0c;我是小卡皮巴拉 文章目录 目录 引言 一.堆排序 1.1 版本一 核心概念 堆排序过程 1.2 版本二 堆排序函数 HeapSort 向下调整算法 AdjustDown 向上调整算法 AdjustUp 二.链式二叉树 2.1 前中后序遍历 链式二叉树的结构 创建链式二叉树 前序遍历…

【LinuxC编程】06 - 守护进程,线程

进程组和会话 概念和特性 进程组&#xff0c;也称之为作业。BSD于1980年前后向Unix中增加的一个新特性。代表一个或多个进程的集合。每个进程都属于一个进程组。在waitpid函数和kill函数的参数中都曾使用到。操作系统设计的进程组的概念&#xff0c;是为了简化对多个进程的管…

【MongoDB】MongoDB的聚合(Aggregate、Map Reduce)与管道(Pipline) 及索引详解(附详细案例)

文章目录 MongoDB的聚合操作&#xff08;Aggregate&#xff09;MongoDB的管道&#xff08;Pipline操作&#xff09;MongoDB的聚合&#xff08;Map Reduce&#xff09;MongoDB的索引 更多相关内容可查看 MongoDB的聚合操作&#xff08;Aggregate&#xff09; 简单理解&#xff…

Python的函数(补充浅拷贝和深拷贝)

一、定义 函数的定义&#xff1a;实现【特定功能】的代码块。 形参&#xff1a;函数定义时的参数&#xff0c;没有实际意义 实参&#xff1a;函数调用/使用时的参数&#xff0c;有实际意义 函数的作用&#xff1a; 简化代码提高代码重用性便于维护和修改提高代码的可扩展性…

Unity常见问题合集(一)

PS&#xff1a;不定期更新...... 目录 &#xff08;1&#xff09;无法关闭自动编译&#xff08;Edit — Preference — General — Auto Refresh&#xff09; &#xff08;1&#xff09;无法关闭自动编译&#xff08;Edit — Preference — General — Auto Refresh&#xff0…

HTB:Sightless[WriteUP]

目录 连接至HTB服务器并启动靶机 使用nmap对靶机TCP端口进行开放扫描 继续使用nmap对靶机开放的TCP端口进行脚本、服务扫描 首先尝试对靶机FTP服务进行匿名登录 使用curl访问靶机80端口 使用浏览器可以直接访问该域名 使用浏览器直接访问该子域 Getshell 横向移动 查…

深度学习-神经网络基础-网络搭建-损失函数-网络优化-正则化方法

一. 神经网络搭建和参数计算 一个继承(nn.model), 两个方法(init, forward) 简介 在pytorch中定义深度神经网络其实就是层堆叠的过程&#xff0c;继承自nn.Module&#xff0c;实现两个方法&#xff1a; init方法中定义网络中的层结构&#xff0c;主要是全连接层&#xff0c;…

全彩LED显示屏有几种安装方式?

全彩LED显示屏的安装方式多样&#xff0c;根据不同的使用场景和安装环境&#xff0c;可以选择最合适的安装方法。以下是一些常见的全彩LED显示屏安装方式&#xff1a; 室内显示屏安装方式 吊装&#xff1a;适用于室内承重混凝土顶&#xff0c;可以使用标准吊件&#xff0c;吊杆…