Linux进程信号

  • 你怎么能识别信号呢?识别信号是内置的,进程识别信号,是内核程序员写的内置特性。
  • 信号产生之后,你知道怎么处理吗?知道。如果信号没有产生,你知道怎么处理信号吗?知道。所以,信号的处理方法,在信号产生之前,已经准备好了。
  • 处理信号,立即处理吗?我可能正在做优先级更高的事情,不会立即处理?什么时候?合适的时候。
  • 信号到来 | 信号保存 | 信号处理
  • 怎么进行信号处理啊?a.默认 b.忽略 c.自定义, 后续都叫做信号捕捉。
cpp 复制代码
// 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)

signal

cpp 复制代码
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号信号时不执行默认的函数,转而使用自定义函数,预期结果:打印信号而不是杀死进程

cpp 复制代码
#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.默认处理的动做

cpp 复制代码
#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.忽略处理

cpp 复制代码
#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文件,用于事后调试(后面详谈)

给所有比特位都注册信号

cpp 复制代码
#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.

cpp 复制代码
#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 函数可以给当前进程发送指定的信号(自己给自己发信号)。

cpp 复制代码
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.
cpp 复制代码
#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 函数使当前进程接收到信号而异常终止。

cpp 复制代码
NAME
    abort - cause abnormal process termination
SYNOPSIS
    #include <stdlib.h>
    void abort(void);
RETURN VALUE
    The abort() function never returns.
    // 就像exit函数⼀样,abort函数总是会成功的,所以没有返回值。
cpp 复制代码
#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)信号给自己,要求进程必须处理,目的就是终止进程

由软件条件产生信号

SIGPIPE 是一种由软件条件产生的信号,在"管道"中已经介绍过了。本节主要介绍 alarm 函数

和 SIGALRM 信号。

cpp 复制代码
NAME
    alarm - set an alarm clock for delivery of a signal
SYNOPSIS
    #include <unistd.h>
    unsigned int alarm(unsigned int seconds);
RETURN VALUE
    alarm() returns the number of seconds remaining until any previously
    scheduled alarm was due to be delivered, or zero if there was no previ‐
    ously scheduled alarm.
  • 调用 alarm 函数可以设定一个闹钟,也就是告诉内核在 seconds 秒之后给当前进程发
    SIGALRM 信号,该信号的默认处理动作是终止当前进程。
  • 这个函数的返回值是0或者是以前设定的闹钟时间还余下的秒数。打个比方,某人要小睡一觉,设定闹钟为30分钟之后响,20分钟后被人吵醒了,还想多睡一会儿,于是重新设定闹钟为15分钟之后响,"以前设定的闹钟时间还余下的时间"就是10分钟。如果seconds值为0,表示取消以前设定的闹钟,函数的返回值仍然是以前设定的闹钟时间还余下的秒数。

基本alarm验证-体会IO效率问题

程序的作用是1秒钟之内不停地数数,1秒钟到了就被SIGALRM信号终止。必要的时候,对SIGALRM信号进行捕捉

1.IO多
cpp 复制代码
// IO 多
#include <iostream>
#include <unistd.h>
#include <signal.h>
int main()
{
int count = 0;
alarm(1);
while(true)
{
std::cout << "count : "
<< count << std::endl;
count++;
}
return 0;
}
2.IO少
cpp 复制代码
// IO 少
#include <iostream>
#include <unistd.h>
#include <signal.h>
int count = 0;
void handler(int signumber)
{
std::cout << "count : " <<
count << std::endl;
exit(0);
}
int main()
{
signal(SIGALRM, handler);
alarm(1);
while (true)
{
count++;
}
return 0;
}

明显下面的count计数更快,上面是每加一次IO一次,下面是加完在IO,IO频繁得读写文件更慢

总结:

  • 闹钟会响一次,默认终止进程
  • 有IO效率低

一次性闹钟

不断打点,1s后收到SIGALRM信号后打出该信号然后停留一秒,然后无限制打点

cpp 复制代码
// 一次性闹钟
#include <iostream>
#include <unistd.h>
#include <signal.h>
int count = 0;
void handler(int signumber)
{
    std::cout << "获得了一个信号:" << SIGALRM << std::endl;
    sleep(1);
}
int main()
{
    signal(SIGALRM, handler);
    alarm(1);
    while (true)
    {
        std::cout << "." << std::endl;
    }
    return 0;
}

重复闹钟

在自定义函数内继续发闹钟

cpp 复制代码
// 一次性闹钟
#include <iostream>
#include <unistd.h>
#include <signal.h>
int count = 0;
void handler(int signumber)
{
    std::cout << "获得了一个信号:" << signumber << "pid:" << getpid() << std::endl;
    alarm(1);
}
int main()
{
    signal(SIGALRM, handler);
    alarm(1);
    while (true)
    {
        std::cout << "." << "pid:" << getpid() << std::endl;
        sleep(1);
    }
    return 0;
}

pause

等待一个信号出错时返回-1,没有信号时暂停,信号捕捉时返回时才会返回

cpp 复制代码
NAME
    pause - wait for signal
SYNOPSIS
    #include <unistd.h>
    int pause(void);
DESCRIPTION
    pause() causes the calling process (or thread) to sleep until a signal
    is delivered that either terminates the process or causes the invoca‐
    tion of a signal-catching function.
RETURN VALUE
    pause() returns only when a signal was caught and the signal-catching
    function returned. In this case, pause() returns -1, and errno is set
    to EINTR. 

进程处于暂停状态,一收到信号,转而实现各种功能,每隔1s收到一个信号,周期性打印

cpp 复制代码
#include <iostream>
#include <unistd.h>
#include <signal.h>
#include<functional>
#include<vector>
using func_t=std::function<void()>;
std::vector<func_t>funcs;
///
void sche()
{
    std::cout<<"我是进程调度"<<std::endl;

}
void memmanger()
{
    std::cout<<"我是周期性内存管理"<<std::endl;

}
void Fflush()
{
    std::cout<<"我是刷新程序"<<std::endl;

}
///
void handler(int signumber)
{
    for(auto f:funcs)
    {
        f();
    }
   int n= alarm(1);
   
}
int main()
{   funcs.push_back(sche);
    funcs.push_back(memmanger);
    funcs.push_back(Fflush);
    signal(SIGALRM, handler);
    alarm(1);
    while (true)
    {  
        pause();
    }
    return 0;
}

进程处于暂停状态,由外部驱动发送信号从而实现特定功能,这也是操作系统

4.异常

1.除零错误

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

cpp 复制代码
#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就能找到对于的进程从而发送信号

2.野指针访问

cpp 复制代码
#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号信号,终止进程

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

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

相关推荐
daily_23331 小时前
coding ability 展开第九幕(位运算——进阶篇)超详细!!!!
算法·位运算
柏木乃一1 小时前
双向链表增删改查的模拟实现
开发语言·数据结构·算法·链表
学也不会1 小时前
Ubuntu-安装redis
linux·运维·ubuntu
共享家95272 小时前
Linux常用命令详解:从基础到进阶
linux·服务器·数据库
小徐Chao努力3 小时前
【centos】经常使用的脚本
linux·运维·centos
whltaoin3 小时前
Java实现N皇后问题的双路径探索:递归回溯与迭代回溯算法详解
java·算法
慈云数据3 小时前
从开发到上线:基于 Linux 云服务器的前后端分离项目部署实践(Vue + Node.js)
linux·服务器·vue.js
同勉共进4 小时前
虚函数表里有什么?(二)——普通单继承下的虚函数表
c++·单继承·虚函数表·dynamic_cast·rtii
梭七y5 小时前
【力扣hot100题】(032)排序链表
算法·leetcode·链表
SsummerC5 小时前
【leetcode100】数组中的第K个最大元素
python·算法·leetcode