Linux信号(一)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/puliao4167/article/details/85324844

信号的概述

    信号是事件发生时对进程的通知机制,也成为软件中断,是进程之间通信的方式之一。信号分为两大类,一组用于内核向进程通知事件,构成所谓的传统或标准信号;另一组由实时信号构成。

    信号因某些事件而产生,信号产生后,会于稍后传递给某个进程,进程即会采取相应的措施来相应信号。但是有时候需要保证一段代码不被传递来的信号所中断,即可以将信号添加到进程的信号掩码中(其作用就是阻塞该信号的到达),如产生的信号处于该阻塞之列,那么信号将保持等待状态。

    信号到达后,进程视具体信号执行如下默认操作之一:

  1. 忽略信号。
  2. 终止(杀死)进程。
  3. 产生核心转储文件,同时进程终止
  4. 停止进程:暂停进程的执行
  5. 与之前停止后再度恢复进程执行。

    除此之外,程序也可以改变信号到达时候的响应行为,执行信号处理器程序(用于为响应传递来的信号而执行适当任务)。

信号类型及默认行为

    以下给出了几种常见的信号及默认行为。信号的默认行为:term表示信号终止进程,core表示产生核心转储文件并退出,ignore表示忽略信号,stop表示信号停止进程,cont表示信号恢复一个已停止的进程。

SIGABRT 当进程调用abort()时,该信号终止进程,并产生核心转储文件。core
SIGALRM 经调用alarm()、setitimer()而设置定时器到期,内核产生该信号。term
SIGBUS 产生该信号(总线错误)即表示产生某种内存访问错误。core
SIGCHLD 当父进程的某一个子进程终止、停止或恢复,内核向父进程发送信号。ignore
SIGCONT 将信号发送给已经停止的进程,进程将会恢复允许,若进程非停止状态则忽略该信号。cont
SIGINT 当用户键入终端中断字符(control-c),终端驱动程序发该信号给前台进程。term
SIGKILL 此信号用于终止进程,处理器程序无法将其阻塞、忽略或捕获,保证杀死。term
SIGPIPE 当某一进程试图向管道、FIFO或套接字写入信息时,若这些设备并无响应阅读进程,即会产生此信号。term
SIGSEGV 当应用程序对内存的引用无效时,即会产生该信号。core
SIGTERM 用来终止进程的标准信号。term
SIGSTOP 是一个必停信号,从能让进程停止。stop

信号处理器简介

    信号处理器程序是当指定信号传递给进程时,将会调用的一个函数。调用信号处理器程序,可能会打断主程序流程。内核代表进程来调用处理器程序,当处理器返回时,主程序会在处理器打断的位置恢复执行。

    接下来介绍一下signal系统调用。由于主要推荐用sigaction,因此不多介绍。

#include <signal.h>

void (*signal(int sig,void(*handler)(int)) ) (int);//失败返回SIG_ERR

//handler的参数int用于同一个处理程序捕捉不同类型的信号,用此参数来判断

kill()发送信号

    一个进程可以使用kill()系统调用向另一个进程发送信号。当pid>0,那么发送信号会给pid指定的进程;pid=0,那么发送信号给与调用进程同组的每个进程,包括调用进程自身。pid=-1,则调用进程有权将信号发送每个目标进程(除了init和自身)

#include <signal.h>

int kill(pid_t pid,int sig);

int raise(int sig);

int killpg(pid_t pgrp,int sig);

    kill()系统调用还可以检查进程的存在,将参数sig设定为0(即空信号),kill就会查看是否可以向目标进程发送信号。

    除此之外,还可以使用raise()系统调用让进程向自身发送信号。killpg()向某一格进程组所有成员发送信号。

信号集

    多个信号可使用一个称之为信号集的数据结构表示,用系统数据结构sigset_t表示。很多信号相关的系统调用都是以组的形式执行的,以下介绍几种常见的。

#include <signal.h>

//必须使用以下两个初始化方式之一来初始化信号集
int sigemptyset(sigset_t *set);//初始化一个未包含任何成员的信号集
int sigfillset(sigset_t *set); //初始化一个信号集,使其包含所有信号(包括实时信号)

int sigaddset(sigset_t *set ,int sig);//向信号集中添加单个信号
int sigdelset(sigset_t *set ,int sig);//向信号集中删除单个信号

int sigismember(const sigset_t *set,int sig);//判断信号是否是信号集成员,是返回1,否返回0

int sigandset(sigset_t *dest, sigset_t *left, sigset_t *right);//left和right交集置于dest
int sigorset(sigset_t *dest, sigset_t *left, sigset_t *right);//left和right并集置于dest

int sigisemptyset(const sigset_t *set);//set为空返回1,否则返回0

char* strssignal(int sig);//显示某个信号的描述

    信号掩码:内核会为每一个进程维护一个信号掩码,即一组信号,并将阻塞其针对该进程的传递。系统将忽略阻塞SIGKILL和SIGSTOP的请求。向信号掩码中添加一个信号三种方式:

  1. 当调用信号处理器程序时,可将引发调用的信号自动添加到信号掩码中(根据sigaction标志而定)
  2. 使用sigaction函数建立信号处理器程序时,可以指定一组额外信号,当调用该处理器程序时会将其阻塞。
  3. 使用sigprocmask()系统调用,显式地向信号掩码中添加或移除信号。
#include <signal.h>

int sigprocmask(int how,const sigset_t *set,sigset_t *oldset);

//how=SIG_BLOCK 将set指定信号集内的信号添加到信号掩码中
//how=SIG_UNBLOCK 将set指定信号集内的信号从信号掩码中移除
//how=SIG_SETMASK 将set指定信号集内的信号赋值给信号掩码中
//若oldset不为空,则用于存储改变之前的信号掩码,若想获得信号掩码而不改变其,则set置空,忽略how

int sigpending(sigset_t *set);//set返回该进程处于等待的信号集

    若进程接受了一个该进程正在阻塞的信号,则会将信号添加到进程的等待信号集中,当解除了对该信号的锁定,会随之将信号传递给进程。用sigpending()获取进程处于等待状态的信号。等待信号集只是一个掩码,仅表示一个信号是否在等待,而未表明其发生的次数,即若同一个信号在阻塞状态下产生多次,那么该信号在等待信号集中,并在稍后仅传递一次(标准信号与实时信号差异之一,实时信号进行排队处理)。

sigaction()信号处置

    sigaction()较signal()更加方便。其允许在获取信号处置的同时不对其进行改变,并且可以设置各种属性对调用信号处理器程序控制。

#include <signal.h>

int sigaction(int sig,const struct sigaction *act,struct sigaction *oldact);
//sig标识想要获取或者改变的信号编号,act指向描述信号新处理的数据结构,oldact返回之前信号处理的相关信息


struct sigaction
{
    void (*sa_handler)(int); //信号处理函数
    sigset_t sa_mask;//在调用信号处理器时将阻塞该组信号,引发处理器程序调用的信号自动加入信号掩码
    int sa_flags;//用于控制信号处理过程的各种选项
    void (*sa_restorer)(void);
}

sa_flags的各种选项:

SA_NOCLDSTOP:若sig为SIGCHLD信号,则当因接受一信号而停止或恢复某一子进程时,将不会产生此信号
SA_NOCLDWAIT:若sig为SIGCHLD信号,则当子进程终止时不会将其转换为僵尸
SA_ONSTACK:针对此信号调用处理器函数时,使用了由sigalstack()安装的备选栈
SA_RESTART:自动重启由信号处理器函数中断的系统调用
SA_SIGINFO:调用信号处理器程序时可携带额外参数

信号处理器函数

    设计信号处理器函数中,并非所有的系统调用和库函数均可以安全调用。要注意可重入函数和异步信号安全函数。

  • 可重入函数:如果同一个进程的多条线程可以同时安全地调用某一函数,那么该函数是可重入地。更新全局变量或静态数据结构地函数,诗经静态分配内存来返回信息的函数,将静态数据结构用于内部记账的函数,这些都是不可重入的。
  • 异步信号安全函数是指当从信号处理器函数调用时,可以保证其实现是安全的

    尽管可能存在可重入问题,但有时候主程序和信号处理程序之间需共享全局变量。这时候应该定义如下变量。volatile可防止编译器将其优化到寄存器中,sig_atomic_t类型可保证读写操作的原子性:

volatile sig_atomic_t flag;

    终止信号处理器函数的方法:

  1. 使用_exit()终止进程,不能使用exit()因为其不安全(在调用_exit()之前刷新缓冲区)
  2. 使用kill()信号来杀死进程
  3. 从信号处理器函数中指向非本地跳转,sigsetjmp()和siglongjmp()
  4. 使用abort()函数终止进程,并产生核心转储。

参考《TLPI》《APUE》

猜你喜欢

转载自blog.csdn.net/puliao4167/article/details/85324844