信号处理机制

目录
  1. 常用信号
  2. 信号集
  3. 信号处理函数
  4. 阻塞信号
  5. 产生信号函数
  6. 睡眠函数及定时器
  7. 线程与信号

常用信号

信号定义在文件中,可以通过在shell下键入kill –l查看信号列表,或者man 7 signal查看说明。
不能忽略的信号:SIGKILL及SIGSTOP 这两个信号是不能忽略的,即进程接收到这两个信号后,只能接受系统的默认处理,及终止进行。
中断信号:SIGINT(ctrl+c触发),SIGQIUT(ctrl+\触发)

信号集

POSIX.1 定义数据类型sigset_t用于表示一个信号集,并且定义了以下的一组信号能对sigset_t结构体进行处理的函数,用于初始化及增删信号集:

int sigemptyset(sigset_t *set);      //清空信号集合set
int sigfillset(sigset_t *set);      //将所有信号填充进set中
int sigaddset(sigset_t *set, int signum);    //往set中添加信号signum
int sigdelset(sigset_t *set, int signum);    //从set中移除信号signum
int sigismember(const sigset_t *set, int signum); //判断signnum是不是包含在set中
int sigpending(sigset_t *set);  //返回一信号集

信号处理函数

  1. signal
    其不能处理当正在执行信号处理函数期间又触发相同信号的情况。
    对应的信号处理函数:void handler(int)

  2. sigaction
    其为signal函数的升级版,此函数取代了unix早期版本使用的signal函数。其原型如下:
    int sigaction(int signo, const struct sigaction *act, struct sigaction *oldact);
    其中,signo是要检测或修改其具体动作的信号编号,act指针是需要设置的动作参数,oact参数用于存放该信号的上一个动作。其中struct sigation定义如下:

struct  sigaction {
             void (*sa_handler)(int);  /* signal handler */
             void (*sa_sigaction)(int, siginfo_t *, void *);
             sigset_t sa_mask;               /* signal mask to apply */
             int     sa_flags;               /* see signal options below */
     };

其中,sa_handler和sa_sigaction为信号处理函数。通常,使用sa_handler作为信号处理函数,如果在sigaction结构中使用SA_SIGINFO标志,则使用sa_sigaction作为信号处理程序为。有的系统把sa_handler和sa_sigaction用一个union表示(如mac os),所以应用只能一次使用这两个字段中的一个。sa_handler和sa_sigaction的原型如下:
void sa_handler(int)
void sa_sigaction (int iSignNum, siginfo_t *pSignInfo, void *pReserved);
对应的三个参数含义为:

  • iSignNum: 传入的信号
  • pSignInfo: 与信号相关的一些信息
  • pReserved: 保留
    sa_mask字段为信号屏蔽字,其指出来在调用该信号捕捉程序之前,这一信号集要加到进程的信号屏蔽字中;仅当从信号捕捉函数返回时再将进程的信号屏蔽字恢复为原先值。
    一旦对给定的信号设置一个动作,那么在调用sigaction显式地改变它之前,该设置就一直有效,这种方式与早期的不可靠信号机制不同,符合POSIX.1的要求。
    sa_flags指定对信号进行处理的各个选项,如SA_INTERRUPT(由信号中断的系统调用不自动重启),SA_RESTART(信号中断的系统调用自动重启),SA_SIGINFO(使用si_sigation处理函数,支持附加siginfo信息)
    其中,几个常见的选项描述如下:
  • SA_RESETHAND 处理完毕要捕捉的信号后,将自动撤消信号处理函数的注册,即必须再重新注册信号处理函数,才能继续处理接下来产生的信号。该选项不符合一般的信号处理流程,现已经被废弃。
  • SA_NODEFER 在处理信号时,如果又发生了其它的信号,则立即进入其它信号的处理,等其它信号处理完毕后,再继续处理当前的信号,即递规地处理。如果sa_flags包含了该掩码,则结构体sigaction的sa_mask将无效!
  • SA_RESTART 如果在发生信号时,程序正阻塞在某个系统调用,例如调用read()函数,则在处理完毕信号后,接着从阻塞的系统返回。该掩码符合普通的程序处理流程,所以一般来说,应该设置该掩码,否则信号处理完后,阻塞的系统调用将会返回失败!
  • SA_SIGINFO 指示结构体的信号处理函数指针是哪个有效,如果sa_flags包含该掩码,则sa_sigactiion指针有效,否则是sa_handler指针有效。

    阻塞信号

  1. sigaction阻塞
    函数sigaction中设置的被阻塞信号集合只是针对于要处理的信号,例如
struct sigaction act;
sigemptyset(&act.sa_mask);
sigaddset(&act.sa_mask,SIGQUIT);
sigaction(SIGINT,&act,NULL);

表示只有在处理信号SIGINT时,才阻塞信号SIGQUIT;

  1. sigprocmask阻塞
    函数sigprocmask是全程阻塞,在sigprocmask中设置了阻塞集合后,被阻塞的信号将不能再被信号处理函数捕捉,直到重新设置阻塞信号集合。其原型为:
    int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
    参数how的值为如下3者之一:
  • SIG_BLOCK: 将参数2的信号集合添加到进程原有的阻塞信号集合中
  • SIG_UNBLOCK: 从进程原有的阻塞信号集合移除参数2中包含的信号
  • SIG_SET: 重新设置进程的阻塞信号集为参数2的信号集
    参数set为阻塞信号集
    参数oldset是传出参数,存放进程原有的信号集。

产生信号函数

  • kill
    int kill(pid_t pid, int sig);
    用于将信号发送给进程或进程组.根据pid的参数设置,kill有以下4种不同的情况:
    1、 pid > 0 将信号发送给进程ID为pid的进程
    2、 pid == 0 将信号发送给与发送进程同一个进程组的所有进程
    3、 pid == -1 将信号发送给发送进程有权限向它们发送信号的所有进程
    4、 pid < 0 将信号发送给其进程组ID等于pid绝对值,而且发送进程具有权限向其发送信号的所有进程。
    信号编号为0的信号为空信号。如果signo为0,则kill仍执行正常的错误检查,但不发送信号,这常被用来确定一个特点进程是否仍然存在。如果向一个并不存在的进程发送信号,则kill返回-1,errno被设置为ESRH.
    返回值:成功返回0,否则为-1
  • raise
    int raise(int signo);
    等价于 kill(getpid(), signo) 即进程向自身发送信号。
  • alarm
    unsigned int alarm(unsigned int seconds);
    seconds指定经过多少秒产生SIGALRM信号;alarm函数可以设置一个定时器,在将来的某个时刻该定时器会超时,产生SIGALRM信号,如果忽略或不捕捉该信号,则其默认动作是终止调用alarm函数的进程。
    如果seconds值为0,则取消以前的闹钟时间,其余留值作为alarm函数的返回值。
  • sigqueue
    sigqueue也可以发送信号,并且能传递附加的信息
    int sigqueue(pid_t pid, int sig, const union sigval value);
    其中value为一整型与指针类型的联合体
union sigval {
int sival_int;
void *sival_ptr;
};

由sigqueue函数发送的信号的第3个参数value的值,可以被进程的信号处理函数的第2个参数info->si_ptr接收到。
附注:
siginfo_t结构体可以通过man sigaction查看其定义。

另外,pause函数可以使调用进程挂起直至捕获到一个信号。

睡眠函数及定时器

  1. sleep类函数
    sleep及usleep 前一个是以秒为单位,后一个是以毫秒为单位
    nanosleep 和sleep类似,但提高了纳秒级的精度
    其中sleep是采用信号机制进行处理的
    用到的函数有:
    unsigned int alarm(unsigned int seconds);
    int pause();
    因此:不要将alarm和sleep混用,不然会出现问题。而nanosleep则没有这个问题。
  2. 定时器
    Linux下的计时器
    Linux为每个进程维护3个计时器,分别是真实计时器、虚拟计时器和实用计时器。
  • 真实计时器(ITIMER_REAL)计算的是程序运行的实际时间;
  • 虚拟计时器(ITIMER_VITUAL)计算的是程序运行在用户态时所消耗的时间(可认为是实际时间减掉(系统调用和程序睡眠所消耗)的时间);
  • 实用计时器(ITIMER_PROF)计算的是程序处于用户态和处于内核态所消耗的时间之和。
    发送的信号分别为SIGALRM,SIGVTALRM,SIGPROF
    可结合setitimer函数来实现自己的定时器,但如果需要实现多个定时器的话,需要结果list结构来实现,可以参考用setitimer实现多个定时器

    线程与信号

    每个线程都有自己的信号屏蔽字,但是信号的处理是进程中所有线程共享的。这意味着当某个线程改变了某个给定信号的相关处理行为时,所有线程都必须共享这个处理行为的改变,单个线程可以通过设置自己的信号屏蔽字来达到屏蔽各自不需要的信号。
    线程中使用pthread_sigmask来设置线程的信号屏蔽字,其用法同sigprocmask,但其失败时返回错误码,不再像sigprocmask中那样设置errno并返回-1.
    线程可以使用sigwait来等待一个或多个信号的出现。其原型如下:
    int sigwait(const sigset_t * restrict set, int *restrict signop);
    其中,set参数指定了线程等待的信号集;signop返回接收的信号。函数成功时返回0;错误时返回正的错误编号.
    在sigwait返回前,sigwait将从进程中移除那些处于挂起等待的信号;如果具体实现支持信号排队,那么sigwait将会移除该信号的一个实例,其余的实例还要继续排队。
    为了避免错误发生,线程在调用sigwait之前,必须阻塞(通过调用pthread_sigmask)那些它正在等待的信号。sig_wait会原子性地取消信号集地阻塞状态,直到接收到新的信号。在返回之前,sigwait将恢复线程的信号屏蔽字。这样不这样做,那么在线程完成对sigwait调用之前的时间窗中,信号就可以发送给该线程,从而造成sig_wait长时间阻塞不返回。
    如果多个线程在sigwait的调用中等待同一个信号,那么在信号递送的时候,就只有一个线程可以从sigwait中返回(类似于条件变量的pthread_cond_wait)。如果这时即有sigwait调用,又有sigaction调用,那么将由操作心态决定是让sigwait返回,还是调用sigaction信号处理函数,但两者不会同时发生。
    另外,可以通过调用pthread_kill将信号发送给指定pthread_t tid的线程,支持传递signo = 0的信号用来检查线程是否存在。
本站总访问量