Linux进程信号

发布于:2025-04-05 ⋅ 阅读:(19) ⋅ 点赞:(0)
  • 你怎么能识别信号呢?识别信号是内置的,进程识别信号,是内核程序员写的内置特性。
  • 信号产生之后,你知道怎么处理吗?知道。如果信号没有产生,你知道怎么处理信号吗?知道。所以,信号的处理方法,在信号产生之前,已经准备好了。
  • 处理信号,立即处理吗?我可能正在做优先级更高的事情,不会立即处理?什么时候?合适的时候。
  • 信号到来 | 信号保存 | 信号处理
  • 怎么进行信号处理啊?a.默认 b.忽略 c.自定义, 后续都叫做信号捕捉。
// sig.cc
#include <iostream>
#include <unistd.h>
int main()
{
while(true){
std::cout << "I am a process, I am waiting signal!" << std::endl;
sleep(1);
}
}

编写了一个死程序,按理说会无限循环下去,但是为什么用户ctrl + c会终止运行呢?

  1. 用户输入命令,在Shell下启动一个前台进程
  2. 用户按下 Ctrl+C ,这个键盘输入产生一个硬件中断,被OS获取,解释成信号,发送给目标前台进程
  3. 前台进程因为收到信号,进而引起进程退出

 信号的本质就是宏,而1~31个信号分别用比特位的01代表示

 

而其实, Ctrl+C 的本质是向前台进程发送 SIGINT 即 2 号信号,我们证明⼀下,这里需要引入一
个系统调用函数(signal)

NAME
     signal - ANSI C signal handling
SYNOPSIS
     #include <signal.h>
     typedef void (*sighandler_t)(int);
     sighandler_t signal(int signum, sighandler_t handler);
参数说明:
signum:信号编号[后⾯解释,只需要知道是数字即可]
handler:函数指针,表⽰更改信号的处理动作,当收到对应的信号,就回调执⾏handler⽅法

开始测试

编写一个死循环,同时接收到2号信号时不执行默认的函数,转而使用自定义函数,预期结果:打印信号而不是杀死进程

#include<iostream>
#include<unistd.h>
#include<signal.h>
void handlerSig(int sig)
{
std::cout<<getpid()<<"获得了一个信号:"<<sig<<std::endl;
}
int main()
{ //进程收到2号信号时转而调用自定义函数handlerSig并将sig传递给自定义函数
    signal(SIGINT,handlerSig);
    int cnt=0;
    while(true)
    {
        std::cout<<getpid()<<": "<<"hello word"<<cnt++<<std::endl;
        sleep(1);
    }

 
}

此时ctrl + C杀不掉只能使用kill -9 

这里进程为什么不退出?signal调用将信号编号交给默认的函数处理,而2号对于默认的函数是杀掉进程,现在转而执行自定义函数
这个例子能说明哪些问题?信号处理,不是自己处理,而是调用别的函数
 

注意

  • 要注意的是,signal函数仅仅是设置了特定信号的捕捉作为处理方式,并不是直接调用处理动作。如果后续特定信号没有产生,设置的捕捉函数永远也不会被调用!!
  • Ctrl-C 产生的信号只能发给前台进程。一个命令后面加个&可以放到后台运行,这样Shell不必等待进程结束就可以接受新的命令,启动新的进程。
  • Shell可以同时运行一个前台进程和任意多个后台进程,只有前台进程才能接到像 Ctrl-C这种控制键产生的信号。
  • 前台进程在运行过程中用户随时可能按下 Ctrl-C 而产生一个信号,也就是说该进程的用户空间代码执行到任何地方都有可能收到 SIGINT 信号而终止,所以信号相对于进程的控制流程来说是异步(Asynchronous)的。
  • 关于进程间关系,我们在⽹络部分会专门来讲,现在就了解即可。
  • 可以渗透 & 和 nohup

信号处理

处理信号有三种方式:

1.默认处理的动做

#include<iostream>
#include<unistd.h>
#include<signal.h>
void handlerSig(int sig)
{
std::cout<<getpid()<<"获得了一个信号:"<<sig<<std::endl;
}
int main()
{ //进程收到2号信号时转而调用自定义函数handlerSig并将sig传递给自定义函数
    signal(SIGINT/*2*/, SIG_DFL); // 设置忽略信号的宏
    int cnt=0;
    while(true)
    {
        std::cout<<getpid()<<": "<<"hello word"<<cnt++<<std::endl;
        sleep(1);
    }

 
}

2.自定义处理动作

如之前代码

提供一个信号处理函数,要求内核在处理该信号时切换到用户态执行这个处理函数,这种方式称为自
定义捕捉(Catch)一个信号。

3.忽略处理

#include<iostream>
#include<unistd.h>
#include<signal.h>
void handlerSig(int sig)
{
std::cout<<getpid()<<"获得了一个信号:"<<sig<<std::endl;
}
int main()
{ //进程收到2号信号时转而调用自定义函数handlerSig并将sig传递给自定义函数
    signal(SIGINT/*2*/, SIG_IGN); // 设置忽略信号的宏
    int cnt=0;
    while(true)
    {
        std::cout<<getpid()<<": "<<"hello word"<<cnt++<<std::endl;
        sleep(1);
    }

 
}

 上面的所有内容,我们都没有做非常多的解释,主要是先用起来,然后渗透部分概念和共识,下面我们从理论和实操两个层面,来进⾏对信号的详细学习、论证和理解。为了保证条理,我们采用如下思路来进行阐述:

通过终端按键产生信号 

  • Ctrl+C (SIGINT) 已经验证过,这里不再重复
  • Ctrl+\(SIGQUIT)可以发送终止信号并生成core dump文件,用于事后调试(后面详谈)

 给所有比特位都注册信号

#include<iostream>
#include<unistd.h>
#include<signal.h>
void handlerSig(int sig)
{
std::cout<<getpid()<<"获得了一个信号:"<<sig<<std::endl;
}
int main()
{ 
    for(int i=1;i<=31;i++)
    signal(i, handlerSig); //给1~31号信号都注册自定义函数
    int cnt=0;
    while(true)
    {
        std::cout<<getpid()<<": "<<"hello word"<<cnt++<<std::endl;
        sleep(1);
    }

 
}

 产生信号的方式:

1.调用系统命令kill向进程发信号

2.通过键盘

3.调用系统调用函数

kill

kill 命令是调用 kill 函数实现的。 kill 函数可以给一个指定的进程发送指定的信号

NAME
       kill - send signal to a process
SYNOPSIS
       #include <sys/types.h>
       #include <signal.h>
       int kill(pid_t pid, int sig);
RETURN VALUE
   On success (at least one signal was sent), zero is returned. On error,
   -1 is returned, and errno is set appropriately.

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

//  ./mykill  signumber pid
int main( int argc ,char* argv[])
{
if(argc !=3)
{
    std::cout<<"./mykill  signumber pid"<<std::endl;
    return 1;
}
int signum =std::stoi(argv[1]);
pid_t target =std::stoi(argv[2]);
int n=kill(target,signum);
if(n==0)
{
    std::cout<<"发送"<<signum<<"到"<<target<<"成功"<<std::endl;
}
return 0;
}

 在kill这个系统调用中,可以发送进程信号给指定进程

 raise
raise 函数可以给当前进程发送指定的信号(自己给自己发信号)。

NAME
    raise - send a signal to the caller
SYNOPSIS
    #include <signal.h>
    int raise(int sig);
RETURN VALUE
    raise() returns 0 on success, and nonzero for failure.
#include<iostream>
#include<unistd.h>
#include<signal.h>
void handlerSig(int sig)
{
std::cout<<getpid()<<"获得了一个信号:"<<sig<<std::endl;
}
int main()
{ 
    for(int i=1;i<=31;i++)
    signal(i, handlerSig); //给1~31号信号都注册自定义函数
    for(int i=1;i<=31;i++)
    {
        sleep(1);
        raise(i);
    }

 
}

 到9就停止是因为9号信号是强制执行的,系统调用对他无效,即便是调用自定义函数也仍然会杀死进程,比2号进程还猛

 abort
abort 函数使当前进程接收到信号而异常终止。

NAME
    abort - cause abnormal process termination
SYNOPSIS
    #include <stdlib.h>
    void abort(void);
RETURN VALUE
    The abort() function never returns.
    // 就像exit函数⼀样,abort函数总是会成功的,所以没有返回值。

#include<iostream>
#include<unistd.h>
#include<signal.h>
void handlerSig(int sig)
{
std::cout<<getpid()<<"获得了一个信号:"<<sig<<std::endl;
}
int main()
{ 
    for(int i=1;i<=31;i++)
    signal(i, handlerSig); //给1~31号信号都注册自定义函数

    int cnt=0;
    while(true)
    {
        std::cout<<getpid()<<": "<<"hello word"<<cnt++<<std::endl;
        abort();
        sleep(1);
    }

 
}

 发送固定的6号(SIGABRT)信号给自己,要求进程必须处理,目的就是终止进程

 4.异常

程序跑起来变成进程了,进程因为语法错误或使用错误导致崩掉了从而发送异常信号

#include<iostream>
#include<unistd.h>
#include<signal.h>
void handlerSig(int sig)
{
std::cout<<getpid()<<"获得了一个信号:"<<sig<<std::endl;
}
int main()
{ 
    for(int i=1;i<=31;i++)
    signal(i, handlerSig); //给1~31号信号都注册自定义函数

    int cnt=1;
   //除零错误
    cnt/=0;

 
}

kill -l查表,8号是SIGFPE浮点数错误

 cpu里有一个状态寄存器,在跑程序执行运算时出错了会修改标志寄存器,操作系统是软硬件资源的管理者,通过全局指针current就能找到对于的进程从而发送信号

#include<iostream>
#include<unistd.h>
#include<signal.h>
void handlerSig(int sig)
{
std::cout<<getpid()<<"获得了一个信号:"<<sig<<std::endl;
}
int main()
{ 
    for(int i=1;i<=31;i++)
    signal(i, handlerSig); //给1~31号信号都注册自定义函数

int *p =nullptr;
*p=1;//野指针

}

 对野指针的访问,11号信号是SIGSEGV段错误

 访问零号地址,操作系统拿到的是虚拟地址,cr3寄存器可通过页表的地址找到页表,MMU将cr3的页表地址和要访问的0号地址做虚拟的页表转化,但是页表下在零号地址没有对应的映射关系,转化失败,硬件报错,cpu的寄存器无法把100写到0号地址处,发送11号信号,终止进程

信号全部都是由操作系统发送的,程序犯错了,操着系统识别到了,根据犯错类型发送信号回该进程执行对于的函数

信号产生之后并不是立即处理,所以要求进程必须把信号记入下来