代码改变世界

11.Linux进程信号(一) - 实践

2025-12-03 20:33  tlnshuju  阅读(6)  评论(0)    收藏  举报

一.信号快速认识

二.信号的产生

1.键盘产生

那么,我们怎么结束我们的命令行呢?

方式一.

方式二.

ctrl + c只能杀掉前台进程,没法杀死后台进程

2.signal接收信号

这个signal方法,能够修改我们对应的信号所对应的方法

我们只看1-31号信号(普通信号),32-64号为实时信号

a.SIGINT信号

ctrl + c表示的是2号信号(SIGINT)

#include 
#include 
#include 
void Handler(int signo)
{
    //当对应的信号被触发,内核会将对应的信号进行编号,传递给自定义方法
    std::cout << "Get a signal,signal number is : " << signo << std::endl;
}
int main()
{
    signal(SIGINT,Handler);//默认终止 -> 执行自定义方法: Handler
    while(true)
    {
        std::cout << "hello world" << std::endl;
        sleep(1);
    }
    return 0;
}

ctrl + \也能终止进程

所以,我们就知道了,对于我们的信号有默认的处理方式,并且我们还可以设置相关的信号处理方式

对于默认的处理方式,我们可以使用man 7 signal进行查看

b.SIGQUIT信号(ctrl + \)

#include 
#include 
#include 
void Handler(int signo)
{
    //当对应的信号被触发,内核会将对应的信号进行编号,传递给自定义方法
    std::cout << "Get a signal,signal number is : " << signo << std::endl;
}
int main()
{
    signal(SIGQUIT,Handler);//默认终止 -> 执行自定义方法: Handler
    while(true)
    {
        std::cout << "hello world" << std::endl;
        sleep(1);
    }
    return 0;
}

c.问题一

signal为啥不放在循环里面?

signal只要被设置一次即可,后续如果收到该信号,就能直接触发handler

d.问题二

signal: 如果没有产生2或3号信号呢?

那我们的handler就永远都不调用

3.无法捕捉的信号

如果我们将所以的信号全部都进行捕捉,那是不是我们对应的进程就永远都杀不死了?

#include 
#include 
#include 
void Handler(int signo)
{
    //当对应的信号被触发,内核会将对应的信号进行编号,传递给自定义方法
    std::cout << "Get a signal,signal number is : " << signo << std::endl;
}
int main()
{
    for(int signo = 1;signo < 32;signo++)
    {
        signal(signo,Handler);//默认终止 -> 执行自定义方法: Handler
        std::cout << "自定义捕捉信号: " << signo << std::endl;
    }
    while(true)
    {
        std::cout << "hello world" << std::endl;
        sleep(1);
    }
    return 0;
}

4.理解信号处理

a.软件

然后根据我们的位图,就能确定要执行的是哪一个函数处理方法

b.硬件

当我们的键盘按下的时候,我们的键盘会向操作系统发送硬件中断

有了硬件中断,所以我们发现硬件和OS能并行执行了

5.指令产生信号

6.kill函数

#include 
#include 
#include 
#include 
#include 
void Usage(std::string proc)
{
    std::cout << "Usage: " << proc << " signumber processid" << std::endl;
}
// ./mykill 9 1234
int main(int argc,char* argv[])
{
    if(argc != 3)
    {
        Usage(argv[0]);
        exit(1);
    }
    int signumber = std::stoi(argv[1]);
    pid_t id = std::stoi(argv[2]);
    int n = ::kill(id,signumber);
    if(n < 0)
    {
        perror("kill");
        exit(2);
    }
    exit(0);
    return 0;
}

这样我们的程序就和命令行的使用方式差不多了

这里我们启动了一个cat进程

7.raise函数

#include 
#include 
#include 
#include 
#include 
// ./mykill 9 1234
int main()
{
    int cnt = 5;
    while(true)
    {
        std::cout << "hahaha alive" << std::endl;
        cnt--;
        if(cnt <= 0)
        {
            raise(9);
        }
        sleep(1);
    }
    return 0;
}

8.abort函数

#include 
#include 
#include 
#include 
#include 
// ./mykill 9 1234
int main()
{
    int cnt = 5;
    while(true)
    {
        std::cout << "hahaha alive" << std::endl;
        cnt--;
        if(cnt <= 0)
        {
            abort();
        }
        sleep(1);
    }
    return 0;
}

三.信号产生的软件条件

本质上还是操作系统进行信号发送的

#include 
#include 
#include 
#include 
#include 
int main()
{
    alarm(1);
    int number = 0;
    while(true)
    {
        printf("count: %d\n",number++);
    }
    return 0;
}

我们这1秒钟才跑5-6万次,是不是太慢了?

#include 
#include 
#include 
#include 
#include 
int number = 0;
void die(int signumber)
{
    printf("get a sig : %d, count : %d\n",signumber,number);
    exit(0);
}
int main()
{
    alarm(1); // 我们自己,会在1S之后会收到一个SIGALRM信号
    signal(SIGALRM,die);
    while(true)
    {
        number++;
    }
    return 0;
}

但是,我们看到了这个数累加的比上面多了很多,这是为啥呢?

#include 
#include 
#include 
#include 
#include 
int main()
{
    alarm(10);
    sleep(4);
    int n = alarm(0);//取消闹钟
    std::cout << "n: " << n << std::endl;
    return 0;
}

当我们不想要之前的闹钟了,我们可以考虑重置闹钟,返回值就是之间的差值

四.设计一个定时器完成对应任务

#include 
#include 
#include 
#include 
#include 
#include 
#include 
using func_t = std::function;
int gcount = 0;
std::vector gfuncs;
void handler(int signo)
{
    for(auto& f : gfuncs)
    {
        f();
    }
    std::cout << "gcount : " << gcount << std::endl;
}
int main()
{
    gfuncs.push_back([](){
        std::cout << "我是一个日志任务" << std::endl;
    });
    gfuncs.push_back([](){
        std::cout << "我是一个下载任务" << std::endl;
    });
    gfuncs.push_back([](){
        std::cout << "我是一个mysql任务" << std::endl;
    });
    alarm(1);
    signal(SIGALRM,handler);
    while(true)
    {
        gcount++;
    }
    return 0;
}

闹钟是一次性的闹钟,超时自动取消

#include 
#include 
#include 
#include 
#include 
#include 
#include 
using func_t = std::function;
int gcount = 0;
std::vector gfuncs;
void handler(int signo)
{
    for(auto& f : gfuncs)
    {
        f();
    }
    std::cout << "gcount : " << gcount << std::endl;
    alarm(1);
}
int main()
{
    gfuncs.push_back([](){
        std::cout << "我是一个日志任务" << std::endl;
    });
    gfuncs.push_back([](){
        std::cout << "我是一个下载任务" << std::endl;
    });
    gfuncs.push_back([](){
        std::cout << "我是一个mysql任务" << std::endl;
    });
    alarm(1);
    signal(SIGALRM,handler);
    while(true)
    {
        gcount++;
    }
    return 0;
}

#include 
#include 
#include 
#include 
#include 
#include 
#include 
using func_t = std::function;
int gcount = 0;
std::vector gfuncs;
void handler(int signo)
{
    for(auto& f : gfuncs)
    {
        f();
    }
    std::cout << "gcount : " << gcount << std::endl;
    alarm(1);
}
int main()
{
    gfuncs.push_back([](){
        std::cout << "我是一个日志任务" << std::endl;
    });
    gfuncs.push_back([](){
        std::cout << "我是一个下载任务" << std::endl;
    });
    gfuncs.push_back([](){
        std::cout << "我是一个mysql任务" << std::endl;
    });
    alarm(1);
    signal(SIGALRM,handler);
    while(true)
    {
        pause();
        std::cout << "我醒来了..." << std::endl;
        gcount++;
    }
    return 0;
}

如果将我们的 alarm 切换成 硬件中断 ,这样就是操作系统的原理

五.异常(信号产生的一种方式)

#include 
#include 
#include 
#include 
#include 
#include 
#include 
int main()
{
    int* p = nullptr;
    *p = 100;
    while(true)
    {
    }
    return 0;
}

很明显,我们这个程序出现了段错误(一般是野指针)

#include 
#include 
#include 
#include 
#include 
#include 
#include 
int main()
{
    int a = 10 / 0;
    while(true)
    {
    }
    return 0;
}

#include 
#include 
#include 
#include 
#include 
#include 
#include 
void handler(int signo)
{
    std::cout << "get a signo: " << signo <

但是,我们的进程为啥没有退出呢?

OS不会处理Eflags,只会处理信号,所以Eflags里面的标志位一直都是1,所以就是死循环

这个错误也一直存在,所以也会死循环

六.Core和Term的区别

1.Core

核心转储

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

环境不一样,生成的core就不一样(可能是core.xxx)

这里默认将core进行关闭的,但是如果进程出错了,那么就会重启(这样就一直出错,一直重启),那么哦我们的磁盘上很容易被打满core.xxx

我们的新一点的服务器,只会生成core(避免一直生成core.xxx,一直只有一个)

BIN=timer
CC=g++
SRC=$(shell ls *.cc)
OBJ=$(SRC:.cc=.o)
$(BIN):$(OBJ)
	$(CC) -o $@ $^ -std=c++11
%.o:%.cc
	$(CC) -c $< -g -std=c++11
.PHONY:clean
clean:
	rm -f $(BIN) $(OBJ)

我们,要加上对应的-g选项,支持debug

我们可以先出错,然后再利用核心转储,进行事后调试

第八个比特位是core dump标志位

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
int main()
{
    if(fork() == 0)
    {
        sleep(1);
        int a = 10;
        a /= 0;
        exit(0);
    }
    int status = 0;
    waitpid(-1,&status,0);
    printf("exit signal : %d, core dump: %d\n",status&0x7F,(status>>7)&1);
    return 0;
}

服务器core功能关闭,我们的代码也不能进行获得core dump标志位

2.Term

Term就是表示我们正常的退出,不需要进行debug