Linux C++下网络编程之信号

1. 什么是信号

信号是由用户、系统或者进程发送给目标进程的信息,以通知目标进程某个状态的改变或系统异常。
信号的可能来源包含三个方面:

  • 用户,例如我们经常使用Ctrl+C来终止程序,它实际上就是给程序发送了一个中断信号;
  • 系统,这里包括系统某些状态的变化或者异常,比如设备就绪、内存分配失败等;
  • 进程,一个进程给另一个进程发送信号,收到信号的进程然后就可以执行某些特定操作。

2. Linux信号概述

2.1 发送信号

一个进程使用kill函数来给其他进程发送信号。

#include <signal.h>

/* Send signal SIG to process number PID.  If PID is zero,
   send SIG to all processes in the current process's process group.
   If PID is < -1, send SIG to all processes in process group - PID.  */
#ifdef __USE_POSIX
extern int kill (__pid_t __pid, int __sig) __THROW;
#endif /* Use POSIX.  */

参数说明:

  • pid:目标进程号
  • sig:信号值,linux中定义的信号值都大于0,如果sig取值为0,则kill函数不发送任何信号

返回值:
kill函数成功时返回0,失败时则返回-1,并设置errno

2.2 信号处理方式

目标进程在收到信号时,需要定义一个接收函数来处理之。信号处理函数的原型如下:

#include <signal.h>

/* Type of a signal handler.  */
typedef void (*__sighandler_t) (int);

参数说明:

  • 信号处理函数只带有一个整型参数,该参数用来指示信号类型

除了用户自定义信号处理函数外,bits/signum.h头文件中还定义了信号的其他几种处理方式——SIG_ERRSIG_IGNSIG_DEL

/* Fake signal functions.  */

#define	SIG_ERR	 ((__sighandler_t) -1)	/* Error return.  */
#define	SIG_DFL	 ((__sighandler_t)  0)	/* Default action.  */
#define	SIG_IGN	 ((__sighandler_t)  1)	/* Ignore signal.  */

#ifdef __USE_XOPEN
# define SIG_HOLD ((__sighandler_t) 2)	/* Add signal to hold mask.  */
#endif

2.3 Linux信号

Linux的可用信号都定义在bits/signum.h头文件中,其中包括标准信号和POSIX实时信号。

2.4 中断系统调用

如果程序在执行处于阻塞状态的系统调用时接收到信号,并且我们为该信号设置了信号处理函数,则默认情况下系统调用将被中断,并且errno被设置为EINTR。我们可以使用sigaction函数为信号设置SA_RESTART标志以自动重启被该信号中断的系统调用。

3. 信号函数

3.1 signal系统调用

可以使用signal系统调用为一个信号设置处理函数。

#include <signal.h>

/* Set the handler for the signal SIG to HANDLER, returning the old
   handler, or SIG_ERR on error.
   By default `signal' has the BSD semantic.  */
extern __sighandler_t signal (int __sig, __sighandler_t __handler)
     __THROW;

参数说明:

  • sig:指出要捕获的信号类型
  • handler:指定信号sig的处理函数

返回值:

  • signal函数成功时返回一个函数指针,这个返回值是前一次调用signal函数时传入的函数指针
  • signal函数失败时返回SIG_ERR,并设置errno

3.2 sigaction系统调用

设置信号处理函数的更健壮的接口是如下的系统调用:

#include <signal.h>

/* Get and/or set the action for signal SIG.  */
extern int sigaction (int __sig, const struct sigaction *__restrict __act,
		      struct sigaction *__restrict __oact) __THROW;

参数说明:

  • sig:指出要捕获的信号类型
  • act:指定新的信号处理方式
  • oact:输出信号之前的处理方式(如果不为NULL

actoact都是sigaction结构体类型的指针,其定义如下:

/* Structure describing the action to be taken when a signal arrives.  */
struct sigaction
  {
    /* Signal handler.  */
#if defined __USE_POSIX199309 || defined __USE_XOPEN_EXTENDED
    union
      {
	/* Used if SA_SIGINFO is not set.  */
	__sighandler_t sa_handler;
	/* Used if SA_SIGINFO is set.  */
	void (*sa_sigaction) (int, siginfo_t *, void *);
      }
    __sigaction_handler;
# define sa_handler	__sigaction_handler.sa_handler
# define sa_sigaction	__sigaction_handler.sa_sigaction
#else
    __sighandler_t sa_handler;
#endif

    /* Additional set of signals to be blocked.  */
    __sigset_t sa_mask;

    /* Special flags.  */
    int sa_flags;

    /* Restore handler.  */
    void (*sa_restorer) (void);
  };

成员说明:

  • sa_handler:指定信号处理函数
  • sa_mask:设置进程的信号掩码(在进程原有信号掩码的基础上增加信号掩码),以指定哪些信号不能发送给本进程
  • sa_flags:设置程序收到信号时的行为,例如2.4小节中提到的设置SA_RESTART标志以自动重启被该信号中断的系统调用
  • sa_restorer:已过时,最好不用

sa_mask是信号集__sigset_t类型,该类型指定一组信号。

4. 信号集

4.1 信号集函数

Linux使用数据结构__sigset_t来表示一组信号,其定义如下:

#define _SIGSET_NWORDS (1024 / (8 * sizeof (unsigned long int)))
typedef struct
{
  unsigned long int __val[_SIGSET_NWORDS];
} __sigset_t;

参数说明:

  • __sigset_t结构体中只有一个长整型数组**__val**,数组的每个元素的每个位表示一个信号

Linux提供了如下一组函数来设置修改删除查询信号集:

/* Clear all signals from SET.  */
extern int sigemptyset (sigset_t *__set) __THROW __nonnull ((1));

/* Set all signals in SET.  */
extern int sigfillset (sigset_t *__set) __THROW __nonnull ((1));

/* Add SIGNO to SET.  */
extern int sigaddset (sigset_t *__set, int __signo) __THROW __nonnull ((1));

/* Remove SIGNO from SET.  */
extern int sigdelset (sigset_t *__set, int __signo) __THROW __nonnull ((1));

/* Return 1 if SIGNO is in SET, 0 if not.  */
extern int sigismember (const sigset_t *__set, int __signo)
     __THROW __nonnull ((1));

4.2 进程信号掩码

除了可以使用sigaction结构体的sa_mask成员来设置进程的信号掩码,还可以使用sigprocmask来设置或查看进程的信号掩码:

/* Get and/or change the set of blocked signals.  */
extern int sigprocmask (int __how, const sigset_t *__restrict __set,
			sigset_t *__restrict __oset) __THROW;

参数说明:

  • how:如果set参数不为NULL,则how参数指定设置进程信号掩码的方式,其可选值如下表
  • set:指定新的信号言码,如果setNULL,则进程信号掩码不变
  • oset:输出原来的信号掩码
how参数含义
SIG_BLOCK新的进程信号掩码是当前值和set指定信号集的并集
SIG_UNBLOCK新的进程信号掩码是当前值和~set指定信号集的并集,因此set指定的信号集将不被屏蔽
SIG_SETMASK直接将进程信号掩码设置成set

返回值:
sigprocmask成功时返回0,失败时返回-1并设置errno

4.3 被挂起的信号

设置进程信号掩码后,被屏蔽的信号将不能被进程接收。如果给进程发送一个被屏蔽的信号,则操作系统将该信号设置为进程的一个被挂起的信号。如果取消对被挂起信号的屏蔽,则它能立即被进程接收到。sigpending函数可以获得进程当前被挂起的信号集:

/* Put in SET all signals that are blocked and waiting to be delivered.  */
extern int sigpending (sigset_t *__set) __THROW __nonnull ((1));

参数说明:

  • set:保存被挂起的信号集

返回值:
sigpending成功时返回0,失败时返回-1并设置errno

5. 示例代码

#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <errno.h>

/* 信号处理函数 */
void sig_handler(int sig)
{
    int save_errno = errno;
    printf("start process signal: %d\n", sig);
    sleep(5);
    printf("end process\n");
    errno = save_errno;
}

/* 为信号sig设置它的处理函数 */
void addsig(int sig)
{
    struct sigaction sa;
    memset(&sa, '\0', sizeof(sa));
    sa.sa_handler = sig_handler;
    sa.sa_flags |= SA_RESTART;
    /*
        sigfillset(&sa.sa_mask)的作用是:在信号处理函数执行过程中,屏蔽所有信号
        (1)如果执行过程中,接收到其他信号,则把它先挂起,等此处理函数执行完毕再去处理接收到的信号
        (2)如果执行过程中多次接收到同一个信号,处理函数执行完毕后只去处理接收到的信号一次
        可在本程序代码触发一次的sleep的5s期间验证这两点
    */ 
    sigfillset(&sa.sa_mask);
    assert(sigaction(sig, &sa, NULL) != -1);
}

int main()
{
    addsig(SIGINT); // 2
    char ch;
    while (ch != 'q')
    {
        printf("press 'Ctrl+C' to generate a SIGINT and 'q' to exit\n");
        scanf("%c", &ch);
    }
    
    return 0;
}

示例代码运行结果

6. 参考书籍

Linux高性能服务器编程

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

MinBadGuy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值