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()

c 复制代码
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int sig, sighandler_t handler);

handler 可以是一个函数指针,也可以指定为如下值:

SIG_DFL 将信号重置为默认值。

SIG_IGN 忽略信号。

返回值:这次改变之前的处理信号的行为。

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

c 复制代码
#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;
}

信号处理简介

调用信号处理器程序,可能随时会打断主程序流程。

内核代表进程来调用处理器程序,当处理器返回时,主程序会在处理器打断的位置恢复执行。

另外:信号会打断阻塞,

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

c 复制代码
#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()

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

c 复制代码
#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号,

bash 复制代码
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。

看个例子就更清楚了:

c 复制代码
//默认情况下
   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()

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

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

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

4、向进程组发送信号killpg

c 复制代码
#include <signal.h>

int killpg(pid_t pgrp, int sig);

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

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

5、信号描述

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

c 复制代码
#include <string.h>
char *strsignal(int sig);  

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

看例子:

c 复制代码
#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() 初始化一个信号集,包含所有信号。

c 复制代码
#include <signal.h>

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

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

添加或移除单个信号

c 复制代码
#include <signal.h>

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

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

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

c 复制代码
#include <signal.h>

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

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

也可叫信号屏蔽。内核会为每个进程维护一个信号掩码,即一组信号,并将阻塞其针对该进程传递。

在多线程环境中,每个线程都可以使用pthread_sigmask()函数来独立检查和修改其信号。

c 复制代码
#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 信号的传递。

c 复制代码
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个信号以外的任何信号。

c 复制代码
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

写一下例子:

#include <stdio.h>

#include <string.h>

#include <signal.h>

#include <unistd.h>

int main()

{

sigset_t blockSet, prevMask,pendingset;

sigemptyset(&blockSet);
sigaddset(&blockSet, SIGINT);

if (sigprocmask(SIG_BLOCK, &blockSet, &prevMask) == -1)
    perror("sigprocmask1");

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

for(int i=0;i<5;i++){
    sleep(1);
    printf("i:%d\n",i);
}

if (sigpending(&pendingset)==-1){
    perror("sigpending");
}

if(sigismember(&pendingset,SIGINT)){
    printf("SIGINT exist  in pendingset! \n");
}else{
    printf("SIGINT not exist  in 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;

}

相关推荐
A懿轩A8 分钟前
C/C++ 数据结构与算法【数组】 数组详细解析【日常学习,考研必备】带图+详细代码
c语言·数据结构·c++·学习·考研·算法·数组
半盏茶香13 分钟前
在21世纪的我用C语言探寻世界本质 ——编译和链接(编译环境和运行环境)
c语言·开发语言·c++·算法
字节高级特工1 小时前
【C++】深入剖析默认成员函数3:拷贝构造函数
c语言·c++
计算机学长大白2 小时前
C中设计不允许继承的类的实现方法是什么?
c语言·开发语言
XH华8 小时前
初识C语言之二维数组(下)
c语言·算法
Uu_05kkq11 小时前
【C语言1】C语言常见概念(总结复习篇)——库函数、ASCII码、转义字符
c语言·数据结构·算法
嵌入式科普13 小时前
十一、从0开始卷出一个新项目之瑞萨RA6M5串口DTC接收不定长
c语言·stm32·cubeide·e2studio·ra6m5·dma接收不定长
A懿轩A13 小时前
C/C++ 数据结构与算法【栈和队列】 栈+队列详细解析【日常学习,考研必备】带图+详细代码
c语言·数据结构·c++·学习·考研·算法·栈和队列
1 9 J14 小时前
数据结构 C/C++(实验五:图)
c语言·数据结构·c++·学习·算法
仍然探索未知中16 小时前
C语言经典100例
c语言