信号是软中断。很多比较重要的应用程序都需处理信号。信号提供了一种处理异步事件的方法。例如,终端用户键入中断键,会通过信号机制停止一个程序,或及早地终止管道中的下一个程序。
首先,每个信号都有一个名字。这些名字都是以3个字符SIG开头。例如:
不存在编号为0的信号。很多条件可以产生信号。
当用户按某些终端键时,引发终端产生信号。在终端上按Delet键(很多系统上时是Ctrl+c)通常产生中断信号(SIGINT
)。这是停止一个已经失去控制的程序的方法。
硬件异常产生信号:除数为0、无效的内存引用等。这些条件通常由硬件检测到,并通知内核。然后由内核为该条件正在运行时的进程产生适当的信号。例如,对执行一个无效内存引用的进程产生SIGSEGV信号。
进程调用kill函数可以将任意信号发送给另一个进程或进程组。限制是:接收信号进程和发送信号进程的所有者必须相同,或发送信号的进程必须是超级用户。
用户可用kill命令将信号发送给其他进程。此命令只是kill函数的接口。常用此命令终止一个失控的后台进程。
当检测到某种软件条件已经发生,并将其通知有关进程时也产生信号。这里不是指硬件产生条件,而是软件条件。
信号是异步事件的经典实例。产生信号的事件对进程而言是随机出现的。进程不能简单地测试一个变量来判断是否发生了一个信号(同步处理),而是必须告诉内核“此信号发生时,请执行下列操作”(异步处理)。
在某个信号出现时,可以告诉内核按下列3种方式之一进行处理,我们称之为信号的处理或与信号相关的动作。
忽略此信号。大多数信号都可以使用这种方式进行处理,但有两种信号却绝不能被忽略。它们是SIGKILL和SIGSTOP。这两种信号不能被忽略的原因是:它们向内核和超级用户提供了使进程终止或停止的可靠方法。另外,如果忽略某些有硬件异常产生的信号(如除以0),则信号的运行行为是未定义的。
捕捉信号。为了做到这一点,要通知内核在某种信号发生时,调用一个用户函数。在用户函数中,可执行用户希望对这种事件进行的处理。注意,不能捕捉SIGKILL和SIGSTOP信号。
执行系统默认动作。对大多数信号的默认动作是终止该进程。
下面列出了所有信号的名字,以及其默认动作。“终止+core”表示在进程当前目录的core文件中复制了该进程的内存映像,该文件名为core。大多数UNIX系统调试程序都使用core文件检查进程终止时的状态。
名字 | 说明 | 默认动作 |
---|---|---|
SIGABRT | 异常终止(abort) | 终止+core |
SIGALRM | 定时器超时(abort) | 终止 |
SIGBUS | 硬件故障 | 终止+core |
SIGCHLD | 子进程状态改变 | 忽略 |
SIGCONT | 使暂停进程继续 | 继续/忽略 |
SIGEMT | 硬件故障 | 终止+core |
SIGFPE | 算术异常 | 终止+core |
SIGHUP | 连接断开 | 终止 |
SIGILL | 非法硬件质量 | 终止+core |
SIGITN | 终端中断符 | 终止 |
SIGIO | 异步IO | 终止/忽略 |
SIGIOT | 硬件故障 | 终止+core |
SIGKILL | 终止 | 终止 |
SIGPIPE | 写至无读进程的管道 | 终止 |
SIGPOLL | 可轮询事件(poll) | 终止 |
SIGPROF | 梗概时间超时(setitimer) | 终止 |
SIGPWR | 电源失效/重启动 | 终止/忽略 |
SIGQUIT | 终端退出符 | 终止+core |
SIGSEGV | 无效内存引用 | 终止+core |
SIGSTKFLT | 协处理器栈故障 | 终止 |
SIGSTOP | 停止 | 停止进程 |
SIGSYS | 无效系统调用 | 终止+core |
SIGTERM | 终止 | 终止 |
SIGTRAP | 硬件故障 | 终止+core |
SIGTSTP | 终端停止符 | 停止进程 |
SIGTTIN | 后台读控制tty | 停止进程 |
SIGTTOU | 后台写向控制tty | 停止进程 |
SIGGURG | 紧急情况(套接字) | 忽略 |
SIGGUSR1 | 用户定义信号,可用于应用程序 | 终止 |
SIGGUSR2 | 用户定义信号,可用于应用程序 | 终止 |
SIGVTALRM | 虚拟时间闹钟(setitimer) | 终止 |
SIGWINCH | 终端窗口大小改变 | 忽略 |
SIGXCPU | 超过CPU限制(setrlimit) | 终止或终止+core |
SIGXFSZ | 超过文件长度限制(setrlimit) | 终止或终止+core |
下面详细地说明一下个信号的含义:
UNIX系统信号机制最简单的接口是signal函数。
#include
void (*signal (int signo,void(*func)(int)))(int);
//参数1:信号编号;参数2:信号处理函数指针;同时该函数返回之前该信号的信号处理函数
signal函数由ISO C定义。因为ISO C不涉及多进程、进程组以及终端IO等,所有它对信号的定义非常含糊,以至于对UNIX系统而言几乎毫无用处。
signo参数是信号名;
func的值是常量SIG_IGN、常量SIG_DFL或当连接到此信号后要调用的函数的地址。
SIG_IGN,向内核表示忽略此信号,有两个信号不能忽略。
SIG_DFL,向内核表示接到此信号后的动作是系统默认动作。
当指定函数地址时,则在信号发生时,调用该函数,我们称这种处理为捕捉该信号,称此函数为信号处理程序(signal handler)或信号捕捉函数(signal-catching function)。
之前的signal函数写法比较复杂,可以简化为以下:
typedef void sigfunc(int);
sigfunc *signal(int ,sigfunc *);
如果查看signal.h头文件可以看到:
#define SIG_ERR (void (*)())-1
#define SIG_DFL(void (*)())0
#define SIG_IGN(void (*)())1
#include
#include
static void sig_usr(int sign); //信号处理函数
int main(void)
{
if(signal(SIGUSR1,sig_usr)==SIG_ERR) //返回SIG_ERR说明系统不支持SIGUSR1
printf("cant catch SIGUSR1 \n");
if(signal(SIGUSR2,sig_usr)==SIG_ERR)
printf("cant catch SIGUSR2 \n");
while(1);//等待信号被捕获
return 0;
}
static void sig_usr(int signo)//信号处理函数
{
if(signo==SIGUSR1)
printf("received SIGUSR1\n");
else if(signo==SIGUSR2)
printf("received SIGUSR2\n");
else
printf("received signal %d \n",signo);
}
———-重点内容
1. 启动程序
当执行一个程序时,所有信号的状态都是系统默认或忽略。通常所有信号都被设置为它们的默认动作,除非调用exec的进程忽略该信号。确切的讲,exec函数将原先设置为要捕捉的信号都更改为默认动作,其他信号状态则不变。(因为原先进程的信号处理函数的地址在新的程序中已经没有意义了。)
从signal函数可以看出,不更改信号的处理方式就不能确定信号的当前处理方式。在后面,我们将说明sigaction函数,可以确定一个函数的处理方式,而无需改变它。
2. 进程创建
当一个进程调用fork时,其子进程继承父进程的信号处理方式。因为子进程在开始时复制了父进程内存映像,所有信号捕捉函数的地址在子进程中是有意义的。
早期的某些实现,信号是不可靠的。
早期的UNIX系统的一个特性是:如果进程在执行一个低速系统调用而阻塞期间捕获到一个信号,则该系统调用就被中断不再继续执行。该系统调用返回出错,其errno设置为EINTR。(这里要分清系统调用和函数。当捕捉到某个信号时,被中断的是内核中执行的系统调用。)
为了支持这种特性,将系统调用分为两类:低速系统调用和其他系统调用。低速系统调用是可能会使进程永远阻塞的一类系统调用,包括:
被中断的系统调用必须显式的处理出错返回。例如:
again:
if((n=fread(fd,buf,BUFSIZE)<0)){
if(errno==EINTER)
goto again;
}
在系统调用被信号中断后我们希望重新启动它。
为了帮助应用程序不必处理被中断的系统调用,4.2BSD引进了某些被中断
的系统调用自动重启。自动重启的系统调用包括:ioctl、read、readv、write、writev、wait和waitpid。
某些应用程序并不希望这些函数被中断后重启,为此4.3BSD允许进程基于每个信号禁用此功能。
SUS说明了在信号处理程序中保证调用安全的函数。这些函数时可重入的并被称为是异步信号安全的(async-signal safe)。除了可重入外,在信号处理操作期间,它会阻塞任何会引起不一致信号的发送。异步信号安全的函数如下:
以下形式的函数是不可重入的:
SIGCLD和SIGCHLD这两个信号很容易被混淆。
我们需要先定义一些在讨论信号时会用到的术语。
首先,当造成信号的事件发生时,为进程产生一个信号(或向进程发送一个信号)。事件可以是硬件异常(如除以0)、软件条件(如alarm定时器超时)、终端产生的信号或调用kill函数。
当一个信号发生时,内核通常在进程表中以某种形式设置一个标志。在信号产生(generation)和递送(delivery)之间的时间间隔内,称信号是未决的(pending)。
进程可以选用“阻塞信号递送”。如果为进程产生了一个阻塞的信号,而且对该信号的动作是系统默认动作或捕捉该信号,则为该进程将此信号保持为未决状态,直到该进程对此信号解除了阻塞,或者将对此信号的动作更改为忽略。(如果信号是阻塞递送的,则不调用处理函数,直到解除阻塞或忽略该信号)
内核在信号递送时(而不是在信号产生时)决定对它的处理方式。于是进程在信号达到前仍可以改变对信号的动作。进程调用sigpending函数来判断哪些信号是设置为阻塞并处于未决状态的。
如果系统实现支持POSIX.1实时扩展,那么信号多次递送给一个进程,就会排队。如果不支持,那么这些信号值递送一次。
kill函数将信号发送给进程或进程组。
raise函数则允许进程向自身发送信号。
#include
int kill(pid_t pid,int signo);
int raise(int signo);
raise(signo)
等价于kill(getpid(),signo);
kill的pid参数有以下4种不同的情况:
使用alarm函数可以设置一个定时器(闹钟时间),在将来的某个时刻定时器会超时,当定时器超时时,产生SIGALRM信号。如果忽略或不捕捉此信号,则其默认动作是终止调用该alarm函数的进程。
#include
unsigend int alarm(unsigend int seconds);//返回上次alarm未超时的剩余时间。如果second为0,则取消上次为超时的闹钟。
pause函数使调用进程挂起直至捕捉到一个信号。
#include
int pause(void);
只有执行了一个信号处理程序并从其返回时,pause才返回。在这种情况下,pause返回-1,errno设置为EINTR.
我们需要一个能表示多个信号-信号集(signal set)的数据类型。我们将在sigprocmask类函数中使用这种数据类型,以便告诉内核不允许发生该信号集中的信号。
POSIX.1 定义数据类型sigset_t以包含一个信号集,并定义了下列5个处理信号集的函数:
#include
int sigemptyset(sigset_t *set); //初始化set,清除所有信号。必须初始化一次,以屏蔽不同系统之间的差异。
int sigfillset(sigset_t *set); //初始化set,添加所有信号。
int sigaddset(sigset_t *set,int signo); //添加一个指定的信号。
int sigdelset(sigset_t *set,int signo); //删除一个指定的信号。
int sigismember(const sigset_t *set ,int signo); //判断一个信号是否在信号集中。
一个进程的信号屏蔽字规定了当前阻塞而不能递送给该进程的信号集。
函数sigprocmask函数可以检测或更改,或同时进行检测和更改进程的信号屏蔽字:
#include
int sigprocmask(int how,const sigset_t *restrict set,sigset_t *restrict oset);
how | 说明 |
---|---|
SIG_BLOCK | 该进程新的信号屏蔽字是当前信号屏蔽字和set指向信号集的并集。set包含了希望阻塞的附加信号 |
SIG_UNBLOCK | 该进程新的信号屏蔽字是当前信号屏蔽字和set指向信号集的补集的交集。set包含了希望解除阻塞的附加信号 |
SIG_SETMASK | 该进程新的信号屏蔽是set指向的值 |
在调用sigprocmask后如果有任何未决的、不再阻塞的信号,则在sigprocmask返回前,至少将其中之一递送给该进程。
sigprocmask是仅为单线程进程定义的。多线程中信号的屏蔽使用另一个函数。
sigpending函数返回一信号集,对于调用进程而言,其中各信号是阻塞不能递送的,因而也一定是当前未决的。该信号集通过set参数返回。
#include
int sigpending(sigset_t *set);
static void sig_quit()
{
printf("caught SIGQUIT. \n");
if(signal(SIGQUIT,SIG_DFL)==SIG_ERR)
printf("cant reset SIGQUIT. \n");
}
int main(void)
{
sigset newmask, oldmask, pendmask;
if(signal(SIGQUIT,sig_quit)==SIG_ERR)//注册SIGQUIT信号处理函数
printf("cant catch SIGQUIT. \n");
/*
阻塞SIGQUIT信号并保存当前信号屏蔽字
*/
sigemptyset(&newmansk); //初始化newmask信号集
sigaddset(&newmansk,SIGQUIT); //新增SIGQUIT信号
if(sigprocmask(SET_BLOCK,&newmask,&oldmask)<0) //阻塞newmask信号集
printf("SIG_BLOCK error. \n");
sleep(5);
if(sigpending(&pendingmask)<0) //读取当前未递送信号
printf("pending error. \n");
if(sigismember(&pendmaks,SIGQUIT)) //判断当前未递送信号是否包括SIGQUIT信号
printf("SIGQUIT pending. \n");
/*
恢复没有屏蔽SIGQUIT的信号屏蔽字
*/
if(sigprocmask(SET_SETMASK,&oldmansk,NULL)<0) //恢复信号屏蔽字设置
printf("SIG_SETMAKS error. \n");
printf("SIGQUIT unblocked. \n");
sleep(5);
exit(0);
}
我们执行此程序:
$ ./a.out
^\ //产生信号一次(5s内)
SIGQUIT pending //从sleep返回后
caught SIGQUIT //在信号处理程序中捕获(sigprocmask(SET_SETMASK,&oldmansk,NULL)返回前)
SIGQUIT unblocked //从sigprocmask返回后
^\Quit(coredump) //再次产生信号
$ ./a.out
^\^\^\^\^\^\ //产生多次信号(5s内)
SIGQUIT pending //从sleep返回后
caught SIGQUIT //在信号处理程序中捕获(sigprocmask(SET_SETMASK,&oldmansk,NULL)返回前)
SIGQUIT unblocked //从sigprocmask返回后
^\Quit(coredump) //再次产生信号
shell发现其子进程异常终止时输出QUIT(coredump)信息。在第二次运行程序时,在休眠期间使SIGQUIT信号产生了多次,但解除对该信号的阻塞后,只向进程传递一次SIGQUIT信号。从中可以看到在此系统上没有将信号进行排队。
sigaction函数的功能是检查或修改(或检查并修改)与指定信号相关联的处理动作。此函数取代了UNIX早期版本使用的signal函数。在本节末尾使用sigaction函数实现了signal。
#include
int sigaction(int signo,const struct sigaction *restrict act,struct sigaction *restrict oact);
struct sigaction{
void (*sa_handler)(int);//信号处理函数,或SIG_IGN或SIG_DFL
sigset_t sa_mask;//信号集屏蔽字
int sa_flags;//信号选项
void (*sa_sigaction)(int,siginfo_t *,void *);
};
当更改信号动作时,如果sa_handler字段包括一个信号捕捉函数的地址(不是常量SIG_IGN或SIG_DFL),则sa_mask字段说明了一个信号集,在调用该信号捕捉函数之前,这一信号集要加到进程的信号屏蔽字中。仅当从信号捕捉函数返回时再将进程的信号屏蔽字恢复为原先值。
这样在调用信号处理程序时就能阻塞某些信号。在信号处理程序被调用时,操作系统建立的新信号屏蔽字包括正被递送的信号。因此保证了在处理一个给定信号时,如果这种信号再次发生,那么它会被阻塞到对前一个信号处理结束为止。
若同一信号多次发生,通常并不将它们加入队列,所以如果在某种信号被阻塞时,这种信号发生了多次,那么对这种信号解除阻塞后,其信号处理函数通常只会被调用一次。
act结构的sa_flag字段指定对信号进行处理的各个选项。以下列出了这些选项的意义:
选项 | 说明 |
---|---|
SA_INTERRUPT | 由此信号中断的系统调用不自动重启动。 |
SA_NOCLDSTOP | 若signo是SIGCHLD,当子进程停止,不产生此信号;当子进程停止后继续运行,不产生此信号;当子进程终止时产生此信号。 |
SA_NOCLDWAIT | 若signo是SIGCHLD,当调用进程的子进程终止时,不创建僵死进程。若调用进程随后调用wait,则阻塞到它所有子进程都终止,此时返回-1,errno设置为ECHILD。 |
SA_NODEFER | 当捕捉到此信号时,在执行器信号捕捉函数时,系统不自动阻塞此信号(除非sa_mask包括了此信号) |
SA_NOSTACK | 若signalstack已声明了一个替换栈,则此信号递送给替换栈上的进程。(没懂) |
SA_RESETHAND | 在此信号捕捉函数的入口处,将此信号的处理方式重置为SIG_DFL,并清除SA_SIGINFO标志。 |
SA_RESTART | 被此信号中断的系统调用自动重启 |
SA_SIGINFO | 此选项对信号处理程序提供了附加信息:一个指向siginfo结构的指针以及一个指向进程上下文标识符的指针。 |
sa_sigaction字段是一个替代的信号处理程序,在sigaction结构中使用了SA_SIGINFO标志时,使用该信号处理程序。
若使用了SA_SIGINFO标志,那么按这种方式调用信号处理程序:void (*sa_sigaction)(int,siginfo_t *,void *);
这里的siginfo结构包含了信号产生原因的有关信息。
struct siginfo{
int si_signo; //信号编号
int si_erron; //包含错误编号,它对应于造成信号产生的条件
int si_code; //附加信息
pid_t si_pid; //发送的进程ID
uid_t si_uid; //发送的用户ID
void *si_addr; //导致故障的根源地址,该地址可能不准
int si_status; //退出号或信号编号
union sigval si_value;
};
sigval联合包含以下字段:
int sival_int;
void *sival_ptr;
应用程序在传递信号时,在si_value.sival_int
中传递一个整型数或在si_value.sival_ptr
中传递一个指针值。
(这一节不太懂)
7.10节说明了用于非局部转移的setjmp和longjmp函数。
在信号处理程序中经常调用longjmp函数以返回到程序的主循环中,而不是从该程序返回。但是longjmp有一个问题。当捕捉到一个信号时,进入信号捕捉函数,此时当前信号被自动地加到进程的信号屏蔽字中。这阻止了后来产生的这种信号中断该信号处理程序。如果在信号处理程序中使用longjmp跳出信号处理程序,那么对此进程的信号屏蔽字会发生什么,不同的实现处理方法并不相同。
因此POSIX.1并没有指定setjmp和longjmp对信号屏蔽字的作用,而是定义了两新函数sigsetjmp和siglongjmp。在信号处理程序中进行非局部转移时应当使用这两个函数。
#include
int simsetjmp(sigjmp_buf env,int savemask);//直接调用返回0;从siglongjmp调用返回非0
void siglongjmp(sigjmp_buf env,int val);
这两个函数和setjmp、longjmp之间的唯一区别是sigsetjmp增加了一个参数。如果savemask非0,则sigsetjmp在env中保存当前信号屏蔽字。掉siglongjmp时,如果带非0 sacemask的sigsetjmp调用已经保存了env,则siglongjmp从其中恢复保存的信号屏蔽字。
当调用一个信号处理程序时,被捕捉到的信号加到进程的当前信号屏蔽字中。当从信号处理程序返回时,返回原来的屏蔽字。
上面已经说明,更改进程的信号屏蔽字可以阻塞所选择的信号,或解除对它们的阻塞。使用这种技术可以保护不希望由信号中断的代码临界区。
函数sigsuspend可以挂起进程,直到其信号屏蔽字之外的信号被捕获并从其信号处理程序返回。返回之后系统的信号屏蔽字恢复为调用sigsuspend之前的信号屏蔽字。
#include
int sigsuspend(const sigset_t *sigmask);
#include
#include
static void sig_int(int sign)
{
printf("in sig_int handler. \n");
}
static void sig_usr1(int sign)
{
printf("in sig_usr1 handler. \n");
}
int main(void)
{
sigset_t newmask,oldmask,waitmask;
printf("program start.\n");
if(signal(SIGINT,sig_int)==SIG_ERR) printf("signal err. \n");
if(signal(SIGUSR1,sig_usr1)==SIG_ERR) printf("signal err. \n");
sigemptyset(&waitmask);
sigaddset(&waitmask,SIGUSR1); //将SIGUSR1信号添加到信号屏蔽字waitmask
sigemptyset(&newmask);
sigaddset(&newmask,SIGINT); //将SIGINT信号添加到信号屏蔽字newmask
//阻塞屏蔽字中的信号,此后屏蔽了信号SIGUSR1
if(sigprocmask(SIG_BLOCK,&waitmask,&oldmask)<0) printf("SIG_BLOCK err. \n");
//挂起进程,直到屏蔽字以外的信号被捕获到并从其信号处理程序返回后,进程继续运行
if(sigsuspend(&newmask)!= -1) printf("sigsuspend error. \n");
printf("after return sigsuspend . \n");
//此时阻塞了信号SIGINT
pause();
return 0;
}
sigsuspend的另一种应用是等待一个信号处理程序设置一个全局变量。例如:
static volatile sig_atmoic_t sigflag;
int sig_proc(int signo)
{
if(signo==SIGINT)
printf("interrupt. \n");
else if(signo==SIGQUIT)
sigflag=1;
}
int main(void)
{
sigset_t newmask,oldmask,zeromask;
if(signal(SIGINT,sig_proc)==SIGERR)printf("sig error.\n");
if(signalSIGQUIT,sig_procSIGERR)printf("sig error.\n");
//intilization sigset
sigemptyset(&zeromask);
sigemptyset(&newmask);
sigaddset(&newmask,SIGQUIT);
//block SIGQUIT and save current signal mask
if(sigprocmask(SIG_BLOCK,&newmask,&oldmask)<0) printf("sigprocmask error. \n");
//sigsuspend zeromask and release SIG_QUIT
while(sigflag==0)//这里会一直循环,直到sigflag=1。也就是SIGQUIT信号,程序继续往下走。
sigsuspend(&zeromask);
sigflag=0;
if(sigprocmask(SIG_SETMASK,&oldmaks,NULL)<0) printf("sigprocmask error. \n");
}
前面已经提及abort函数的功能是使程序异常终止。
#include
void abort(void);
此函数将SIGABRT信号发送给调用进程(不应忽略此信号)。调用abort将向主机环境递送一个未成功终止的通知,其方法是raise(SIGABRT)函数。
ISO C要求若捕捉到此信号而且相应信号处理程序返回,abort仍不会返回到其调用者。如果捕捉到此信号,则信号处理程序不能返回的唯一方法是它调用exit、_exit、longjmp或siglongjmp。POSIX.1也说明abort并不理会进程对此信号的阻塞和忽略。
让进程捕捉SIGABRT的意图是:在进程终止之前由其执行所需的清理操作。如果进程并不在信号处理程序中终止自己,POSIX.1声明当信号处理程序返回时,abort终止该线程。
ISO C针对此函数的规范将是否冲洗数据留个实现决定。
POSIX.1则要求当调用abort终止进程前,则它对所有打开的标准IO流执行冲洗操作。
POSIX.1要求system忽略SIGINT和SIGQUIT,阻塞SIGCHLD.
ed编辑器不熟悉,看的晕晕的。以后再说。
#include
unsigned int sleep(unsigned int seconds);
此函数使调用进程挂起直到满足下面两个条件之一:
seconds
所指定的墙上时钟时间。alarm
函数一样,由于其他系统活动,实际返回时间比所要求的会迟一些。 sleep
提早返回时,返回值是未休眠完的秒数(所要求的时间减去实际休眠时间)。nanosleep函数与sleep函数类似,但提供了纳秒级的精度。
#include
int nanosleep(const struct timespec *reqtp,struct timespec *remtp);
这个函数调用挂起进程,直到要求的时间已经超时或者某个信号中断了该函数。
reqtp
参数用秒和纳秒指定了需要休眠的时间长度。
remtp
参数指向的timespec结构就会被设置为未休眠完的时间长度。如果对未休眠完的时间并不感兴趣,可以把该参数设置为NULL
。
如果系统并不支持纳秒这一精度,要求的时间就会取整。因为nanosleep
函数并不涉及产生任何信号,所以不需要担心与其他函数的交互。
随着多个系统时钟的引入,需要使用相对于特定时钟的延时时间来挂起调用线程。clock_nanosleep
函数提供了这种功能。
#include
int clock_nanosleep(clockid_t clock_id,int flags,const struct timespec *reqtp,struct timespec *remtp);
clock_nanosleep(CLOCK_REALTIME,0,reqtp,remtp);
和nanosleep(reqtp,remtp);
作用是相同的。使用绝对时间改善了延时精度。因为相对时间取决于系统调度。
有些系统开始增加对信号排队的支持。除了信号排队以外,这些扩展允许应用程序在递交信号时传递更多的信息。这些信息嵌入在siginfo结构中。 除了系统提供的信息,应用程序还可以向信号处理程序传递整数或者包含更多信息的缓冲区指针。
使用排队信号必须做以下几个操作:
#include
int sigqueue(pid_t pid,int signo,const union sigval value);
sigque函数只能把信号发送给单个进程,可以使用value参数向信号处理程序传递整数和指针,除此之外,sigqueue函数与kill函数类似。
所有的信号中,POSIX.1认为有以下6个与作业控制有关。
SIGTTOU 后台进程组成员写控制终端。
出了SIGCHLD以外,大多数应用程序并不处理这些信号,交互式shell通常会处理这些信号的所有工作。
当键入挂起字符(Ctrl+Z)时,SIGTSTP信号被送至前台进程组的所有进程。
本节介绍如何在信号编号和信号名之间进行映射。某些系统提供数组
extern char *sys_siglist[];
数组下标是信号编号,数组中的元素是指向信号名字符串的指针。
可以使用psignal函数打印与信号编号对应的字符串。
#include
void psignal(int signp,char *msg);
如果在sigaction信号处理程序中有siginfo结构,可以使用psiginfo函数打印信号信息。
#include
void *strsignal(int signp); //说明描述信号的字符串是全局变量。
给出一个信号编号,strsignal将返回描述该信号的字符串。引用程序可以用该字符串打印关于接收到信号的出错消息。
信号用于大多数复杂的应用程序中。理解进行信号处理的原因和方式对于高级UNIX编程极为重要。
本章首先说明了早起信号实现的问题以及它们是如何显现出来的。