apue2 阅读笔记--第十章

1. 定义

首先,每个信号都有一个名字。这些名字都以三个字符 S I G开头。例如,S I G A B RT是夭折信号,当进程调用a b o r t函数时产生这种信号。S I G A L R M是闹钟信号,当由a l a r m函数设置的时间已经超过后产生此信号。V 7有1 5种不同的信号,S V R 4和4 . 3 + B S D均有3 1种不同的信号。
在头文件< s i g n a l . h >中,这些信号都被定义为正整数(信号编号)。没有一个信号其编号为0。在1 0 . 9节中将会看到k i l l函数,对信号编号0有特殊的应用。P O S I X . 1将此种信号编号值称为空信号。
很多条件可以产生一个信号。
• 当用户按某些终端键时,产生信号。在终端上按D E L E T E键通常产生中断信号(S I G I N T)。
这是停止一个已失去控制程序的方法。(第11章将说明此信号可被映射为终端上的任一字符。)
• 硬件异常产生信号:除数为0、无效的存储访问等等。这些条件通常由硬件检测到,并将其通知内核。然后内核为该条件发生时正在运行的进程产生适当的信号。例如,对执行一个无效存储访问的进程产生一个S I G S E G V。
• 进程用k i l l ( 2 )函数可将信号发送给另一个进程或进程组。自然,有些限制:接收信号进程
和发送信号进程的所有者必须相同,或发送信号进程的所有者必须是超级用户。
•  用户可用k i l l ( 1 )命令将信号发送给其他进程。此程序是 k i l l函数的界面。常用此命令终止一个失控的后台进程。
•  当检测到某种软件条件已经发生,并将其通知有关进程时也产生信号。这里并不是指硬件产生条件(如被0除),而是软件条件。例如 S I G U R G (在网络连接上传来非规定波特率的数据)、S I G P I P E (在管道的读进程已终止后一个进程写此管道 ),以及S I G A L R M (进程所设置的闹钟时间已经超时)。
信号是异步事件的经典实例。产生信号的事件对进程而言是随机出现的。进程不能只是测试一个变量(例如e r r n o )来判别是否发生了一个信号,而是必须告诉内核“在此信号发生时,请

执行下列操作”。
可以要求系统在某个信号出现时按照下列三种方式中的一种进行操作。
(1)  忽略此信号。大多数信号都可使用这种方式进行处理,但有两种信号却决不能被忽略。
它们是:S I G K I L L和S I G S TO P。这两种信号不能被忽略的原因是:它们向超级用户提供一种使进程终止或停止的可靠方法。另外,如果忽略某些由硬件异常产生的信号(例如非法存储访问
或除以0),则进程的行为是未定义的。
(2)  捕捉信号。为了做到这一点要通知内核在某种信号发生时,调用一个用户函数。在用户函数中,可执行用户希望对这种事件进行的处理。例如,若编写一个命令解释器,当用户用键盘产生中断信号时,很可能希望返回到程序的主循环,终止系统正在为该用户执行的命令。
如果捕捉到S I G C H L D信号,则表示子进程已经终止,所以此信号的捕捉函数可以调用 w a i t p i d以取得该子进程的进程I D以及它的终止状态。又例如,如果进程创建了临时文件,那么可能要为S I G T E R M信号编写一个信号捕捉函数以清除临时文件( k i l l命令传送的系统默认信号是终止信号)。
(3)  执行系统默认动作。表1 0 - 1给出了对每一种信号的系统默认动作。注意,对大多数信号的系统默认动作是终止该进程。

2 signal函数

#include <signal.h>
void (*signal (int  s i g n o, void (*f u n c)(int))) (int);
返回:成功则为以前的信号处理配置,若出错则为 S I G _ E R R

s i g n o参数是表1 0 - 1中的信号名。f u n c的值是:( a )常数S I G _ I G N,或( b )常数S I G _ D F L,或( c )当接到此信号后要调用的函数的地址。如果指定 S I G _ I G N,则向内核表示忽略此信号。(记住有两个信号S I G K I L L和S I G S TO P不能忽略。)如果指定S I G _ D F L,则表示接到此信号后的动
作是系统默认动作(见表1 0 - 1中的最后1列)。当指定函数地址时,我们称此为捕捉此信号。我们称此函数为信号处理程序(signal handler)或信号捕捉函数(signal-catching function)。

pause函数使进程睡眠。

当一个进程调用f o r k时,其子进程继承父进程的信号处理方式。因为子进程在开始时复制了父进程存储图像,所以信号捕捉函数的地址在子进程中是有意义的。

3.

为了帮助应用程序使其不必处理被中断的系统调用, 4 . 2 B S D引进了某些被中断的系统调用的自动再起动。自动再起动的系统调用包括: i o c t l、r e a d、r e a d v、w r i t e、w r i t e v、w a i t和w a i t p i d。正如前述,其中前五个函数只有对低速设备进行操作时才会被信号中断。而 w a i t和w a i t p i d在捕捉到信号时总是被中断。

4.

进程捕捉到信号并继续执行时,它首先执行该信号处理程序中的指令。如果从信号处理程序返回(例如没有调用e x i t或l o n g j m p),则继续执行在捕捉到信号时进程正在执行的正常指令序列(这类似于硬件中断发生时所做的)。但在信号处理程序中,不能判断捕捉到信号时进程执行到何处。如果进程正在执行m a l l o c,在其堆中分配另外的存储空间,而此时由于捕捉到信号,插入执行该信号处理程序,其中又调用 m a l l o c,这时会发生什么 ?又例如若进程正在执行g e t p w n a m(见6 . 2节)这种将其结果存放在静态存储单元中的函数,而插入执行的信号处理程
序中又调用这样的函数,这时又会发生什么呢 ?  在m a l l o c例中子,可能会对进程造成破坏,因为m a l l o c通常为它所分配的存储区保持一个连接表,而插入执行信号处理程序时,进程可能正在更改此连接表。在g e t p w n a m的例子中,正常返回给调用者的信息可能由返回至信号处理程序的信息覆盖。

例子代码:

#include "apue.h"
#include <pwd.h>

static void
my_alarm(int signo)
{
    struct passwd   *rootptr;

    printf("in signal handler\n");
    if ((rootptr = getpwnam("root")) == NULL)
            err_sys("getpwnam(root) error");
    alarm(1);
}

int
main(void)
{
    struct passwd   *ptr;

    signal(SIGALRM, my_alarm);
    alarm(1);
    for ( ; ; ) {
        if ((ptr = getpwnam("sar")) == NULL)
            err_sys("getpwnam error");
        if (strcmp(ptr->pw_name, "sar") != 0)
            printf("return value corrupted!, pw_name = %s\n",
                    ptr->pw_name);
    }
}

5.

(1)kill 和 raise 函数

k i l l函数将信号发送给进程或进程组。r a i s e函数则允许进程向自身发送信号。

#include <sys/types.h>
#include <signal.h>
int kill(pid_t p i d, int s i g n o) ;

int raise(int s i g n o) ;
两个函数返回:若成功则为0,若出错则为-1

(2)alarm和p a u s e函数

使用a l a r m函数可以设置一个时间值(闹钟时间),在将来的某个时刻该时间值会被超过。当所设置的时间值被超过后,产生 S I G A L R M信号。如果不忽略或不捕捉此信号,则其默认动作是终止该进程。

#include <unistd.h>
unsigned int alarm(unsigned int   s e c o n d s) ;
返回:0或以前设置的闹钟时间的余留秒数

其中,参数s e c o n d s的值是秒数,经过了指定的s e c o n d s秒后会产生信号S I G A L R M。要了解的是,经过了指定秒后,信号由内核产生,由于进程调度的延迟,进程得到控制能够处理该信号还需一段时间。

6. 信号集

我们需要有一个能表示多个信号——信号集( signal set)的数据类型。将在s i g p r o c m a s k(下一节中说明)这样的函数中使用这种数据类型,以告诉内核不允许发生该信号集中的信号。
如前所述,信号种类数目可能超过一个整型量所包含的位数,所以一般而言,不能用整型量中的一位代表一种信号。P O S I X . 1定义数据类型s i g s e t _ t以包含一个信号集,并且定义了下列五个处理信号集的函数。

#include <signal.h>
int sigemptyset(sigset_t *  s e t) ;
int sigfillset(sigset_t *  s e t) ;
int sigaddset(sigset_t * s e t, int s i g n o) ;
int sigdelset(sigset_t * s e t, int s i g n o) ;
四个函数返回:若成功则为0,若出错则为-1
int sigismember(const sigset_t *  s e t, int s i g n o) ;
返回:若真则为1,若假则为0

1 0 . 8节曾说明一个进程的信号屏蔽字规定了当前阻塞而不能递送给该进程的信号集。调用函数s i g p r o c m a s k可以检测或更改(或两者)进程的信号屏蔽字。

# include <signal.h>
int sigprocmask(int  h o w, const sigset_t * s e t, sigset_t *o s e t) ;
返回:若成功则为0,若出错则为-1

首先,o s e t是非空指针,进程的当前信号屏蔽字通过 o s e t返回。其次,若s e t是一个非空指针,则参数h o w指示如何修改当前信号屏蔽字。表1 0 - 4说明了h o w可选用的值。S I G _ B L O C K是或操作,而S I G _ S E T M A S K则是赋值操作。

how可能的取值:

S I G _ B L O C K 该该进程新的信号屏蔽字是其当前信号屏蔽字和 s e t指向信号集的并集。s e t包含了我们希望阻塞的附加信号S I G _ U N B L O C K        该该进程新的信号屏蔽字是其当前信号屏蔽字和 s e t所指向信号集的交集。s e t包含了
我们希望解除阻塞的信号S I G _ S E T M A S K        该该进程新的信号屏蔽是s e t指向的值。

如果s e t是个空指针,则不改变该进程的信号屏蔽字,h o w的值也无意义。
如果在调用s i g p r o c m a s k后有任何未决的、不再阻塞的信号,则在 s i g p r o c m a s k返回前,至少将其中之一递送给该进程。

The sigpending function returns the set of signals that are blocked from delivery and currently pending for the calling process. The set of signals is returned through the set argument.

 

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

Returns: 0 if OK, 1 on error

返回被阻塞的信号集。

sigaction 函数 (这个函数非常重要,可以以此替代signal函数,实现更精细的控制)

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

Returns: 0 if OK, 1 on error

     struct sigaction {
       void      (*sa_handler)(int);   /* addr of signal handler, */
                                       /* or SIG_IGN, or SIG_DFL */
       sigset_t sa_mask;               /* additional signals to block */
       int      sa_flags;              /* signal options, Figure 10.16 */

       /* alternate handler */
       void     (*sa_sigaction)(int, siginfo_t *, void *);
    };

When changing the action for a signal, if the sa_handler field contains the address of a signal-catching function (as opposed to the constants SIG_IGN or SIG_DFL), then the sa_mask field specifies a set of signals that are added to the signal mask of the process before the signal-catching function is called. If and when the signal-catching function returns, the signal mask of the process is reset to its previous value. This way, we are able to block certain signals whenever a signal handler is invoked. The operating system includes the signal being delivered in the signal mask when the handler is invoked. Hence, we are guaranteed that whenever we are processing a given signal, another occurrence of that same signal is blocked until we're finished processing the first occurrence. Recall from Section 10.8 that additional occurrences of the same signal are usually not queued. If the signal occurs five times while it is blocked, when we unblock the signal, the signal-handling function for that signal will usually be invoked only one time.

Once we install an action for a given signal, that action remains installed until we explicitly change it by calling sigaction. Unlike earlier systems with their unreliable signals, POSIX.1 requires that a signal handler remain installed until explicitly changed.

The sa_flags field of the act structure specifies various options for the handling of this signal. Figure 10.16 details the meaning of these options when set. The SUS column contains • if the flag is defined as part of the base POSIX.1 specification, and XSI if it is defined as an XSI extension to the base.

sa_flags 的值一般取SA_RESTART。

The sa_sigaction field is an alternate signal handler used when the SA_SIGINFO flag is used with sigaction. Implementations might use the same storage for both the sa_sigaction field and the sa_handler field, so applications can use only one of these fields at a time.

Normally, the signal handler is called as

   void handler(int signo);
but if the SA_SIGINFO flag is set, the signal handler is called as
   void handler(int signo, siginfo_t *info, void *context);

The siginfo_t structure contains information about why the signal was generated. An example of what it might look like is shown below. All POSIX.1-compliant implementations must include at least the si_signo and si_code members. Additionally, implementations that are XSI compliant contain at least the following fields:

    struct siginfo {
      int    si_signo;  /* signal number */
      int    si_errno;  /* if nonzero, errno value from <errno.h> */
      int    si_code;   /* additional info (depends on signal) */
      pid_t  si_pid;    /* sending process ID */
      uid_t  si_uid;    /* sending process real user ID */
      void  *si_addr;   /* address that caused the fault */
      int    si_status; /* exit value or signal number */
      long   si_band;   /* band number for SIGPOLL */
      /* possibly other fields also */
    };
来个用sigaction实现signal函数的小例子:
#include "apue.h"

/* Reliable version of signal(), using POSIX sigaction(). */
Sigfunc *
signal(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);
}
用信号实现父子进程的同步示例代码:
#include "apue.h"

static volatile sig_atomic_t sigflag; /* set nonzero by sig handler */
static sigset_t newmask, oldmask, zeromask;

static void
sig_usr(int signo) /* one signal handler for SIGUSR1 and SIGUSR2 */
{
sigflag = 1;
}

void
TELL_WAIT(void)
{
if (signal(SIGUSR1, sig_usr) == SIG_ERR)
err_sys("signal(SIGUSR1) error");
if (signal(SIGUSR2, sig_usr) == SIG_ERR)
err_sys("signal(SIGUSR2) error");
sigemptyset(&zeromask);
sigemptyset(&newmask);
sigaddset(&newmask, SIGUSR1);
sigaddset(&newmask, SIGUSR2);

/*
* Block SIGUSR1 and SIGUSR2, and save current signal mask.
*/
if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)
err_sys("SIG_BLOCK error");
}

void
TELL_PARENT(pid_t pid)
{
kill(pid, SIGUSR2); /* tell parent we're done */
}

void
WAIT_PARENT(void)
{
while (sigflag == 0)
sigsuspend(&zeromask); /* and wait for parent */
sigflag = 0;

/*
* Reset signal mask to original value.
*/
if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
err_sys("SIG_SETMASK error");
}

void
TELL_CHILD(pid_t pid)
{
kill(pid, SIGUSR1); /* tell child we're done */
}

void
WAIT_CHILD(void)
{
while (sigflag == 0)
sigsuspend(&zeromask); /* and wait for child */
sigflag = 0;

/*
* Reset signal mask to original value.
*/
if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
err_sys("SIG_SETMASK error");
}
7. abort 和 sleep函数的实现是基于信号的,system函数的实现与信号关系也比较大。
这个两个函数也比较有意思:
#include <signal.h>

int sig2str(int signo, char *str);

int str2sig(const char *str, int *signop);

Both return: 0 if OK, 1 on error




posted @ 2011-11-26 17:03  jialejiahi  阅读(323)  评论(0编辑  收藏  举报