信号是软件中断。
#include <signal.h>
void (*signal(int signo, void (*func)(int)))(int);
该函数有2各参数,第一个为信号名,第二个为信号处理程序,参数为int。
该函数返回一个参数为int的函数指针,指向func。
以下定义更清楚一些
typedef void Sigfunc(int);
Sigfunc *signal(int, Sigfunc *);
捕捉SIGUSR1和SIGUSR2的简单程序
#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();
return 0;
}
static void sig_usr(int signo)
{
if(signo == SIGUSR1)
printf("received SIGUSR1\n");
else if(signo == SIGUSR2)
printf("reveived SIGUSR2\n");
else
err_dump("received signal %d\n", signo);
}
在信号处理程序中调用不可重入函数
不可重入原因:
1、已知使用静态数据结构。如若级才能正在执行getpwnam,这种将其结果放在静态存储单元中的函数,旗舰插入执行信号处理程序,它又调用这样的函数,返回给正常调用者的信息可能被返回给信号处理程序的信息覆盖。
2、调用mallco或free。如果进程正在执行mallco,在其堆内分配另外的存储空间,而此时由于捕捉到信号而插入执行该信号处理程序,其中又调用malloc,可能会对进程造成破坏,因为malloc通常为它所分配的存储区维护一个链表,而插入执行信号处理程序时,进程可能正在更改此链表。
3、是标准I/O函数
#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); /* 每秒产生一次SIGALARM信号 */
for(;;)
{
if((ptr = getpwnam("yjp")) == NULL)
err_sys("getpwnam error");
if(strcmp(ptr->pw_name, "yjp") != 0)
printf("return value corrupted!, pw_name = %s\n", ptr->pw_name);
}
return 0;
}
结果不具有随意性?
不能正常工作的系统V SIGCLD处理程序
SIGCHLD:子进程状态改变后产生此信号,父进程需调用一个wait函数已确定发生了什么。
SIGCLD:如果配置为SIG_IGN,则调用进程的子进程将不产生僵死进程,终止时,将状态丢弃。如果随后调用wait函数,将阻塞到所有子进程终止,然后wait返回-1,并将errno设置为ECHILD。如果配置为捕捉,内核立即检查是否又子进程准备好被等待,调用SIGCLD处理程序。
在linux上,SIGCLD和SIGCHLD定义为同一值。但如果定义了SIGCLD的信号处理程序,则不再调用SIGCHLD的信号处理程序。
#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(); /* parent */
return 0;
}
static void sig_cld(int sigon) /* interrupts pause() */
{
pid_t pid;
int status;
printf("SIGCLD received\n");
if(signal(SIGCLD, sig_cld) == SIG_ERR) /* reestablish handler */
perror("signal error");
if((pid = wait(&status)) < 0) /* fetch child status */
perror("wait error");
printf("pid = %d\n", pid);
}
可见在linux上运行正常。
使用longjmp,带超时限制调用read
#include "apue.h"
#include <setjmp.h>
static void sig_alrm(int);
static jmp_buf env_alrm;
int main(void)
{
int n;
char line[MAXLINE];
if(signal(SIGALRM, sig_alrm) == SIG_ERR)
err_sys("signal(SIGALRM) error");
if(setjmp(env_alrm) != 0)
err_quit("\nread timeout");
/*
第一次alarm调用和read调用之间有一个竞争条件。
如果内核在这两个函数之间使进程阻塞,且超过闹钟时间,则read可能永远阻塞。
使用longjmp则无需担心是否被中断。
*/
alarm(10); /* start the timer */
if((n = read(STDIN_FILENO, line, MAXLINE)) < 0)
err_sys("read error");
alarm(0); /* turn off timer, return unslept time */
write(STDOUT_FILENO, line, n);
exit(0);
}
static void sig_alrm(int signo)
{
longjmp(env_alrm, 1);
}
该程序可能于其他信号处理程序交互的问题,如使得其他信号处理程序提前退出。
为进程打印信号屏蔽字
进程阻塞SIGQUIT信号,保存了当前信号屏蔽字,然后休眠5秒。
在此期间所产生的退出信号SIGQUIT都会被阻塞。
休眠结束后,检查该信号是否为未决的,然后恢复信号屏蔽字,将SIGQUIT设置为不再阻塞。
#include "apue.h"
static void sig_quit(int);
int main(void)
{
sigset_t newmask, oldmask, pendmask;
if(signal(SIGQUIT, sig_quit) == SIG_ERR)
err_sys("can't catch SIGQUIT");
/*
Block SIGQUIT and save current signal mask
*/
sigemptyset(&newmask);
sigaddset(&newmask, SIGQUIT);
/*
进程的当前信号屏蔽字通过oldmask返回
将当前信号屏蔽字和newmask指向的信号集的并集作为进程新的信号屏蔽字
*/
if(sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)
err_sys("SIG_BLOCK, error");
sleep(5); /* SIGQUIT here will terminate with core file */
/*
int sigpending(sigset_t *set);
返回在阻塞期间接收到阻塞信号的集合
*/
if(sigpending(&pendmask) < 0)
err_sys("sigpending error");
if(sigismember(&pendmask, SIGQUIT))
printf("\nSIGQUIT pending\n");
/* Reset signal mask which unblocks SIGQUIT */
if(sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
err_sys("SIG_SETMASK error");
printf("SIGQUIT unblock\n");
sleep(5);
return 0;
}
static void sig_quit(int signo)
{
printf("caught SIGQUIT\n");
if(signal(SIGQUIT, SIG_DFL) == SIG_ERR)
err_sys("can't reset SIGQUIT");
}
结果
yjp@yjp-VirtualBox:~/apue/10signals$ ./suspend1
^\^\^\^\^\ 休眠期间产生多次信号
SIGQUIT pending 从sleep返回后
caught SIGQUIT 信号处理程序中 只产生信号一次
SIGQUIT unblock 从sigprocmask返回后
^\Quit (core dumped) 再次输出信号
信号屏蔽字、sigsetjmp和siglongjmp实例
信号处理程序被调用时,系统所设置的信号屏蔽字自动包括刚被捕捉的信号。
#include "apue.h"
#include <setjmp.h>
#include <time.h>
static void sig_usr1(int), sig_alrm(int);
static sigjmp_buf jmpbuf;
/*
写此种类型的变量时不会被中断
该变量由两个不同的控制线程--main和异步执行的信号处理程序访问
*/
static volatile sig_atomic_t canjump;
int main(void)
{
if(signal(SIGUSR1, sig_usr1) == SIG_ERR)
err_sys("signal(SIGUSR1) error");
if(signal(SIGALRM, sig_alrm) == SIG_ERR)
err_sys("signal(SIGALRM) error");
pr_mask("starting main: ");
/*
int sigsetjmp(sigjmp_buf env, int savemask);
如果savemask非0,则在env中保存进程的当前信号屏蔽字
siglongjmp从其中恢复保存的信号屏蔽字
*/
if(sigsetjmp(jmpbuf, 1))
{
pr_mask("ending main: ");
exit(0);
}
canjump = 1; /* now sigsetjmp() is OK */
for(;;)
pause();
return 0;
}
static void sig_usr1(int signo)
{
time_t starttime;
if(canjump == 0)
return; /* unexpected signal, ignore */
pr_mask("starting sig_usr1: ");
alarm(3); /* SIGALRM in 3 seconds */
starttime = time(NULL);
for(;;) /* busy wait for 5 seconds */
if(time(NULL) > starttime + 5)
break;
pr_mask("finishing sig_usr1: ");
canjump = 0;
siglongjmp(jmpbuf, 1);
}
static void sig_alrm(int signo)
{
pr_mask("in sig_alrm: ");
}
start main 和 ending main后面都没有阻塞信号。
保护临界区不被信号中断
1、重置SIGINT信号处理函数。
2、保存之前的信号屏蔽字,新的信号屏蔽字为SIGINT。
3、进入临界区。
4、调用sigsuspend挂起,此时信号屏蔽字改为SIGUSR1。
5、当SIGINT信号处理函数返回时,唤醒sigsuspend,信号屏蔽字恢复为SIGINT。
6、恢复之前的信号屏蔽字。
#include "apue.h"
static void sig_int(int);
int main(void)
{
sigset_t newmask, oldmask, waitmask;
pr_mask("program start: ");
if(signal(SIGINT, sig_int) == SIG_ERR)
err_sys("signal(SIGINT) error");
sigemptyset(&waitmask);
sigaddset(&waitmask, SIGUSR1);
sigemptyset(&newmask);
sigaddset(&newmask, SIGINT);
/* Block SIGINT and save current signal mask */
if(sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)
err_sys("SIG_BLOCK error");
/* Critical region of code */
pr_mask("in critical region: ");
/* Pause, allowing all signals except SIGUSR1 */
/*
int sigsuspend(const sigset_t *sigmask);
将进程的信号屏蔽字设置为由sigmask指向的值
捕捉到信号或发生会终止该进程的信号前,该进程被挂起。
捕捉到信号且从信号处理程序返回,则sigsuspend返回,并将
该进程的信号屏蔽字设置为调用sigsuspend之前的值。
返回:总是-1,并将errno设置为EINTR
*/
if(sigsuspend(&waitmask) != -1)
err_sys("sigsuspend error");
pr_mask("after return from sigsuspend: ");
/* Reset signal mask which unblocks SIGINT */
if(sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
err_sys("SIG_SETMASK error");
/* And continue processing ... */
pr_mask("program exit: ");
exit(0);
}
static void sig_int(int signo)
{
pr_mask("\nin sig_int: ");
}
调用sigsuspend时,将SIGUSR1加入了信号屏蔽字中,所以在信号处理程序中发现信号屏蔽字改变了。
sigsuspend返回时,将信号屏蔽字恢复为调用它之前的值。
用sigsuspend等待一个全局变量被设置
sigsuspend另一个应用时等待一个信号处理程序设置一个全局变量。
捕捉到中断信号后,只是打印”interrupt”,不退出。进程继续被挂起。
只有捕捉到退出信号后,主例程被唤醒,退出。
#include "apue.h"
volatile sig_atomic_t quitflag; /* set nonzero by signal handler */
static void sig_int(int signo)
{
if(signo == SIGINT)
printf("\ninterrupt\n");
else if(signo == SIGQUIT)
quitflag = 1; /* set flag for main loop */
}
int main(void)
{
sigset_t newmask, oldmask, zeromask;
if(signal(SIGINT, sig_int) == SIG_ERR)
err_sys("signal(SIGINT) error");
if(signal(SIGQUIT, sig_int) == SIG_ERR)
err_sys("signal(SIGQUIT) error");
sigemptyset(&zeromask);
sigemptyset(&newmask);
sigaddset(&newmask, SIGQUIT);
/* Blcok SIGQUIT and save current signal mask */
if(sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)
err_sys("SIG_BLOCK error");
while(quitflag == 0)
sigsuspend(&zeromask);
/* SIGQUIT has been caught and is now blocked; do whatever */
quitflag = 0;
/* Reset signal mask which unblocks SIGQUIT */
if(sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
err_sys("SIG_SETMASK error");
return 0;
}
父子进程可用来实现同步的例程
调用sigsuspend可使得进程在等待信号期间休眠。
该程序对于单线程,等待信号期间无法调用其他系统函数。多线程则可专门安排一个线程处理信号。
#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)
{
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(sigprockmask(SIG_BLOCK, &newmask, &oldmask) < 0)
err_sys("SIG_BLOCK error");
}
void TELL_PARRENT(pid_t pid)
{
kill(pid, SIGUSR2); /* tell parent we're done */
}
void WAIT_PARENT(void)
{
while(sigflag == 0)
sigsuspend(&zeromask); /* and wait for parent */
/* 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");
}
abort的POSIX.1实现
1、保存之前的SIGABRT信号处理函数。当SIGABRT信号处理函数为忽略时,重置为默认。
2、保存当前信号屏蔽字,并解除对信号SIGABRT的阻塞。
3、发送SIGABRT。
4、冲洗所有标准流缓冲区。
5、恢复之前的SIGABRT信号处理函数。
6、再次发送SIGABRT,而不是简单的调用_exit,是为了说明进程的终止状态是SIGABRT造成的。
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void abort(void)
{
sigset_t mask;
struct sigaction action;
/* Caller can't ignore SIGABORT, if so reset to default */
/*
int sigaction(int signo, const struct sigaction *restrict act,
struct sigaction * restrict oact);
返回:成功0,失败-1
signo: 要检测 或修改器具体动作的信号编号。
若act非空,则修改其动作。若oact非空,则由oact返回该信号的上一个动作。
struct sigaction
{
void (*sa_handler)(int); addr of signal handler, or SIG_IGN, or SIG_DFL
sigset_t s_mask; additional signals to block,保证信号处理函数只调用一次
int sa_flags; signal options
alternate handler,当使用SA_SIGINFO标志时,使用该信号处理程序
与sa_handler可能使用同一存储区,所以一次只能使用这两个中的一个
void (*sa_sigaction(int, siginfo_t *, void *);
}
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(SIGABRT, NULL, &action);
if(action.sa_handler == SIG_IGN)
{
action.sa_handler = SIG_DFL;
sigaction(SIGABRT, &action, NULL);
}
if(action.sa_handler == SIG_DFL)
fflush(NULL); /* flush all open stdio streams */
/* Caller can't block SIGABORT; make sure it's unblocked */
sigfillset(&mask);
sigdelset(&mask, SIGABRT); /* mask has only SIGABRT turned off */
sigprocmask(SIG_SETMASK, &mask, NULL);
kill(getpid(), SIGABRT); /* send the signal */
/* If we're here, process caught SIGABRT and returned */
fflush(NULL); /* flush all open stdio streams */
action.sa_handler = SIG_DFL;
sigaction(SIGABRT, &action, NULL); /* reset to default */
sigprocmask(SIG_SETMASK, &mask, NULL); /* just in case ... */
kill(getpid(), SIGABRT); /* and one more time */
exit(1); /* this should never be exected */
}
用sigaction实现的signal函数
#include "apue.h"
typedef void Sigfunc(int);
Sigfunc *signal(int signo, Sigfunc *func)
{
struct sigaction act, oact;
act.sa_handler = func;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
/* 对SIGALRM以外的所有信号,都尝试设置SA_RESTART标志,
于是这些信号中断的系统调用都能重启动。*/
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);
}
用system调用ed编辑器
若从shell调用ed编辑器,键入中断字符,捕捉并输出问号,对退出符的处理方式设置为忽略。
#include "apue.h"
static void sig_int(int signo)
{
printf("caught SIGINT\n");
}
static void sig_chld(int signo)
{
printf("caught SIGCHLD\n");
}
int main(void)
{
if(signal(SIGINT, sig_int) == SIG_ERR)
err_sys("signal(SIGINT) error");
if(signal(SIGCHLD, sig_chld) == SIG_ERR)
err_sys("signal(SIGCHLD) error");
if(system("/bin/ed") < 0)
err_sys("system() error");
exit(0);
}
结果
yjp@yjp-VirtualBox:~/apue/10signals$ ./system
a 将正文添加到编辑器缓冲区
hello world
. 行首的点表示停止添加
1, $p 观察缓冲区第一行到最后一行
hello world
w temp.foo 将缓冲区写到一个文件
12 写了12个字符
^C 键入中断符
? 编辑器捕捉并打印中断符,但是父进程没有捕捉中断并打印“caught SIGINT”?
^C
?
q 离开编辑器
caught SIGCHLD
本例与书中的不符,中断信号没有被送到所有前台进程,可能system已经被改善了。
system函数的POSIX.1正确实现
1、当键入中断或退出字符时,不向调用进程发生信号。因为在调用进程已经设置为忽略信号处理函数,在子进程中恢复信号处理,并调用execl。
2、当ed命令终止时,不向进程发送SIGCHLD信号。在调用进程waitpid取得子进程终止状态后,解除对SIGCHLD的阻塞。
#include <sys/wait.h>
#include <errno.h>
#include <signal.h>
#include <unistd.h>
int system(const char *cmdstring)
{
pid_t pid;
int status;
struct sigaction ignore, saveintr, savequit;
sigset_t chldmask, savemask;
if(cmdstring == NULL)
return (1); /* always a command processor with UNIX */
ignore.sa_handler = SIG_IGN; /* ignore SIGINT and SIGQUIT */
sigemptyset(&ignore.sa_mask);
ignore.sa_flags = 0;
if(sigaction(SIGINT, &ignore, &saveintr) < 0)
return (-1);
if(sigaction(SIGQUIT, &ignore, &savequit) < 0)
return (-1);
sigemptyset(&chldmask); /* now block SIGHLD */
sigaddset(&chldmask, SIGCHLD);
if(sigprocmask(SIG_BLOCK, &chldmask, &savemask) < 0)
return (-1);
if((pid = fork()) < 0)
{
status = -1; /* probably out of process */
}
else if(pid == 0) /* child */
{
/* restore previous signal actions & reset signal mask */
sigaction(SIGINT, &saveintr, NULL);
sigaction(SIGQUIT, &savequit, NULL);
sigprocmask(SIG_SETMASK, &savemask, NULL);
execl("/bin/sh", "sh", "-c", cmdstring, (char *)0);
_exit(127);
}
else /* parent */
{
while(waitpid(pid, &status, 0) < 0)
{
if(errno != EINTR)
{
status = -1; /* error other than EINTR from waitpid() */
break;
}
}
}
/* restore previous signal actions & reset signal mask */
if(sigaction(SIGINT, &saveintr, NULL) < 0)
return (-1);
if(sigaction(SIGQUIT, &savequit, NULL) < 0)
return (-1);
if(sigprocmask(SIG_SETMASK, &savemask, NULL) < 0)
return (-1);
}
sleep的可靠实现
1、保存之前的SIGALRM信号处理函数,并设置新的SIGALRM信号处理函数。
2、保存信号屏蔽字,并阻塞SIGALRM信号。
3、计时开始。
4、到时后,解除对信号SIGALRM信号的阻塞,信号处理程序sig_alrm返回后唤醒sigsuspend
5、清除计时器。
6、恢复SIGALRM信号处理函数。
7、恢复信号屏蔽字。
#include "apue.h"
static void sig_alrm(int signo)
{
/* nothing to do, just returning wakes up sigsuspend() */
}
unsigned int sleep(unsigned int nsecs)
{
struct sigaction newact, oldact;
sigset_t newmask, oldmask, suspmask;
unsigned int unslept;
/* set our handler, save previous information */
newact.sa_handler = sig_alrm;
sigemptyset(&newact.sa_mask);
newact.sa_flags = 0;
sigaction(SIGALRM, &newact, &oldact);
/* block SIGALRM and save current signal mask */
sigemptyset(&newmask);
sigaddset(&newmask, SIGALRM);
sigprocmask(SIG_BLOCK, &newmask, &oldmask);
alarm(nsecs);
suspmask = oldmask;
sigdelset(&suspmask, SIGALRM); /* make sure SIGALRM isn't blocked */
sigsuspend(&suspmask); /* wait for any signal to be caught */
unslept = alarm(0);
sigaction(SIGALRM, &oldact, NULL); /* reset previous action */
/* reset signal mask, which unblocks SIGALRM */
sigprocmask(SIG_SETMASK, &oldmask, NULL);
return (unslept);
}
如何处理交互式停止信号SIGTSTP
当键入挂起字符,调用SIGTSTP信号处理程序。应进行与终端有关的处理:将光标移到左下角,恢复终端工作方式等。
在将SIGTSTP复位为默认值,并解除对信号的阻塞后,向自己发送SIGTSTP,系统停止该进程。仅当某个进程向该进程发送SIGCONT信号时,才继续。这时从kill返回,程序继续运行时,将SIGTSTP信号复位为捕捉,并做终端处理,如重新绘制屏幕。
#include "apue.h"
#define BUFFSIZE 1024
static void sig_tstp(int);
int main(void)
{
int n;
char buf[BUFFSIZE];
if(signal(SIGTSTP, SIG_IGN) == SIG_DFL)
signal(SIGTSTP, sig_tstp);
while((n = read(STDIN_FILENO, buf, BUFFSIZE)) > 0)
if(write(STDOUT_FILENO, buf, n) != n)
err_sys("write error");
if(n < 0)
err_sys("read error");
exit(0);
}
/* signal handler for SIGTSTP */
static void sig_tstp(int signo)
{
sigset_t mask;
/* ... move cursor to lower left corner, reset tty mode ... */
/* Unblcok SIGTSTP, since it's blocked while we're handling it */
sigemptyset(&mask);
sigaddset(&mask, SIGTSTP);
sigprocmask(SIG_UNBLOCK, &mask, NULL);
signal(SIGTSTP, SIG_DFL); /* reset disposition to default */
kill(getpid(), SIGTSTP); /* and send the signal to ourself */
/* we won't return from the kill untile we're continued */
signal(SIGTSTP, sig_tstp); /* restablish signal handler */
/* ... reset tty mode, redraw screen ... */
}
这个例子没懂