信号时软件中断,基本上所有应用程序都处理信号,信号是处理异步事件的方法。
10.2信号概念
每个信号都有一个名字,以SIG开头,在头文件<signal.h>中,信号被定义为正整数。
编号为0的信号称为空信号。
对信号的处理方式:忽略此信号、捕捉此信号、执行系统默认动作。
一些信号详细说明:
SIGABRT 调用abort函数产生此信号,进程异常终止。
SIGCHLD 在一个信号终止或者停止时,这个信号发送给父进程。
SIGCONT 此信号发送给当前需要继续运行,而且处于停止状态的进程。
SIGEMT 指示一个现实定义的硬件故障。
SIGHUP 如果 终端接口检测到一个连接断开,发送到与终端相关的控制进程。
SIGKILL 这是两个不能被捕捉或者忽略的信号之一,向系统管理员提供杀死一个进程的可靠方法。
10.3signal函数
linux信号机制最简单的接口是signal函数。
#include <signal.h>
void (*signal(int signo, void (*func) (int))) (int);
若成功返回信号以前的处理配置,出错则返回SIG_ERR。
func的值是常量SIG_IGN、常量SIG_DFL、或者接到此信号后调用的函数的地址。
huangcd@localhost apue]$ ./a.out &
[1] 25254
[huangcd@localhost apue]$ kill -USR1 25254
received SIGUSR1
[huangcd@localhost apue]$ kill -USR2 25254
[huangcd@localhost apue]$ received SIGUSR2
[huangcd@localhost apue]$ kill 25254
[huangcd@localhost apue]$ cat page241.c
#include "apue.h"
static void sig_usr(int);
int main(void)
{
if(signal(SIGUSR1, sig_usr) == SIG_ERR)
err_sys("can't catch SIGUSR1");
if(signal(SIGUSR2, sig_usr) == SIG_ERR)
err_sys("can't catch SIGUSR2");
for(;;)
pause();
}
static void
sig_usr(int signo)
{
if (signo == SIGUSR1)
printf("received SIGUSR1\n");
else if (signo == SIGUSR2)
printf("received SIGUSR2\n");
else
err_dump("received signal %d\n",signo);
}
当创建一个子进程时,其继承父进程的信号处理方式。
10.6可重入函数
不可重入函数:他们使用静态数据结构、他们调用malloc或fre、他们是标准I/O函数。
每个线程只有一个errno变量。
[root@localhost apue]# ./a.out
int signal handler
[root@localhost apue]# cat page247.c
#include "apue.h"
#include <pwd.h>
static void my_alarm(int signo)
{
struct passwd *rootptr;
printf("int 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("huangcd")) == NULL)
err_sys("getpwnam error");
if (strcmp(ptr->pw_name, "huangcd") != 0)
printf("return value corrupted!, pw_name = %s\n", ptr->pw_name);
}
}
10.7SIGCLD语义
子进程状态改变以后产生此信号,父进程需要调用一个wait函数以确定发生什么。
这个信号可以配置为SIG_IGN或者捕捉。处理方式不同。
[root@localhost apue]# gcc page249.c
[root@localhost apue]# ./a.out
SIGCLD received
pid = 26486
[root@localhost apue]# ./a.out
SIGCLD received
pid = 26489
[root@localhost apue]# cat page249.c
#include "apue.h"
#include <sys/wait.h>
static void sig_cld(int);
int main()
{
pid_t pid;
if (signal(SIGCLD, sig_cld) == SIG_ERR)
perror("signal error");
if((pid = fork()) < 0)
perror("fork error");
else if (pid == 0)
{
sleep(2);
_exit(0);
}
pause();
exit(0);
}
static void sig_cld(int signo)
{
pid_t pid;
int status;
printf("SIGCLD received\n");
if(signal(SIGCLD, sig_cld) == SIG_ERR)
perror("signal error");
if ((pid = wait(&status)) < 0)
perror("wait error");
printf("pid = %d\n", pid);
}
10.8可靠信号术语和语义
当对信号采取某种动作时,我们说进程递送了一个信号,在产生信号和递送之间的时间间隔内,称信号是未决的。
进程调用sigpending函数来决定哪些信号是设置为阻塞并处于未决状态的。
进程可以调用sigprocmask来检测盒改变其当前信号屏蔽字。
10.9kill和raise函数
kill函数将信号发送给进程或者进程组。raise函数允许进程向自己发送信号。
#include <signal.h>
int kill (pid_t pid, int signo)
int raise(int signo)
kill函数的pid分为四种不同的情况。具体看书。
10.10 alarm和pause函数
使用alarm函数可以设置一个计时器。当超过此计时器时,产生SIGALRM信号,如果吧忽略或不捕捉此信号,默认动作时终止调用此函数的进程。
#include <unistd.h>
unsigned int alarm (unsigned int seconds)
返回0或者以前设置的闹钟时间的剩余秒数。
pause函数是调用进程挂起直至捕捉到一个信号。
#include <unistd.h>
int pause(void);
下面的实例让进程休眠一段时间:
#include <signal.h>
#include <unistd.h>
static void sig_alrm(int signo)
{}
unsigned int sleep1(unsigned int nsecs)
{
if (signal(SIGALRM, sig_alrm) == SIG_ERR)
return(nsecs);
alarm(nsecs);
pause();
return (alarm(0));
}
setjmp和longjmp.为了让你实现复杂的流控制,程序在系统里面运行完全依靠内存(代码段,全局段,堆存储器,栈存储器)和寄存器的内容(栈指针,基地址,计数器),
setjmp保存当前的寄存器里面的内容,longjmp是恢复这些内容.longjmp返回setjmp程序当前的状态.
#include < setjmp.h >
int setjmp(jmp_buf env);
保存当前寄存器的状态到env这个结构体里面.
非局部跳转语句---setjmp和longjmp函数。非局部指的是,这不是由普通C语言goto,语句在一个函数内实施的跳转,而是在栈上跳过若干调用帧,返回到当前函数调用路径上的某一个函数中。
#include <setjmp.h>
Int setjmp(jmp_buf env);
返回值:若直接调用则返回0,若从longjmp调用返回则返回非0值
Void longjmp(jmp_buf env,int val);
在希望返回到的位置调用setjmp,此位置在main函数中,因为直接调用该函数,所以其返回值为0.setjmp参数evn的类型是一个特殊的类型jmp_buf,这一数据类型是某种形式的数组,其中存放在调用longjmp时能用来恢复栈状态的所有信息。因为需要在另一个函数中引用env变量,所以规范的处理方式是将env变量定义为全局变量。
当检查到一个错误时,则以两个参数调用longjmp函数,第一个就是在调用setjmp时所用的env,第二个参数是具有非0值的val,它将成为从setjmp处返回的值。使用第二个参数的原因是对于一个setjmp可以有多个longjmp。
[root@localhost apue]# cat page253_1.c
#include <setjmp.h>
#include <signal.h>
#include <unistd.h>
static jmp_buf env_alrm;
static void sig_alrm(int signo)
{
longjmp(env_alrm,1);
}
unsigned int sleep2(unsigned int nsecs)
{
if (signal(SIGALRM,sig_alrm) == SIG_ERR)
return(nsecs);
if (setjmp(env_alrm) == 0)
{
alarm(nsecs);
pause();
}
return(alarm(0));
}
10.11信号集
信号集类型是能表示多个信号的类型。可以用sigset_t以包含一个信号集。
下面五个函数用于处理信号集:
#include <signal.h>
int sigemptyset( sigset_t *set)
int sigfillset( sigset_t *set)
int sigaddset( sigset_t *set, int signo)
int sieismember( const sigset_t *set, int signo)
int sigdelset( sigset_t *set, int signo)
任何一个信号集都需要前两个函数来处理了才能用。
[root@localhost apue]# cat page257.c
#include <signal.h>
#include <error.h>
#define SIGBAD(signo) ((signo) <= 0 || (signo) >= NSIG)
int sigaddset(sigset_t *set,int signo)
{
if (SIGBAD(signo)) {errno = EINVAL; return (-1);}
*set |= 1 << (signo - 1);
return (0);
}
int sigdelset(sigset_t *set,int signo)
{
if (SIGBAD(signo)) {errno = EINVAL; return (-1);}
*set &= ~(1 << (signo - 1));
return (0);
}
int sigismember(const sigset_t *set,int signo)
{
if (SIGBAD(signo)) {errno = EINVAL; return (-1);}
return ((*set & (1 << (signo - 1))) != 0);
10.12sigprocmask函数
调用此函数可以检测或者修改一个进程的信号屏蔽字。
#include <signal.h>
int sigprocmask(int how, const sigset_t *restrict set, sigset_t *restrict oset)
如果oset是非空指针,那么进程的当前信号屏蔽字通过oset返回。
[root@localhost apue]# cat page259.c
#include "apue.h"
#include <errno.h>
void pr_mask(const char *str)
{
sigset_t sigset;
int errno_save;
errno_save = errno;
if (sigprocmask(0, NULL, &sigset) < 0)
err_sys("sigprocmask errno");
printf("%s",str);
if(sigismember(&sigset, SIGINT)) printf("SIGINT");
if(sigismember(&sigset, SIGQUIT)) printf("QUIT");
if(sigismember(&sigset, SIGUSR1)) printf("SIGUSR1");
if(sigismember(&sigset, SIGALRM)) printf("SIGALRM");
printf("\n");
errno = errno_save;
}
10.13 sigpending函数
这个函数返回信号集,通过参数中的set返回。
#include <signal.h>
int sigpending ( sigset_t *set)
10.14 sigaction函数
此函数检查或者修改与指定信号关联的处理动作。
#include <signal.h>
int sigaction(int signo, const struct sigaction *restrict act, struct sigaction *restrict oact)
第一个参数是要查询或者修改的具体动作的信号编号。
结构体sigaction用于指定信号处理程序地址或者新的处理程序。
struct sigaction{
void (*sa_handler) (int); //信号处理程序的地址
sigset_t sa_mask; /、保存进入信号处理程序以前的屏蔽字。
int sa_flags; //指定对信号的处理选项。
void (*sa_sigaction) ( int, siginfo_t *,void *) //如果修改信号处理程序,用这个方法保存。
}
现在都是用sigaction实现signal函数的功能。本书中signal函数的定义如下;
[root@localhost apue]# cat page265.c
#include "apue.h"
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_RESTATRT
act.sa_flags |=SA_RESTATRT;
#endif
}
if (sigaction(signo, &act, &oact) < 0)
return(SIG_ERR);
return (oact.sa_handler);
}
10.15sigsetjmp和siglongjmp函数
这两个函数用于处理longjmp函数的不足之处。
#include <setjmp.h>
int sigsetjmp(sigjmp_buf env, int savemask)
void siglongjmp(sigjmp_buf env, int val)
对于第一个函数,如果savemask不等于0,则第二个函数调用结束以后,信号屏蔽字从savemask恢复。