10章 信号

来源:互联网 发布:龙渊网络手游 编辑:程序博客网 时间:2024/05/17 17:57

信号及其宏定义

bash配置的组合按键:
ctrl+c 产生 SIGINT 中断信号
ctrl+z产生 SIGSTOP信号
ctrl+\产生 SIGQUIT信号
kill -** PID 向某某进程发送信号
ctrl+d产生eof符号,并非信号

/* Signals.  */#define SIGHUP      1   /* Hangup (POSIX).  */#define SIGINT      2   /* Interrupt (ANSI).  */#define SIGQUIT     3   /* Quit (POSIX).  */#define SIGILL      4   /* Illegal instruction (ANSI).  */#define SIGTRAP     5   /* Trace trap (POSIX).  */#define SIGABRT     6   /* Abort (ANSI).  */#define SIGIOT      6   /* IOT trap (4.2 BSD).  */#define SIGBUS      7   /* BUS error (4.2 BSD).  */#define SIGFPE      8   /* Floating-point exception (ANSI).  */#define SIGKILL     9   /* Kill, unblockable (POSIX).  */#define SIGUSR1     10  /* User-defined signal 1 (POSIX).  */#define SIGSEGV     11  /* Segmentation violation (ANSI).  */#define SIGUSR2     12  /* User-defined signal 2 (POSIX).  */#define SIGPIPE     13  /* Broken pipe (POSIX).  */#define SIGALRM     14  /* Alarm clock (POSIX).  */#define SIGTERM     15  /* Termination (ANSI).  */#define SIGSTKFLT   16  /* Stack fault.  */#define SIGCLD      SIGCHLD /* Same as SIGCHLD (System V).  */#define SIGCHLD     17  /* Child status has changed (POSIX).  */#define SIGCONT     18  /* Continue (POSIX).  */#define SIGSTOP     19  /* Stop, unblockable (POSIX).  */#define SIGTSTP     20  /* Keyboard stop (POSIX).  */#define SIGTTIN     21  /* Background read from tty (POSIX).  */#define SIGTTOU     22  /* Background write to tty (POSIX).  */#define SIGURG      23  /* Urgent condition on socket (4.2 BSD).  */#define SIGXCPU     24  /* CPU limit exceeded (4.2 BSD).  */#define SIGXFSZ     25  /* File size limit exceeded (4.2 BSD).  */#define SIGVTALRM   26  /* Virtual alarm clock (4.2 BSD).  */#define SIGPROF     27  /* Profiling alarm clock (4.2 BSD).  */#define SIGWINCH    28  /* Window size change (4.3 BSD, Sun).  */#define SIGPOLL     SIGIO   /* Pollable event occurred (System V).  */#define SIGIO       29  /* I/O now possible (4.2 BSD).  */#define SIGPWR      30  /* Power failure restart (System V).  */#define SIGSYS      31  /* Bad system call.  */#define SIGUNUSED   31

这里写图片描述
这里写图片描述

longjmp导致另外信号处理终止

这里显示,设计信号的时候,必须精细的思考,一个信号是不是会打断另外一个信号处理实例。

static jmp_buf env_alrm;//设置存储跳转缓存的变量.static void sig_alrm(int signo);//sig_alrmstatic void sig_int(int signo);//sig_intunsigned int sleep2(unsigned int sec);int main(void)//测试两个信号竞争问题{    unsigned int unsleep = 0;    if(signal(SIGINT , sig_int) == SIG_ERR)        printf("signal(SIGINT) error!");    unsleep = sleep2(5);//5s之后,闹钟返回    printf("sleep2 ret %u!\n",unsleep);    exit(0);}static void sig_alrm(int signo){    printf("sig_alrm is starting\n");    longjmp(env_alrm , 1);//调用这个,直接跳转到setjmp函数,并且setjmp返回1.}static void sig_int(int signo){    int i,j;    volatile int k;//声明这个变量是易变的,编译器不要优化这个    printf("sig_int is starting\n");    for(i = 0 ; i<300000 ; i++)        for(j = 0 ; j<4000 ; j++)            k += i*j;//在此处易变,不要优化    printf("sig_int finished!\n");}unsigned int sleep2(unsigned int sec){    if(signal(SIGALRM , sig_alrm) == SIG_ERR)//向系统注册对应信号处理函数,若错误返回err        return (sec);//错误返回传入参数    if(setjmp(env_alrm) == 0){//第一次调用set返回0,之后通过long跳转到此,返回值和long参数有关.        alarm(sec);//第一次来上闹钟上闹钟来了给此进程发送信号快点起来做事        pause();//将进程挂起,知道捕捉到一个信号才返回,进程被唤醒.    }//防止了alarm和pause竞争导致进程永远挂起.    return(alarm(0));//取消以前闹钟,以前余留值作为返回.}

读低速设备可能阻塞的操作,通过alarm一定始终后通过闹钟终止此操作。但是这里有竞争alarm和read竞争,alarm之后进程被挂起,超过alarm时间,然后再read阻塞了。

static void sig_alrm(int signo){    printf("\nsig_alrm is starting\n");    exit(0);}int main(void){    int n;    char line[MAXLINE];    if(signal(SIGALRM , sig_alrm) == SIG_ERR)        err_sys("signal(SIGALRM) error\n");    alarm(10);    if((n = read(STDIN_FILENO , line , MAXLINE)) < 0)            err_sys("read error");//并没有阻塞    printf("alarm return %d\n",alarm(0));    write(STDOUT_FILENO , line , n);    exit(0);}

处理上面竞争的问题,竞争之后。

static jmp_buf env_alrm;static void sig_alrm(int signo){    printf("\nsig_alrm is starting\n");    longjmp(env_alrm , 1);//}int main(void){    int n;    char line[MAXLINE];    if(setjmp(env_alrm) != 0)        err_quit("read timeout!");//竞争之后一样可以返回    if(signal(SIGALRM , sig_alrm) == SIG_ERR)        err_sys("signal(SIGALRM) error\n");    alarm(10);    if((n = read(STDIN_FILENO , line , MAXLINE)) < 0)            err_sys("read error");//并没有阻塞    printf("alarm return %d\n",alarm(0));    write(STDOUT_FILENO , line , n);    exit(0);}

说那么多,一个程序解释各个函数问题。
信号阻塞问题的综合应用:

void pr_mask(const char *str)//打印信号屏蔽字屏蔽的信号名{    //正常进程不会主动屏蔽信号.每一个信号没有处理函数都有默认的处理方法.    sigset_t sigset;    int errno_save;    errno_save = errno;    //如果&sigset非空,则返回原来信号屏蔽字,前两个参数用来设置信号屏蔽字    if(sigprocmask(SIG_BLOCK , NULL , &sigset) < 0)//&sigset非空,那么就是返回当前信号屏蔽集合于sigset.        err_ret("sigprocmask error\n");    else{        printf("%s" , str);        if(sigismember(&sigset , SIGINT))//测试屏蔽里面是否含有SIGINT屏蔽位.直接与测试即可.return 真或者假            printf("SIGINT");        if(sigismember(&sigset , SIGQUIT))//测试屏蔽里面是否含有SIGINT屏蔽位.直接与测试即可.return 真或者假            printf("SIGQUIT");        if(sigismember(&sigset , SIGUSR1))//测试屏蔽里面是否含有SIGINT屏蔽位.直接与测试即可.return 真或者假            printf("SIGUSR1");        if(sigismember(&sigset , SIGALRM))//测试屏蔽里面是否含有SIGINT屏蔽位.直接与测试即可.return 真或者假            printf("SIGALRM");        printf("\n");    }    errno = errno_save;//保存调用信号处理函数之前的errno状态,忽略这些函数调用对errno的影响,以便外面处理.}static void sig_quit(int signo){    printf("caught SIGQUIT\n");    if(signal(SIGQUIT , SIG_DFL) == SIG_ERR)//注册默认处理函数        err_sys("can't reset SIGQUIT");}int main(void){    sigset_t newmask , oldmask , pendmask;    if(signal(SIGQUIT , sig_quit) == SIG_ERR)        err_sys("signal(SIGQUIT) error\n");    sigemptyset(&newmask);//清零    sigaddset(&newmask , SIGQUIT);//通过或操作,将这个为置1表示屏蔽也叫做该进程阻塞SIGQUIT信号    pr_mask("before sigprocmask:");//打印进程默认屏蔽字    if(sigprocmask(SIG_BLOCK , &newmask , &oldmask) <0)//通过与默认屏蔽字或操作,将newmask对应位设置,并且返回原来屏蔽字        err_sys("SIG_BLOCK error");    pr_mask("after sigprocmask:");//打印设置之后的屏蔽字    sleep(15);//睡觉5s    if(sigpending(&pendmask) < 0)//被阻塞的信号成为系统未决的信号,此函数就是找出这些信号.        err_sys("sigpending error");    if(sigismember(&pendmask , SIGQUIT))        printf("\n SIGQUIT pending\n");    if(sigprocmask(SIG_SETMASK , &oldmask , NULL) <0)//还原默认屏蔽字        err_sys("SIG_SETMASK error");    printf("SIGQUIT unblocked\n");//没有屏蔽了,可以继续到来了    sleep(15);    exit(0);}

before sigprocmask: //表示系统默认不屏蔽任何信号。
after sigprocmask:SIGQUIT
^\^\^\^\^\

SIGQUIT pending //信号未决。
caught SIGQUIT //仅仅响应了一次。
SIGQUIT unblocked //取消阻塞了,且信号回复了默认功能。

开始阻塞SIGQUIT,当在sleep期间,信号到来,没有进入,然后睡觉结束通过sigpending函数可以得到SIGQUIT信号是处于未决的信号(就是此信号来了,但是进程没有进行任何处理)。取消阻塞以后,内核将仅仅会响应一次刚刚的SIGQUIT信号。

sigaction

extern int sigaction (int __sig, const struct sigaction *__restrict __act,              struct sigaction *__restrict __oact) __THROW;/*__sig:信号编号__act:非空,表示通过这个修改编号对应的动作__oact:非空,表示返回信号编号对应的上一个动作。oldaction*//* Structure describing the action to be taken when a signal arrives.  */struct sigaction  {    /* Signal handler.  */#ifdef __USE_POSIX199309    union      {    /* Used if SA_SIGINFO is not set.  */    __sighandler_t sa_handler;    /* Used if SA_SIGINFO is set.  */    void (*sa_sigaction) (int, siginfo_t *, void *);      }    __sigaction_handler;# define sa_handler __sigaction_handler.sa_handler# define sa_sigaction   __sigaction_handler.sa_sigaction#else    __sighandler_t sa_handler;#endif    /* Additional set of signals to be blocked.  */    __sigset_t sa_mask;//设定信号发生时候的信号屏蔽集,调用完毕取消这里设置的信号屏蔽集合。    /* Special flags.  */    int sa_flags;//设置对信号进行处理的各个选项。如SA_INTERRUPT信号到来了直接中断系统调用。    /* Restore handler.  */    void (*sa_restorer) (void);  };

代替不安全的signal,检查或者修改与指定信号相关联的额处理动作。函数也具有修改和返回的功能。
利用sigaction实现signal也就是可靠版本的signal:

//定义一个新的类型了//typedef   void    (*Sigfunc)(int);Sigfunc mysignal(int signo , Sigfunc func)//返回指向函数的指针,参数是int和只想函数的指针{    struct sigaction act , oact;//设置新动作,备份老动作    act.sa_handler = func;    sigemptyset(&act.sa_mask);//清空这个信号屏蔽集,    //不能确保=0,会做同样的事情.    act.sa_flags = 0;//    if(signo == SIGALRM){#ifdef SA_INTERRUPT        act.sa_flags |= SA_INTERRUPT;//被信号中断的系统调用不能自动重启.#endif//alarm信号不重启是因为希望可以定时IO操作,过时则终止IO操作,执行其他事情.    }    else{        act.sa_flags  |= SA_RESTART;//被信号中断的系统调用可以自动重启就是信号函数处理完毕以后继续执行刚刚可能一直阻塞的函数操作.    }    if(sigaction(signo , &act , &oact) < 0 )        return (SIG_ERR);    return (oact.sa_handler);//返回指向某个函数的指针.}//这里将许多信号都设置成可以重启,除了alarm信号。static void func(int signo){    printf("\nSIGINT occer!\n");}int main(void){    char c;    mysignal(SIGINT , func);    //signal(SIGINT , func);    read(0 , &c , 1);    printf("read:%c\n" , c);    exit(0);}

^C
SIGINT occer!
^C
SIGINT occer!
^C
SIGINT occer!
^C
SIGINT occer!
^C
SIGINT occer!
可以持续进程read,中断之后重启,继续read。

sigsetjmp siglongjmp

区别与setjmp,longjmp可以处理完毕恢复信号屏蔽字。
一个程序就可以明白这个功能是干什么用的。程序注释里面就解释很清楚了看程序可以明白一切。

static void sig_user1(int signo);static void sig_alrm(int signo);void pr_mask(const char *str);Sigfunc mysignal(int signo , Sigfunc func);static sigjmp_buf jmpbuf;static volatile sig_atomic_t canjmp;//原子变量,不会被中断.int main(void){    if(signal(SIGUSR1 , sig_user1) == SIG_ERR)//时刻记住检测返回值,饭后容易定位错误给我们自己看.这个必须清楚        err_sys("signal(SIGUSR1) error \n");    if(signal(SIGALRM , sig_alrm) == SIG_ERR)        err_sys("signal(SIGALRM) error \n");    pr_mask("starting main:");//这里信号屏蔽字肯定是0,因为默认不屏蔽任何信号.    //第一次直接调用返回0,若从siglongjmp调用返回,则是非0.跳转类似goto.    if(sigsetjmp(jmpbuf,1)){//savemask非0,那么通过env保存当前信号屏蔽字        pr_mask("ending main:");//从long中返回,信号屏蔽恢复以前,没有信号屏蔽.        exit(0);    }    canjmp = 1;//使能跳转功能,这是一种保护机制,使得初始化sigsetjmp再执行,siglongjmp    for( ; ; )        pause();//挂起坐等信号到来.}Sigfunc mysignal(int signo , Sigfunc func)//返回指向函数的指针,参数是int和只想函数的指针{    struct sigaction act , oact;//设置新动作,备份老动作    act.sa_handler = func;    sigemptyset(&act.sa_mask);//清空这个信号屏蔽集,    //不能确保=0,会做同样的事情.    act.sa_flags = 0;//    if(signo == SIGALRM){#ifdef SA_INTERRUPT        act.sa_flags |= SA_INTERRUPT;//被信号中断的系统调用不能自动重启.#endif//alarm信号不重启是因为希望可以定时IO操作,过时则终止IO操作,执行其他事情.    }    else{        act.sa_flags  |= SA_RESTART;//被信号中断的系统调用可以自动重启就是信号函数处理完毕以后继续执行刚刚可能一直阻塞的函数操作.    }    if(sigaction(signo , &act , &oact) < 0 )        return (SIG_ERR);    return (oact.sa_handler);//返回指向某个函数的指针.}void pr_mask(const char *str)//打印信号屏蔽字屏蔽的信号名{    //正常进程不会主动屏蔽信号.每一个信号没有处理函数都有默认的处理方法.    sigset_t sigset;    int errno_save;    errno_save = errno;    //如果&sigset非空,则返回原来信号屏蔽字,前两个参数用来设置信号屏蔽字    if(sigprocmask(SIG_BLOCK , NULL , &sigset) < 0)//&sigset非空,那么就是返回当前信号屏蔽集合于sigset.        err_ret("sigprocmask error\n");    else{        printf("\n%s" , str);        if(sigismember(&sigset , SIGINT))//测试屏蔽里面是否含有SIGINT屏蔽位.直接与测试即可.return 真或者假            printf("SIGINT ");        if(sigismember(&sigset , SIGQUIT))//测试屏蔽里面是否含有SIGINT屏蔽位.直接与测试即可.return 真或者假            printf("SIGQUIT ");        if(sigismember(&sigset , SIGUSR1))//测试屏蔽里面是否含有SIGINT屏蔽位.直接与测试即可.return 真或者假            printf("SIGUSR1 ");        if(sigismember(&sigset , SIGALRM))//测试屏蔽里面是否含有SIGINT屏蔽位.直接与测试即可.return 真或者假            printf("SIGALRM ");        printf("\n");    }    errno = errno_save;//保存调用信号处理函数之前的errno状态,忽略这些函数调用对errno的影响,以便外面处理.}static void sig_alrm(int signo){    pr_mask("in sig_arlm:");//SIGUSR1|SIGALRM,因为alrm信号处理程序还没有返回.}static void sig_user1(int signo){    time_t starttime;    if(canjmp == 0)//如果还没有完成sigsetjmp,那么返回.        return;    pr_mask("starting sig_usr1:");//这里信号屏蔽字肯定是SIGUSR1.因为这个时候正在usr1里面,要是正在执行来了,那么就等待下一次执行,现在暂时阻塞.    //系统默认当调用一个信号处理程序时候,捕捉到的信号加入进程当前信号屏蔽字,只有从信号处理程序返回,才恢复原来屏蔽字.也可以通过这里的例子恢复.    alarm(3);//定时3s,在这里触发alrm信号.    starttime = time(NULL);    for( ; ; ){        if(time(NULL) > starttime + 5)//等待5s,进入alrm之后返回            break;    }    pr_mask("finish sig_usr1:");//SIGUSR1,因为alarm已经返回    canjmp = 0;    siglongjmp(jmpbuf , 1);//跳转到main里面,并且将信号屏蔽字还原.    //跨越函数直接的跳转,动态控制作用.}

wangjun@ubuntu:~/DataStruct$ ./apueDebug/apue &
[1] 12626
starting main: //开始肯定没有阻塞任何信号
wangjun@ubuntu:~/DataStruct$ kill -USR1 12626
starting sig_usr1:SIGUSR1 //在此信号处理程序,因此暂时阻塞此信号
in sig_arlm:SIGUSR1 SIGALRM //第一个没有返回进入第二个信号处理,因此一起阻塞
finish sig_usr1:SIGUSR1//第二个返回,不阻塞
ending main: //直接跳转回来,恢复调用之前屏蔽字。

将上面的sigsetjmp和siglongjmp换成setjmp,longjmp,因为sig_usr1没有正常返回,所以最后结果会是如下:
wangjun@ubuntu:~/DataStruct$
starting main:
starting sig_usr1:SIGUSR1
in sig_arlm:SIGUSR1 SIGALRM
finish sig_usr1:SIGUSR1
ending main:SIGUSR1 //注意这个返回,因为处理程序不是正常返回且没有恢复所以信号屏蔽字暂时还在。利用这个就会导致以后不能接受这个信号了,很烦人,很烦人。

sigsuspend

sigsuspend用于在接收到某个信号之前,临时用mask替换进程的信号掩码,并暂停进程执行,直到收到信号为止。sigsuspend返回后将恢复调用之前的的信号掩码。信号处理函数完成后,进程将继续执行。该系统调用始终返回-1,并将errno设置为EINTR.

更改进程的信号屏蔽字可以阻塞所选择的信号,或解除对它们的阻塞。使用这种
技术可以保护不希望由信号中断的代码临界区。如果希望对一个信号解除阻塞,然后pause以等待以前被阻塞的信号发生,这样就可能发送解除阻塞和pause执行之前信号到来,然后引起pause一直等待以后的信号使得进程阻塞。这也是早期不可靠信号机制的另外一个问题。于是需要在原子操作中恢复信号屏蔽字。也就是恢复期间信号到来先进入缓冲,恢复完了再处理。
问题引出:希望对一个信号解除阻塞,然后pause等待以前被阻塞信号发生。
当我们的程序在进行一些业务处理的时候不想被一些信号所中断,我们就可以先屏蔽这些信号,在这个业务处理结束时我们可以使用sigsuspend函数处理在排队的信号(在这个过程中也可指定屏蔽的信号),处理完成后,在恢复之前的信号屏蔽,并处理下个业务处理。

static void sig_int(int signo){    pr_mask("\nin sig_int:");}int main(void){    sigset_t newmask , oldmask , waitmask;    pr_mask("program start:");    if(signal(SIGINT , sig_int) == SIG_ERR)        err_sys("signal(SIGINIT) error");    sigemptyset(&newmask);    sigaddset(&newmask , SIGINT);    sigemptyset(&waitmask);    sigaddset(&waitmask , SIGUSR1);    if(sigprocmask(SIG_BLOCK , &newmask , &oldmask) < 0)//屏蔽INT信号        err_sys("SIG_BLOCK err");    pr_mask("in critical region:");//在此处做一些不能被INT打断的事情    //sigsuspend在原子操作中恢复信号屏蔽字.    if(sigsuspend(&waitmask) != -1)//屏蔽usr1信号,并且在发生一个信号或者会终止该进程信号之前,进程一直被挂起.        err_sys("SIG_SETMASK error");    pr_mask("after return from sigsuspend:");    if(sigprocmask(SIG_SETMASK , &oldmask , NULL) < 0)//恢复INT信号        err_sys("SIG_SETMASK error");    pr_mask("program exit:");}

program start:
in critical region:SIGINT //开始屏蔽SIGINT
^C
in sig_int:SIGINT SIGUSR1 //在等待中收到INT信号到来,执行信号函数
after return from sigsuspend:SIGINT //sigsuspend函数返回恢复调用之前信号屏蔽字。
program exit: //恢复原来信号。
按 来关闭窗口…
这样做的目的就是为了防止pause使进程一直阻塞。因为假如在信号阻塞期间信号,等待信号恢复,会继续执行。

static void sig_int(int signo){    pr_mask("0\n");}int main(void){    sigset_t newmask , oldmask , waitmask;    int i;    if(signal(SIGINT , sig_int) == SIG_ERR)        err_sys("signal(SIGINIT) error");    sigemptyset(&newmask);    sigaddset(&newmask , SIGINT);    sigemptyset(&waitmask);    if(sigprocmask(SIG_BLOCK , &newmask , &oldmask) < 0)//屏蔽INT信号        err_sys("SIG_BLOCK err");    while(1){        for(i = 0 ; i < 5 ; i++ ){            write(1 , "# " , strlen("# "));            sleep(1);//假如在打印期间中断信号到来.        }        printf("\n");#if 1                if(sigprocmask(SIG_SETMASK , &oldmask , NULL) < 0)//屏蔽INT信号            err_sys("SIG_BLOCK err");        pause();//这里用有缺陷的做法,假如在屏蔽期间信号到来,那么在上一句恢复屏蔽字之后立马自动进入信号处理程序,然后pause,可能信号不再到来,那么程序将一直阻塞#else        if(sigsuspend(&waitmask) != -1)//等待信号到来,暂时用waitpid代替屏蔽字,所以屏蔽期间信号到来,执行到这一步一样可以执行并且返回。            err_sys("SIG_SETMASK error");//这里用的没有缺陷的做法,在屏蔽期间信号到来。#endif                }    exit(0);}

区别:
sigsuspend 未打印到5 个星时,中间 ctrl+c 中断,5个到之后会自动换行继续执行。
pause 则继续func函数后,不会继续往下执行。需再中断一次。

sigsuspend(sigset_t sigs);功能: 屏蔽新的信号,原来屏蔽的信号失效。sigsuspend是阻塞函数,对参数信号屏蔽,对参数没有指定的信号不屏蔽,但当没有屏蔽的信号处理函数调用完毕sigsuspend函数返回。
sigsuspend返回条件:
信号发生,并且信号是非屏蔽信号
信号必须要处理,而且处理函数返回后sigsuspend才返回。
sigsuspend设置新的屏蔽信号,保存旧的屏蔽信号,而且当sigsuspend返回的时候,恢复旧的屏蔽信号。
其实可以这样理解:sigsuspend=pause()+指定屏蔽的信号

等待一个信号处理设置一个全局变量,然后唤醒进程:

static void sig_int(int signo){    if(signo == SIGINT)        printf("\n interrupt \n");    else if(signo == SIGQUIT)        quitflag = 1;//等待信号到来,标志位置1}int main(void)//让进程等待信号,且仅仅因为退出信号才唤醒主进程{    sigset_t newmask , oldmask , waitmask;    if(signal(SIGINT , sig_int) == SIG_ERR)        err_sys("signal(SIGINIT) error");    if(signal(SIGQUIT , sig_int) == SIG_ERR)        err_sys("signal(SIGQUIT) error");    sigemptyset(&newmask);    sigaddset(&newmask , SIGINT);    sigemptyset(&waitmask);//    if(sigprocmask(SIG_BLOCK , &newmask , &oldmask) < 0)//屏蔽INT信号        err_sys("SIG_BLOCK err");    /*        写不给int信号打扰的关键性代码    */    while(quitflag == 0){//等待退出信号        if(sigsuspend(&waitmask) != -1)//等待信号到来            err_sys("SIG_SETMASK error");    }    printf("program continue\n");    quitflag = 0;//标志位清0 信号到来,继续执行下面的操作.    if(sigprocmask(SIG_SETMASK , &oldmask , NULL) < 0)//恢复INT信号        err_sys("SIG_SETMASK error");    exit(0);}

^C
interrupt
^C
interrupt
^C
interrupt
^C
interrupt
^\program continue
按 来关闭窗口…

原创粉丝点击