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

写一下例子:
#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;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/649687.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

参数高效微调PEFT(一)快速入门BitFit、Prompt Tuning、Prefix Tuning

参数高效微调PEFT(一)快速入门BitFit、Prompt Tuning、Prefix Tuning 目前&#xff0c;模型最全的网站是HuggingFace&#xff0c;但是国内需要魔法流量才能访问。另外&#xff0c;现在大模型权重文件都较大&#xff0c;也会浪费不少流量&#xff0c;因此这里推荐使用魔搭社区下…

如何画泳道图?

一、绘制泳道图 1、新建一个绘图&#xff0c; 工具箱搜索“泳道图” 2、修改泳道图标题及风格 3、绘制基本的流程图 4、导出Visio格式 选择文件导出&#xff0c;visio格式

实时工业数据采集分析平台:推动工厂智能化的关键

在当今的工业领域&#xff0c;随着科技的飞速发展和竞争的日益激烈&#xff0c;实现工厂的智能化已成为企业追求持续发展的关键目标。而实时工业数据采集分析平台作为推动工厂智能化的重要力量&#xff0c;正发挥着重要的作用。 实时工业数据采集分析平台能够全方位、高精度地…

switch-case语句——kettle开发17

switch case组件的主要三大部分包括switch判断的字段&#xff0c;类似于前面说到的表达式&#xff0c;然后选择case值的数据类型&#xff0c;建议使用整形&#xff0c;因为这样运行速度相对更高&#xff0c;然后就是case 值分组标志&#xff0c;即case值0时输出一个结果&#x…

JWT-登录后下发令牌

后端 写一个jwt工具类&#xff0c;处理令牌的生成和校验&#xff0c;如&#xff1a; 响应数据样例&#xff1a; 前端要做的&#xff1a;

C++模版初阶STL简介

目录 1.泛型编程 2.函数模版 2.1概念 2.2格式 2.3原理 2.4函数模版的实例化 2.5模版参数的匹配原则 3.类模板 3.1类模板的定义格式 3.2类模板的实例化 1.泛型编程 如何实现一个通用的交换函数呢&#xff1f; void Swap(int& left, int& right) {int temp left;l…

乡村振兴的乡村人才引进与培养:引进和培养乡村人才,激发乡村发展活力,为乡村振兴提供人才保障

目录 一、引言 二、乡村人才引进与培养的重要性 &#xff08;一&#xff09;人才是乡村振兴的核心动力 &#xff08;二&#xff09;人才是乡村文化传承的载体 &#xff08;三&#xff09;人才是乡村社会治理的基石 三、乡村人才引进与培养的现状 &#xff08;一&#xf…

C字符串和内存函数介绍(二)——长度不固定的字符串函数

前面我们一起学习了strlen&#xff0c;strcpy&#xff0c;strcmp&#xff0c;strcat的使用以及它们的模拟实现&#xff0c;它们的特点是你传参的时候&#xff0c;传过去的是数组首元素的地址&#xff0c;然后无论是计算长度&#xff0c;实现拷贝&#xff0c;相互比较还是进行追…

VR法治教育展厅互动体验突破了地域限制

VR全景互动展厅搭建编辑器以其卓越的特点、强大的功能及实际应用中的显著优势&#xff0c;成为企业级VR应用的得力助手。这款软件不仅能够制作逼真的虚拟场景&#xff0c;更能让用户沉浸其中&#xff0c;体验前所未有的真实感。 它拥有高度逼真的视觉效果&#xff0c;采用先进的…

基于微信小程序+ JAVA后端实现的【微信小程序跑腿平台】设计与实现 (内附设计LW + PPT+ 源码+ 演示视频 下载)

项目名称 项目名称&#xff1a; 《微信小程序跑腿平台的设计与实现》 项目技术栈 该项目采用了以下核心技术栈&#xff1a; 后端框架/库&#xff1a; Java, SSM框架数据库&#xff1a; MySQL前端技术&#xff1a; 微信小程序, HTML…&#xff08;其它相关技术&#xff09; …

obsidian Excalidraw 更换字体 最新版 手写字体

背景 Excalidraw 是 obsidian 中最厉害的插件之一&#xff0c;长期霸占插件排行榜第一。以其强悍的性能和灵活的可塑性受到大家的喜爱&#xff0c;可默认的字体对中文并不友好&#xff0c;网上大多数教程要不是过时了&#xff0c;要不是错的&#xff0c;还有就是太复杂&#x…

SpringBoot+layuimini实现角色权限菜单增删改查(layui扩展组件 dtree)

角色菜单 相关组件方法效果图MySQL代码实现资源菜单树组件实现权限树方法js这里我先主要实现权限树的整体实现方法&#xff0c;如果是直接查看使用的话可以只看这里&#xff01; 后端代码Controlle层代码Service代码及实现类代码Service代码ServiceImpl代码 resourceMapper 代码…

C# --- 浮点数类型 float, double, decimal

C# --- 浮点数类型 float, double, decimal float, double, decimaldecimal float, double, decimal decimal double 和 float 的采用base 2, 不能精确的表示浮点数, 进行加减乘除的操作的时候会出现精度丢失的问题decimal 采用base 10&#xff0c;可以精确的表示浮点数&#x…

智慧树下做游戏

游戏开发工程师致力于游戏总体设计 &#xff0c;负责游戏开发工具和运营维护工具的设计与开发 &#xff0c;并配合主程序完成游戏架构及各大功能的设计、开发、调试和其他技术支持 就业方向&#xff1a; 一般有客户端游戏开发和服务器游戏开发 客户端开发&#xff1a; 主要负…

Linux 中的进程优先级管理

在 Linux 系统中&#xff0c;理解和管理进程优先级是维护系统性能的关键因素。本文将详细介绍进程优先级&#xff08;priority&#xff09;的基本概念、如何查看和调整进程优先级&#xff0c;以及 nice 值对优先级的影响。 基本概念 在多任务操作系统中&#xff0c;CPU 资源的…

MySQL事务篇2:InnoDB引擎

InnoDB是MySQL的默认存储引擎&#xff0c;支持ACID事务、行级锁定和外键约束&#xff0c;通过多版本并发控制&#xff08;MVCC&#xff09;实现高并发性能。InnoDB使用聚簇索引存储数据&#xff0c;具备崩溃恢复能力&#xff0c;确保数据一致性和完整性。其主要特性包括数据和索…

今日选题。

诱导读者点开文章的9引真经&#xff08;一&#xff09; 标题重要么&#xff1f;新媒体、博客文通常在手机上阅读。首先所有的内容不同于纸媒&#xff0c;手机只展现标题&#xff0c;而内容都是折叠。其次读者能像看内容一样看4、5条或者7、8条标题&#xff08;区别于不同的主流…

2024年流行效果插件,助你打造非凡设计!

设计图片太普通了&#xff1f;加班挑细节&#xff1f;你不能达到你想要的效果吗&#xff1f;作为一名设计师&#xff0c;你总是无法逃脱这样的噩梦&#xff01;如何改变工作中的类似困境&#xff1f;除了提高自我设计技能外&#xff0c;选择一些辅助效果插件“插件”也非常重要…

CSS 【实战】 “四合院”布局

效果预览 页面要求&#xff1a; 上下固定高度左右固定宽度中间区域自适应宽高整个页面内容撑满全屏&#xff0c;没有滚动条 技术要点 使用 html5 语义化标签 header 网页内的标题区域nav 导航区域aside 侧边栏footer 页脚区域section 内容分区article 文章区域 清除浏览器默…