Linux进程信号之初识信号

发布时间:2024年01月15日

(。・?・)ノ゙嗨!你好这里是ky233的主页:这里是ky233的主页,欢迎光临~icon-default.png?t=N7T8https://blog.csdn.net/ky233?type=blog

点个关注不迷路?'?'?

目录

一、初识信号?

1.什么是Linux信号

2.结论

3.信号是如何产生的

1.信号的常见处理动作

2.常见的信号

3.如何理解组合键变成信号呢?

4.如何理解信号被进程保存

5.如何理解信号发送的本质

4.signal函数

5.核心转储

二、系统调用接口

1.kill

2.raise

?3.abort

4.如何理解

三、软件条件产生信号

1.alarm

四、硬件异常产生信号

1.除0错误

2.野指针

五、总结


一、初识信号?

1.什么是Linux信号

linux信号本身是一种通知机制,用户和操作系统(本质上都是操作系统)通过发送一定的信号,来通知进程,某些事件已经发生,要来进程后续进行处理

2.结论

  1. 进程要处理信号必须具备信号识别的能力(看到+动作处理)
  2. 那么为什么进程能够识别信号呢?这是因为程序员在写代码的时候写入的
  3. 信号是随机产生的,进程可能正在马自己的事情,所以,信号的后续处理,可能不是立即处理的!
  4. 信号会临时的记录下对应的信号,方便后续进行处理
  5. 进程会在合适的时候处理信号
  6. 一般而言,信号的产生相对于进程而言是异步的

3.信号是如何产生的

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

using namespace std;
int main()
{
    while(1)
    {
        cout<<"hello Linux"<<endl;
        sleep(3);
    }
    return 0;
}

平常我们随便写一个进程,可以用ctrl+c来终止进程,但本质上这是像这个进程发送了2号(SIGINT)信号

1.信号的常见处理动作

  1. 默认(进程自带的,程序员写好的逻辑)
  2. 忽略(也是信号处理的一种方式)
  3. 自定义动作(捕捉信号)

2.常见的信号

我们通过kill -l,就可以插件Linux里常见的信号!

  • 1-31是普通信号是最常用的
  • 34-64的是实时信号

man 7 signal,这个代码可以查看信号常用的描述

3.如何理解组合键变成信号呢?

键盘的工作方式是通过:中断方式进行的,当然也可以识别组合键ctrl+c!

OS解释组合键->查找进程列表->前台运行的进程->OS写入对应的信号到进程内部的位图结构中!

4.如何理解信号被进程保存

首先我们要知道是什么信号,是否产生呢?

进程必须具有保存信号的相关数据结构那么用什么呢?

很显然是位图,我们创建一个unisgned int,用比特位对应的位数,以及代表的1和0就可以解决!

进程的PCB内部保存了信号位图字段?

5.如何理解信号发送的本质

信号位图是在task_struct中保存的,而task_struct内核数据结构是由操作系统来管理的,所以只有OS有资格来更改!所以,所有信号都是由操作系统来发送的!!!

所以信号发送的本质就是:OS向目标进程写信号!修改PCB中的指定位图结构,完成“发送”信号的过程!

4.signal函数

?首先我们知道信号常见处理动作有“自定义捕捉”那么如何捕捉呢?

这就要用到signal函数了

typedef void(*sighandler_t)(int)
sighandler_t signal(int signum,sighandler_t handler)
  • 首先是一个函数指针,返回值是void参数是int
  • 参数一:要捕捉的信号
  • 参数二:传刚刚的函数指针

那么如何使用呢

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

using namespace std;

void catchsig(int signum)
{
    cout<<"捕捉到了一个信号,正在处理中:"<<signum<<"      pid:"<<getpid()<<endl;
}

int main()
{
    signal(SIGINT,catchsig);

    while(1)
    {
        cout<<"我是一个进程,我正在运行...  pid:"<<getpid()<<endl;
        sleep(1);
    }

    return 0;
}

那么我们可以发现两个现象

  1. 首先我们捕捉了一个信号
  2. 那么ctrl+c为什么不能终止这个进程了呢

这是因为特定信号的处理动作,一般只有一个,比如捕捉了就不能忽略了!

signal函数,仅仅是修改进程对信号的后续处理动作,不是直接调用对应的处理动作

比如,我们这次直接就是更改了进程的处理方法将之变成打印,而并没有将之关闭

而signal函数仅仅是注册,而后续没有收到2号信号的话则不进行调用!

同时我们也证明了,ctrl+c就是我们的2号命令

5.核心转储

我们可以发现3号命令后面加了个Core这个是什么呢?这个叫做核心转储。

一般而言,云服务器(生产环境)的核心转储功能是被关闭的

我们可以用这个命令打开我们的核心转储功能

ulimit -c 10240

此时我们在此kill -3就会发现,多了一个core的文件。那么这个文件是什么呢?

所谓核心转储就是当进程出现某种异常情况的时候,是否由OS将当前进程在内存中的相关核心数据,转存到磁盘中,这样做主要是为了调试。而core文件就是核心转储的数据!

二、系统调用接口

1.kill

int kill(pid_t pid int sig)
  • 参数1:指定的进程
  • 参数2:指定的信号
  • 总结就是向指定的进程发送指定的信号

mykill:

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

static void Usage(string proc)
{
    cout<<"Usage:\r\n\t"<<proc<<"signumber processid"<<endl;
}

int main(int argc,char *argv[])
{
    if(argc!=3)
    {
        Usage(argv[0]);
        exit(0);
    }
    int signumber=atoi(argv[1]);
    int procid=atoi(argv[2]);

    kill(procid,signumber);
    return 0;
}

2.raise

int raise(int sig)
  • 参数1:指定的信号
  • 给自己发送指定的信号
int main(int argc,char *argv[])
{
    cout<<"开始运行"<<endl;
    raise(8);
    return 0;
}

?3.abort

void abort(void)
  • 无参数
  • 给自己发送一个abort信号(6号信号,退出),通常用来终止进程!
int main(int argc,char *argv[])
{
    cout<<"开始运行"<<endl;
    abort();
    return 0;
}

4.如何理解

用户调用系统接口,进程执行OS对应的系统调用代码,OS提取接口的参数,然后向目标写入信号,修改对应的进程信号标记位,等待进程独立并执行对应的动作!

三、软件条件产生信号

管道,读端不光不读,而且还关闭了,写端一直写,这个时候写是没有意义的,OS会自动终止对应的写的进程,是通过发送信号的方式,发送SIGPIPE信号

也就是说当这个进程并不能够满足我们的某种行为的时候,OS就会发送信号终止这个进程

1.alarm

unsigned int alarm(unsigned int seconds)
  • 参数一:想要设定的时间

调用alarm函数可以设定一个闹钟,也就是告诉内核在seconds秒之后给当前进程发SIGALRM信号, 该信号的默认处理动 作是终止当前进程

int main(int argc,char *argv[])
{
    alarm(1);
    int count = 0;
    while(1)
    {
        cout<<count++<<endl;
    }
    return 0;
}

我们发现才两万多,为什么这计算这么点呢?

因为这是我们的cout+云服务器网络发送过来+显示到外设=IO所以才会这么慢!

如果我们想要单纯的计算算力,则可以用信号捕捉的方式来计算

int count = 0;

void catchsig(int signum)
{
    cout<<count<<endl;
}

int main(int argc,char *argv[])
{
    alarm(1);
    signal(SIGALRM,catchsig);
    while(1)
    {
        count++;
    }
    return 0;
}

? ?

但是我们设定了一个闹钟,一旦触发一次就会自动移除,所以我们可以捕捉的时候再加上一次就可以了?

int count = 0;

void catchsig(int signum)
{
    cout<<count<<endl;
    alarm(1);
}

int main(int argc,char *argv[])
{
    alarm(1);
    signal(SIGALRM,catchsig);
    while(1)
    {
        count++;
    }
    return 0;
}

这样我们就最基本的完成了一个定时器功能

四、硬件异常产生信号

1.除0错误

如果我们有一个除0错误的代码,OS会给进程发送8号信号

void catchsig(int signum)
{
    sleep(1);
    cout<<"捕获了一个信号: "<<signum<<endl;
}

int main(int argc,char *argv[])
{
    int a=100;
    a/=0;
    signal(SIGFPE,catchsig);
    while(1)
    {
       sleep(1);
    }
    return 0;
}

我们会发现一直在死循环的捕捉,但是为什么会一直死循环呢?

首先我们要理解除0:

首先计算的是cpu,是一个硬件,CPU内部有一个寄存器,叫做状态寄存器(位图),是有对应的状态标记位进行溢出标记的,OS会自动进行计算完毕之后的检测!如果溢出标记位为1,则OS里面会识别到有溢出问题,立即只要找到当前谁在运行,提取出PID,os完成发送信号,进程就会再合适的时候处理

但硬件出现异常,进程一定会退出吗?不一定,只是默认是退出,但是即便不退出,我们也不能做什么。

那么为什么会死循环呢?这是因为寄存器中的异常一直没有被解决!

2.野指针

就比如我们的野指针问题也是一样的,会出现段错误,11号信号(SIGSEGV)

那么如何理解野指针或者越界问题呢?
首先我们的指针找到对应的地址,都是通过地址才找到目标位置的,我们语言上面的地址,全部都是虚拟地址,是由虚拟地址转换成物理地址的,是经过页表+MMU(硬件!)找到对应的物理地址的!所以,野指针或者越界,是非法地址,所以MMU转换的时候,是一定会报错的!

五、总结

所有的信号,有他的来源,但最经全部都是被OS识别,解释,并发送的!

文章来源:https://blog.csdn.net/ky233/article/details/134276183
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。