ITPub博客

首页 > IT职业 > IT职场 > Linux系统编程之信号中断处理(上)

Linux系统编程之信号中断处理(上)

原创 IT职场 作者:千锋教育官方 时间:2019-09-05 16:41:39 0 删除 编辑


     什么是信号?

信号是 Linux 进程间通信的最古老的方式。信号是软件中断,它是在软件层次上对中断机制的一种模拟,是一种异步通信的方式   。信号可以导致一个正在运行的进程被另一个正在运行的异步进程中断,转而处理某一个突发事件。

“ 中断 ” 在我们生活中经常遇到,譬如,我正在房间里打游戏,突然送快递的来了,把正在玩游戏的我给 “ 中断 ” 了,我去签收快递 ( 处理中断 ) ,处理完成后,再继续玩我的游戏。这里我们学习的 “ 信号 ” 就是属于这么一种 “ 中断 ” 。我们在终端上敲 “Ctrl+c” ,就产生一个 “ 中断 ” ,相当于产生一个信号,接着就会处理这么一个 “ 中断任务 ” (默认的处理方式为中断当前进程)。

信号可以直接进行用户空间进程和内核空间进程的交互,内核进程可以利用它来通知用户空间进程发生了哪些系统事件。

一个完整的信号周期包括三个部分:信号的产生,信号在进程中的注册,信号在进程中的注销,执行信号处理函数。如下图所示:

 

 

注意:这里信号的产生,注册,注销时信号的内部机制,而不是信号的函数实现。

Linux 可使用命令: kill -l ( "l" 为字母) , 查看相应的信号。


 

 

 

列表中,编号为 1 ~ 31 的信号为传统 UNIX 支持的信号,是不可靠信号(非实时的),编号为 32 ~ 63 的信号是后来扩充的,称做可靠信号(实时信号)。不可靠信号和可靠信号的区别在于前者不支持排队,可能会造成信号丢失,而后者不会。非可靠信号一般都有确定的用途及含义 ,   可靠信号则可以让用户自定义使用。更多详情,请看《 Linux 信号列表》。

 

信号的产生方式

1 )当用户按某些终端键时,将产生信号。

终端上按“Ctrl+c” 组合键通常产生中断信号 SIGINT ,终端上按 “Ctrl+\” 键通常产生中断信号 SIGQUIT ,终端上按 “Ctrl+z” 键通常产生中断信号 SIGSTOP 等。

2 )硬件异常将产生信号。

除数为 0 ,无效的内存访问等。这些情况通常由硬件检测到,并通知内核,然后内核产生适当的信号发送给相应的进程。

3 )软件异常将产生信号。

当检测到某种软件条件已发生,并将其通知有关进程时,产生信号。

4 )调用 kill() 函数将发送信号。

注意:接收信号进程和发送信号进程的所有者必须相同,或发送信号进程的所有者必须是超级用户。

5 )运行 kill 命令将发送信号。

此程序实际上是使用 kill 函数来发送信号。也常用此命令终止一个失控的后台进程。

 

信号的常用操作:

 

发送信号

所需头文件:

#include <sys/types.h>

#include <signal.h>

 

int kill(pid_t pid, intsignum);

功能:

给指定进程发送信号。

注意:使用 kill() 函数发送信号,接收信号进程和发送信号进程的所有者必须相同,或者发送信号进程的所有者是超级用户。

 

参数:

pid: 取值有 4 种情况 :

pid > 0: 将信号传送给进程 ID 为 pid 的进程。

pid = 0: 将信号传送给当前进程所在进程组中的所有进程。

pid = -1: 将信号传送给系统内所有的进程。

pid < -1: 将信号传给指定进程组的所有进程。这个进程组号等于 pid 的绝对值。

signum: 信号的编号,这里可以填数字编号,也可以填信号的宏定义,可以通过命令 kill -l ("l" 为字母 ) 进行相应查看。

 

返回值:

成功:0

失败:-1

下面为测试代码,本来父子进程各自每隔一秒打印一句话,3 秒后,父进程通过 kill() 函数给子进程发送一个中断信号 SIGINT ( 2 号信号),最终,子进程结束,剩下父进程在打印信息:

 

 

#include <stdio.h>  

#include <stdlib.h>  

#include <unistd.h>  

#include <sys/types.h>  

#include <signal.h>  

  

int main(int argc, char *argv[])  

{  

    pid_t pid;  

    int i = 0;  

  

    pid = fork(); // 创建进程  

    if( pid < 0 ){ // 出错  

        perror("fork");  

    }  

      

    if(pid == 0){ // 子进程  

        while(1){  

            printf("I am son\n");  

            sleep(1);  

        }  

    }else if(pid > 0){ // 父进程  

        while(1){  

            printf("I am father\n");  

            sleep(1);  

              

            i++;  

            if(3 == i){// 3秒后  

                kill(pid, SIGINT); // 给子进程 pid ,发送中断信号 SIGINT  

                // kill(pid, 2); // 等级于kill(pid, SIGINT);  

            }  

        }  

    }  

  

    return 0;  

}  

 

运行结果:

等待信号

所需头文件:

#include <unistd.h>

int pause(void);

功能:

等待信号的到来(此函数会阻塞)。将调用进程挂起直至捕捉到信号为止,此函数通常用于判断信号是否已到。

参数:无。

返回值:

直到捕获到信号才返回 -1 ,且 errno 被设置成 EINTR 。

测试代码如下:

 

 

#include <unistd.h>  

#include <stdio.h>  

  

int main(int argc, char *argv[])  

{  

    printf("in pause function\n");  

    pause();  

      

    return 0;  

}  

 

没有产生信号前,进程一直阻塞在 pause() 不会往下执行,假如,我们按 “Ctrl+c” , pause() 会捕获到此信号,中断当前进程

 

 

处理信号

一个进程收到一个信号的时候,可以用如下方法进行处理:

1 )执行系统默认动作

对大多数信号来说,系统默认动作是用来终止该进程。  

2 )忽略此信号

接收到此信号后没有任何动作。

3 )执行自定义信号处理函数

用用户定义的信号处理函数处理该信号。

注意:SIGKILL 和 SIGSTOP 不能更改信号的处理方式,因为它们向用户提供了一种使进程终止的可靠方法。

产生一个信号,我们可以让其执行自定义信号处理函数。假如有函数 A, B, C ,我们如何确定信号产生后只调用函数 A ,而不是函数 B 或 C 。这时候,我们需要一种规则规定,信号产生后就调用函数 A ,就像交通规则一样,红灯走绿灯行,信号注册函数 signal() 就是做这样的事情。

 

所需头文件:

#include <signal.h>

 

 

typedef void (*sighandler_t)(int);// 回调函数的声明

sighandler_t signal(int signum,sighandler_t handler);

功能:

注册信号处理函数(不可用于 SIGKILL 、 SIGSTOP 信号),即确定收到信号后处理函数的入口地址。此函数不会阻塞。

参数:

signum :信号的编号,这里可以填数字编号,也可以填信号的宏定义,可以通过命令 kill -l ("l" 为字母 ) 进行相应查看。

handler: 取值有 3 种情况:

SIG_IGN :忽略该信号

SIG_DFL :执行系统默认动作

信号处理函数名:自定义信号处理函数,如:fun

回调函数的定义如下:

void fun(int signo)

{

// signo 为触发的信号,为 signal() 第一个参数的值

}

注意:信号处理函数应该为可重入函数,关于可重入函数的更多详情,请《浅谈可重入函数与不可重入函数》。

返回值:

成功:第一次返回 NULL ,下一次返回此信号上一次注册的信号处理函数的地址。如果需要使用此返回值,必须在前面先声明此函数指针的类型。

失败:返回 SIG_ERR

实例1 :

#include <stdio.h>

#include <signal.h>

#include <unistd.h>

 

// 信号处理函数

void signal_handler(int signo)

{

if(signo == SIGINT){

printf("recv SIGINT\n");

}else if(signo == SIGQUIT){

printf("recv SIGQUIT\n");

}

}

 

int main(int argc, char *argv[])

{

printf("wait for SIGINT OR SIGQUIT\n");

/* SIGINT: Ctrl+c ; SIGQUIT: Ctrl+\ */

// 信号注册函数

signal(SIGINT, signal_handler);

signal(SIGQUIT, signal_handler);

// 等待信号

pause();

pause();

return 0;

}

 

运行结果

 

实例2 :

 

#include <stdio.h>

#include <signal.h>

#include <unistd.h>

 

// 回调函数的声明

typedef void (*sighandler_t)(int);

 

void fun1(int signo)

{

printf("in fun1\n");

}

 

void fun2(int signo)

{

printf("in fun2\n");

}

 

int main(int argc, char *argv[])

{

sighandler_t previous = NULL;

// 第一次返回 NULL

previous = signal(SIGINT,fun1);

if(previous == NULL)

{

printf("return fun addr is NULL\n");

}

// 下一次返回此信号上一次注册的信号处理函数的地址。

previous = signal(SIGINT, fun2);

if(previous == fun1)

{

printf("return fun addr is fun1\n");

}

// 还是返回 NULL,因为处理的信号变了

previous = signal(SIGQUIT,fun1);

if(previous == NULL)

{

printf("return fun addr is NULL\n");

}

return 0;

}

运行结果:

 

 

 

 

最后:

关注回复“物联网”即可获取物联网全套视频教程

 


来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/69914734/viewspace-2656130/ ,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论
千锋IT职业教育——做真实的自己 用良心做教育

注册时间:2019-03-28

  • 博文量
    140
  • 访问量
    92334