linux系统编程之signal信号处理程序

1 信号处理程序

1.1 概述

  1. 信号,软中断信号,用来通知进程发生了某些事件,是一种软件层面上对中断机制的一种模拟。信号本身是一段非常小的信息,一般都只是一个整数。
  2. 信号的目的有两个:(1)通知进程一个特定的事件发生了;(2)让进程执行一个信号处理函数。
  3. 使用kill -l查看系统支持的信号类型如下:

    这里写图片描述

  4. 其中,1-31是定期信号,32-64是实时信号。两者的区别在于:内核不支持定期信号的排队,导致,如果某一个定期信号在得到处理前被重复发送了多次,那么只会保留最后一个发送的信号;内核支持实时信号的排队,即重复发送的多个相同的实时信号都会被进程接收并处理。

  5. 信号的一个重要特征是不可预测性。信号传输包括两个阶段:信号产生和信号发出,信号产生即内核更新进程结构体来表示一个新的信号被发送,信号发出即内核通过改变进程执行状态来强制进程执行一个特定的信号处理函数。每个产生的信号最多被发出一次,信号是一种非常消耗CPU的资源,因为一旦一个信号被发出后,进程描述符的某些信息便会失效,需要更新。
  6. 信号在产生后、被发出前的这段时间内被称为pending signals(未发信号),一个信号处于未发信号的时间长短是不可预测的。需要考虑的因素如下:(1)信号一般只被发送到当前进程current;(2)一个进程可以阻塞某个信号,那么该种信号便会一直处于阻塞状态,直到进程取消该阻塞;(3)一个进程在执行一个信号处理函数的时候,通常会阻塞其他的信号直到该信号处理程序执行完毕。
  7. 信号机制中,内核的工作如下:(1)记录哪些信号被进程阻塞;(2)当从内核态切换到用户态的时候,判断进程是否有信号到达;(3)判断哪些信号可以被忽略,主要考虑:目标进程是否被其他进程跟踪、信号是否被阻塞、信号是否默认被忽略;(4)处理信号,保存执行上下文、切换进程状态到信号处理程序、恢复执行执行上下文。

1.2 系统调用

  1. kill():发送一个信号给线程组;
  2. tkill():发送一个信号给进程; tgkill():发送信号给一个特定线程组里的进程;
  3. sigaction():改变信号关联的行为; signal():与sigaction类似;
  4. sigpending():检查是否有pending signal; sigprocmask():修改阻塞信号的集合;
  5. sigsuspend():等待一个信号; rt_sigaction():改变一个实时信号关联的行为;
  6. rt_sigpending():检查是否有pending real-time signal;
  7. rt_sigprocmask():修改阻塞的实时信号的集合; rt_sigqueueinfo():发送一个实时信号到一个线程组;
  8. rt_sigsuspend():等待一个实时信号; rt_sigtimewait():与rt_sigsuslend类似。

1.3 示例程序

#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<time.h>

#define UNKNOWN "unknown"
#define name_path 20
char sig_list[][name_path] = {
    "SIGHUP", "SIGINT", "SIGQUIT", "SIGILL", "SIGTRAP", "SIGABRT", "SIGBUS", "SIGFPE", "SIGKILL", "SIGUSR1", "SIGSEGV", "SIGUSR2", "SIGPIPE", "SIGALRM", "SIGTERM", "SIGSTKFLT", "SIGCHLD", "SIGCONT", "SIGSTOP", "SIGTSTP", "SIGTTIN", "SIGTTOU", "SIGURG", "SIGXCPU", "SIGXFSZ", "SIGVTALRM", "SIGPROF", "SIGWINCH", "SIGIO", "SIGPWR", "SIGSYS", UNKNOWN, UNKNOWN, "SIGRTMIN", "SIGRTMIN+1", "SIGRTMIN+2", "SIGRTMIN+3", "SIGRTMIN+4", "SIGRTMIN+5", "SIGRTMIN+6", "SIGRTMIN+7", "SIGRTMIN+8", "SIGRTMIN+9", "SIGRTMIN+10", "SIGRTMIN+11", "SIGRTMIN+12", "SIGRTMIN+13", "SIGRTMIN+14", "SIGRTMIN+15", "SIGRTMAX-14", "SIGRTMAX-13", "SIGRTMAX-12", "SIGRTMAX-11", "SIGRTMAX-10", "SIGRTMAX-9", "SIGRTMAX-8", "SIGRTMAX-7", "SIGRTMAX-6", "SIGRTMAX-5", "SIGRTMAX-4", "SIGRTMAX-3", "SIGRTMAX-2", "SIGRTMAX-1", "SIGRTMAX"
};
int list_len = 64;

void current_time(){
    struct timeval tv;
    gettimeofday(&tv,NULL);
    printf("current time = %ldms\n", tv.tv_sec * 1000 + tv.tv_usec / 1000);
}

void sig_op(int sig, siginfo_t *info, void *other){
    sigset_t set;
    if(sigemptyset(&set) < 0){
        printf("sig_op: sigemptyset error!\n");
        return;
    }
    if(sigaddset(&set, sig) < 0){
        printf("sig_op: sigaddset error!\n");
        return;
    }
    if(sigprocmask(SIG_BLOCK, &set, NULL) < 0){
        printf("sig_op: sigprocmask error!\n");
        return;
    }
    printf("Recievd sig %d\n", sig);
    switch(sig){
        case 62:
            printf("I am going to sleep 10 sec.\n");
            current_time();
            sleep(10);
            current_time();
            printf("I have been waken up.\n");
            break;
        case 63:
            printf("I am doing something cool!\n");
            break;
        case 64:
            printf("Bye!\n");
            exit(0);
    }
    if(sigprocmask(SIG_UNBLOCK, &set, NULL) < 0){
        printf("sig_op: sigprocmask unblock error!\n");
        return;
    }
}

int main(int argc, char *argv[]){
    struct sigaction act, old_act;
    act.sa_sigaction = sig_op;
    act.sa_flags = SA_SIGINFO;
    if(sigemptyset(&act.sa_mask) < 0){
        printf("sigemptyset error!\n");
        exit(-1);
    }
    int i;
    for(i = 0; i < list_len; i ++){
        if(sigaction(i + 1, &act, &old_act) < 0){
            printf("%s = %d: sigaction fail!\n", sig_list[i], i + 1);
        }
    }
    while(1){};
    return 0;
}
  • 连续发送两次信号62,执行第一次信号62期间,第二次的信号62被阻塞,如下图:

这里写图片描述

  • 当第一次的信号62执行完毕,第二次被阻塞的信号62接着执行:

这里写图片描述

  • 当连续发送一次信号62和一次信号63的时候,信号62的sleep时间没有10秒,如下图:

这里写图片描述

  • 说明,信号62对应的sleep(10)执行期间,信号63到达,内核发现信号63不是阻塞信号,于是保存进程上下文、改变进程执行状态去执行信号63对应的函数,当信号63执行完毕,直接返回到信号62对应的sleep(10)的下一个指令继续执行。
  • 原因解释 ==> sleep()函数的实现机制分为三步:(1)注册一个信号signal(SIGALRM,
    handler),接收内核给出任何一个信号;(2)调用alarm函数,告诉内核经过一定时间后给当前进程发送SIGALRM信号;(3)调用pause函数,等待任何一个信号。于是,当执行到第三步pause时,第三方程序可以向当前进程发送任何一个信号(比如:信号63)以达到提前结束sleep函数的执行的目的。
  • 如下图所示:

这里写图片描述

猜你喜欢

转载自blog.csdn.net/u011414616/article/details/80876442
今日推荐