Bootstrap

C语言-信号

信号

一、信号是什么东西

信号是事件发生时通知进程的一种机制,有时也称之为软件中断。

信号的到来会打断了程序执行的正常流程。

大多数情况下,无法预测信号到达的精确时间。

一个(具有合适权限的)进程能够向另一进程发送信号。
信号的这一用法可作为一种同步技术,甚至是进程间通信( IPC )的原始形式。

进程也可以向自身发送信一号。

发往进程的诸多信号,通常都是源于内核,引发内核为进程产生信号的各类事件如下:
1、硬件发生异常,即硬件检测到一个错误条件并通知内核,随即再由内核发送相应信号给相关进程。
硬件异常的例子包括执行一条异常的机器语言指令,诸如,被 0 除,或者引用了无法访问的内存区域。

2、用户键入了能够产生信号的终端特殊字符。比如:中断字符,通常是 Ctrl+C 。

3、发生了软件事件。
例如,针对文件描述符的输出变为有效,调整了终端窗口大小,定时器到期,进程执行的 CPU 时间超限,
或者该进程的某个子进程退出。

查看信号命令:kill -l
在这里插入图片描述

1-31:标准信号
32-64:实时信号

标准信号的简要说明:

  1. SIGHUP
    本信号在用户终端连接(正常或非正常)结束时发出, 通常是在终端的控制进程结束时, 通知同一session内的各个作业, 这时它们与控制终端不再关联。登录Linux时,系统会分配给登录用户一个终端(Session)。在这个终端运行的所有程序,包括前台进程组和后台进程组,一般都属于这个Session。当用户退出Linux登录时,前台进程组和后台有对终端输出的进程将会收到SIGHUP信号。这个信号的默认操作为终止进程,因此前台进程组和后台有终端输出的进程就会中止。不过可以捕获这个信号,比如wget能捕获SIGHUP信号,并忽略它,这样就算退出了Linux登录,wget也能继续下载。
    此外,对于与终端脱离关系的守护进程,这个信号用于通知它重新读取配置文件。
  2. SIGINT
    程序终止(interrupt)信号, 在用户键入INTR字符(通常是Ctrl+C)时发出,用于通知前台进程组终止进程。
  3. SIGQUIT
    和SIGINT类似, 但由QUIT字符(通常是Ctrl-)让进程在因收到SIGQUIT退出时会产生core文件, 在这个意义上类似于一个程序错误信号。
  4. SIGILL
    执行了非法指令. 通常是因为可执行文件本身出现错误, 或者试图执行数据段. 堆栈溢出时也有可能产生这个信号。
  5. SIGTRAP
    由断点指令或其它trap指令产生. 由debugger使用。
  6. SIGABRT
    调用abort函数生成的信号。
  7. SIGBUS
    非法地址, 包括内存地址对齐(alignment)出错。比如访问一个四个字长的整数, 但其地址不是4的倍数。它与SIGSEGV的区别在于后者是由于对合法存储地址的非法访问触发的(如访问不属于自己存储空间或只读存储空间)。
  8. SIGFPE
    在发生致命的算术运算错误时发出. 不仅包括浮点运算错误, 还包括溢出及除数为0等其它所有的算术的错误。
  9. SIGKILL
    用来立即结束程序的运行. 本信号不能被阻塞、处理和忽略。如果管理员发现某个进程终止不了,可尝试发送这个信号。
  10. SIGUSR1
    留给用户使用
  11. SIGSEGV
    试图访问未分配给自己的内存, 或试图往没有写权限的内存地址写数据.
  12. SIGUSR2
    留给用户使用
  13. SIGPIPE
    管道破裂。这个信号通常在进程间通信产生,比如采用FIFO(管道)通信的两个进程,读管道没打开或者意外终止就往管道写,写进程会收到SIGPIPE信号。此外用Socket通信的两个进程,写进程在写Socket的时候,读进程已经终止。
  14. SIGALRM
    时钟定时信号, 计算的是实际的时间或时钟时间. alarm函数使用该信号.
  15. SIGTERM
    程序结束(terminate)信号, 与SIGKILL不同的是该信号可以被阻塞和处理。通常用来要求程序自己正常退出,shell命令kill缺省产生这个信号。如果进程终止不了,我们才会尝试SIGKILL。
  16. SIGCHLD
    子进程结束时, 父进程会收到这个信号。
    如果父进程没有处理这个信号,也没有等待(wait)子进程,子进程虽然终止,但是还会在内核进程表中占有表项,这时的子进程称为僵尸进程。这种情况我们应该避免(父进程或者忽略SIGCHILD信号,或者捕捉它,或者wait它派生的子进程,或者父进程先终止,这时子进程的终止自动由init进程来接管)。
  17. SIGCONT
    让一个停止(stopped)的进程继续执行. 本信号不能被阻塞. 可以用一个handler来让程序在由stopped状态变为继续执行时完成特定的工作. 例如, 重新显示提示符
  18. SIGSTOP
    停止(stopped)进程的执行. 注意它和terminate以及interrupt的区别:该进程还未结束, 只是暂停执行. 本信号不能被阻塞, 处理或忽略.
  19. SIGTSTP
    停止进程的运行, 但该信号可以被处理和忽略. 用户键入SUSP字符时(通常是Ctrl-Z)发出这个信号
  20. SIGTTIN
    当后台作业要从用户终端读数据时, 该作业中的所有进程会收到SIGTTIN信号. 缺省时这些进程会停止执行.
  21. SIGTTOU
    类似于SIGTTIN, 但在写终端(或修改终端模式)时收到.
  22. SIGURG
    有"紧急"数据或out-of-band数据到达socket时产生.
  23. SIGXCPU
    超过CPU时间资源限制. 这个限制可以由getrlimit/setrlimit来读取/改变。
  24. SIGXFSZ
    当进程企图扩大文件以至于超过文件大小资源限制。
  25. SIGVTALRM
    虚拟时钟信号. 类似于SIGALRM, 但是计算的是该进程占用的CPU时间.
  26. SIGPROF
    类似于SIGALRM/SIGVTALRM, 但包括该进程用的CPU时间以及系统调用的时间.
  27. SIGWINCH
    窗口大小改变时发出.
  28. SIGIO
    文件描述符准备就绪, 可以开始进行输入/输出操作.
  29. SIGPWR
    Power failure
  30. SIGSYS
    非法的系统调用。

二、标准信号相关函数

1、改变信号处置:signal()

#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int sig, sighandler_t handler);

handler 可以是一个函数指针,也可以指定为如下值:
SIG_DFL 将信号重置为默认值。
SIG_IGN 忽略信号。
返回值:这次改变之前的处理信号的行为。

用程序终止信号SIGINT来举例子:

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main() {
    void (*oldHandler)(int);
    oldHandler = signal(SIGINT, SIG_IGN);  //IGN忽略中断信号,则按ctrl+c不会罗软中断
    if (oldHandler == SIG_ERR) perror("signal");

    for (int i = 0; i < 10; i++) {
        sleep(1);
        printf("*");  //按ctrl+c不管用,中断不了,一直到10s后。
        fflush(stdout);
    }

 // oldHandler = signal(SIGINT, oldHandler);
    oldHandler = signal(SIGINT, SIG_DFL);
    if (oldHandler == SIG_ERR) perror("signal");

    for (int i = 0; i < 10; i++) {
        sleep(1);
        printf("*");  //ctrl+c管用了
        fflush(stdout);
    }

    return 0;
}

信号处理简介
   调用信号处理器程序,可能随时会打断主程序流程。
   内核代表进程来调用处理器程序,当处理器返回时,主程序会在处理器打断的位置恢复执行。
在这里插入图片描述

另外:信号会打断阻塞,
      所以当我们调用会阻塞的函数的时候,判断错误的时候,应该排除收到信号打断阻塞的情况。

#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>

int main(void) {
    int fd = -1;
    int ret = 0;
    fd = open("/dev/stdin", O_RDONLY);
    if (fd < 0) {
        perror("open stdin is fail");
        exit(-1);
    }
    char buf[1024] = {0};
    while(1){
        ret = read(fd, buf, 1024);  //会阻塞
        if (ret < 0) { 
            //出错,也有假错,比如这里遇到信号,所以这种可那个,健壮的程序,应该考虑到
            if(errno == EINTR){
                continue;
            }
            perror("read error");
            return -1;
        }
        printf("ret:%d-%s", ret, buf);
    }

    close(fd);

    return 0;
}

2、发送信号:kill()

一个进程可以向另一个进程发送信号(必须要有权限)。

#include <signal.h>
#include <sys/types.h>

int kill(pid_t pid, int sig);

成功返回0, 失败-1

pid > 0发送为指定进程
pid = 0发送给当前进程组里所有进程包括自己
pid < -1 发送给进程组id = pid的绝对值的进程组下的每个进程,效果等同killpg(pid_t pgrp, int sig);
pid = -1发送给所有进程(必须有权限)除了1号进程(初始化时)
sig = 0为空信号用来检测进程是否存在,不会用的,不太正确

进程是否存在,不正确,下面的如30号进程,杀死了,又创建的进程具有可能仍为30号,

int is_p_alive(pid_t pid) {
    int kill_rc = kill(pid, 0);  //发送0号信号,测试是否存活
    if (kill_rc == ESRCH) {      //进程不存在,不可靠,Unix/Linux系统经过一段时间会重复使用进程ID,
        return false;           //一个所给定进程ID存在并不一定是你想要找的进程,或许它是一个新的进程,
    }                        //你需要的进程早已经死亡消失在内存中!
    return true;
}

特权级进程可以向任何进程发送信号,非特权级必须满足如下图条件。
在这里插入图片描述

进程的实际用户ID:
      标识我们实际上是谁,是当前登录的用户ID.

进程的有效用户ID:
      决定的是文件的访问权.通常有效用户ID就是实际用户ID.

进程的设置-用户-ID,
      是一个特殊标志,当该标志设置时,执行该文件时的有效用户ID就是文件的所有者ID。
      比如,当一个文件的所有者是root当你以另一个用户登录时,如果没有设置-用户-ID,执行该文件时的有效ID和实际ID就是登录用户的ID,是无权限操作这个文件的。但如果设置了设置-用户-ID,实际用户ID是登录用户ID,而有效用户ID是root用户的ID。

看个例子就更清楚了:

//默认情况下
   zhonghao执行cat,系统创建一个cat进程,进程的属主属组取程序发起者,也就是zhonghao
cat进程访问/etc/shadow,由于进程属主属组是zhonghao,/etc/shadow的属组属主都不匹配,所以被拒绝访问.
 
给cat设置SUID之后
zhonghao执行cat.系统创建一个cat进程,进程的属主取cat的属主,属组取程序发起者,就是root
cat进程访问/etc/shadow,由于进程属主是root,/etc/shadow的属主匹配,所以被允许访问. 
which cat
/usr/bin/cat
chmod u+s /usr/bin/cat   //s是特殊权限,具有设置到用户ID 作用
su - zhonghao
cat /etc/shadow   //可以看了

3、向自己发送信号:raise()

#include <signal.h>
int raise(int sig);
//成功返回0,失败返回非0

//单线程程序相当于
kill(getpid(), sig);

//多线程相当于
pthread_kill(pthread_self(), sig);  //当前线程ID号,信号

4、向进程组发送信号killpg

#include <signal.h>

int killpg(pid_t pgrp, int sig);

//成功返回0,失败-1

//相当于kill函数中pid<-1的模式
kill(-pgrp, sig);

5、信号描述

  &nbsp   ;每个信号都有一段与之相对应的描述,可以用strsignal() 函数获取。

#include <string.h>
char *strsignal(int sig);  

//也可以直接打印到错误输出
#include <signal.h>
void psignal(int sig, const char *msg);

看例子:

#include <signal.h>
#include <stdio.h>
#include <string.h>

int main(){
    char *tmp = strsignal(SIGINT);  
    printf("%s\n", tmp);    //Interrupt,中断的意思
    
    psignal(SIGINT,"sigerr");  //sigerr:Interrupt
}

6、信号集

      许多信号相关的系统调用都需要能表示一组不同的信号。
      多个信号可使用一个称之为信号集的数据结构来表示,数据类型为 sigset_t。
sigemptyset() 初始化一个未包含任何成员的信号集。
sigfillset() 初始化一个信号集,包含所有信号。

#include <signal.h>

int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
//成功返回0,失败-1

      必须使用上述2个函数初始化信号集,因为C语言不会对自动变量进行初始化。

添加或移除单个信号

#include <signal.h>

int sigaddset(sigset_t *set, int sig);
int sigdelset(sigset_t *set, int sig);

//成功返回0,失败-1

测试信号 sig 是否为 set的成员

#include <signal.h>

int sigismember(const sigset_t *set, int sig);
//1表示是,0不是

7、信号掩码(阻塞信号传递)

      也可叫信号屏蔽。内核会为每个进程维护一个信号掩码,即一组信号,并将阻塞其针对该进程传递。
      在多线程环境中,每个线程都可以使用pthread_sigmask()函数来独立检查和修改其信号。

#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
//Returns 0 on success, or –1 on error

sigprocmask() 既可以修改信号掩码,又可获取现有的掩码,或两者兼具。

how:参数指定了该函数信号掩码带来的变化:
   SIG_BLOCK 将set 指向信号集并集到当前信号掩码中。
   SIG_UNBLOCK 将set指向的信号集中的信号从信号掩码中移除。
   SIG_SETMASK 将set指向的信号集设置为信号掩码。
   如果 oldset 参数不为NULL,则其指向sigset_t 结构缓冲区,用于返回之前的信号掩码。
如果只是想获取信号掩码, set 参数设为NULL即可,这时将忽略how 参数。

上面讲的屏蔽实质上是一种阻塞,如果解除了对某个等待信号的阻塞,会立刻将该信号传递给进程。

下面代码时暂时阻止 SIGINT 信号的传递。

sigset_t blockSet, prevMask;

sigemptyset(&blockSet);   //初始化信号集,初始为NULL
sigaddset(&blockSet, SIGINT);  //增加信号SIGINT

if (sigprocmask(SIG_BLOCK, &blockSet, &prevMask) == -1) //并集到prevMask集中
    perror("sigprocmask1");

if (sigprocmask(SIG_SETMASK, &prevMask, NULL) == -1)
    perror("sigprocmask2");

      SIGKILL 和 SIGSTOP 信号是不允许阻塞的,也就是下面的代码会阻塞除这2个信号以外的任何信号。

sigfillset(&blockSet);
if (sigprocmask(SIG_BLOCK, &blockSet, NULL) == -1)
    perror("sigprocmask");

8、获取等待中的信号集

      如果某进程接收了一个正在阻塞的信号,那么会将该信号添加到进程的等待信号集中。之后如果解除了对该信号的阻塞,就会把该信号传递给此进程(就算在阻塞期间发生了N次,解除时只会传递1次,而实时信号可以排队)。

sigpending() 系统调用返回进程处于等待状态的信号集,并存入set 指向的sigset_t 结构中。

#include <signal.h>
int sigpending(sigset_t *set);
//Returns 0 on success, or –1 on error

写一下例子:我阻塞SIGINT信号不让其进入当前进程中,再我们又按 ctrl+c看看SIGINT是否在当前等待信号集中,结果是肯定的。

#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>

int main(){
    //定义三个信号集的变量
    sigset_t blockset,prvmask,pendingset;

    //blockset初始化:空
    sigemptyset(&blockset);

    //屏弊SIGINT
    sigaddset(&blockset,SIGINT);   //给空集加SIGINT信号(linux中的ctrl+c软中断信号)
    if(sigprocmask(SIG_BLOCK,&blockset,&prvmask)==-1){   //SIG_BLOCK将blockset集并到掩码集中,即阻塞其进入进程中,最后保存到prvmask集中

        perror("sigprocmask出错,没有掩码了!");
    }
    int i=0;
     //下面代码5秒钟中打出五个数,由于上面屏蔽了SIGINT信号,导致在打出(5秒之内—)时,按ctrl+c不管用,中断不了
    for(i=0;i<5;i++){
        sleep(1);
        printf("i=%d    ",i);
        fflush(stdout);
    }

     //试图把等待着的信号集拿出来,判断一下SIGINT在不在里边(按一下ctrl+c,应在里面)
    if(sigpending(&pendingset)== -1){
        perror("sigpending!error!");
    }
    if(sigismember(&pendingset,SIGINT)){
        printf("SIGINT 在pendingset里面!\n");
    }else{
        printf("SIGINT 不在pendingset里面!\n");
    }
    
}

在这里插入图片描述

9、改变信号处置另一个函数:sigaction()

      除signal() 之外,sigaction() 系统调用是设置信号处置的另一选择。
用法复杂一点,但是功能强大,且可移植性强。

#include <signal.h>
int sigaction(int sig, const struct sigaction *act, struct sigaction *oldact);
//Returns 0 on success, or –1 on error

      sig为想要获取或改变的信号编号,除去 SIGKILL和SIGSTOP。
      act是指向信号新处置的数据结构,
      oldact用来返回之前的信号处置结构,都可以设置为 NULL。

struct sigaction {
    void (*sa_handler)(int);
    void (*sa_sigaction)(int, siginfo_t *, void *);
    sigset_t sa_mask;
    int sa_flags;
    void (*sa_restorer)(void);  //废弃
}

      sa_handler此参数和signal()的参数handler相同,代表新的信号处理函数。
sa_sigaction 则是另一个信号处理函数,它有三个参数,可以获得关于信号的更详细的信息。
      sa_mask 用来设置在处理该信号时暂时将sa_mask 指定的信号集里的信号屏蔽掉
      sa_flags 用来设置信号处理的其他相关操作,下列的数值可用:
    ◆ SA_RESTART:使被信号打断的系统调用自动重新发起。
    ◆ SA_NOCLDSTOP:使父进程在它的子进程暂停或继续运行时不会收到 SIGCHLD 信号。
    ◆ SA_NOCLDWAIT:使父进程在它的子进程退出时不会收到 SIGCHLD 信号,这时子进程如果退出也不会成为僵尸进程。
   ◆ SA_NODEFER:使对信号的屏蔽无效,即在信号处理函数执行期间仍能发出这个信号。
   ◆ SA_RESETHAND:信号处理之后重新设置为默认的处理方式。
    ◆ SA_SIGINFO:使用 sa_sigaction 成员而不是 sa_handler 作为信号处理函数。

看一个例子:

#include <signal.h>
#include <stdio.h>
#include <unistd.h>

void show_handler(int sig)
{
    printf("I got signal %d\n", sig);
    int i;
    for(i = 0; i < 5; i++) 
   {
        printf("i = %d\n", i);
        sleep(1);
    }
}
 
int main(void)
{
    int i = 0;
    struct sigaction act, oldact;
    act.sa_handler = show_handler;
    sigaddset(&act.sa_mask, SIGQUIT); 
    sigaddset(&act.sa_mask, SIGTERM); 
    act.sa_flags = SA_RESETHAND | SA_NODEFER;
 
    sigaction(SIGINT, &act, &oldact);
    while(1) 
   {
        sleep(1);
        printf("sleeping %d\n", i);
        i++;
    }
}

三、实时信号
实时信号意在弥补对标准信号的诸多限制。
其优势如下:
1、实时信号的信号范围有所扩大,可应用于自定义目的,
而标准信号中可提供随意使用的只有SIGUSR1和 SIGUSR2。
2、实时信号为队列化管理,同一信号发送多次将会多次传递给进程。
3、发送信号可以伴随数据(一整形或者指针值)
4、不同实时信号同时处于等待状态时,那么率先传递较小编号的信号。
如果排队的是同一类型的信号,那么信号的传递顺序会按照发送来的的顺序传给进程。

发送实时信号,sigqueue 函数
#include <signal.h>

int sigqueue(pid_t pid, int sig, const union sigval value);

union sigval {
int sival_int; /* Integer value for accompanying data */
void sival_ptr; / 很少用到 */
};

//成功返回0,失败-1
一旦触及对排队信号的数量限制(ulimit -i查看和修改),sigqueue()调用会失败,errno置为EAGAIN。
其他kill()、killpg()、raise()调用也能发送实时信号,但是排不排队由具体实现决定,linux下是排队的。

处理实时信号
可以像标准信号一样,使用常规(1个参数)信号处理器来处理实时信号。
也可以用带有3个参数的信号处理器函数来处理实时信号。
一旦采用了第二种方式,第二个参数是一个siginfo_t 结构。
对于一个实时信号而言,会在siginfo_t 结构中设置如下字段:
1.si_signo,其值与传递给信号处理器函数的第一个参数相同。
2.si_code,表示信号来源,由sigqueue()发送的实时信号来说,该值是SI_QUEUE,由用户用kill命令发送的信号,该值是SI_USER。
3.si_value,为进程于 sigqueue() 带过来的额外参数,sigval union。
4.si_pid 和 si_uid,分别为信号发送进程的进程id,实际用户id。

例子
main.c 用来接收信号。可带2个参数:程序休眠时间(为了让信号排队)、信号处理器函数处理间隔时间。
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>

//demo会用到的一些全局变量
static volatile int handlerSleepTime; //信号处理器的休眠间隔时间
static volatile int sigCnt; //信号处理次数的统计
static volatile int allDone; //信号队列里的信号是不是都处理完的标志

//处理信号的函数,一个参数,三个参数
static void siginfoHandler(int sig, siginfo_t *si,void *ucontext){
//如果来的信号是SIGINT SIGTERM,结束处理
if(sig == SIGINT || sig == SIGTERM){
allDone = 1;
return;
}

sigCnt++;
printf(“到达处理的信号是:%d\n”,sig);
printf(“si_signo:%d----si_code:%d (%s)-----si_pid:%d----si_uid:%d \n”, si->si_signo, si->si_code,
(si->si_code == SI_USER) ? “SI_USER” :
(si->si_code == SI_QUEUE) ? “SI_QUEUE” : “other”,si->si_pid,si->si_uid);

sleep(handlerSleepTime); //休眠是为了,一会我们体现标准信号会丢弃,实时队列管理,不会丢失
}

//执行这个函数的时候,传两个参数过来,第一个参数主程序休眠的间隔时间,信号处理器休眠间隔时间
int main(int argc, char *argv[]) {
//声明要用的局部变量
struct sigaction sa;
int sig;
sigset_t prevMask,blockMask;

printf(“要去接受处理信号的进程的id:%ld\n”,(long)getpid());

handlerSleepTime = atoi(argv[2]);

sa.sa_sigaction = siginfoHandler;
sa.sa_flags = SA_SIGINFO;
sigfillset(&sa.sa_mask);

sigfillset(&blockMask);
sigdelset(&blockMask,SIGINT);
sigdelset(&blockMask,SIGTERM);

//所有信息的处置方式都设置为sa
for(sig=1;sig<NSIG;sig++){
sigaction(sig,&sa,NULL);
}

//信号屏蔽
if(sigprocmask(SIG_SETMASK,&blockMask,&prevMask)==-1){
perror(“sigprocmask”);
}

printf(“main 开始休眠…\n”);
sleep(atoi(argv[1]));
printf(“main 休眠结束…\n”);

//接触信号屏蔽
if(sigprocmask(SIG_SETMASK,&prevMask,NULL)==-1){
perror(“sigprocmask”);
}

while(!allDone){
pause(); //等信号到达,直接alldone为1
}

printf(“main 程序终止!\n”);
return 0;
}

send.c 用来发送信号
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>

int main(int argc, char *argv[]) {
union sigval sv;

 if (sigqueue(atoi(argv[1]), atoi(argv[2]), sv) == -1)
        perror("sigqueue");

return 0;

}

使用掩码来等待信号:sigsuspend()
对信号编程时偶尔会遇到如下情况:
1.临时阻塞一个信号,以防止在处理关键代码时被此信号打断
2.关键代码执行完后,需要暂停执行,等到有信号到达,在放行

sissuspend() 系统调用将以mask 所指向的信号集来替换信号掩码,然后挂起进程的执行,直到其捕获到信号,
并从处理器函数返回,返回后,信号掩码恢复为调用前的值。
#include <signal.h>
int sigsuspend(const sigset_t *mask);
//(Normally) returns –1 with errno set to EINTR

例子:
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

#define MYRTSIG (SIGRTMIN+6)

//自定义的信号处置函数
static void mysig_handler(int s){
printf(" mysig_handler run! ");
fflush(stdout);
}

int main() {
int i=0,j=0;
sigset_t set,oset,saveset;

printf(“我的进程id:%ld\n”,(long)getpid());

signal(MYRTSIG,mysig_handler);
sigemptyset(&set);
sigaddset(&set,MYRTSIG);
sigprocmask(SIG_BLOCK,&set,&saveset);
for(i=0;i<1000;i++){
for(j=0;j<10;j++){
write(1,“*”,1);
sleep(1);
}
write(1,“\n”,1);
sigsuspend(&oset);
}
sigprocmask(SIG_SETMASK,&saveset,NULL);

return 0;
}

以同步方式等待信号
同步方式等待信号会相对简单易控一点。
#include <signal.h>
int sigwaitinfo(const sigset_t *set, siginfo_t *info);
//成功返回信号编号,失败-1
int sigtimedwait(const sigset_t *set, siginfo_t *info, const struct timespec *timeout);
//可以指定超时时间,如果调用超时而又没有收到信号返回-1
//并设置errno为EAGAIN
sigwaitinfo() 系统调用挂起进程的执行,直至set指向的信号集中的某一信号到达。
如果有信号处于等待状态,则立马返回。

info 如果不为NULL,则会指向经过初始化处理的 siginfo_t 结构。

sigwaitinfo() 接受信号的顺序和排队特性与信号处理器所捕获的信号相同。
标准信号不排队,实时信号按照低编号优先。
除了不需要编写信号处理器,它的速度也更快一点。

例子
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

// SIGQUIT 用
void handler(int s) { printf(“handler :%d\n”, s); }

int main(int argc, char** argv) {
printf(“PID:%ld\n”,(long)getpid());
signal(SIGQUIT, handler); // ctrl + , 此信号没BLOCK
siginfo_t info;
sigset_t mask;
// sigfillset(&mask);
sigaddset(&mask, SIGRTMIN + 1);
sigaddset(&mask, SIGRTMIN + 2);
sigprocmask(SIG_BLOCK, &mask, NULL); //设置屏蔽

int sig = -1;
while (1) {
//阻塞等待
sig = sigwaitinfo(&mask, &info);
if (sig < 0) {
if (errno == EINTR) {
// 上面 sigquit 将在这里中断
perror(“sigwaitinfo false”);
continue;
}
perror(“sigwaitinfo”);
break;
}
printf(“sig:%d , from pid:%d , code:%d , signo:%d\n”, sig, info.si_pid,
info.si_code, info.si_signo);

// ctrl+c 就结束了
if (SIGINT == sig) {
  break;
}

}
return 0;
}

;