zoukankan      html  css  js  c++  java
  • apue学习笔记(第十章 信号)

    本章先对信号机制进行综述,并说明每种信号的一般用法。

    信号概念

    每个信号都有一个名字,这些名字都以3个字符SIG开头。在头文件<signal.h>中,信号名都被定义为正整形常量。

    在某个信号出现时,可以按下列3种方式之一进行处理:

    1 忽略该信号。大多数信号都可以使用这种方式进行处理,但有两种信号却决不能被忽略:SIGKILL和SIGSTOP(只能执行系统默认动作)。

    2 捕获信号。通知内核在某信号发生时,调用一个用户函数对这种时间进行处理。

    3 执行系统默认动作。对于大多数信号的系统默认动作是终止该进程。

    信号类型

    程序错误类信号:默认动作使进程流产,产生core文件。

    SIGABRT:   调用abort函数生成的信号。

    SIGFPE:      浮点计算错误。

    SIGILL:      非法指令错误。

    SIGBUS/SIGSEGV: 硬件错误-非法地址访问。

    SIGEMT:    硬件错误

    SIGSYS:       非法系统调用。

    SIGTRAP:   硬件错误(通常为断点指令)。

    程序终止类信号:默认动作使进程终止,我们通常要处理这类信号,做一些清理工作,句柄函数应在结束时为此信号指定默认动作,然后再次生成该信号,使得程序终止。

    SIGHUP:终端断开连接时,生成此信号给控制进程。

    SIGINT:Ctrl-C或Delete按下时,由终端驱动生成,并发送给前台进程组中的所有进程。

    SIGKILL:使程序立即终止,不能被捕获或忽略,也不能被阻塞。

    SIGQUIT:Ctrl-\,如SIGINT,并且产生core。

    SIGTERM:该信号使程序终止,但是可以阻塞、捕获、忽略。

    闹钟类信号:通知定时器到期,默认动作是终止程序,但通常会设置句柄。

    SIGALRM:alarm/setitimer函数设置定时到期后,会产生此信号。

    SIGPROF:

    SIGVTALRM:

    I/O类信号:通知进程在描述字上发生了感兴趣事件,支持信号驱动IO。

    SIGIO: fd准备执行输入输出时发送此信号。

    SIGPOLL:异步I/O信号。

    SIGURG:网络收到带外数据时可选择生成此信号。

    作业控制类信号:

    SIGCHLD:   进程终止或停止时会向其父进程发送该信号,默认动作为忽略。

    SIGCONT:     使停止的进程恢复运行。

    SIGSTOP:      停止进程。

    SIGTSTP/SIGTTIN/SIGTTOU:

    操作错误类信号:默认动作终止程序。

    SIGPIPE:    管道破裂。

    SIGXCPU/SIGXFSZ:

    函数signal

    UNIX系统信号机制最简单的接口是signal函数

    #include <signal.h>
    void (*signal(int signo,void (*func)(int)))(int);

    可以使用typedef使其变得简单一点

    typedef void Sigfunc(int);
    Sigfunc *signal(int,Sigfunc *);

    第一个int参数是要捕获的信号(整形常量),第二个参数是一个函数指针(处理函数),该函数指针指向的函数返回值是void,参数是int。

    下面给出一个简单得信号处理程序:

     1 #include "apue.h"
     2 
     3 static void    sig_usr(int);    /* one handler for both signals */
     4 
     5 int
     6 main(void)
     7 {
     8     if (signal(SIGUSR1, sig_usr) == SIG_ERR)
     9         err_sys("can't catch SIGUSR1");
    10     if (signal(SIGUSR2, sig_usr) == SIG_ERR)
    11         err_sys("can't catch SIGUSR2");
    12     for ( ; ; )
    13         pause();
    14 }
    15 
    16 static void
    17 sig_usr(int signo)        /* argument is signal number */
    18 {
    19     if (signo == SIGUSR1)
    20         printf("received SIGUSR1
    ");
    21     else if (signo == SIGUSR2)
    22         printf("received SIGUSR2
    ");
    23     else
    24         err_dump("received signal %d
    ", signo);
    25 }
    View Code

    我们使该程序在后台运行,而且用kill命令将信号发送给它:

    中断的系统调用

    早期UNIX系统的一个特性是:如果进程在执行一个低速系统调用而阻塞期间捕获到一个信号,则该系统调用就被中断不再继续执行。

    该系统调用返回出错,其errno设置为EINTR。后面的章节会更多的涉及到被中断的系统调用。

    可重入函数

    进程捕获到信号并对其进行处理时,进程正在执行的正常指令序列就被信号处理程序临时中断,它首先执行该信号处理程序中的指令。

    如果从信号处理程序返回,则继续执行在捕获到信号时进程正在执行的正常指令序列。

    在信号处理函数中调用某些函数可能对导致安全问题(其结果是不可预知的),下面列出了这些异步信号安全的函数,没有列入图中的大多数函数是不可重入的。

    函数kill和raise

    kill函数将信号发送给进程或进程组,raise函数则允许进程向自身发送信号。

    #include <signal.h>
    int kill(pid_t pid,int signo);
    int raise(int signo);

    函数alarm和pause

    函数alarm设置一个定时器,当定时器超时时,产生SIGALRM信号。

    #include <unistd.h>
    unsigned int alarm(unsigned int seconds);

    pause函数使调用进程挂起直至捕捉到一个信号

    #include <signal.h>
    int pause(void);

    只有执行了一个信号处理程序并从其返回时,pause才返回。此时,pause返回-1,errno设置为EINTR。

    信号集

    信号集是能表示多个信号的数据结构(sigset_t),下面列出5个处理信号集的函数

    #include <signal.h>
    int sigemptyset(sigset_t *set);
    int sigfillset(sigset_t *set);
    int sigaddset(sigset_t *set,int signo);
    int sigdelset(sigset_t *set,int signo);
    int siggismember(const sigset_t *set,int signo);

    在使用信号集之前,要对该信号集进行初始化(调用sigemptyset或者sigfillset)。

    函数sigprocmask

    进程的信号屏蔽字规定了当前阻塞而不能递送给该进程的信号集。调用函数sigprocmask可以检测或更改进程的信号屏蔽字。

    #include <signal.h>
    int sigprocmask(int how,const sigset_t *restrict set,sigset_t *restrict oset);

    若oset是非空指针,那么进程的当前信号屏蔽字通过oset返回。

    若set是一个非空指针,则参数how指示如何修改当前信号屏蔽字。SIG_BLOCK是或操作,SIG_SETMASK则是赋值操作

    函数sigpending

    sigpending函数返回一信号集,对于调用进程而言,其中的各信号是阻塞不能递送的,因而也一定是当前未决的。

    #include <signal.h>
    ing sigpending(sigset_t *set);

    下面展示信号设置和sigprocmask实例

     1 #include "apue.h"
     2 
     3 static void    sig_quit(int);
     4 
     5 int
     6 main(void)
     7 {
     8     sigset_t    newmask, oldmask, pendmask;
     9 
    10     if (signal(SIGQUIT, sig_quit) == SIG_ERR)
    11         err_sys("can't catch SIGQUIT");
    12 
    13     /*
    14      * Block SIGQUIT and save current signal mask.
    15      */
    16     sigemptyset(&newmask);
    17     sigaddset(&newmask, SIGQUIT);
    18     if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)
    19         err_sys("SIG_BLOCK error");
    20 
    21     sleep(5);    /* SIGQUIT here will remain pending */
    22 
    23     if (sigpending(&pendmask) < 0)
    24         err_sys("sigpending error");
    25     if (sigismember(&pendmask, SIGQUIT))
    26         printf("
    SIGQUIT pending
    ");
    27 
    28     /*
    29      * Restore signal mask which unblocks SIGQUIT.
    30      */
    31     if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
    32         err_sys("SIG_SETMASK error");
    33     printf("SIGQUIT unblocked
    ");
    34 
    35     sleep(5);    /* SIGQUIT here will terminate with core file */
    36     exit(0);
    37 }
    38 
    39 static void
    40 sig_quit(int signo)
    41 {
    42     printf("caught SIGQUIT
    ");
    43     if (signal(SIGQUIT, SIG_DFL) == SIG_ERR)
    44         err_sys("can't reset SIGQUIT");
    45 }
    View Code

    进程开始阻塞SIGQUIT信号,保存了当前信号屏蔽字(以便以后恢复),然后休眠5秒。在此期间所产生的退出信号SIGQUIT都被阻塞,不递送至该进程。

    5秒休眠后,检查该信号是否是未决的,然后将SIGQUIT设置为不再阻塞。

    运行程序,在5s之内键入退出字符Ctril+(产生SIGQUIT信号),然后在第二个5s之内再次键入退出字符。

    函数sigaction

    sigaction函数的功能是检查或修改与制定信号相关联的处理动作。此函数取代了UNIX早期版本使用的signal函数。

    #include <signal.h>
    int sigaction(int signo,const struct sigction *restrict act,struct sigaction *restrict oact);

    参数signo是要检测或修改其具体动作的信号编号。若act指针非空,则根据参数act修改其动作。若oact指针非空,则由oact指针返回该信号的上一个动作。

    此函数使用下列结构:

    struct sigaction {
            void     (*sa_handler)(int);
            void     (*sa_sigaction)(int, siginfo_t *, void *);
            sigset_t   sa_mask;
            int        sa_flags;
    };

    sa_handler字段包含一个信号捕捉函数的地址。

    sa_mask字段说明了一个信号集,在调用该信号捕捉函数之前,这一信号集要加到进程的信号屏蔽字中。仅当从信号捕捉函数返回时将进程的信号屏蔽字恢复为原先值。

    sa_flags字段指定对信号进行处理的各个选项。

    sa_sigaction字段是一个替代的信号处理程序,当sa_flags设置为SA_SIGINFO时,使用该信号处理程序。

    通常按下列方式调用信号处理程序:

    void handler(int signo);

    在设置了SA_SIGINFO标志,那么按下列凡是调用信号处理程序:

    void handler(int signo,siginfo_t *info,void *context);

    下面使用sigaction实现signal函数,它力图阻止被中断的系统调用重启动

    typedef void Sigfunc(int);
    Sigfunc* mysignal(int signo,Sigfunc *func) {

    struct sigaction act,oact; act.sa_handler = func; sigemptyset(&act.sa_mask); act.sa_flags = 0; if(signo == SIGALRM) { #ifdef SA_INTERRUPT act.sa_flags |= SA_INTERRUPT; #endif } else { #ifdef SA_RESTART act.sa_flags |= SA_RESTART; #endif } if(sigaction(signo,&act,&oact)<0) return (SIG_ERR); return (oact.sa_handler); }

    函数sigsetjmp和siglongjmp

    之前说明了setjmp和longjmp函数可以用户非局部转移,sigsetjmp跟siglongjmp指定了对信号屏蔽字的作用。

    在信号处理程序中进行非局部转移时应当使用这两个函数。

    #include <setjmp.h>
    int sigsetjmp(sigjmp_buf env,int savemask);
    void siglongjmp(sigjmp_buf env,int val);

    与setjmp和longjmp函数唯一的区别是sigsetjmp增加了一个参数savemask。

    如果savemask非0,则sigsetjmp在env中保存在env中保存进程的当前信号屏蔽字。调用siglongjmp时,从已经保存的env中恢复保存的信号屏蔽字。

    函数sigsuspend

    sigsuspend用于在接收到某个信号之前,临时用sigmask替换进程的信号屏蔽字,并暂停进程执行,直到捕捉到一个信号而且从该信号处理程序返回,并且进程的信号屏蔽字设置为调用sigsuspend之前的值。

    #include <signal.h>
    int sigsuspend(const sigset_t *sigmask);

    下面显示了保护代码临界区,使其不被特定信号中断的正确方法

     1 #include "apue.h"
     2 
     3 static void    sig_int(int);
     4 
     5 int
     6 main(void)
     7 {
     8     sigset_t    newmask, oldmask, waitmask;
     9 
    10     pr_mask("program start: ");
    11 
    12     if (signal(SIGINT, sig_int) == SIG_ERR)
    13         err_sys("signal(SIGINT) error");
    14     sigemptyset(&waitmask);
    15     sigaddset(&waitmask, SIGUSR1);
    16     sigemptyset(&newmask);
    17     sigaddset(&newmask, SIGINT);
    18 
    19     /*
    20      * Block SIGINT and save current signal mask.
    21      */
    22     if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)
    23         err_sys("SIG_BLOCK error");
    24 
    25     /*
    26      * 代码临界区
    27      */
    28     pr_mask("in critical region: ");
    29 
    30     /*
    31      * Pause, allowing all signals except SIGUSR1.
    32      */
    33     if (sigsuspend(&waitmask) != -1)
    34         err_sys("sigsuspend error");
    35 
    36     pr_mask("after return from sigsuspend: ");
    37 
    38     /*
    39      * Reset signal mask which unblocks SIGINT.
    40      */
    41     if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
    42         err_sys("SIG_SETMASK error");
    43 
    44     /*
    45      * And continue processing ...
    46      */
    47     pr_mask("program exit: ");
    48 
    49     exit(0);
    50 }
    51 
    52 static void
    53 sig_int(int signo)
    54 {
    55     pr_mask("
    in sig_int: ");
    56 }
    View Code

    下面是程序运行结果:

    函数abort

    abort函数的功能是使程序异常终止

    #include <stdlib.h>
    void abort(void);

    此函数将SIGABRT信号发送给调用进程。让进程捕捉SIGABRT信号目的是在进程终止之前由其执行所需的清理操作。默认情况是终止调用进程。

    函数system

    POSIX.1要求system函数忽略SIGINT和SITQUIT信号,阻塞SIGCHLD。

    函数sleep

    此函数使调用进程被挂起,直到满足下列条件之一:

    (1)已经经过seconds所指定的墙上时钟时间。

    (2)调用进程捕捉到一个信号并从信号处理程序返回。

  • 相关阅读:
    管理 node 版本,选择 nvm 还是 n?
    JDBC性能优化方案
    JDBC基础-setFetchSize方法
    JDBC的fetchsize和maxrows
    正确使用MySQL JDBC setFetchSize()方法解决JDBC处理大结果
    10种简单的Java性能优化
    35+ 个 Java 代码性能优化总结
    一线架构师带你玩性能优化
    诊断Java代码中常见的数据库性能热点问题应该这么做!
    十个 JDBC 的最佳实践
  • 原文地址:https://www.cnblogs.com/runnyu/p/4641346.html
Copyright © 2011-2022 走看看