Linux进程间通信(01) — 信号

Linux信号机制是一种异步通知机制,用于通知进程某个事件的发生。常被看做是一种软中断,类似于硬中断,但作用对象是进程而非 CPU,这是Glib提供的功能,所以源码需要到Glib代码库中查看。

1
2
3
4
5
6
7
8
#define SIGHUP		 1
#define SIGINT		 2
#define SIGQUIT		 3
#define SIGILL		 4
#define SIGTRAP		 5
#define SIGABRT		 6
#define SIGIOT		 6
// ......

常见的有SIGINT、SIGKILL、SIGUSR1、SIGUSR2、SIGTERM等等,使用kill -l命令可以查看系统中的所有信号。

SIGINT 中断信号,用户按下 Ctrl+C 触发,用于终止前台进程
SIGKILL 强制终止进程,无法被忽略或捕获
SIGTERM 优雅终止信号,默认由 kill 命令发送,允许进程清理资源
SIGUSR1
SIGUSR2
用户自定义信号 2,可由程序自定义用途

1. 注册信号处理器

1.1. signal

指定进程收到某个信号后要去做的事情。

1
2
// 若设置失败,返回 SIG_ERR,并设置 errno 表示错误原因。
extern __sighandler_t signal (int __sig, __sighandler_t __handler) __THROW;

其中 __sighandler_t:

1
typedef void (*__sighandler_t) (int);

所以__sighandler_t是一个函数指针类型。

1.2. sigaction

1
2
3
4
5
6
7
8
/*
 *  __act:  内部包含了信号处理回调函数。
 *  __oact: 对于原来的信号处理回调函数的备份,一般为NULL,不备份。  
 */
extern int sigaction (int __sig, 
                      const struct sigaction *__restrict __act, 
                      struct sigaction *__restrict __oact
) __THROW;

struct sigaction的原型:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
struct sigaction
  {
#if defined __USE_POSIX199309 || defined __USE_XOPEN_EXTENDED
    union
    {
        __sighandler_t sa_handler;                        // 普通信号处理函数。  
        void (*sa_sigaction) (int, siginfo_t *, void *);  // 带有额外数据的信号处理函数,需要将sa_flags成员设置为SA_SIGINFO。  
    } __sigaction_handler;


# define sa_handler	    __sigaction_handler.sa_handler
# define sa_sigaction	__sigaction_handler.sa_sigaction

# else
    __sighandler_t sa_handler;
#endif

    __sigset_t sa_mask;         // 信号处理期间阻塞的信号集,当前处理的信号会自动被阻塞(防止递归触发)。
    int sa_flags;               // 标志位,控制信号行为
    void (*sa_restorer) (void); // 已弃用,保留历史兼容性
  };

对于sa_mask:

1
2
3
// sa是struct sigaction类型变量
sigemptyset(&sa.sa_mask);       // 清空信号集
sigaddset(&sa.sa_mask, SIGINT); // 添加 SIGINT 到阻塞列表

对于siginfo_t:
siginfo_t 是 POSIX 信号处理中用于传递 信号详细信息 的结构体,通常与 SA_SIGINFO 标志一起使用。它提供了比传统 signal() 函数更丰富的上下文信息。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
typedef struct
{
    int si_signo;        // 信号编号。
    int si_errno;        // 错误码,如果非零,表示与信号相关的系统错误码。
    int si_code;         // 指示信号产生的具体原因,分为系统生成(SI_KERNEL)和用户生成(SI_USER),需要的时候在进一步查询。 
    __pid_t si_pid;		 // 发送信号的进程 ID。  
    __uid_t si_uid;	     // 发送信号的用户 ID。  
    void *si_addr;       // 指向引发信号的内存地址(如段错误时的无效地址)。
    int si_status;       // 传递子进程的退出状态或信号值。  
    long int si_band;    // 与 SIGPOLL 或 SIGIO 信号一起使用,表示文件描述符的带事件(如 I/O 就绪)。
    __sigval_t si_value; // 通过 sigqueue() 发送的自定义信号值(sigval)。
} siginfo_t;

2. 发送信号

发送信号有两个函数: kill、sigqueue,其中sigqueue可以携带额外的数据。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
/*
 *  __pid: 目标进程ID。
 *  __sig: 要发送的信号。
 */
extern int kill (__pid_t __pid, int __sig);

/*
 *  __val: 携带的额外数据。  
 */
extern int sigqueue (__pid_t __pid, int __sig, const union sigval __val); 

union sigval的结构如下:

1
2
3
4
5
union sigval
{
  int sival_int;
  void *sival_ptr;
};

3. 使用

以带数据的信号为例:

  1. 信号接收方:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// 信号处理函数
void handler(int sig, siginfo_t *info, void *old)
{
    // 检查信号,sig。  
    // 从info中读取信号上下文。  
}

struct sigaction act;
act.sa_sigaction = handler;
act.sa_flags = SA_SIGINFO;
sigaction(SIGUSR1, &act, NULL);
  1. 信号发送方:
1
2
3
union signal value;
value.sigval_int = 99;
sigqueue(getpid(), SIGUSR1, value);
comments powered by Disqus