【Linux操作系统】进程信号(一)

文章目录

    • 1. 信号介绍
    • 2. 信号的产生
      • 2.1 键盘产生
      • 2.2 程序中存在异常
      • 2.3 系统调用
        • ① kill
        • ② raise
        • ③ abort
      • 2.4 软件条件
        • ① alarm
      • 2.5 OS给进程发送信号

1. 信号介绍

信号产生之后,是发给进程的,进程要在合适的时候执行对应的动作

进程在没有收到信号的时候,就已经知道了哪种信号对应哪一个动作,进程具有识别信号并处理信号的能力,远远早于信号的产生

进程收到某种信号的时候,并不一定是立即处理的,而是在合适的时候,因为信号随时都有能产生(异步的),但进程当前可能在执行更重要的事

因此信号不能被立即处理的时候,收到的信号需要被保存起来,保存在struct task_struct中,信号的本质也是数据,所以信号的发送就变成了往进程的task_struct中写入信号数据,这个操作由操作系统来完成,因而无论我们的信号是如何发送的,本质都是底层在通过操作系统发送的

信号产生前:信号产生的各种方式 -> 信号产生中:信号的保存方式 -> 信号产生后:信号处理的方式

各种信号(64个)

【Linux操作系统】进程信号(一)_第1张图片

ctrl + c发出的就是2号信号


【Linux操作系统】进程信号(一)_第2张图片

typedef void (*sighandler_t)(int):为一个函数指针

signum:信号对应的整型值

handler:修改进程对信号的默认处理动作

#include 
#include 
#include 

void handler(int signum)
{
    printf("\nrecieve a signal, signal num: %d, process pid: %d\n", signum, getpid());
}

int main()
{
    // 通过signal注册对2号信号的处理动作,改成我们自己自定义的动作
    signal(2, handler);

    while (1) {
        printf("hello, my pid: %d\n", getpid());
        sleep(1);
    }

    return 0;
}

注意:注册函数的时候,不会调用这个函数,只有当信号到来的时候,这个函数才会被调用

使用ctrl + c向该进程发出2号信号

【Linux操作系统】进程信号(一)_第3张图片

使用kill命令同样可以向该进程发出2号信号

【Linux操作系统】进程信号(一)_第4张图片

2号信号无法退出,可以使用ctrl + \退出进程

2. 信号的产生

2.1 键盘产生

信号产生的方式其中一种就是通过键盘产生,注意键盘产生的信号只能用来终止前台进程,加上&运行的后台进程不行

一般而言,进程收到信号的处理方式有三种

  • 默认动作——如终止自己、暂停等
  • 忽略动作——是一种信号处理的方式,只不过动作就是什么也不干
  • 自定义动作(信号的捕捉)——如signal方法,就是在修改信号的处理动作由默认动作 -> 自定义动作
#include 
#include 
#include 

void handler(int signum)
{
    switch(signum) {
        case 2:
            printf("hello, this is No.2, pid: %d\n", getpid());
            break;
        case 3:
            printf("hello, this is No.3, pid: %d\n", getpid());
            break;
        case 9:
            printf("hello, this is No.9, pid: %d\n", getpid());
            break;
        default:
            printf("hello, this is No.%d, pid: %d\n", signum, getpid());
            break;
    }
}

int main()
{
    int sig = 1;
    // 注册1~31号信号
    for (; sig <= 31; sig++) {
        signal(sig, handler);
    }

    while (1) {
        printf("hello, my pid: %d\n", getpid());
        sleep(1);
    }

    return 0;
}

【Linux操作系统】进程信号(一)_第5张图片

发现9号信号不可以被捕捉,即无法自定义动作

2.2 程序中存在异常

程序中存在异常问题,会导致进程收到信号退出

#include 

int main()
{
    while (1) {
        int *p = NULL;
        *p = 100;
        printf("hello, my pid: %d\n", getpid());
        sleep(1);
    }

    return 0;
}

程序会发生段错误:Segmentation fault,即进程的崩溃,这是因为进程收到了11号信号

在Windows或Linux下,进程崩溃的本质就是收到了对应的信号,然后进程执行信号的默认处理动作

当进程崩溃时,我们需要知道崩溃的原因,waitpid()status会携带进程异常退出的原因

【Linux操作系统】进程信号(一)_第6张图片

进程如果异常的时候,core-dump该位置会被设置为1

我们还需要知道程序是在哪一行崩溃

  • 在Linux中,当一个进程推出的时候,它的退出码和推出信号都会被设置(正常情况)
  • 当一个进程异常的时候,进程的终止信号会被设置,表明当前进程退出的原因
  • 如果必要,操作系统会设置退出信息中的core dump标志位,并将进程在内存中的数据转储到磁盘当中,方便后期调试

【Linux操作系统】进程信号(一)_第7张图片

云服务器上core file默认是关闭的,这种情况下程序崩溃会发生:

在这里插入图片描述

core file打开之后

【Linux操作系统】进程信号(一)_第8张图片

8398为异常进程的pid

【Linux操作系统】进程信号(一)_第9张图片

如何查看异常情况(事后调式)

  • 编译时加上选项-g
  • 运行让程序产生异常,生成core文件
  • 再用gdb调试该程序
  • 在gdb中输入core-file [core文件名],即可查看异常的详细信息

【Linux操作系统】进程信号(一)_第10张图片
验证打开core file之后异常退出后core dump被设置为1

#include 
#include 
#include 
#include 

int main()
{
    if (fork() == 0) {
        while (1) {
            printf("I am child process...\n");
            int a = 10;
            a /= 0;
        }
    }

    int status = 0;
    waitpid(-1, &status, 0);

    printf("exit code: %d, exit sig: %d, core dump: %d\n", (status >> 8) & 0xff, status & 0x7f, (status >> 7) & 1);

    return 0;
}

在这里插入图片描述

2.3 系统调用

① kill

【Linux操作系统】进程信号(一)_第11张图片

模拟实现一个kill命令

#include 
#include 
#include 
#include 
#include 
#include 

static void Usage(const char *proc)
{
    printf("Usage: \n\t %s signal who\n", proc);
}

int main(int argc, char *argv[])
{
    if (argc != 3) {
        Usage(argv[0]);
        return 1;
    }

    int signo = atoi(argv[1]);
    int who = atoi(argv[2]);

    kill(who, signo);

    printf("signal: %d, who: %d\n", signo, who);

    return 1;
}

【Linux操作系统】进程信号(一)_第12张图片

② raise

可以给当前进程发送指定的信号,即自己给自己发信号

【Linux操作系统】进程信号(一)_第13张图片

#include 
#include 

int main()
{
    raise(11);

    return 0;
}

在这里插入图片描述

成功返回0,错误返回-1

③ abort

使当前进程接收到信号而异常终止

【Linux操作系统】进程信号(一)_第14张图片

#include 
#include 

int main()
{
    abort();

    return 0;
}

在这里插入图片描述

就像exit函数一样,abort函数总是会成功的,所以没有返回值

2.4 软件条件

通过某种软件(OS)来触发信号的发送,系统层面设置了定时器,或者某种操作而导致条件不就绪等这样的场景下,触发信号的发送

举例:进程间通信时,当读端不仅不读,而且还关闭了读fd,写端一直在写,最终进程会收到SIGPIPE(13),就是一种典型的软件条件触发的信号发送

① alarm

【Linux操作系统】进程信号(一)_第15张图片

调用alarm函数可以设定一个闹钟,也就是告诉内核在seconds秒之后给当前进程发SIGALRM信号,该信号的默认处理动作是终止当前进程

这个函数的返回值是0或者是以前设定的闹钟时间还余下的秒数

打个比方,某人要小睡一觉,设定闹钟为30分钟之后响,20分钟后被人吵醒了,还想多睡一会儿,于是重新设定闹钟为15分钟之后响,“以前设定的闹钟时间还余下的时间”就是10分钟。如果seconds值为0,表示取消以前设定的闹钟,函数的返回值仍然是以前设定的闹钟时间还余下的秒数

#include 
#include 

int main()
{
    int ret = alarm(20);

    while (1) {
        printf("I am a process, ret = %d\n", ret);
        sleep(5);

        int res = alarm(0); //取消闹钟
        printf("res = %d\n", res);
    }

    return 0;
}

【Linux操作系统】进程信号(一)_第16张图片

利用alarm来比较有IO与无IO的情况下,CPU执行效率

  • 有IO

    #include 
    #include 
    
    int count = 0;
    
    int main()
    {
        alarm(1); // 没有设置alarm的捕捉动作,就执行默认动作,终止进程
    
        while(1) {
            printf("hello, %d\n",count++);
        }
    
        return 0;
    }
    

    【Linux操作系统】进程信号(一)_第17张图片

  • 无IO

    #include 
    #include 
    #include 
    #include 
    
    int count = 0;
    
    void handlerAlarm(int signum) {
        printf("hello, %d\n", count);
    
        exit(1);
    }
    
    int main()
    {
        signal(SIGALRM, handlerAlarm);
    
        alarm(1); // 没有设置alarm的捕捉动作,就执行默认动作,终止进程
    
        while(1) {
            count++;
        }
    
        return 0;
    }
    

    【Linux操作系统】进程信号(一)_第18张图片

无IO的情况下会快很多

2.5 OS给进程发送信号

信号产生的方式种类虽然非常多,但是无论产生信号的方式千差万别,但是最终一定都是通过OS向目标进程发送信号

产生信号的方式,其实都是OS发送信号数据给task_struct

struct task_struct中有进程的各种属性,那么其中也一定有对应的数据变量,来保存是否收到了对应的信号,而信号的编号也是有规律的1~31

进程中采用uint32_t sigs;——位图结构来标识该进程是否收到信号

第31个<--------------------------------------------第1个

0000 0000 0000 0000 0000 0000 0000 0000

比特位的位置(第几个)代表的就是哪一个信号,比特位的内容(0或1),代表的就是是否收到了信号

故本质是OS向指定进程的task_struct中的信号位图写入比特1,即完成信号的发送,也可以说是信号的写入

你可能感兴趣的:(Linux,1024程序员节)