标签:style blog class code java tar
1、概念
信号提供了一种处理异步事件的方法。
不存在编号为0的信号,kill函数对信号编号0有特殊的应用。POSIX.1 将此种信号编号值称为空信号。
2、信号的相关动作
a、忽略此信号。大多数信号都可使用这种方式进行处理,但有两种信号却决不能被忽略,它们是SIGKILL和SIGSTOP。这两种信号不能不忽略的原因是:它们向超级用户提供了使进程终止和停止的可靠方法;
b、捕捉信号。为了做到这一点,要通知内核在某种信号发生时调用一个用户函数。(SIGTERM是终止信号,kill命令传送的系统默认信号是终止信号)。注意,不能捕捉SIGKILL和SIGSTOP信号;
c、执行系统默认动作。针对大多数信号的系统默认动作是终止进程。
3、程序启动(exec处理信号的方式)
当执行一个程序时,所有信号的状态都是系统默认或忽略,除非调用exec的进程忽略该信号。确切地说,exec函数将原先设置为要捕捉的信号都更改为它们的默认动作,其他信号的状态不变。如在一个交互式shell如何处理针对后台进程的中断和退出信号,如:
cc main.c &
shell自动将后台进程对中断和退出信号的处理方式设置为忽略,于是,当按中断键时就不会影响到后台进程。
进程启动(fork处理信号的方式)
当一个进程调用fork时,其子进程继承父进程的信号处理方式,因为子进程在开始时复制了父进程的存储映像,所以信号捕捉函数的地址在子进程中是有意义的。
4、不可靠信号的两个例子
前提,早期版本中的问题是在进程每次接到信号对其进行处理时,随即将该信号动作复位为默认值。如:
int sig_init(); // my signal handling function ... signal(SIGINT,sig_init); // establish handler ... sig_init() { signal(SIGINT,sig_init); // reestablish handler for next time ... // process the signal }
问题:从信号发生之后到在信号处理程序中调用signal函数之前这段时间中有一个时间窗口。在此段时间中,可能发生另一次中断信号。第二个信号会导致执行默认动作,而针对终端信号的默认动作是终止该进程。
另一个问题:在进程不希望某种信号发生时,它不能关闭该信号。进程能做的一切就是忽略该信号。有时希望通知系统“阻止下列信号发生,如果它们确实发生了,请记住了它们。”能够显现这种缺陷的经典实例有下列程序段,它捕捉了一个信号,然后设置一个表示该信号已发生的标志:
int sig_init_flag; // set nonzero when signal occurs main() { int sig_init(); // my signal handling function ... signal(SIGINT,sig_init); // establish handler ... while(sig_init_flag == 0) pause(); // go to sleep,waiting for signal ... } sig_init() { signal(SIGINT,sig_init); // reestablish handler for next time sig_init_flag = 1; // set flag for main loop to examine }
其中,进程调用pause函数使自己休眠,直至捕捉到一个信号。当捕捉到信号时,信号处理程序将标志sig_init_flag设置为非0值。从信号处理程序返回后,内核自动将该进程唤醒,它检测到该标志为非0值,然后执行它所需做的工作。但是这里也有一个时间窗口,在此窗口中操作可能失误。如果在测试sig_init_flag(while语句)之后和调用pause之前发生SIGINT信号,则此进程在调用pause时入睡,并且长眠不醒(假定此信号不会再次发生)。于是,这次发生的信号也就丢失了。pause函数使调用进程在接到一个信号前挂起。
5、中断的系统调用
早期UNIX系统的一个特性是:如果进程在执行一个低速系统调用而阻塞期间捕捉到一个信号,则该系统调用就被中断。该系统调用返回出错,其errno被设置为EINTR,这样处理的理由是:因为一个信号发生了,进程捕捉到了它,这意味着已经发生了某种事情,所以是个理应当唤醒其他被阻塞的系统调用的好机会。
与被中断的系统调用相关的问题是必须显式地处理出错返回。典型的代码序列(假定进行一个读操作,它被中断,我们希望重新启动它)可能如下所示:
again: if((n = read(fd,buf,BUFFSIZE)) < 0) { if(errno == EINTR) goto again; // just an interrupted system call // handler other errors }
为了帮助应用程序使其不必处理被中断的系统调用,4.2 BSD 引入了某些被中断的系统调用的自动重启动。自动重启动的系统调用包括ioctl、read、readv、write、writev、wait和waitpid。正如前述,其中前5个函数只有对低速设备进行操作时才会被信号中断。而wait和waitpid在捕捉到信号时总是被中断。因为这种自动重启动的处理方式会带来问题,所以某些应用程序并不希望这些函数被中断后重启动。为此4.3 BSD允许进程基于每个信号禁用此功能(见程序清单10-12)。
引入自动重启动的理由是:有时用户并不知道所使用的输入、输出设备是否是低速设备,如果我们编写的程序可以用交互方式运行,则它可能读、写低速终端设备。如果在程序中捕捉信号,而且系统不提供重启动功能,则对每次读、写系统调用都要进行是否出错返回的测试,如果是被中断的,则再调用读、写系统调用。
6、可重入函数
进程捕捉到信号并对其进行处理时,进程正在执行的指令序列就被信号处理程序临时中断,它首先执行该信号处理程序中的指令。如果从信号处理程序返回(例如没有调用exit或longjmp),则继续执行在捕捉到信号时进程正在执行的正常指令序列。但在信号处理程序中,不能判断捕捉到信号时进程在何处执行。
a、如果进程正在执行malloc,在其堆中分配另外的存储空间,而此时由于捕捉到信号而插入执行该信号处理程序,其中又调用malloc,这时会发生什么?
b、若进程正在执行getpwnam,这种将其结果存放在静态存储单元(可能是静态变量,当发生两次getpwnam时,该变量值只保存最后一次getpwnam的值,并且恢复不了第一次getpwnam时的值)中的函数,期间插入执行信号处理程序,它又调用这样的函数,这时又会发生什么?
在malloc中,可能会对进程造成破坏,因为malloc通常为它所分配的存储区维护一个链接表,而插入执行信号处理程序时,进程可能正在更改此链接表。
在getpwnam中,返回给正常调用者的信息可能被返回给信号处理程序的信息覆盖。
函数时不可重入的原因为:
a、已知它们使用静态数据结构;
b、它们调用malloc或free;
c、它们是标准IO函数;
标准IO库的很多实现都以不可重入方式使用全局数据结构。
可重入函数另一个说法,当函数正在执行而被中断,而且中断返回,函数继续执行后的结果,与没有发生中断执行后的结果一样,那么这个函数就是可重入函数。
应当了解,即使在信号处理程序中使用了可重入函数(不会破坏被中断的进程的函数),但是由于每个线程只有一个errno变量,所以信号处理程序可能会修改其原先值。如,一个信号处理程序,它恰好在main刚刚设置errno之后被调用,而该信号处理程序调用read这类函数,则它可能更改errno的值,从而取代了刚刚由main设置的值。因此,作为一个通用的规则,当在信号处理程序中调用了可重入函数时,应当在其前保存,在其后恢复errno。
例子:
static void my_alarm(void) { struct passwd *rootptr; printf("in signal handler\n"); if((rootptr = getpwnam("root")) == NULL) printf("getpwnam(root) error!\n"); alarm(1); } int main() { struct passwd *ptr; // 只是指针,内存由getpwnam分配,并放在静态存储区中 signal(SIGALRM,my_alarm); alarm(1); for(;;) { if((ptr = getpwnam("zzc")) == NULL) printf("getpwnam error!\n"); if(strcmp(ptr->pw_name,"zzc") != 0) printf("return value corrupted!,pw_name = %s\n",ptr->pw_name); } }
结果:
:~/program/c$ ./test in signal handler ^C(强制中断进程运行)
如果把信号处理函数中的
if((rootptr = getpwnam("root")) == NULL) printf("getpwnam(root) error!\n");
注释掉的话(没有覆盖main中getpwnam函数的内容),结果为:
:~/program/c$ ./test in signal handler in signal handler in signal handler in signal handler in signal handler ... ^C
7、可靠信号术语和语义
在产生了信号时,内核在进程表中设置一个某种形式的标志。当对信号采取了这种行动时,我们说向进程递送了一个信号。在信号产生和递送之间的时间间隔内,称信号是未决的(pending)。
如果为进程产生了一个设置为阻塞(该信号在信号屏蔽字中的对应位被设置)的信号,而且对该信号的动作是系统默认动作或捕捉该信号,则为该进程将此信号保持为未决状态,直到该进程
a、对此信号解除阻塞;
b、将此信号的动作更改为忽略。注意,内核在递送一个原来被阻塞的信号给进程时(而不是在产生该信号时),才决定对它的处理方式。于是进程在信号递送给它之前仍可改变对该信号的动作(即忽略该函数或者默认动作或者进行信号处理)。
进程调用sigpending函数来判定哪些信号是设置为阻塞并处于未决状态的。
每个进程都有一个信号屏蔽字,它规定了当前要阻塞递送到该进程的信号集,对于每种可能产生的信号,该屏蔽字中都有一位与之对应。对于某种信号,若其对应位已设置,则它当前是被阻塞的。进程可以调用sigprocmask来检测和更改其当前信号屏蔽字。
8、kill、raise、alarm、pause函数
kill函数将信号发送给进程或进程组。
raise函数则允许进程向自身发送信号。
alarm函数
如果我们向捕捉SIGALRM信号,则必须在调用alarm之前设置该信号的处理程序。如果我们先调用alarm,然后在我们能够设置SIGALRM处理程序之前已接收到该信号,那么进程将终止。
pause函数
pause函数使调用进程挂起, 直到捕捉到一个信号,执行了信号处理程序并从其返回时,pause才返回。
9、sleep简单而不完整的实现
使用alarm和pause,进程可使自己休眠一段指定的时间。如下面的sleep1函数:
static void sig_alarm() { ... } unsigned int sleep1(unsigned int nsecs) { if(signal(SIGALRM,sig_alarm) == SIG_ERR) return (nsecs); alarm(nsecs); // start the timer pause(); // next caught signal wakes us up return (alarm(0)); // turn off timer,return unslept time }
在第一次调用alarm和调用pause之间有一个竞争条件:在一个繁忙的系统中,可能alarm在调用pause之前超时,并调用了信号处理程序。如果发生这种情况,则在调用pause后,如果没有捕捉到其他信号,则调用者将永远被挂起。
解决方法:
a、使用setjmp;
b、使用sigprocmask和sigsuspend;
方法a:
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); // start the timer pause(); // next caught signal wakes us up } return (alarm(0)); // turn off timer,return unslept time }
在此,竞争条件已被避免。即使pause从未执行,在发生SIGALRM时,sleep2也会返回。
出现的另一个问题:它涉及与其他信号的交互,如果SIGALRM中断了某个其他信号处理程序,则调用longjmp会提早终止该信号处理程序。如:
// 超过5s static void sig_int(int signo) { int i,j; volatile int k; printf("\nsig_int starting!\n"); for(i = 0;i < 300000;i++) for(j = 0;j < 40000;j++) k += i * j; printf("sig_int finished!\n"); } int main() { unsigned int unslept; if(signal(SIGINT,sig_int) == SIG_ERR) { printf("signal(SIGINT) error!\n"); exit(1); } unslept = sleep2(5); printf("sleep2 returnd :%u\n",unslept); exit(0); }
输出结果为:
:~/program/c$ ./test ^? // we type the interrupt character sig_int starting sleep2 returnd :0
除了用来实现sleep函数外,alarm还常用于对可能阻塞的操作设置时间上限值。例如,程序中有一个读低速设备的可能阻塞的操作,我们希望超过一定时间量后就停止执行该操作:
static void sig_alrm(int); int main() { int n; char line[MAXLINES]; if(signal(SIGALRM,sig_alrm) == SIG_ERR) { printf("signal(SIGALRM) error!\n"); exit(1); } alarm(10); if((n = read(STDIN_FILENO,line,MAXLINE)) < 0) { printf("read error!\n"); exit(1); } alarm(0); write(STDOUT_FILENO,line,n); exit(0); } static void sig_alrm(int signo) { // nothing to do,just return to interrupt the read }
遇到的问题:①跟上次程序类似,在第一次alarm调用和read调用之间有一个竞争条件。如果内核在这两个函数之间使进程阻塞,而其时间长度又超过闹钟时间,则read可能永远阻塞。②在read时候被alarm中断,如果系统调用是自动重启动的,则当从SIGALRM信号处理程序返回时,read并不被中断,在这种情形下,设置时间限制不起作用。
解决方法:
static void sig_alrm(int); static jmp_buf env_alrm; int main() { int n; char line[MAXLINES]; if(signal(SIGALRM,sig_alrm) == SIG_ALRM) { printf("signal(SIGALRM) error!\n"); exit(1); } if(setjmp(env_alrm) != 0) { printf("read timeout!\n"); exit(1); } alarm(10); if((n = read(STDIN_FILENO,line,MAXLINE)) < 0) { printf("read error!\n"); exit(1); } alarm(0); write(STDOUT_FILENO,line,n); exit(0); } static void sig_alrm(int signo) { longjmp(env_alrm,1); }
不管系统是否重新启动中断的系统调用,该程序都会如期的那样工作,但是要知道,该程序跟上次的程序一样存在于其他信号处理程序交互的问题。
10、信号集
所有应用程序在使用信号集前,都要对信号集调用sigemptyset或sigfillset一次,因为C编译器把未赋初值的外部和静态变量都初始化为0,而这是否与给定系统上信号集的实现相对应却并不清楚。
11、sigprocmask函数
sigprocmask函数用来检测或获取或更改当前信号屏蔽字(即阻塞还是非阻塞)。
在调用sigprocmask后如果有任何未决的、不再阻塞的信号,则在sigprocmask返回前,至少会将其中一个信号递送给该进程。
sigprocmask是仅为单线程的进程定义的。为处理多线程的进程中信号的屏蔽,提供了另一个单独的函数。
12、sigpending函数
sigpending函数返回未决的信号集,其中的各个信号对于调用进程是阻塞的而不能递送,因而也一定是当前未决的。如:
static void sig_quit(int); int main() { sigset_t newmask,oldmask,pendmask; if(signal(SIGQUIT,sig_quit) == SIG_ERR) printf("can‘t catch SIGQUIT!\n"); // block SIGQUIT and save current signal mask sigemptyset(&newmask); sigaddset(&newmask,SIGQUIT); if(sigprocmask(SIG_BLOCK,&newmask,&oldmask) < 0) // ① printf("SIG_BLOCK error!\n"); sleep(5); if(sigpending(&pendmask) < 0) printf("sigpending error!\n"); if(sigismember(&pendmask,SIGQUIT)) printf("SIGQUIT pending!\n"); // ② // reset signal mask which unblocks SIGQUIT if(sigprocmask(SIG_SETMASK,&oldmask,NULL) < 0) // ③ printf("SIG_SETMASK error!\n"); printf("SIGQUIT unblocked!\n"); sleep(5); exit(0); } static void sig_quit(int signo) { printf("caught SIGQUIT!\n"); if(signal(SIGQUIT,SIG_DFL) == SIG_ERR) printf("can‘t reset SIGQUIT!\n"); }
运行结果:
:~/program/c$ ./test ^\ 产生信号一次(在5秒中之内) SIGQUIT pending! 从sleep返回后 caught SIGQUIT! 在信号处理程序中 SIGQUIT unblocked! 从sigprocmask返回后 :~/program/c$
①与②之间,SIGQUIT信号是被阻塞的,所以也是未决的。
③调用后,SIGQUIT信号的阻塞被释放,在sigprocmask返回前,任何未决的、不再阻塞的信号会被递送给该进程。所以,SIGQUIT的信号处理函数会被调用,最后才处理sigprocmask后面的程序。
:~/program/c$ ./test SIGQUIT unblocked! 没有产生信号 :~/program/c$
SIGQUIT处理程序(sig_quit)中的printf语句先执行,然后再执行sigprocmask之后的printf语句,因为sigprocmask(SIG_SETMASK,&oldmask,NULL)执行完后,会立即递交SIGQUIT信号(之前被阻塞,现在解除阻塞),相应的信号处理函数会被立即调用。
然后该进程再休眠5秒钟,如果在此期间再产生退出信号,那么因为在上次捕捉到该信号时,已将其处理方式设置为默认动作,所以这一次它就会使该进程终止。
13、sigaction函数
此函数的功能是检查或修改与指定信号相关联的处理动作(或同时执行这两种操作)。此函数取代了UNIX早期版本使用的signal函数。
int sigaction(int signo,const struct sigaction *restrict act,struct sigaction *restrict oact);
其中,参数signo是检测或修改其具体动作的信号编号。
若act指针非空,则要修改其动作;
若oact指针非空,则系统经由oact指针返回该信号的上一个动作。此函数使用下列结构:
struct sigaction { void (*sa_handler)(int); // addr of signal handler,or SIG_IGN,or SIG_DEF sigset_t sa_mask; // additional signals to block int sa_flags; // signal options // alternative handler void (*sa_sigaction)(int,siginfo_t *,void *); };
当更改信号动作时,如果sa_handler字段包含一个信号捕捉函数的地址(与常量SIG_IGN或SIG_DFL相对),则sa_mask字段说明了一个信号集,在调用该信号捕捉函数之前,这一信号集要加到进程的信号屏蔽字中。仅当从信号捕捉函数返回时再将进程的信号屏蔽字复位为原先值。
在信号处理程序被调用时,操作系统建立的新信号屏蔽字包括正被递送的信号。因此保证了在处理一个给定的信号时,如果这种信号再次发生,那么它会被阻塞到对前一个信号的处理结束为止。若同一种信号多次发生,通常并不将它们排队,所以如果在某种信号被阻塞时它发生了五次,那么对这种信号解除阻塞后,其信号处理函数通常只会被调用一次。
一旦对给定的信号设置了一个动作,那么在调用sigaction显式地改变它之前,该设置就一直有效。不像旧的不可靠信号语义signal函数,对给定的信号设置了一个动作后,需要在信号处理函数中再次设置一次。
sa_sigaction字段是一个替代的信号处理程序,当在sigaction结构中使用了SA_SIGNOF标志时,使用该信号处理程序。对于sa_sigaction字段和sa_handler字段这两者,其实现可能使用同一存储器,所以应用程序只能一次使用这两个字段中的一个。
下列程序用sigaction实现的signal函数
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); }
某些早期系统定义了SA_INTERRUPT标志。这些系统的默认方式是重新启动被中断的系统调用,而指定此标志则使系统调用被中断后不再重启动。Linux定义了SA_UNTERRUPT标志,以便与使用该标志的应用程序兼容。但是,如若信号处理程序是用sigaction设置的,那么其默认方式是不重新启动系统调用。除非说明了SA_RESTART标志,否则sigaction函数不再重启动被中断的系统调用。
14、sigsetjmp和siglongjmp函数
调用longjmp有一个问题。当捕捉到一个信号时,进入信号捕捉函数,此时当前信号被自动地加到进程的信号屏蔽字中。这阻止了后来产生的这种信号中断该信号处理程序。如果用longjmp跳出信号处理程序,那么,对此进程的信号屏蔽字会发生什么呢?
为了允许这两种形式存在,定义了两个新函数sigsetjmp和siglongjmp。
int sigsetjmp(sigjmp_buf env,int savemask); void siglongjmp(sigjmp_buf env,int val);
如果savemask非0,则sigsetjmp在env中保存进程的当前信号屏蔽字。调用siglongjmp时,如果带非0savemask的sigsetjmp调用已经保存了env,则siglongjmp从其中恢复保存的信号屏蔽字。如:
下面程序演示了在信号处理程序被调用时,系统所设置的信号屏蔽字如何自动地包括刚被捕捉到的信号:
static sigjmp_buf jmpbuf; static volatile sig_atomic_t canjump; static void sig_usr1(int),sig_alarm(int); static void pr_mask(const char *str) { sigset_t sigset; int errno_save; errno_save = errno; if(sigprocmask(0,NULL,&sigset) < 0) { printf("sigprocmask error!\n"); exit(1); } printf("%s\n",str); if(sigismember(&sigset,SIGINT)) printf("SIGINT\n"); if(sigismember(&sigset,SIGQUIT)) printf("SIGQUIT\n"); if(sigismember(&sigset,SIGUSR1)) printf("SIGUSR1\n"); if(sigismember(&sigset,SIGALRM)) printf("SIGALRM!\n"); printf("\n"); errno = errno_save; } int main() { if(signal(SIGUSR1,sig_usr1) == SIG_ERR) { printf("signal(SIGUSR1) error!\n"); exit(1); } if(signal(SIGALRM,sig_alarm) == SIG_ERR) { printf("signal(SIGALRM) error!\n"); exit(1); } if(sigsetjmp(jmpbuf,1)) { pr_mask("ending main:"); exit(0); } canjump = 1; for(;;) pause(); } static void sig_usr1(int signo) { time_t starttime; if(canjump == 0) return; pr_mask("starting sig_usr1:"); alarm(3); starttime = time(NULL); for(;;) { if(time(NULL) > starttime + 5) break; } pr_mask("finishing sig_usr1:"); canjump = 0; siglongjmp(jmpbuf,1); } static void sig_alarm(int signo) { pr_mask("in sig_alarm:"); }
运行结果为:
:~/program/c$ ./test & [1] 1280 :~/program/c$ kill -USR1 1280 :~/program/c$ starting sig_usr1: SIGUSR1 in sig_alarm: SIGUSR1 SIGALRM! finishing sig_usr1: SIGUSR1 ending main: [1]+ Done ./test
15、sigsuspend函数
更改进程的信号屏蔽字可以阻塞所选择的信号,或解除对它们的阻塞,使用这种技术可以保护不希望由信号中断的代码临界区。如果希望对一个信号解除阻塞,然后pause以等待以前被阻塞的信号发生,则又将如何呢?假定信号是SIGINT,实现这一点的一种不正确的方法是:
sigset_t newmask,oldmask; sigemptyset(&newmask); sigaddset(&newmask,SIGINT); // block SIGINT and save current signal mask if(sigprocmask(SIG_BLOCK,&newmask,&oldmask) < 0) printf("SIG_BLOCK error!\n"); // critical region of code if(sigprocmask(SIG_SETMASK,&oldmask,NULL) < 0) printf("SIG_SETMASK error!\n"); // ---------------- [1] ------------- // window is open pause(); // wait for signal to occur // ---------------- [2]-------------- // continue to process
如果在信号阻塞时将其发送给进程,那么该信号的传递就被推迟直到对它解除了阻塞。对应用程序而言,该信号好像发生在解除对SIGINT的阻塞和pause之间。如果发生了这种情况,或者如果在解除阻塞时刻和pause之间确实发生了信号,那么就产生了问题。因为我们可能不会再见到该信号(在[1]和[2]之间发生了SIGINT信号,则跳到信号处理函数中进行处理,处理后返回执行pause函数,pause因为等待SIGINT信号而导致阻塞,所以需要原子操作),所以从这种意义上说,在此时间窗口中发生的信号丢失了,这样就使得pause永远阻塞。
为了纠正此问题,需要一个原子操作中先恢复信号屏蔽字,然后使进程休眠。这种功能是由sigsuspend函数提供的:
int sigsuspend(const sigset_t *sigmask);
将进程的信号屏蔽字设置为由sigmask指向的值。在捕捉到一个信号或发生了一个会终止该进程的信号之前,该进程被挂起。如果捕捉到一个信号而且从该信号处理程序返回,则sigsuspend返回,并且将该进程的信号屏蔽字设置为调用sigsuspend之前的值。
下面程序显示了保护临界区,使其不被特定信号中断的正确方法:
static void sig_init(int signo); static void pr_mask(const char *str) { sigset_t sigset; int errno_save; errno_save = errno; if(sigprocmask(0,NULL,&sigset) < 0) { printf("sigprocmask error!\n"); exit(1); } printf("%s\n",str); if(sigismember(&sigset,SIGINT)) printf("SIGINT\n"); if(sigismember(&sigset,SIGQUIT)) printf("SIGQUIT\n"); if(sigismember(&sigset,SIGUSR1)) printf("SIGUSR1\n"); if(sigismember(&sigset,SIGALRM)) printf("SIGALRM!\n"); printf("\n"); errno = errno_save; } int main() { sigset_t newmask,oldmask,waitmask; pr_mask("program start :"); if(signal(SIGINT,sig_init) == SIG_ERR) { printf("signal(SIGINT) error!\n"); exit(1); } sigemptyset(&waitmask); sigaddset(&waitmask,SIGUSR1); sigemptyset(&newmask); sigaddset(&newmask,SIGINT); // block SIGINT and save current signal mask if(sigprocmask(SIG_BLOCK,&newmask,&oldmask) < 0) { printf("SIG_BLOCK error!\n"); exit(1); } // critical region of code pr_mask("in critical region :"); // pause,allowing all signals except SIGUSR1 if(sigsuspend(&waitmask) != -1) { printf("sigsuspend error!\n"); exit(1); } pr_mask("after return from sigsuspend:"); // reset signal mask which unblocks SIGINT if(sigprocmask(SIG_SETMASK,&oldmask,NULL) < 0) { printf("SIG_SETMASK error!\n"); exit(1); } pr_mask("program exit:"); exit(0); } static void sig_init(int signo) { pr_mask("in sig_init:"); }
输出结果为:
:~/program/c$ ./test program start : in critical region : SIGINT ^C // 键入中断字符 in sig_init: SIGINT SIGUSR1 after return from sigsuspend: SIGINT program exit: :~/program/c$
可以用信号实现父、子进程之间的同步:
static void charactatime(char *str) { char *ptr; int c; setbuf(stdout,NULL); for(ptr = str;(c = *ptr++) != 0;) putc(c,stdout); } static volatile sig_atomic_t sigflag; static sigset_t newmask,oldmask,zeromask; static void sig_usr(int signo) { sigflag = 1; } void TELL_WAIT() { if(signal(SIGUSR1,sig_usr) == SIG_ERR) { printf("signal(SIGUSR1) error!\n"); exit(1); } if(signal(SIGUSR2,sig_usr) == SIG_ERR) { printf("signal(SIGUSR2) error!\n"); exit(1); } 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) { printf("SIG_BLOCK error!\n"); exit(1); } } void TELL_PARENT(pid_t pid) { kill(pid,SIGUSR2); } void WAIT_PARENT() { while(sigflag == 0) sigsuspend(&zeromask); sigflag = 0; // reset signal mask to original value if(sigprocmask(SIG_SETMASK,&oldmask,NULL) < 0) { printf("SIG_SETMASK error!\n"); exit(1); } } void TELL_CHILD(pid_t pid) { kill(pid,SIGUSR1); } void WAIT_CHILD() { while(sigflag == 0) sigsuspend(&zeromask); sigflag = 0; // reset signal mask to original value. if(sigprocmask(SIG_SETMASK,&oldmask,NULL) < 0) { printf("SIG_SETMASK error!\n"); exit(1); } } int main() { pid_t pid; TELL_WAIT(); if((pid = fork()) < 0) { printf("fork error!\n"); exit(1); } else if(pid == 0) { WAIT_PARENT(); charactatime("output from child!\n"); } else { charactatime("output from parent!\n"); TELL_CHILD(pid); } exit(0); }
如果在等待信号发生时希望去休眠,则适用sigsuspend函数是非常适当的,但是如果在等待信号期间希望调用其他系统函数,那么将会怎样呢?不幸的是,在单线程环境下对此问题没有妥善的解决方法。如果可以使用多线程,则可专门安排一个线程处理信号。
如果不使用线程,那么我们能尽力做到最好的是,当信号发生时,在信号捕捉程序中对一个全局变量置1.
16、sleep函数
unsigned int sleep(unsigned int seconds);
此函数使调用进程被挂起,直到满足以下条件之一:
a、已经过了seconds所指定的墙上的时钟时间;
b、调用进程捕捉到一个信号并从信号处理程序返回;
在第一种情形中,返回值是0.当由于捕捉到某个信号sleep提早返回时(第二种情形),返回值是未睡够的秒数。
标签:style blog class code java tar
原文地址:http://www.cnblogs.com/sheshiji/p/3704179.html