Linux - signal通信

对于 Linux来说,信号实际是软中断,许多重要的程序都需要处理信号。

信号,为 Linux 提供了一种处理异步事件的方法。比如,终端用户输入了 ctrl+c 来中断程序,会通过信号机制停止一个程序。

 

1. 信号概述

1.1 信号的名字和编号

每个信号都有一个名字和编号,这些名字都以“SIG”开头,例如“SIGIO ”、“SIGCHLD”等等。信号定义在signal.h头文件中,信号名都定义为正整数。
使用kill -l来查看信号的名字以及序号,信号是从1开始编号的,不存在0号信号。kill对于信号0又特殊的应用。

 

1.2 信号的处理

    信号的处理有三种方法,分别是:忽略、捕捉和默认动作

  • 忽略信号,大多数信号可以使用这个方式来处理,但是有两种信号不能被忽略(分别是 SIGKILLSIGSTOP)。因为他们向内核和超级用户提供了进程终止和停止的可靠方法,如果忽略了,那么这个进程就变成了没人能管理的的进程,显然是内核设计者不希望看到的场景
  • 捕捉信号,需要告诉内核,用户希望如何处理某一种信号,说白了就是写一个信号处理函数,然后将这个函数告诉内核。当该信号产生时,由内核来调用用户自定义的函数,以此来实现某种信号的处理。
  • 系统默认动作,对于每个信号来说,系统都对应由默认的处理动作,当发生了该信号,系统会自动执行。不过,对系统来说,大部分的处理方式都比较粗暴,就是直接杀死该进程。
    具体的信号默认动作可以使用man 7 signal来查看系统的具体定义。在此,我就不详细展开了,需要查看的,可以自行查看。也可以参考 《UNIX 环境高级编程(第三部)》的 P251——P256中间对于每个信号有详细的说明。

 

2. 信号使用

Linux常用的 kill 命令就是一个发送信号的工具,kill -9 PID来杀死进程。

比如,我在后台运行了一个 top 工具,通过 ps 命令可以查看他的 PID,通过 kill 9 来发送了一个终止进程的信号(SIGKILL)来结束了 top 进程。

 

对于信号来说,最大的意义不是为了杀死信号,而是实现一些异步通讯的手段

 

2.1 信号处理函数的注册

信号处理函数的注册不只一种方法,分为入门版和高级版

  • 入门版:函数signal
  • 高级版:函数sigaction

2.2 信号处理发送函数

信号发送函数也不止一个,同样分为入门版和高级版

  • 入门版:kill
  • 高级版:sigqueue

 

3. demo - 入门版

3.1 信号接收:

#include <signal.h>
typedef void (*sighandler_t)(int);  //信号处理函数
sighandler_t signal(int signum, sighandler_t handler); //注册函数
#include <stdio.h>
#include <unistd.h>
#include <signal.h>


void handler(int signum)
{
    if(signum == SIGIO) {
        printf("SIGIO  signal: %d\n", signum);

    } else if(signum == SIGUSR1) {
        printf("SIGUSR1 signal: %d\n", signum);

    } else {
        //未注册,应用程序终止进程
        printf("error \n");
    }
}

int main(void)
{
    signal(SIGIO, handler);
    signal(SIGUSR1, handler);

    printf("%d, %d\n", SIGIO, SIGUSR1);

    while(1) {
        sleep(1000);
    }

    return 0;
}

 

注:信号的处理还有两种状态,分别是默认处理和忽略,这两种设置很简单,只需要将 handler 设置为 SIG_IGN(忽略信号)或 SIG_DFL(默认动作)即可。

在此还有两个问题需要说明一下:

  1. 当执行一个程序时,所有信号的状态都是系统默认或者忽略状态的。除非是 调用exec进程忽略了某些信号。exec 函数将原先设置为要捕捉的信号都更改为默认动作,其他信号的状态则不会改变 。
  2. 当一个进程调动了 fork 函数,那么子进程会继承父进程的信号处理方式。

 

3.2 信号发送:(也可使用 终端kill

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


int main(int argc, char *argv[])
{
    if( argc != 3) {
        printf("%s <Target PID> <Signal_Number>\n", argv[0]);

        return -1;
    }

    int pid = atoi(argv[1]);
    int sig = atoi(argv[2]);

    printf("pid = %d, sig = %d\n", pid, sig);
    if(pid > 0 && sig > 0) {
        kill(pid, sig);
    } else {
        printf("Target_PID or Signal_Number must > 0\n");
    }

    return 0;
}

    关于 kill 函数,还有一点需要额外说明, kill 函数传入的 pid 可以是小于等于0的整数
pid > 0:将发送个该 pid 的进程
pid == 0:将会把信号发送给与发送进程属于同一进程组的所有进程,并且发送进程具有权限想这些进程发送信号。
pid < 0:将信号发送给进程组ID 为 pid 的绝对值得,并且发送进程具有权限向其发送信号的所有进程
pid == -1:将该信号发送给发送进程的有权限向他发送信号的所有进程。(不包括系统进程集中的进程)

 



 

4. demo升级版(带数据)

4.1 信号接收:

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

struct sigaction {
   void       (*sa_handler)(int); //信号处理程序,不接受额外数据,SIG_IGN 为忽略,SIG_DFL 为默认动作
   void       (*sa_sigaction)(int, siginfo_t *, void *); //信号处理程序,能够接受额外数据和sigqueue配合使用
   sigset_t   sa_mask;//阻塞关键字的信号集,可以再调用捕捉函数之前,把信号添加到信号阻塞字,信号捕捉函数返回之前恢复为原先的值。
   int        sa_flags;//影响信号的行为SA_SIGINFO表示能够接受数据
 };
//回调函数句柄sa_handler、sa_sigaction只能任选其一

 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;      /* Sending process ID */
               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;    /* Signal value */
               int      si_int;      /* POSIX.1b signal */
               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 */
}

在函数原型中,signum:注册的信号的编号;act:对该信号有新的配置;oldact:对之前的信号配置进行备份,以方便之后进行恢复。

#include <signal.h>
#include <stdio.h>
#include <unistd.h>


void handler(int signum, siginfo_t *info, void *context)
{
    if(signum == SIGIO) {
        printf("SIGIO  signal: %d\n", signum);

    } else if (signum == SIGUSR1) {
        printf("SIGUSR1  signal: %d\n", signum);

    } else {
        printf("error\n");
    }

    if(context) {
        printf("content: %d\n", info->si_value.sival_int);
    }
}


int main(void)
{
    struct sigaction act = {0};


    act.sa_sigaction = handler;
    act.sa_flags = SA_SIGINFO; //表明信号处理程序带有附加信息

    sigaction(SIGIO, &act, NULL);
    sigaction(SIGUSR1, &act, NULL);

    while(1) {
        sleep(10);
    }

    return 0;
}

4.2 信号发送:

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

sigqueue 函数 只能把信号发送给单个进程,可以使用 value 参数向信号处理程序传递整数值或者指针值

sigqueue 函数 不但可以发送额外的数据,还可以让信号进行排队(操作系统必须实现了 POSIX.1的实时扩展),对于设置了阻塞的信号,使用 sigqueue 发送多个同一信号,在解除阻塞时,接受者会接收到发送的信号队列中的信号,而不是直接收到一次。

但是,信号不能无限的排队,信号排队的最大值受到SIGQUEUE_MAX的限制,达到最大限制后,sigqueue 会失败,errno 会被设置为 EAGAIN。

 

#include <sys/types.h>
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>


int main(int argc, char *argv[]) 
{
    if(4 != argc) {
        printf("%s <Target_pid> <Signal_number> <content>\n", argv[0]);
        return -1;
    }

    union sigval val;

    int pid = atoi(argv[1]);
    int sig = atoi(argv[2]);
    val.sival_int = atoi(argv[3]);

    if(pid > 0 && sig > 0) {
        printf("send %d to pid_%d\n", val.sival_int, pid);
        sigqueue(pid, sig, val);

    } else {
        printf("Target_pid or Signal_number musb > 0\n");
    }
}


 

 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值