Linux信号——信号的产生(1)

注:信号vs信号量:两者没有任何关系!

信号是什么?

Linux系统提供的,让用户(进程)给其他进程发送异步信息的一种方式。

进程看待信号的方式:

1.信号在没有发生的时候,进程已经知道信号发生时该如何处理。

2.进程能够认识进程,很早之前,有人给进程中设置了识别特定信号的方式。

3.信号到来的时候,进程正在处理更重要的事情,进程暂时不能处理到来的信号,进程必须暂时要将到来的信号进行临时保存。

4.信号到了,可以不立即处理,可以在合适的时间处理。

5.信号是随时产生的,进程无法准确预料,所以信号是异步发送的。

为什么要有信号?

系统要求进程要有随时响应外部信号的能力,随后做出反应。

信号的具体知识

以这个时间轴进行学习

信号的产生

常见信号

数组和名字都可以标识信号,名字其实就是宏。

使用kill命令查看信号。

1-31为常用信号,右下的红框中是实时信号。

没有0,没有32,33 信号,共62个信号

信号的处理的方式------signal

a. 默认 动作

b. 自定义 处理信号------捕捉

c. 忽略 了信号------是处理了信号吗?是的,处理方式就是忽略。

实际执行信号的处理动作称之为信号的递达,也就是以上三种方式。

signal函数

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

typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

参数说明:
signum :信号编号(如 SIGINT、SIGTERM)。
handler :信号处理函数,可以是:
SIG_DFL :恢复默认行为。
SIG_IGN :忽略信号。
自定义函数指针:用户定义的处理函数。

返回值: (类型函数指针)

成功时返回之前的处理函数的地址。

失败时返回 SIG_ERR。

产生信号的第一种方式:kill命令

kill -9 进程pid :发送一个信号,杀死一个进程

c 复制代码
#include<iostream>
#include<unistd.h>
#include<sys/types.h>
#include<signal.h>
int main()
{
    while(true)
    {
        std::cout << "I am activing...,pid:" << getpid() << std::endl;
        sleep(1);
    }
    return 0;
}

结果:

使用命令kill -9 1121788

进程被杀死。


kill -2 进程pid :发送一个信号,默认行为是使进程自己终止。

1.默认行为------进程自己终止

c 复制代码
#include<iostream>
#include<unistd.h>
#include<sys/types.h>
#include<signal.h>
int main()
{
    while(true)
    {
        std::cout << "I am activing...,pid:" << getpid() << std::endl;
        sleep(1);
    }
    return 0;
}

结果:

在运行的进程中使用kill -2 1121490

进程自己终止。

2.自定义处理信号------捕捉

例子:

c 复制代码
#include<iostream>
#include<unistd.h>
#include<sys/types.h>
#include<signal.h>
void handler(int signo)
{
    std::cout << "get a sig, number is: "<< signo << std::endl;
}
int main()
{
    //signal调用完了,handler方法会被立即执行吗?不会,只是设置对应信号的处理方法。
    //未来我们收到对应的信号才执行handler方法。
    //未来进程如果一直没有收到SIGINT,handler也就永远不会被调用。
    signal(SIGINT,handler);//handler(SIGINT)
    while(true)
    {
        std::cout << "I am activing...,pid:" << getpid() << std::endl;
        sleep(1);
    }
    return 0;
}

结果:

在运行的进程中使用kill -2 1120892

默认行为被更改成其他方法。

3.忽略信号

c 复制代码
#include<iostream>
#include<unistd.h>
#include<sys/types.h>
#include<signal.h>
int main()
{
    signal(SIGINT,SIG_IGN);//ignore忽略
    while(true)
    {
        std::cout << "I am activing...,pid:" << getpid() << std::endl;
        sleep(1);
    }
    return 0;
}

结果:

在运行的进程中使用kill -2 1120892

进程无任何反应------这条信号被忽略。

产生信号的第二种方式:键盘产生信号

Ctrl + c 被操作系统解释成2号信号

Ctrl + \ 被操作系统解释成3号信号

产生信号的第三种方式:系统调用

kill 系统调用函数

对任意进程发送任意信号。

c 复制代码
#include <sys/types.h>
#include <signal.h>

int kill(pid_t pid, int sig);

参数说明:
pid目标进程的进程 ID ,具体行为取决于其取值:

>0:发送信号给 PID 为 pid 的进程。

=0:发送信号给当前进程组内的所有进程。

sig :要发送的信号编号(如 SIGTERM、SIGKILL)。

返回值:

成功返回 0,失败返回 -1,并设置 errno 。

例子:自定义mykill函数

c 复制代码
#include<iostream>
#include<unistd.h>
#include<sys/types.h>
#include<signal.h>
#include<errno.h>
#include<string.h>
using namespace std;

//mykill -9 pid
int main(int argc,char* argv[])
{
    if(argc != 3)
    {
        cout << "Usage: " << argv[0] << " -signumber pid" << endl;
        return 1;
    }

    int signumber = stoi(argv[1]+1);
    int pid = stoi(argv[2]);

    int n = kill(pid, signumber);
    if(n < 0)
    {
        cerr << "kill error, " << strerror(errno) << endl;
    }
    return 0;
}

raise 系统调用函数

对当前进程发送任意信号。

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

int raise(int sig);  // 向当前进程发送信号 sig

参数说明:
sig: 信号编号 (如 SIGINT、SIGTERM)。
返回值:

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

abort系统调用函数

对当前进程发送6号信号。

c 复制代码
#include <stdlib.h>  // 必须包含的头文件

void abort(void);

**参数说明:**无(void)。

**返回值:**无返回值(void)。

产生信号的第四种方式:软件条件

alarm函数
对当前进程等待 seconds 秒后发送14号信号。

c 复制代码
#include <unistd.h>

unsigned int alarm(unsigned int seconds);

参数说明: seconds -- 定时器的时间(秒)。若为 0,表示取消之前设置的定时器。
返回值:

返回之前尚未触发的定时器的剩余秒数。

若之前没有定时器,返回 0。

例子: 向当前进程5秒后发送14号信号,终止进程。

c 复制代码
int main()
{
    alarm(5);//响一次

    int cnt = 0;
    while(true)
    {
        sleep(1);
        cout<< "cnt: "<< cnt++ << endl;
    }
    return 0;
}

结果: 向进程发送了14号信号,终止进程

注:闹钟只响一次。

例: 关于alarm的返回值

c 复制代码
#include<iostream>
#include<unistd.h>
#include<sys/types.h>
#include<signal.h>
using namespace std;

int g_cnt = 0;
int ret = 0;

void handler(int sig)
{
    std::cout << "get a sig: "<< sig <<" g_cnt: "<< g_cnt <<endl;
    int n = alarm(2);
    cout<<"剩余时间:"<< n <<endl;
}

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

    alarm(50);//响一次

    int cnt = 0;
    while(true)
    {
        sleep(1);
        cout<< "cnt: "<< cnt++ << endl;
    }
    return 0;
}

结果: 在 cnt:3 时按下 Ctrl + C

这时获取alarm的返回值是剩余时间。

产生信号的第五种方式:异常

  1. 除0错误
    例子:
c 复制代码
#include<iostream>
#include<unistd.h>

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

    while(true) sleep(1);
    return 0;

}

出现除0错误。

结果:发送8号信号(SIGFPE)

  1. 野指针问题
    例子:
c 复制代码
#include<iostream>
#include<unistd.h>

int main()
{
    int *p = nullptr;
   *p = 100;//野指针

    while(true) sleep(1);
    return 0;

}

结果:发送11号信号(SIGEGV)

关于信号产生的各种情况的理解

信号保存在进程的PCB中,且以位图 的方式保存在PCB中。

在PCB中以 uint32_t pending 变量进行保存。

给进程发送信号,其实就是写入信号。向进程PCB中写入信号数据,PCB是内核数据结构,只有操作系统有权限写入,若用户想写入,操作系统提供系统调用供用户使用。
所以以上的信号产生的5种方式,实际上最终都是交给操作系统进行最后向进程写入信号的操作。

对于异常问题的解释

  1. 除0错误
  2. 野指针

总结

相关推荐
在野靡生.41 分钟前
Ansible(4)—— Playbook
linux·运维·ansible
Linux技术芯1 小时前
Linux内核内存管理 ARM32内核内存布局的详细解析和案例分析
linux
烨鹰1 小时前
戴尔电脑安装Ubuntu双系统
linux·运维·ubuntu
mzak1 小时前
vscode集成deepseek实现辅助编程(银河麒麟系统)【详细自用版】
linux·vscode·编辑器·银河麒麟·deepseek
haoranyyy1 小时前
mac环境中Nginx安装使用 反向代理
linux·服务器·nginx
HX科技1 小时前
Debian系统_主板四个网口1个配置为WAN,3个配置为LAN
linux·运维·网络·debian
kfepiza3 小时前
Debian/Ubuntu的networking的`/etc/network/interfaces`配置文件,如何配置route路由
linux·网络·tcp/ip·ubuntu·debian·ip·tcp
Esun_R3 小时前
使用防火墙与 fail2ban 防止公网服务器被攻击
linux
榆榆欸3 小时前
4.Socket类、InetAddr类、Epoll类实现模块化
linux·c++·tcp/ip
易保山4 小时前
MIT6.S081 - Lab6 Copy-on-Write(写时复制)
linux·操作系统·c