信号与时序竞态

信号

信号基本概念

信号是信息的载体,是Linux及Unix环境下经典的通信方式,现在依然是主要的通信手段。

对于一个完整的信号生命周期(从信号发送到相应的处理函数执行完毕)来说,可以分为三个重要的阶段,这三个阶段由四个重要事件来刻画:信号产生;信号在进程中注册完毕;信号在进程中的注销完毕;信号处理函数执行完毕。相邻两个事件的时间间隔构成信号生命周期的一个阶段。

下面阐述四个事件的实际意义:

  1. 信号"产生"。信号的产生指的是触发信号的事件发生(如检测到硬件异常、定时器超时以及调用信号发送函数kill()或sigqueue()等)。
  2. 信号在目标进程中"注册";进程的task_struct结构中有关于本进程中未决信号的数据成员:
    struct sigpending pending:
    
    struct sigpending{
        struct sigqueue *head, **tail;
        sigset_t signal;
    };
    
    第三个成员是进程中所有未决信号集,第一、第二个成员分别指向一个sigqueue类型的结构链(称之为"未决信号信息链")的首尾,信息链中的每个sigqueue结构刻画一个特定信号所携带的信息,并指向下一个sigqueue结构:
    struct sigqueue{
        struct sigqueue *next;
        siginfo_t info;
    }
    
    信号在进程中注册指的就是信号值加入到进程的未决信号集中(sigpending结构的第二个成员sigset_t signal),并且信号所携带的信息被保留到未决信号信息链的某个sigqueue结构中。 只要信号在进程的未决信号集中,表明进程已经知道这些信号的存在,但还没来得及处理,或者该信号被进程阻塞。
    注:
    当一个实时信号发送给一个进程时,不管该信号是否已经在进程中注册,都会被再注册一次,因此,信号不会丢失,因此,实时信号又叫做"可靠信号"。这意味着同一个实时信号可以在同一个进程的未决信号信息链中占有多个sigqueue结构(进程每收到一个实时信号,都会为它分配一个结构来登记该信号信息,并把该结构添加在未决信号链尾,即所有产生的实时信号都会在目标进程中注册);
    当一个非实时信号(常规信号)发送给一个进程时,如果该信号已经在进程中注册,则该信号将被丢弃,造成信号丢失。因此,非实时信号又叫做"不可靠信号"。这意味着同一个非实时信号在进程的未决信号信息链中,至多占有一个sigqueue结构(一个非实时信号产生后,(1)如果发现相同的信号已经在目标结构中注册,则不再注册,对于进程来说,相当于不知道本次信号发生,信号丢失;(2)如果进程的未决信号中没有相同信号,则在进程中注册自己)。
  3. 信号在进程中的注销。在目标进程执行过程中,会检测是否有信号等待处理(每次从系统空间返回到用户空间时都做这样的检查)。如果存在未决信号等待处理且该信号没有被进程阻塞,则在运行相应的信号处理函数前,进程会把信号在未决信号链中占有的结构卸掉。是否将信号从进程未决信号集中删除对于实时与非实时信号是不同的。对于非实时信号来说,由于在未决信号信息链中最多只占用一个sigqueue结构,因此该结构被释放后,应该把信号在进程未决信号集中删除(信号注销完毕);而对于实时信号来说,可能在未决信号信息链中占用多个sigqueue结构,因此应该针对占用sigqueue结构的数目区别对待:如果只占用一个sigqueue结构(进程只收到该信号一次),则应该把信号在进程的未决信号集中删除(信号注销完毕)。否则,不应该在进程的未决信号集中删除该信号(信号注销完毕)。
    进程在执行信号相应处理函数之前,首先要把信号在进程中注销。
  4. 信号生命终止。进程注销信号后,立即执行相应的信号处理函数,执行完毕后,信号的本次发送对进程的影响彻底结束。
    注:
    1)信号注册与否,与发送信号的函数(如kill()或sigqueue()等)以及信号安装函数(signal()及sigaction())无关,只与信号值有关(信号值小于SIGRTMIN的信号最多只注册一次,信号值在SIGRTMIN及SIGRTMAX之间的信号,只要被进程接收到就被注册)。
    2)在信号被注销到相应的信号处理函数执行完毕这段时间内,如果进程又收到同一信号多次,则对实时信号来说,每一次都会在进程中注册;而对于非实时信号来说,无论收到多少次信号,都会视为只收到一个信号,只在进程中注册一次。

信号的机制:当A(借助于内核)给B发送信号,B收到信号后,不管执行到程序的什么位置,都要暂停运行,去处理信号,信号处理完毕之后再继续执行,与硬件中断类似,其采用异步模式,但信号是软件层面的中断,早期常被称为软中断。

信号的特性:由于信号是通过软件去实现的,其实现手段导致信号具有很大的延时性,但对于用户来说,这个延时短到不易察觉。

每个进程收到的信号都是由内核负责发送,内核负责处理。

信号四要素:

  1. 编号
  2. 名称
  3. 时间
  4. 默认处理动作(man 7 signal查看)
信号相关的事件及状态

信号产生的五种方式:

  1. 按键产生

    Ctrl+C : 2) SIGINT(终止/中断)
    Ctrl+Z : 20) SIGSTOP(暂停/停止)
    Ctrl+\ : 3) SIGQUIT(退出)

  2. 系统调用产生:kill, raise, abort
  3. 软件条件产生:alarm,setitimer
  4. 硬件异常产生,如非法访问内存(段错误),除0操作(浮点数例外),内存对齐错误(总线错误)
  5. 命令产生:kill

信号递达:递送并且到达进程

未决:产生和递达之间的状态,主要由于阻塞(屏蔽)导致该状态

信号处理及信号集

信号的处理方式:

  1. 执行默认动作

    Term: 终止进程
    IGN: 忽略
    Core: 终止进程并生成core文件(查看进程死亡原因,用于gdb调试)
    Stop: 停止(暂停)进程
    Cont: 继续运行进程

  2. 忽略(丢弃)
  3. 捕捉执行用户处理函数

Linux内核的进程控制块是一个结构体,task_struct,处了包含进程ID,状态,工作目录,用户ID,组ID,文件描述符表,还包含了信号相关的信息,主要是指阻塞信号集和未决信号集。

阻塞信号集(信号屏蔽字)

将某些信号加入集合,对他们设置屏蔽,当屏蔽A信号后,再收到A信号,该信号的处理将推迟到解除屏蔽后

未决信号集

信号产生后,未决信号集中描述该信号的位立即翻转为1,表示信号处于未决状态,当信号被处理后对应的位重新反转为0,这一过程通常是瞬态的。

信号分类

使用kill -l查看系统信号

1) SIGHUP	 2) SIGINT	 3) SIGQUIT	 4) SIGILL	 5) SIGTRAP
6) SIGABRT	 7) SIGBUS	 8) SIGFPE	 9) SIGKILL	10) SIGUSR1
11) SIGSEGV	12) SIGUSR2	13) SIGPIPE	14) SIGALRM	15) SIGTERM
16) SIGSTKFLT	17) SIGCHLD	18) SIGCONT	19) SIGSTOP	20) SIGTSTP
21) SIGTTIN	22) SIGTTOU	23) SIGURG	24) SIGXCPU	25) SIGXFSZ
26) SIGVTALRM	27) SIGPROF	28) SIGWINCH	29) SIGIO	30) SIGPWR
31) SIGSYS	34) SIGRTMIN	35) SIGRTMIN+1	36) SIGRTMIN+2	37) SIGRTMIN+3
38) SIGRTMIN+4	39) SIGRTMIN+5	40) SIGRTMIN+6	41) SIGRTMIN+7	42) SIGRTMIN+8
43) SIGRTMIN+9	44) SIGRTMIN+10	45) SIGRTMIN+11	46) SIGRTMIN+12	47) SIGRTMIN+13
48) SIGRTMIN+14	49) SIGRTMIN+15	50) SIGRTMAX-14	51) SIGRTMAX-13	52) SIGRTMAX-12
53) SIGRTMAX-11	54) SIGRTMAX-10	55) SIGRTMAX-9	56) SIGRTMAX-8	57) SIGRTMAX-7
58) SIGRTMAX-6	59) SIGRTMAX-5	60) SIGRTMAX-4	61) SIGRTMAX-3	62) SIGRTMAX-2
63) SIGRTMAX-1	64) SIGRTMAX	

其中,编号1-31为常规信号,31-64为实时信号。
注意,9) SIGKILL 和 19) SIGSTOP信号不允许忽略和捕捉,只能执行默认动作。

函数解析

kill
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);

DESCRIPTION
The kill() system call can be used to send any signal to any process group or process.

If pid is positive, then signal sig is sent to the process with the ID specified by pid.
If pid equals 0, then sig is sent to every process in the process group of the calling 
process.
If pid equals -1, then sig is sent to every process for which the calling process has 
permission to send signals, except for process 1 (init), but see below.
If pid is less than -1, then sig is sent to every process in the process group whose ID 
is -pid.
If sig is 0, then no signal is sent, but error checking is still performed; this can be 
used to check for the existence of a process ID or process group ID.
sigqueue

使用sigqueue()向指定进程发送信号的同时,可以携带一个int或指针参数,但如果传地址,需要注意的是,不同进程之间的虚拟地址空间各自独立,将当前进程的一个地址发送给另一个进程没有任何意义。

#include <signal.h>
int sigqueue(pid_t pid, int sig, const union sigval value);

DESCRIPTION
sigqueue() sends the signal specified in sig to the process whose PID
is given in pid. The permissions required to send a signal are the
same as for kill(2). As with kill(2), the  ___ null signal (0) ____can 
be used to check if a process with a given PID exists.

The value argument is used to specify an accompanying item of data(either 
an integer or a pointer value) to be sent with the signal, and has the 
following type:
    union sigval {
        int   sival_int;
        void *sival_ptr;
    };
If the receiving process has installed a handler for this signal using
the SA_SIGINFO flag to sigaction(2), then it can obtain this data via
the si_value field of the siginfo_t structure passed as the second arg-
ument to the handler. Furthermore, the si_code field of that structure 
will be set to SI_QUEUE.

RETURN VALUE
On success, sigqueue() returns 0, indicating that the signal was suc‐
cessfully queued to the receiving process. Otherwise, -1 is returned
and errno is set to indicate the error.
raise
#include <signal.h>
int raise(int sig);

DESCRIPTION
The raise() function sends a signal to the calling process or thread.
In a single-threaded program it is equivalent to
                    kill(getpid(), sig);
In a multithreaded program it is equivalent to
                    pthread_kill(pthread_self(), sig);
If the signal causes a handler to be called, raise() will return only after the signal 
handler has returned.
If the signal causes a handler to be called, raise() will return only after the signal 
handler has returned.

RETURN VALUE
raise() returns 0 on success, and nonzero for failure.
abort
#include <stdlib.h>
void abort(void);

DESCRIPTION
The abort() first unblocks the SIGABRT signal, and then raises that signal for the calling 
process (as though raise(3) was called). This results in the abnormal termination of the 
process unless the SIGABRT signal is caught and the signal handler does not return (see 
longjmp(3)).

If the abort() function causes process termination, all open streams are closed and flushed.
alarm

设置定时器,在指定seconds后,内核会在当前进程发送14) SIGALRM信号,进程收到该信号后,默认动作为终止。注意,每个进程有且只有一个定时器。

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

DESCRIPTION
The alarm() function shall cause the system to generate a SIGALRM signal for the process 
after the number of realtime seconds specified by seconds have elapsed. Processor scheduling  
delays may prevent the process from handling the signal as soon as it is generated.

If seconds is 0, a pending alarm request, if any, is canceled. Alarm requests are not stacked; 
only one SIGALRM generation can be scheduled in this manner. If the SIGALRM signal has not 
yet been generated, the call shall result in rescheduling the time at which the SIGALRM 
signal is generated.

Interactions between alarm() and setitimer() are unspecified.

RETURN VALUE
If there is a previous alarm() request with time remaining, alarm() shall return a non-zero 
value that is the number of seconds until the previous request would have generated a SIGALRM 
signal. Otherwise, alarm() shall return 0.

使用time命令可查看程序执行时间,程序运行的瓶颈在于IO,优化程序首先优化IO。
实际执行的时间 = 系统时间 + 用户时间 + 等待时间。

setitimer

设置定时器(闹钟),可替代alarm(),精度

#include <sys/time.h>
int setitimer(int which, const struct itimerval *restrict value, struct itimerval *restrict ovalue);
int getitimer(int which, struct itimerval *value);

DESCRIPTION
The getitimer() function shall store the current value of the timer specified by which into the structure 
pointed to by value. The setitimer() function shall set the timer specified by which to the value specified 
in the structure pointed to by value, and if ovalue is not a null pointer, store the previous value of the 
timer in the structure pointed to by ovalue.

A timer value is defined by the itimerval structure, specified in <sys/time.h>. If it_value is non-zero, 
it shall indicate the time to the next timer expiration. If it_interval is non-zero, it shall specify a 
value to be used in reloading it_value when the timer expires. Setting it_value to 0 shall disable a timer, 
regardless of the value of it_interval. Setting it_interval to 0 shall disable a timer after its next 
expiration(assuming it_value is non-zero).

An XSI-conforming implementation provides each process with at least three interval timers, which are 
indicated by the which argument:
ITIMER_PROF   Decrements both in process virtual time and when the system is running on behalf of the 
              process. It is designed to be used by interpreters in statistically profiling the 
              execution of interpreted programs. Each time the ITIMER_PROF timer expires, the SIGPROF 
              signal is delivered.
              //运行时计时(用户+内核),计算占用CPU及系统调用的时间

ITIMER_REAL   Decrements in real time. A SIGALRM signal is delivered when this timer expires.
              //自然定时,与alarm()相同

ITIMER_VIRTUAL
               Decrements in process virtual time. It runs only when the process is executing. A SIGVTALRM
               signal is delivered when it expires.
               //用户空间计时,只计算进程占用CPU的时间

Timer values are defined by the following structures:
struct itimerval {
    struct timeval it_interval; /* Interval for periodic timer */
    struct timeval it_value;    /* Time until next expiration */
};
struct timeval {
    time_t      tv_sec;         /* seconds */
    suseconds_t tv_usec;        /* microseconds */
};

信号集操作函数 1
#include <signal.h>
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set, int signum);
int sigdelset(sigset_t *set, int signum);
int sigismember(const sigset_t *set, int signum);

DESCRIPTION
Objects of type sigset_t must be initialized by a call to either sigemptyset() or sigfillset() 
before being passed to the functions sigaddset(), sigdelset() and sigismember() or the additional 
glibc functions described below (sigisemptyset(), sigandset(), and sigorset()). The results are 
undefined if this is not done.

RETURN VALUE
sigemptyset(), sigfillset(), sigaddset(), and sigdelset() return 0 on success and -1 on error.
sigismember() returns 1 if signum is a member of set, 0 if signum is not a member, and -1 on error.
信号集操作函数 2
#include <signal.h>
int sigpending(sigset_t *set);

DESCRIPTION
sigpending() returns the set of signals that are pending for delivery to the calling thread 
(i.e., the signals which have been raised while blocked). The mask of pending signals is 
returned in set.

RETURN VALUE
sigpending() returns 0 on success and -1 on error. In the event of an error, errno is set to 
indicate the cause.
信号集操作函数 3

sigprocmask()可用来屏蔽信号以及解除屏蔽信号,其本质是读取或修改进程的信号屏蔽字。注意,屏蔽信号只是将信号处理延后执行,而忽略表示将信号丢弃。

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

DESCRIPTION
sigprocmask() is used to fetch and/or change the signal mask of the calling thread. The signal 
mask is the set of signals  whose delivery is currently blocked for the caller (see also signal
(7) for more details).

The behavior of the call is dependent on the value of how, as follows.
SIG_BLOCK
        The set of blocked signals is the union of the current set and the set argument.
        //当how设置为SIG_BLOCK时,set表示需要屏蔽的信号,相当于mask=mask|set
SIG_UNBLOCK
        The signals in set are removed from the current set of blocked signals. It is permissible 
        to attempt to unblock a signal which is not blocked.
        //当how设置为SIG_UNBLOCK时,set表示需要解除屏蔽的信号,相当于mask=mask&~set
SIG_SETMASK
        The set of blocked signals is set to the argument set.
        /*当how设置为SIG_SETMASK时,set表示用于代替原始屏蔽字的新屏蔽字,相当于mask=set,调用
        sigprocmask()解除当前对多个信号的阻塞,那么在sigprocmask()返回之前,至少有一个信号递达*/
If oldset is non-NULL, the previous value of the signal mask is stored in oldset.
If set is NULL, then the signal mask is unchanged (i.e., how  is ignored), but the current value 
of the signal mask is nevertheless returned in oldset (if it is not NULL).

The use of sigprocmask() is unspecified in a multithreaded process; see pthread_sigmask(3).
示例:循环打印未决信号集
#include<stdio.h>
#include<signal.h>
#include<unistd.h>

void printPengdingSignal(sigset_t *pending)
{
    for(int i=1;i<32;i++)
    {
        if(sigismember(pending,i)==1)
        {
            putchar('1');
        }
        else
        {
            putchar('0');
        }
    }
    printf("\n");
}
int main(void)
{
    sigset_t newSet, oldSet, pendingSet;
    int signalSet[3] = {SIGHUP,SIGINT,SIGQUIT};

    sigemptyset(&newSet);
    for(int i=0; i<3; i++)
    {
        sigaddset(&newSet,signalSet[i]);
        sigprocmask(SIG_BLOCK,&newSet,&oldSet);
        kill(getpid(),signalSet[i]);
        sleep(1);
        sigpending(&pendingSet);
        printPengdingSignal(&pendingSet);
        sleep(1);
    }
}
signal
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

DESCRIPTION
signal() sets the disposition of the signal signum to handler, which is either SIG_IGN, 
SIG_DFL, or the address of a programmer-defined function (a "signal handler").

If the signal signum is delivered to the process, then one of the following happens:
* If the disposition is set to SIG_IGN, then the signal is ignored.
* If the disposition is set to SIG_DFL, then the default action associated with the 
signal (see signal(7)) occurs.
* If the disposition is set to a function, then first either the disposition is reset 
to SIG_DFL, or the signal is blocked (see Portability below), and then handler is called 
with argument signum. If invocation of the handler caused the signal to be blocked, then 
the signal is unblocked upon return from the handler.

RETURN VALUE
signal() returns the previous value of the signal handler, or SIG_ERR on error. In the 
event of an error, errno is set to indicate the cause.
利用signal实现简易信号捕捉:
#include<stdio.h>
#include<stdlib.h>
#include<signal.h>
#include<errno.h>
#include<unistd.h>

void catchSignal(int sigNo)
{
    printf("--------------SIGQUIT Catched: %d--------------\n",sigNo);
}

int main(void)
{
    if(signal(SIGQUIT,catchSignal) == SIG_ERR)
    {
        perror("signal error");
        exit(1);
    }
    sleep(1);
    kill(getpid(),SIGQUIT);

    return 0;
}

sigaction
#include <signal.h>
int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);

DESCRIPTION:
The sigaction() system call is used to change the action taken by a process on receipt of 
a specific signal. (See signal(7) for an overview of signals.) signum specifies the signal 
and can be any valid signal except SIGKILL and SIGSTOP.

If act is non-NULL, the new action for signal signum is installed from act. If oldact is 
non-NULL, the previous action is saved in oldact.

The sigaction structure is defined as something like:
struct sigaction {
    void (*sa_handler)(int);  /*指定信号捕捉后的处理函数名(即注册函数), 
                                赋值为SIG_IGN表示忽略,SIG_DFL表示执行默认动作*/
    void (*sa_sigaction)(int, siginfo_t *, void *);
    sigset_t   sa_mask;     /*信号捕捉函数执行期间,所要屏蔽的信号集合,
                            仅在处理函数被调用期间生效*/
    int sa_flags;           /*设置为0,表示默认属性,信号捕捉函数执行期间自动屏蔽本信号,
                            执行(*sa_handler)来执行捕捉函数;
                            设置为SA_SIGINFO表示执行(*sa_sigaction)来指定捕捉函数;
                            设置为SA_INTERRUPT表示系统调用被信号中断后不重启;
                            设置为SA_RESTART表示自动重启;SA_DEFER表示不自动屏蔽本信号*/
    void (*sa_restorer)(void);  // has been obsolete
};
On some architectures a union is involved: do not assign to both sa_handler and sa_sigaction.

sa_handler specifies the action to be associated with signum and may be SIG_DFL for the 
default action, SIG_IGN to ignore this signal, or a pointer to a signal handling function. 
This function receives the signal number as its only argument.

If SA_SIGINFO is specified in sa_flags, then sa_sigaction\(instead of sa_handler) specifies 
the signal-handling function for signum. This function receives the signal number as its 
first argument, a pointer to a siginfo_t as its second argument and a pointer to a ucontext_t 
(cast to void *) as its third argument. (Commonly, the handler function doesn't make any use 
of the third argument.  See getcontext(3) for further information about ucontext_t.)

sa_mask specifies a mask of signals which should be blocked (i.e., added to the signal mask 
of the thread in which the signal handler is invoked) during execution of the signal handler.  
In addition, the signal which triggered the handler will be blocked, unless the SA_NODEFER 
flag is used.

sa_flags specifies a set of flags which modify the behavior of the signal. It is formed by 
the bitwise OR of zero or more of the following:
    SA_NODEFER
        Do not prevent the signal from being received from within its own signal handler. 
        This flag is meaningful only when establishing a signal handler. SA_NOMASK is an 
        obsolete,nonstandard synonym for this flag.
    SA_RESTART
        Provide behavior compatible with BSD signal semantics by making certain system calls 
        restartable across signals. This flag is meaningful only when establishing a signal 
        handler. See signal(7) for a discussion of system call restarting.
    SA_SIGINFO (since Linux 2.2)
        The signal handler takes three arguments, not one. In this case, sa_sigaction should 
        be set instead of sa_handler. This flag is meaningful only when establishing a signal 
        handler.
        The siginfo_t argument to sa_sigaction is a struct with the following fields:
            siginfo_t {
                int      si_signo;     /* Signal number */
                int      si_errno;     /* An errno value */
                int      si_code;      /* Signal code */
                int      si_trapno;    /* Trap number that caused
                                            hardware-generated signal
                                            (unused on most architectures) */
                pid_t    si_pid;       /* Sending process ID */
                uid_t    si_uid;       /* Real user ID of sending process */
                int      si_status;    /* Exit value or signal */
                clock_t  si_utime;     /* User time consumed */
                clock_t  si_stime;     /* System time consumed */
                sigval_t si_value;     /* Signal value */
                int      si_int;       /* POSIX.1b signal */
                void    *si_ptr;       /* POSIX.1b signal */
                int      si_overrun;   /* Timer overrun count;
                                            POSIX.1b timers */
                int      si_timerid;   /* Timer ID; POSIX.1b timers */
                void    *si_addr;      /* Memory location which caused fault */
                long     si_band;      /* Band event (was int in
                                            glibc 2.3.2 and earlier) */
                int      si_fd;        /* File descriptor */
                short    si_addr_lsb;  /* Least significant bit of address
                                            (since Linux 2.6.32) */
                void    *si_call_addr; /* Address of system call instruction
                                            (since Linux 3.5) */
                int      si_syscall;   /* Number of attempted system call
                                         (since Linux 3.5) */
               unsigned int si_arch;  /* Architecture of attempted system call
                                         (since Linux 3.5) */
           }

内核实现信号捕捉的原理

用户->内核->用户->内核->用户
内核实现信号捕捉的原理

时序竞态(竞态条件)

函数解析

pause

调用pause()函数可以造成进程主动挂起,等待信号唤醒。调用该API的进程将处于阻塞状态直到有信号将其唤醒。

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

DESCRIPTION
pause() causes the calling process (or thread) to sleep until a signal is delivered that 
either terminates the process or causes the invocation of a signal-catching function.

RETURN VALUE
pause() returns only when a signal was caught and the signal-catching function returned.  
In this case, pause() returns -1, and errno is set to EINTR.
/*
从上面可以看出,
    1. 如果信号的默认处理动作是终止进程,则进程终止,pause()没有机会返回;
    2. 如果信号的默认处理动作是忽略,进程继续处于挂起状态,pause()不返回;
    3. 如果信号的处理动作是捕捉,则调用完信号处理函数后,pause()返回-1,errno置为EINTR
    4. pause()收到的信号不能被屏蔽,如果被屏蔽,那么pause()就不能被唤醒。
*/
使用pause()实现一个sleep功能
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>
#include<errno.h>

void sigcatch(int signo)
{
    printf("---------signal %d catched----------\n",signo);
}
unsigned int mysleep(unsigned int seconds)   //尽量copy man-page中的原型
{
    int unsleep;
    struct sigaction act, oldact;
    act.sa_flags = 0;
    act.sa_handler = sigcatch;
    sigemptyset(&act.sa_mask);
    sigaction(SIGALRM,&act,&oldact);

    alarm(seconds);
    if(-1 == pause() && errno == EINTR)
    {
        printf("pause successfully");
    }

    //恢复对ALARM信号的默认处理
    unsleep = alarm(0);
    sigaction(SIGALRM,&oldact,NULL);

    return unsleep;
}

int main(void)
{
    mysleep(3);
    return 0;
}
sigsuspend

对上面的mysleep()进行分析:

alarm(seconds);
/*
------当程序在此失去CPU资源,且在失去CPU的这段时间里,alarm()计时结束,
      SIGALRM信号发出,此时pause()将无法被唤醒,造成进程永久挂起-----
*/
if(-1 == pause() && errno == EINTR)

针对上述情况,Linux引入了sigsuspend(),sigsuspend()是原子级的系统函数,相当于对如下三个操作封装成一个不可再分的函数:

    sigprocmask(SIG_SETMASK, &mask, &oldmask);
    pause();
    sigprocmask(SIG_SETMASK, &oldmask, NULL);

函数解析:

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

Description:
1. sigsuspend() temporarily replaces the signal mask of the calling process with the mask 
given by mask and then suspends the process until delivery of a signal whose action is to 
invoke a signal handler or to terminate a process.
2. If the signal terminates the process, then sigsuspend() does not return. If the signal 
is caught, then sigsuspend() returns after the signal handler returns, and the signal mask 
is restored to the state before the call to sigsuspend().
3. It is not possible to block SIGKILL or SIGSTOP; specifying these signals in mask, has 
no effect on the process's signal mask.

RETURN VALUE
sigsuspend() always returns -1, with errno set to indicate the error (normally, EINTR).

ERRORS
EFAULT  mask points to memory which is not a valid part of the process address space. 
EINTR   The call was interrupted by a signal.
  1. sigsuspend()在函数作用时间内,"临时"将信号屏蔽字设为其传入参数mask,并挂起进程直到信号处理函数返回或进程终止(siguspend执行完成后将恢复之前的信号屏蔽字)
  2. 假设在进程挂起期间,一个不在屏蔽信号集mask内的信号s产生了,此时会如何处理该信号:
    首先,再回顾一下Linux下,信号的分类,按信号的可靠性分为可靠信号和非可靠信号,按时间分为实时信号和非实时信号。
    在linux下,对于收到的信号有如下三种处理方式:
    (1)忽略,即对信号不做任何处理,方法之一:signal(signo,SIG_IGN)但有两个信号不能忽略:
    9)SIGKILL 19)SIGSTOP
    (2)捕捉,定义信号处理函数,当信号发生时,执行相应的处理函数;
    (3)执行缺省操作即默认动作,Linux对每种信号都规定了默认操作,同样地,SIGKILL和SIGSTOP只能执行默认动作。
    此外,Linux进程对实时信号的缺省反应是进程终止。但是对于高性能服务器编程来说,这是致命的缺陷
  3. 回到主题,考虑对s的处理的两种情形,
    一:终止进程,由于进程不再存在,那么sigsuspend()无须返回(进程不存在,sigsuspend()也不存在了);
    二:如果信号s的处理函数返回,此时信号屏蔽字恢复到sigsuspend()作用周期以外的信号屏蔽字,然后sigsuspend返回-1并将error置为EINTR.

由此,可改写上节中的mysleep()函数:

改进版的mysleep
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>
#include<errno.h>

void sigcatch(int signo)
{
    printf("---------signal %d catched----------\n",signo);
}
unsigned int mysleep(unsigned int sec)
{
    int ret, unsleep;
    struct sigaction act, oldact;
    sigset_t mask, oldmask, suspendmask;

    //为SIGALRM设置捕捉函数
    act.sa_handler = sigcatch;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    if(sigaction(SIGALRM,&act,&oldact) == -1)
    {
        perror("sigaction error");
        exit(1);
    }

    // 设置阻塞信号集,阻塞SIGALRM
    sigemptyset(&mask);
    sigaddset(&mask,SIGALRM);
    sigprocmask(SIG_BLOCK,&mask,&oldmask);
    
    // 定时结束后产生SIGALRM信号
    alarm(sec);

    //构造一个sigsuspend()作用周期内的临时阻塞信号集,并解除对SIGALRM信号的屏蔽
    suspendmask = oldmask;
    sigdelset(&suspendmask,SIGALRM);

    sigsuspend(&suspendmask);
    
    // 返回未睡时间
    unsleep = alarm(0);

    // 恢复SIGARM原有动作,解除对SIGALRM信号的屏蔽
    sigaction(SIGALRM,&oldact,NULL);
    sigprocmask(SIG_SETMASK,&oldmask,NULL);

    return unsleep;
}

int main(void)
{
    mysleep(3);
    return 0;
}

其他信号

SIGUSR1/2和SIGCHLD

SIGUSR1   30,10,16    Term    User-defined signal 1
SIGUSR2   31,12,17    Term    User-defined signal 2
SIGCHLD   20,17,18    Ign     Child stopped or terminated   //子进程退出或终止
示例:注册捕捉SIGCHLD和SIGUSR1
#include<unistd.h>
#include<stdlib.h>
#include<signal.h>
#include<stdio.h>
#include<errno.h>

void sigcatch(int signo)
{
  printf("------signal catched: %d----------\n",signo);
}

void siguser(int signo)
{
  printf("------signal catched: %d----------\n",signo);
}

int main(void)
{
  pid_t pid;

  pid = fork();
 
  struct sigaction useract;
  useract.sa_flags = 0;
  sigemptyset(&useract.sa_mask);
  useract.sa_handler = siguser;
  sigaction(SIGCHLD,&useract,NULL);
  
  if(pid<0)
  {
    perror("fork error");
    exit(1);
  }
  else if(pid>0)
  {
    struct sigaction act, oldact;
    act.sa_flags = 0;
    sigemptyset(&act.sa_mask);
    act.sa_handler = sigcatch;
    sigaction(SIGUSR1,&act,&oldact);
    sleep(2);
  }
  else
  {
    sleep(1);
    kill(getppid(),SIGUSR1);
  }
  
  return 0;
}

信号捕捉函数注意事项

信号捕捉函数应设计成可重入函数:

函数内不能含有全局变量及static变量,不能使用malloc()及free();
信号捕捉函数可调用的可重入函数可参阅man 7 signal;
没有包含在上述列表中的函数大多是不可重入的,其原因是:

使用了静态数据结构;
调用了malloc()或free();
是标准的IO函数

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值