Linux 信号编程

Posted studying~

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux 信号编程相关的知识,希望对你有一定的参考价值。

对于 Linux来说,实际信号是软中断,类似单片机中的硬中断,许多重要的程序都需要处理信号。信号,为 Linux 提供了一种处理异步事件的方法。比如,终端用户输入了 ctrl+c 来中断程序,会通过信号机制停止一个程序。

信号概述
1.信号的名字和编号:
每个信号都有一个名字和编号,这些名字都以“SIG”开头,例如“SIGIO ”、“SIGCHLD”等等。
信号定义在signal.h头文件中,信号名都定义为正整数。
具体的信号名称可以使用kill -l来查看信号的名字以及序号,信号是从1开始编号的,不存在0号信号。kill对于信号0有特殊的应用。在这里插入图片描述
2.信号的处理:
信号的处理有三种方法,分别是:忽略、捕捉和默认动作
1).忽略信号,大多数信号可以使用这个方式来处理,但是有两种信号不能被忽略(分别是 SIGKILL和SIGSTOP)。因为他们向内核和超级用户提供了进程终止和停止的可靠方法,如果忽略了,那么这个进程就变成了没人能管理的的进程,显然是内核设计者不希望看到的场景。
2).捕捉信号,需要告诉内核,用户希望如何处理某一种信号,说白了就是写一个信号处理函数,然后将这个函数告诉内核。当该信号产生时,由内核来调用用户自定义的函数,以此来实现某种信号的处理。
3).系统默认动作,对于每个信号来说,系统都对应由默认的处理动作,当发生了该信号,系统会自动执行。
具体的信号默认动作可以使用man 7 signal来查看系统的具体定义。在此,我就不详细展开了,需要查看的,可以自行查看。也可以参考 《UNIX 环境高级编程(第三部)》的 P251——P256中间对于每个信号有详细的说明。

Linux信号忽略宏:SIG_IGN

了解了信号的概述,那么,信号是如何来使用呢?

其实对于常用的 kill 命令就是一个发送信号的工具,kill 9 PID来杀死进程。比如,我在后台运行了一个 while1进程,通过 ps -aux 命令可以查看他的 PID,通过 kill 9 来发送了一个终止进程的信号来结束 该进程。如果查看信号编号和名称,可以发现9对应的是 9) SIGKILL,正是杀死该进程的信号。而以下的执行过程实际也就是执行了9号信号的默认动作——杀死进程。

对于信号来说,最大的意义不是为了杀死信号,而是实现一些异步通讯的手段,那么如何来自定义信号的处理函数呢?

信号处理函数的注册
信号处理函数的注册不只一种方法,分为入门版和高级版
1.入门版:函数signal
2.高级版:函数sigaction
信号处理发送函数
信号发送函数也不止一个,同样分为入门版和高级版
1.入门版:函数kill
2.高级版:函数sigqueue

==========================================================================
信号注册函数——入门版(注册的信号处理函数不能接收消息)
signal 的函数原型

#include <signal.h>
typedef void (*sighandler_t)(int);//定义一种数据类型sighandler_t
sighandler_t signal(int signum, sighandler_t handler);

参数说明:
int :信号的编号
signum:注册的信号的编号
handler:中断函数的指针,即中断函数名

信号发送函数——入门版(发送信号的同时不能携带消息)
kill 的函数原型

#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);

参数说明:
pid :接收者的 pid
sig :发送的信号的类型

代码案例1

//demo23.c
#include <stdio.h>
#include <signal.h>
void handler(int signum)
{
    printf("signum = %d\\n",signum);
    switch(signum)
    {
       case 2:  printf("this is SIGINT\\n");
                break;
       case 9:  printf("this is SIGKILL\\n");
                break;
       case 10: printf("this is SIGUSR1\\n");
                break;
    }
    printf("never quit\\n");
}
int main()
{
    signal(SIGINT,handler);//对应终端按下Ctrl+c
    signal(SIGKILL,handler);
    signal(SIGUSR1,handler);
    while(1);
    return 0;
}

通过kill命令发送信号
在这里插入图片描述
通过编程发送信号

//demo24.C
#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <stdlib.h>
int main(int agrc,char** argv)
{
    char cmd[128]={0};
    int signum = atoi(argv[1]);
    int pid = atoi(argv[2]);
    //kill(pid,signum); 或者下列
    sprintf(cmd,"kill -%d %d",signum,pid);
    system(cmd);
    return 0;
}

在这里插入图片描述

代码案例2

//demo23.c
#include <stdio.h>
#include <signal.h>
void handler(int signum)
{
    printf("signum = %d\\n",signum);
    switch(signum)
    {
       case 2:  printf("this is SIGINT\\n");
                break;
       case 9:  printf("this is SIGKILL\\n");
                break;
       case 10: printf("this is SIGUSR1\\n");
                break;
    }
    printf("never quit\\n");
}
int main()
{
    signal(SIGINT,SIG_IGN);//对应终端按下Ctrl+c
    signal(SIGKILL,SIG_IGN);
    signal(SIGUSR1,handler);
    while(1);
    return 0;
}

在这里插入图片描述
通过以上案例可知,SIGKILL不可以被捕捉和忽略

==========================================================================
信号注册函数——高级版(注册的信号处理函数容许接收消息)
sigaction 的函数原型

#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

struct sigaction {
   void       (*sa_handler)(int); //信号处理程序,不能接受额外数据
   void       (*sa_sigaction)(int, siginfo_t *, void *); //信号处理程序,能够接受额外数据和sigqueue配合使用
   sigset_t   sa_mask;//默认阻塞
   int        sa_flags;//写SA_SIGINFO表示能够接受数据
 };

参数说明:
sigaction
signum:注册的信号的编号
act:可以理解为signal中的handler+消息
oldact:对之前的信号配置进行备份,以方便之后进行恢复,写NULL忽略

重点看sa_sigaction()的参数
int:同signum,信号的编号
siginfo_t *:记录接收信号携带的一些消息

 siginfo_t {
               int      si_signo;    /* Signal number */
               int      si_errno;    /* An errno value */
               int      si_code;     /* Signal code */
               int      si_trapno;   /* Trap number that caused
                                        hardware-generated signal
                                        (unused on most architectures) */
            pid_t    si_pid;      /* 发送端的进程pid*/
               uid_t    si_uid;      /* Real user ID of sending process */
               int      si_status;   /* Exit value or signal */
               clock_t  si_utime;    /* User time consumed */
               clock_t  si_stime;    /* System time consumed */
            sigval_t si_value;    /* 发送端发送的消息*/
            int      si_int;      /* 同si_value.sival_int */
               void    *si_ptr;      /* POSIX.1b signal */
               int      si_overrun;  /* Timer overrun count; POSIX.1b timers */
               int      si_timerid;  /* Timer ID; POSIX.1b timers */
               void    *si_addr;     /* Memory location which caused fault */
               int      si_band;     /* Band event */
               int      si_fd;       /* File descriptor */
}

void *:NULL代表没有接收到消息,非空代表接收到消息

信号发送函数——高级版(发送信号的同时可以携带消息)

#include <signal.h>
int sigqueue(pid_t pid, int sig, const union sigval value);
union sigval {
   int   sival_int;
   void *sival_ptr;
 };

参数说明:
同kill,多出的value为传递的消息

代码案例3

//demo25.c
#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
void handler(int signum,siginfo_t *info,void *context)
{
        printf("signum = %d\\n",signum);
        if(context)
        {
                printf("get data = %d\\n",info->si_int);
                printf("get data = %d\\n",info->si_value.sival_int);
                printf("from sendpid = %d\\n",info->si_pid);
        }
}
int main()
{
        struct sigaction act = { .sa_sigaction = handler , .sa_flags = SA_SIGINFO };
        printf("this is pid = %d\\n",getpid());
        sigaction(SIGUSR1,&act,NULL);
        while(1);
        return 0;
}
//demo26.c
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
int main(int agrc,char** argv)
{
        int signum = atoi(argv[1]);
        int pid = atoi(argv[2]);
        union sigval value = {.sival_int = 100};
        sigqueue(pid,signum,value);
        printf("sendpid = %d\\n",getpid());
        return 0;
}

在这里插入图片描述

以上是关于Linux 信号编程的主要内容,如果未能解决你的问题,请参考以下文章

linux打开终端如何启动scala,如何在终端下运行Scala代码片段?

Linux 多线程编程(二)2019-08-10

Linux系统编程-进程概念进程管理信号处理

Linux系统编程-进程概念进程管理信号处理

Linux 信号编程

Linux系统编程--信号篇