Linux:信号(一)

1. 信号是什么

信号的概念

Linux中信号(Signal)是进程间通信的一种基本机制,用于通知进程发生了某种事件或异常。信号是异步的,可能由操作系统、其他进程或进程自身触发。

kill -l 指令查看所有的信号

上面的SIGHUP、SIGINT本质就是define宏定义,上面的1~31是普通信号,34~64信号是实时信号。

信号的基本作用

  1. 通知进程某个事件(终止请求,错误处理等)

  2. 允许进程对其他进程或操作系统内核发送简单消息

  3. 处理异常(段错误,除0错误等等)

基本结论

  1. 进程在信号没有产生的时候就知道信号如何处理了。

  2. 信号的处理,不是立即处理,而是等到合适的时候再处理

  3. 进程早已经内置了对于信号的识别和处理方式。

  4. 给进程产生信号的信号源很多

2. 信号的产生

产生信号的方式有很多

为了验证信号的产生我们,需要使用下面的函数

定义:自己设置signum(第一个参数)信号的处理方式

函数声明与头文件

cpp 复制代码
#include<signal.h>

typedef void (*sighandler_t)(int);

sighandler_t signal(int signum,sighandler_t handler);

参数:

signum:是几号信号即信号的名称。

handler:函数指针,一个返回值类型为void 参数为int的函数

返回值:旧的信号处理函数,(函数指针)

(1). 键盘产生信号

|---------|-----------------------|
| 按键 | 功能 |
| ctrl+c | 向进程发送SIGINT信号,终止进程。 |
| ctrl+\ | 向进程发送SIGQUIT信号,终止进程 |
| ctrl+z | 向进程发送SIGSTP信号,暂停进程的执行 |

以ctrl+c为例

ctrl+c就是给目标进程发送信号(中断信号),默认情况下导致进程终止掉。

ctrl+c只能终止前台进程无法终止后台进程。(在Linux中,前台进程只能有一个,后台进程可以为多个)

./test 前台进程

./test & 后台进程

我们可以看到ctrl+c没有终止进程,通过指令 kill -9 进程id 才将进程杀掉

在程序执行时也可以切换前后台程序

jobs 指令查看所有后台任务

fg 任务号 :将指定的进程变为前台进程

ctrl+z :暂停当前进程。然后把进程切换到后台。

bg 任务号 :让进程恢复运行变为后台进程

我们看看被signal函数修改了处理方法(新处理方法不终止进程)的2号信号能否终止进程

cpp 复制代码
#include<iostream>
#include <sys/types.h>
#include <unistd.h>
#include<signal.h>

int cnt=0;
void handlerSig(int sig)
{
    std::cout<<"信号获取成功 ->"<<sig<<std::endl;

}

int main()
{

    signal(SIGINT,handlerSig);

    for(int i=0;;i++)
    {
        std::cout<<i<<" hello pc pid: "<<getpid()<<std::endl;//效率低

        sleep(1);
    }
    return 0;
}

执行结果如下,ctrl+c并不能终止进程了

(2). 系统接口

kill

功能:向指定的进程或进程组发送信号

函数原型及头文件

cpp 复制代码
#include<signal.h>
int kill(pid_t pid,int sig);

参数:

pid:目标进程的id

sig:要发送的信号编号

返回值:

成功返回0,失败返回-1

raise

功能:向自己发送信号

函数原型

cpp 复制代码
#include<signal.h>
int raise(int  sig);

参数:

sig:发送的信号编号

返回值:

成功返回0,失败返回非0值

abort

功能:向自己发送6号信号来终止程序执行。不会被signal所影响(会执行signal的自定义函数)进程还是会退出

函数原型与头文件

cpp 复制代码
#include<stdlib.h>

void abort(void);

参数:无

(3). 硬件异常
cpp 复制代码
#include<iostream>
#include <sys/types.h>
#include <unistd.h>
#include<signal.h>

void handlerSig(int sig)
{
    std::cout<<"信号获取成功 ->"<<sig<<std::endl;

     raise(9);
    // abort();
}

int main()
{
    for(int i=1;i<=32;i++)
    signal(i,handlerSig);

    for(int i=0;;i++)
    {
        std::cout<<i<<" hello pc pid: "<<getpid()<<std::endl;//效率低
        // abort();//固定给自己发送信号6号,要求进程必须处理,不会继续执行程序了
 
        int a=10;
        int sum=0;
        a/=sum;//  /0错误  8号信号
 
        // int *p=nullptr;
        // *p=100;//野指针11号错误
        sleep(1);
    }
    return 0;
}

除0错误触发8号信号与野指针触发11号信号,下面分别演示

OS为什么会知道硬件异常了呢?

信号全部都是操作系统发送的。程序犯错了后被OS识别到进程犯错()再发送信号

我们要访问一个变量进程控制块task_struct一定要会经过页表映射将虚拟地址转换为物理地址后访问,在CPU的MMU(内存管理单元)负责处理CPU的内存访问请求的计算机硬件。MMU中有相应的状态信息,访问不属于我们的虚拟地址,虚拟地址转换为物理地址时就会发生错误,将错误写入到自己的状态信息中,然后操作系统就会识别到错误,进而发送信号。

(4). 软件条件

管道通信时如果读端关闭了,写端进程还一直向管道写入数据,那此时写端进程就会收到SIGPIPE信号(13号信号),因为不读了,写就没有意义。操作系统不做没有意义的事情就直接终止了。SIGPIPE就是防止进程在无意义操作中浪费资源与实践。

cpp 复制代码
#include<stdio.h>
#include<iostream>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<signal.h>
void handlerSig(int sig)
{
    std::cout<<"信号获取成功 ->"<<sig<<std::endl;
    // std::cout<<cnt<<std::endl;
    raise(9);
    // abort();
}

int main()
{
    // for(int i=1;i<=32;i++)
    // signal(i,handlerSig);

    int fd[2];
    if(pipe(fd)<0)
    {
        perror("pipe error");
        exit(1);
    }

    pid_t id=fork();
    if(id==0)//子进程
    {
        close(fd[0]);//关闭读
        char buffer[4096]="ttttt skl";
        while(1)
        {
            write(fd[1],buffer,strlen(buffer));
            sleep(1);
        }
        close(fd[1]);
        return 0;
    }
    else
    {
        close(fd[0]);
        close(fd[1]);
        int status = 0;
        waitpid(id,&status,0);//通过进程等待查看信号
        printf("获得信号:%d\n",status&0x7f);
    }

    return 0;
}

&0x7f获取后七位的值

使用signal的结果如下

13号信号不解释,17号是子进程终止时系统向父进程发送的信号

通过进程等待结果如下

还能通过alarm函数来设定闹钟,到达设定时间后向我们的进程发送SIGALRM信号(14号信号)

后调用的alarm会被新的调用覆盖掉之前的定时器

函数原型与头文件

cpp 复制代码
#include<unistd.h>
unsigned int alarm(unsigned int seconds);

参数:

seconds:指定秒数后向当前进程发送SIGALRM信号,如果为0返回之前设置alarm的剩余时间。

返回值:函数返回之前定时器剩余秒数,如果之前没有设置定时器则返回0。(即之前alarm设置的秒数剩余多少秒)。

cpp 复制代码
#include<iostream>
#include <sys/types.h>
#include <unistd.h>
#include<signal.h>

void handlerSig(int sig)
{
    std::cout<<"信号获取成功 ->"<<sig<<std::endl;

    raise(9);
    // abort();
}

int main()
{
    for(int i=1;i<=32;i++)
    signal(i,handlerSig);
    
    alarm(6);

    for(int i=0;;i++)
    {
        std::cout<<i<<" hello pc pid: "<<getpid()<<std::endl;//效率低

        sleep(1);

    }
    return 0;
}

执行结果如下所示


这篇就到这里啦,(๑′ᴗ‵๑)I Lᵒᵛᵉᵧₒᵤ❤

相关推荐
未来并未来19 分钟前
解锁微服务潜能:深入浅出 Nacos
运维·微服务·架构
风好衣轻29 分钟前
【环境配置】在Ubuntu Server上安装5090 PyTorch环境
linux·pytorch·ubuntu
华清远见成都中心1 小时前
Linux嵌入式和单片机嵌入式的区别?
linux·运维·单片机·嵌入式
lisanmengmeng1 小时前
rabbitMQ 高可用
linux·分布式·rabbitmq
小妖6662 小时前
ubuntu 22.04 更换阿里源 (wsl2 参照)
linux·运维·ubuntu
凉、介2 小时前
SylixOS 下的消息队列
linux·wpf·sylixos
egoist20232 小时前
【Linux仓库】进程概念与基本操作【进程·贰】
linux·运维·服务器·指令·进程操作·理解进程
dessler2 小时前
ZooKeeper-监控(Monitor)
linux·运维·zookeeper
延凡科技3 小时前
低空数联无人机AI智慧巡查平台
大数据·运维·人工智能·无人机·智慧城市·制造