Linux 信号 signal,sigaction,sigqueue,kill,相关函数

驱动与应用的结合,参考我的这篇:https://blog.csdn.net/rjszcb/article/details/113573517

一、 什么是信号
信号是软中断,用于通知进程某个事件已经发生。进程可以选择如何响应信号:忽略、默认处理、自定义处理等。
常见信号有:SIGINT(键盘中断)、SIGKILL(强制终止)、SIGSTOP(暂停进程)、SIGCONT(继续运行进程)等。

信号是很短的信息,可以被发送到一个进程或者一组进程。发送给进程唯一信息通常是一个数,以此来标识信号。在标准信号中,对参数、消息或者其他的相随信息没有给予关注。

首先我们来了解一下Linux下的信号机制。
Linux信号机制基本上是从Unix系统中继承过来的。早期Unix系统中的信号机制比较简单和原始,信号值小于SIGRTMIN的信号都是不可靠信号。这就是"不可靠信号"的来源,它的主要问题是信号可能丢失。随着时间的发展,实践证明了有必要对信号的原始机制加以改进和扩充。由于原来定义的信号已有许多应用,不好再做改动,最终只好又新增加了一些信号,并在一开始就把它们定义为可靠信号,这些信号支持排队,不会丢失。我们可以使用 kill -l 命令查看当前系统支持的信号,需要注意的是不同的系统支持的信号是不一样的:
在这里插入图片描述
#include <singal.h>
在这里插入图片描述
在这里插入图片描述
信号的工作流程:
产生信号:通过 kill 命令向进程发送信号,或按 Ctrl+C 键盘中断。
信号递送:内核向目标进程递送信号。
信号捕捉:进程通过 signal() 函数捕捉信号,注册相应的信号处理函数。
信号处理:当信号到达进程时,如果该信号已被捕捉,则执行对应的信号处理函数。否则执行缺省处理动作。
信号返回:信号处理函数返回后,进程将继续执行被中断的代码

模版

#include <singal.h>void signalHandler(int signum) {std::cout << "Received signal: " << signum << std::endl;
}int main() {signal(SIGINT, signalHandler);  // 注册信号处理函数while (1) {// 程序执行主循环}return 0;
}

2、在项目开发时,有很多进程,可以通过这种方式,发送信号,退出进程,

static void TOP_HandleSig(HI_S32 signo)
{signal(SIGINT, SIG_IGN);signal(SIGTERM, SIG_IGN);if (SIGINT == signo || SIGTERM == signo){g_bQuit = HI_TRUE;printf("\033[0;31 TOP_HandleSig!\033[0;39m\n");}
}

退出回收线程的例子

HI_VOID SAMPLE_HIFB_HandleSig(HI_S32 signo)
{static int sig_handled = 0;signal(SIGINT, SIG_IGN);signal(SIGTERM, SIG_IGN);if (!sig_handled && (SIGINT == signo || SIGTERM == signo)){sig_handled = 1;gs_cExitFlag = 'q';if (g_stHifbThread1){pthread_join(g_stHifbThread1, 0);}
}

二、无参信号和带参信号

信号处理的两种方案:无参信号和带参信号
信号是IPC技术其中的一种,IPC的目的是因为进程本身不能实现数据的交互(共享数据),所以通过IPC来进行数据的传递
一个进程执行完某一个过程后向另外一个进程发送信号,使得另外的一个进程中断当前的所有的逻辑去执行信号的操作
相当于一个进程(父进程)可以去操控另一个进程(子进程)【通过一个信号达到一个间接的操控】
可以通过父进程通过发送不同的信号,去执行不同的事情,达到这样一种间接的控制

							**无参                 有参**

信号绑定 signal sigaction
信号发送 kill sigqueue

kill发送信号
在这里插入图片描述
kill -9 xxx:发送第九个信号给某个进程,第九个信号具备的功能是让某个进程停止
进程可以通过调用kill向包括它本身在内的另一个进程发送信号,如果程序没有发送该信号的权限,对kill的调用就将失败
kill函数的作用就是把参数sig给定的信号发送给标识号为pid的进程
要想发送一个信号,发送者进程必须拥有相应的权限,这通常意味着两个进程必须拥有同样的用户ID

参数:
pid:可能选择有以下四种

  1. pid大于零时,pid是信号欲送往的进程的标识。
  2. pid等于零时,信号将送往所有与调用kill()的那个进程属同一个使用组的进程。
  3. pid等于-1时,信号将送往所有调用进程有权给其发送信号的进程,除了进程1(init)。
  4. pid小于-1时,信号将送往以-pid为组标识的进程。

sig:准备发送的信号代码,假如其值为零则没有任何信号送出,但是系统会执行错误检查,通常会利用sig值为零来检验某个进程是否仍在执行。

返回值说明: 成功执行时,返回0。失败返回-1,errno被设为以下的某个值 EINVAL:指定的信号码无效(参数 sig 不合法(INVAL:invalid)) EPERM;权限不够无法传送信号给指定进程 (PERM:permission权限) ESRCH:参数 pid 所指定的进程或进程组不存在(SRCH:search)

kill函数的常用信号,kill函数支持发送多种不同的信号,每种信号对应一个整数编号。下面是一些常用的信号及其用途:
SIGTERM(15): 默认信号,告诉进程终止运行。
SIGKILL(9): 强制终止进程,不可被捕获或忽略。
SIGSTOP(17): 暂停进程的执行。
SIGCONT(19): 恢复进程的执行。

1  #include  < sys / wait.h > 2  #include  < sys / types.h > 3  #include  < stdio.h > 4  #include  < stdlib.h > 5  #include  < signal.h > 6  7  int  main(  void  )8  {9      pid_t childpid;
10       int  status;
11       int  retval;
12      
13      childpid  =  fork();
14       if  (  - 1   ==  childpid )
15      {
16          perror(  " fork() "  );
17          exit( EXIT_FAILURE );
18      }
19       else   if  (  0   ==  childpid )
20      {
21          puts(  " In child process "  );
22          sleep(  100  ); // 让子进程睡眠,看看父进程的行为 
23          exit(EXIT_SUCCESS);
24      }
25       else 
26      {
27           if  (  0   ==  (waitpid( childpid,  & status, WNOHANG )))
28          {
29              retval  =  kill( childpid,SIGKILL );
30              
31               if  ( retval )
32              {
33                  puts(  " kill failed. "  );
34                  perror(  " kill "  );
35                  waitpid( childpid,  & status,  0  );
36              }
37               else 
38              {
39                  printf(  " %d killed\n " , childpid );
40              }
41              
42          }
43      }
44      
45      exit(EXIT_SUCCESS);
46  }

47 // -----------------
48 [root@localhost src]# gcc killer.c
49 [root@localhost src]# . / a. out
50 In child process
51 4545 killed
在确信fork调用成功后,子进程睡眠100秒,然后退出。
同时父进程在子进程上调用waitpid函数,但使用了WNOHANG选项,所以调用waitpid后立即返回。父进程接着杀死子进程,如果kill执行失败,返回-1,否这返回0。如果kill执行失败,父进程第二次调用waitpid,保证他在子进程退出后再停止执行。否则父进程显示一条成功消息后退出。

signal信号绑定函数
对于signal信号绑定函数 无法实现数据传递

#include<iostream>
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>using namespace std;//信号处理函数
void signal_function(int num)
{cout << "signal_function 被触发 pid = " << getpid() << "num = "<<num<< endl;//if (num == 10)//{//	//num  10  做一件事情//}//else if (num == 12)//{//	//num  12  做另外一件事情//}//switch (num)//{//case 10:  //num  10  做一件事情//	break;//case 12:  //num  12  做另外一件事情//	break;//default://	break;//}}int main()
{pid_t pid = 0;//信号绑定signal(SIGUSR1, signal_function);//num--10signal(SIGUSR2, signal_function);//num--12pid = fork();if (pid > 0){sleep(5);for (int i = 0; i < 3; i++){//父进程给子进程发送信号kill(pid, SIGUSR1);//sleep(1);}while (1){}}else if (pid == 0){while (1){cout << "子进程 pid = " << getpid() << endl;sleep(1);}}return 0;
}

信号接收函数signal原型
Linux下可以用signal()信号安装的函数, 其中signal()函数的原型如下:

#include <singal.h>
void (*signal(int sig, void (*func)(int)))(int);//函数指针,函数的成员也是一个函数指针

void (*func)(int) : func 是函数指针,指向的参数是 int,返回 void。
signal(int sig, void (func)(int)) : signal() 函数接受两个参数,第一个参数是信号值 sig,第二个参数是函数指针 func。
void ()(int) : signal() 函数的返回值也是一个函数指针,返回 void。

所以,理解起来比较直观的原型是:如果把上面这个函数声明分解成两个部分就好理解了:

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

第一个参数指定信号的值,
第二个参数指定针对前面信号值的处理,可以忽略该信号(参数设为SIG_IGN);可以采用系统默认方式处理信号(参数设为SIG_DFL);也可以自己实现处理方式(参数指定一个函数地址)。

举个例子,我们可以这么调用:

void handler(int sig) { ... }sign_handler_t  old_handler;old_handler = signal(SIGINT, handler);

定义了信号处理函数 handler(),是将来要回调的函数,signal() 将 SIGINT 信号和 handler 函数关联
old_handler 保存 signal() 返回的旧信号处理函数指针,这样我们就可以在 handler 函数中调用 old_handler,实现链式处理

代码测试signal

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>int g_sigstop = 0;void signal_stop(int signum)
{if(SIGTERM == signum){printf("SIGTERM signal detected\n");}else if(SIGALRM == signum){printf("SIGALRM signal detected\n");g_sigstop = 1;}
}void signal_code(int signum)
{if(SIGINT == signum){printf("SIGINT(CTRL+C) signal\n");}else if(SIGSEGV == signum){printf("SIGSEGV signal detected\n");exit(-1);}
}int main(int argc, char *argv[])
{char              *ptr = NULL;signal(SIGTERM, signal_stop);//kill命令终止signal(SIGALRM, signal_stop);//alarm()signal(SIGSEGV, signal_code);//指针非法操作内存问题signal(SIGINT, signal_code);//指针非法操作内存问题printf("Program start running for 20 seconds...\n");alarm(20);while(!g_sigstop){;}printf("Program start stop running...\n");printf("Invalid pointer operator will raise SIGSEGV signal\n");/*这是非法地使用了指针,报错段错误,于是会触发SIGSEGV信号,就会打印相关的调用函数*/*ptr = 'h';return 0;
}

中间运行的二十秒可以使用CTRL+C来验证SIGINT信号。我们打开新的终端执行killall signal命令可以看到SIGTERM执行结果:
在这里插入图片描述

2、sigaction()信号函数

该函数功能是为信号指定相关的处理程序,但是它在执行信号处理程序时,会把当前信号加入到进程的信号屏蔽字中,从而防止在进行信号处理期间信号丢失。

函数原型

#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

该函数的第二三参数类型是一个结构体,act表示指定新的信号处理方式,oldact参数输出先前信号的处理方式(如果不为NULL的话)。
说白了sigaction函数的作用和之前学过的signal函数的作用一样,全都是针对特定的信号进行信号捕捉,从而进行自定义式的信号处理。

sigaction()和signal()函数的区别:
1.signal只能捕获信号,对信号进行处理。但是不能获取信号的其它信息;
2.sigaction可以使用sigaction结构体的sa_handler函数对信号进行处理(此处等同于signal函数),也可以使用sa_sigactior函数查看信号的各种详细信息;
3.并且sigaction函数还可以通过sa_mask、sa_flags对信号处理时进行很多其他操作。

sigaction结构体
在这里插入图片描述
sa_handler: 类型是函数指针,该成员和signal的参数handler相同,代表捕获普通信号并对齐做处理的函数;
sa_sigaction:该成员与sa_handler也一样是函数指针,但它用于对实时信号的捕获;
sa_mask: 用来设置在处理该信号时暂时将sa_mask 指定的信号集搁置;
sa_flags: 用来设置信号处理的其他相关操作,下列的数值可用:

1.SA RESETHAND:当调用信号处理函数时,将信号的处理函数重置为缺省值;
2.SIG DFLSA RESTART:如果信号中断了进程的某个系统调用,则系统自动启动该系统调用;
3.SA NODEFER:当信号处理函数运行时,内核将阻塞该给定信号。但是如果设置了 SA NODEFER标记, 那么在该信号处理函数运行时,内核将不会阻塞该信号。
sa_flags默认情况下赋值0即可。

代码测试
结合上面的代码进行 sigaction() 信号安装函数的测试:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>int g_sigstop = 0;void signal_stop(int signum)
{if(SIGTERM == signum){printf("SIGTERM signal detected\n");}else if(SIGALRM == signum){printf("SIGALRM signal detected\n");g_sigstop = 1;}
}void signal_code(int signum)
{if(SIGBUS == signum){printf("SIGBUS signal detected\n");}else if(SIGILL == signum){printf("SIGILL signal detected\n");}else if(SIGSEGV == signum){printf("SIGSEGV signal detected\n");}exit(-1);
}void signal_user(int signum)
{if(SIGUSR1 == signum){printf("SIGUSR1 signal detected\n");}else if(SIGUSR2 == signum){printf("SIGUSR2 signal detected\n");}g_sigstop = 1;
}int main(int argc, char *argv[])
{char *ptr = NULL;struct sigaction  sigact,sigign;signal(SIGTERM, signal_stop);//kill命令终止signal(SIGALRM, signal_stop);//alarm()signal(SIGSEGV, signal_code);//指针非法操作内存问题/*用户自定义信号,收到之后执行signal_user函数*/sigemptyset(&sigact.sa_mask);sigact.sa_flags = 0;sigact.sa_handler = signal_user;/*如果是SIGINT(ctrl+z)信号就忽略*/sigemptyset(&sigign.sa_mask);sigign.sa_flags = 0;sigign.sa_handler = SIG_IGN;sigaction(SIGINT, &sigign, 0);sigaction(SIGUSR1, &sigact, 0);sigaction(SIGUSR2, &sigact, 0);printf("Program start running for 20 seconds...\n");alarm(20);while(!g_sigstop){;}printf("Program start stop running...\n");printf("Invalid pointer operator will raise SIGSEGV signal\n");*ptr = 'h';return 0;
}

sigaction函数使用案例1: 对该进程发送指定的信号
在这里插入图片描述
在这里插入图片描述

专门设置的捕获2号信号SIGINT, 查询上表,2号是这个,用ctrl+c键向该进程发送2号信号,被sigaction函数捕获,因为sigaction函数的第二参数为新的处理方式newact代替了旧的处理方式oldact,即对象newact的处理方式为自定义方式,调用该对象的成员handler方法。

每当我发送2号信号,总能被sigaction函数捕获;而当我发送其他信号(例如3号信号)给该进程时,进程收到对3号信号做递达处理,采用默认的递达动作,立即终止进程。

案例2:对该进程发送多个同类型的信号时:
在这里插入图片描述
代码解析: 当2号信号被进程接收并递达时,因为sigcation函数采用的是自定义方式处理,所以调用handler方法,里面有Count函数,Count函数的作用就是一个倒数10秒的定时器,当2号信号被捕获处理时,10秒后可以完成对信号的处理。相比情况1,代码上只增加了这处。

运行结果:
在这里插入图片描述
从上面结果右图可知,我向该进程连续发送5次2号信号后,当发送的第一个2号信号被sigcation函数捕获后,它会告知系统让系统将2号信号加入到进程的信号屏蔽字(阻塞位图sa_mask成员),也就是将该进程阻塞位图的第2比特位置1,让第二次及后面向进程发送的2号信号无法递达该进程。当进程递达(自定义)处理完第一个送来的2号信号后,系统又会解除对2号信号的屏蔽,让第二次发送到进程的2号信号能够被进程收到,递达时被捕获,此时,系统又会对阻塞位图的第2比特位进行屏蔽,直到第二次送来的2号信号被进程递达完毕,才会解除对2号信号的屏蔽。向进程发送来的5次二号信号中,只有前两次被发送来的2号信号被进程递达处理,剩下的3次都被进程丢弃。原因就是sigcation函数只能对多次发送的同类型信号的前两次进行处理,剩下的均被丢弃。这样不会重复执行。

案例3:对该进程发送不同类型的多个信号时,sigaction函数对其他信号的屏蔽:
在这里插入图片描述
代码解析:这段代码上,我将想要屏蔽的信号的 3,4,5存入,当第一个信号2号被发送且被进程接收处理时,OS会将阻塞位图中的2号的3,4,5号信号都屏蔽掉。
在这里插入图片描述
运行解析:对进程连续发送3次2号信号以及两次3号信号,第一个2号信号被进程递达捕获,此时系统将阻塞信号位图中第2、3、4、5位置的比特位置1,那么后面的2、3、4、5号信号都无法被进程递达。等到第一个2号信号递达完毕,系统解除了对2、3、4、5号的阻塞,第2个2号信号被进程捕获处理,同理,当2号信号被处理完毕后,系统又取消了对2、3、4、5号信号的屏蔽,因为同类型的多次2号信号中只有前两次能够被处理,第3个2号信号被丢弃,然后紧接着被发送来的第一个3号信号被进程处理,因为sigaction函数没有设置3号信号的捕获,所以系统对进程做默认递达处理,立马被处理,退出进程。

总结:

1.当我们对某个进程连续发送多个同类型的信号时,进程处理信号的原则是: 串行处理(一个一个处理)同类型的信号,不允许递归处理。所以在上面情况中,5次发送同类型的2号信号,只有前两次的能被处理,而且还是第一次的处理完,第二次的2号信号才能接着被处理,这就是串行处理。
2.当进程下递达某一个信号期间,同举型信号无法递达。因为同类型的信号已经被系统加入到了进程的信号屏蔽字一一block。
3.当该信号被递达完毕,系统会解除对同类型信号的屏蔽,进程就会自动进程递达当前的已取消屏蔽的信号。例:当第一个2号信号被进程涕达完毕,系统解除了同举型2号信号的屏蔽,那么第一个2号信号(之前被屏藏、现在肥消屏蔽)会自动被进程递法注:进程只会自动递达这么一个,不会继续自动递达第3次同类型信号,表明了5次中只有前2次能够被处理,剩余的都被丢弃。

三:带参信号
带参信号的绑定:sigaction
带参信号的发送:sigqueue

sigaction信号绑定
包含头文件<signal.h>
功能:sigaction函数用于改变进程接收到特定信号后的行为
原型:

int  sigaction(int signum,const struct sigaction *act,const struct sigaction *old);

参数:
该函数的第一个参数为信号的值,可以为除sigkill及sigstop外的任何一 个特定有效的信号(为这两个信号定义自己的处理函数,将导致信号安装错误)
第二个参数是指向结构sigaction的一个实例的指针,在结构 sigaction的实例中,指定了对特定信号的处理,可以为空,进程会以缺省方式对信号处理
第三个参数oldact指向的对象用来保存原来对相应信号 的处理,可指定oldact为null

返回值:函数成功返回0,失败返回-1
参数:
第一个参数signum :信号ID
第三个参数oldact放空NULL
第二个参数是指向sigaction的结构体指针
在这里插入图片描述
从中看出结构体中属性1 2 5函数 都是函数指针

属性1类似之前学习的 void signal_function(int num):没有带参数传递的函数指针【无参信号,不能数据传递】
sigaction的操作有两种,一种是带参,一种是不带参,
不带参的写法和void signal_function(int num)差不多,
因此可以看出
图中的结构体中有一个函数指针 void (*sa_handler)(int);就是专门处理不带参的信号

从这个结构体中主要还是需要学习一下带参,也就是
属性2:void (*sa_sigaction)(int, siginfo_t *, void *);【有参信号,可以实现数据传递】
结构体中按道理来说不可以包含函数,但是可以包含函数指针 ,因为函数指针最终还是可以识别为指针变量
sigaction兼容了signal写法,从上图中的结构体的属性中不难看出,sigaction属性1是不带参信号写法,属性2是带参信号的写法

结构体属性中的flag就是一个参数说明,说明当前是带参还是不带参

(因为sigaction结构体属性包含两种 无参和带参)

sigqueue发送信号
新的发送信号系统调用,主要是针对实时信号提出的支持信号带有参数,与函数sigaction()配合使用

原型:

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

参数
第一个参数是指定接收信号的进程id,
第二个参数确定即将发送的信号,
第三个参数是一个联合数据结构union sigval,指定了信号传递的参数,即通常所说的4字节值

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

其中的联合体原型
在这里插入图片描述
有一个int和一个void类型的数据,数据传递可以是一个简单的int数据,也可以是无类型指针(这个无类型指针可以指向任何数据,但是没有void的函数数据处理,信号使用的人很少就没有特意开发这个函数处理,但是这个void是保留下来的,如果以后的版本更新之后就会有了;但目前ubuntu20.04的版本这个void还不可使用)

#include<iostream>
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>using namespace std;//参数void*是预留的,具体目前ubuntu20.04还没有开发出来
void sigaction_function(int num, siginfo_t* pinfo, void* pvo)
{int res = pinfo->si_int;cout << "sigaction_function 接收到的数据 res = " << res << endl;
}int main()
{pid_t pid = 0;//定义结构体struct sigaction act;act.sa_sigaction = sigaction_function;//给结构体属性中的函数指针赋值act.sa_flags = SA_SIGINFO;//表示设置为带参的信号//带参的信号绑定//参数1 信号ID 参数2 结构体指针 参数3 NULLsigaction(SIGUSR1, &act, NULL);pid = fork();if (pid > 0){sleep(5);//准备value union联合体类似结构体union sigval value;value.sival_int = 123456;//sigqueue发送信号sigqueue(pid, SIGUSR1, value);while (1){}}else if (pid == 0){while (1){cout << "子进程 pid = " << getpid() << endl;sleep(1);}}return 0;
}

在这里插入图片描述

利用sigaction也可以写不带参的信号,如下写法
在这里插入图片描述

不带参信号和带参信号可以对比

无参信号:signal绑定信号 kill发送信号 不可数据传递
带参信号:sigaction绑定信号 sigqueue发送信号 可以数据传递(少量数据)

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C语言程序设计-练习篇

华夏波澜壮阔&#xff0c;少年仍需前行。 十&#xff0c;实现一个函数&#xff0c;打印乘法口诀表&#xff0c;口诀表的行数和列数自己指定 #include <stdio.h> //实现一个函数&#xff0c;打印乘法口诀表&#xff0c;口诀表的行数和列数自己指定 void print_table(int …

启动docker镜像

1、运行容器 2、当前运行的进程 3、当前位置和启动时间 4、cat/etc/redhat-release查看版本 5.镜像是模版&#xff0c;容器是实例 6.容器中没有命令运 7.容器总是能轻易获取 8.配置yum 9.安装http 10.修改index⽂件 11.httpd -k start 12.访问 13.退出就没有服务了 14…

Redis配置及idea部分操作

配置Redis远程访问 修改访问IP地址 #跳转到redis安装目录 cd /usr/local/redis-6.2.1 #修改redis.conf配置文件 vi redis.conf #注释redis.conf第69行的配置项 #bind 127.0.0.1 设置登录密码 找到下面这一行并去除注释&#xff0c;并添加密…

【微服务】SpringCloud Alibaba 10-14章

10 SpringCloud Alibaba入门简介 10.1 是什么 诞生 2018.10.31&#xff0c;Spring Cloud Alibaba 正式入驻了 Spring Cloud 官方孵化器&#xff0c;并在 Maven 中央库发布了第一个版本。 Spring Cloud Alibaba 介绍 10.2 能干嘛 https://github.com/alibaba/spring-cloud-al…

git常用操作合集

1 撤销 1.1 适用场景 如果在git上提交了commit&#xff0c;但是当前提交的代码有问题&#xff0c;需要回退到上个版本 1.2 操作命令 1、git log 查看历史提交记录及对应的commit id 找到需要回退的commit id 2、执行git reset回退到之前的状态 git reset --hard <commi…

基于Springboot和BS架构的宠物健康咨询系统pf

TOC springboot509基于Springboot和BS架构的宠物健康咨询系统pf 第一章 课题背景及研究内容 1.1 课题背景 信息数据从传统到当代&#xff0c;是一直在变革当中&#xff0c;突如其来的互联网让传统的信息管理看到了革命性的曙光&#xff0c;因为传统信息管理从时效性&#x…

day37动态规划+三.Github链接本地仓库

一.动态规划 474.一和零 给你一个二进制字符串数组 strs 和两个整数 m 和 n 。 请你找出并返回 strs 的最大子集的长度&#xff0c;该子集中 最多 有 m 个 0 和 n 个 1 。 如果 x 的所有元素也是 y 的元素&#xff0c;集合 x 是集合 y 的 子集 。 思路:这道题更像是另一种的0-…

C++ 设计模式——外观模式

外观模式 C 设计模式——外观模式主要组成部分1. 外观类&#xff08;Facade&#xff09;2. 子系统类&#xff08;Subsystem&#xff09;3. 客户端&#xff08;Client&#xff09; 例一&#xff1a;工作流程示例1. 外观类&#xff08;Facade&#xff09;2. 子系统类&#xff08;…

假期作业--数据结构

1、顺序表实现学生管理系统&#xff08;参照顺序表技能&#xff09;写出菜单界面switch选择&#xff0c;功能1创建顺序表&#xff08;堆区&#xff09;&#xff0c;2录入学生信息&#xff0c;3插入一个学生信息&#xff0c;4删除一个学生信息&#xff0c;5按照位置修改一个学生…

Android.bp和Android.mk文件有的区别

文章目录 1. 构建系统2. 语法和格式3. 可维护性和扩展性4. 编译效率5. 未来趋势 在Android的构建系统中&#xff0c; Android.mk和 android.bp是用于定义如何编译项目文件的两种文件类型&#xff0c;它们有一些显著的区别。 1. 构建系统 Android.mk&#xff1a;使用于基于GN…

探索Facebook的区块链计划:未来社交网络的变革

随着区块链技术的迅速发展&#xff0c;社交网络领域正面临一场深刻的变革。Facebook&#xff0c;作为全球最大且最具影响力的社交平台之一&#xff0c;正在积极探索区块链技术的应用。本文将深入探讨Facebook的区块链计划&#xff0c;分析其潜在的变革性影响&#xff0c;并展望…

一款好看的WordPress REST API 主题

介绍&#xff1a; 主题特色&#xff1a; 使用Nuxtjs WordPress Rest Api 实现前后端分离&#xff0c;可完成多端部署&#xff1b; 主题支持自动切换黑夜模式。 使用说明&#xff1a; service 目录为wordpress主题文件&#xff0c;需要拷贝到wordpress主题目录下&#xff0…

【本社翻译】Unity官方XR开发电子书

上个月&#xff08;2024年7月&#xff09;&#xff0c;Unity 官方发布了一本聚焦 XR 开发的电子书&#xff0c;书名为《Create Virtual and Mixed Reality Experiences in Unity》。本书系统介绍了以 XR Interaction Toolkit 为代表的一系列 Unity XR 开发工具集&#xff0c;深…

java多线程(七)AQS(AbstractQueuedSynchronizer)技术解析:以赛跑起跑场景为例

AQS概括 核心思想 AQS&#xff08;AbstractQueuedSynchronizer&#xff09;是Java并发包中的一个核心同步器框架&#xff0c;它定义了一套多线程访问共享资源的同步机制。 其核心思想是&#xff1a;利用一个volatile的int类型的变量state来表示同步状态&#xff0c;并通过一…

ansible:

ansible&#xff1a; 远程自动化运维 ansible是基于python开发的配置管理和应用部署工具。 也是自动化运维的重要工具。 可以批量配置&#xff0c;部署&#xff0c;管理上千台主机。 只需要在一台主机配置ansible就可以完成其他主机的操作。 操纵模式&#xff1a; 1、模…

黑神话:悟空-配置推荐

显卡推荐&#xff08;按类别整理&#xff09; 1. GTX 10系列、GTX 16系列&#xff1a; 如果希望体验光线追踪&#xff0c;建议根据预算升级到RTX 40系列显卡。对于1080p分辨率&#xff0c;至少需要RTX 4060才能流畅运行。 2. RTX 20系列&#xff1a; RTX 2060、RTX 2070&#…

第二十七节、人物可互动标识

一、多个场景同时存在 方法&#xff1a;将另一个场景拖拽进 当前场景中 这样在一个场景中保存物体&#xff0c;另一个场景切换即可 创建一个场景名为上图&#xff08;这是一个持久化的场景&#xff09; 被激活的场景是粗体字的 二、代码 使用第二个代码获得player的子物体 …

SQL注入(cookie、base64、dnslog外带、搜索型注入)

目录 COOKIE注入 BASE64注入 DNSLOG注入—注入判断 什么是泛解析&#xff1f; UNC路径 网上邻居 LOAD_FILE函数 搜索型注入—注入判断 本文所使用的sql注入靶场为sqli-labs-master&#xff0c;靶场资源文件已上传&#xff0c;如有需要请前往主页或以下链接下载 信安必备…

AVI-Talking——能通过语音生成很自然的 3D 说话面孔

概述 论文地址&#xff1a;https://arxiv.org/pdf/2402.16124v1.pdf 逼真的人脸三维动画在娱乐业中至关重要&#xff0c;包括数字人物动画、电影视觉配音和虚拟化身的创建。以往的研究曾试图建立动态头部姿势与音频节奏之间的关联模型&#xff0c;或使用情感标签或视频剪辑作…

SpringBoot Web请求、响应

一、文章概述 请求方面主要讲&#xff0c;当前端向后端发出请求时&#xff0c;对于不同类型的参数后端都如何接收&#xff1b;对于响应&#xff0c;文章会讲解后端如何向前端响应数据以及如何使返回的数据具有统一的格式。 二、请求 2.1接收简单参数 Controller方法&#xf…