掌握 Linux 信号机制的技巧与策略

news2024/9/21 2:32:55

在这里插入图片描述

目录

  • 一.信号的产生
      • 1.信号的产生(预备)
      • 2.异常
        • (1).硬件异常
        • (2).core dump
        • (3).软件条件产生信号
    • 二.信号的保存
      • 1.信号的发送
      • 2.block.pending.handler(保存)
        • (1).sigset_t类型
      • 三.信号的捕捉处理
      • 1.什么时候捕捉
      • 2.三顾进程地址空间
      • 3.如何处理信号
      • 4.附加知识
      • 5.volatile
      • 6.可重入函数

一.信号的产生

1.信号的产生(预备)

在了解信号产生之前我们先行观察一个现象:
在这里插入图片描述
我们先编写一个循环打印的代码,接着运行起来:
在这里插入图片描述
他就会像这样每隔一秒在屏幕上打印一行信息,这时候如果我们想要这个进程别打印了给我停止退出,我们常会用到ctrl+c操作:
在这里插入图片描述
这样这个进程就如我所愿退出了。
我们再来看一个现象:
在这里插入图片描述

我们在运行程序时,在指令后面加上&,这次跑起来的程序就无法被ctrl+c退出了。
其实啊:Linux中,一次登录中,一个终端,一般会配上一个bash,每一个登陆,只允许一个进程是前台进程(谁来获取键盘输入,谁就是前台进程),可以运行多个进程是后台进程。
在正常运行程序时其可以被ctrl+c退出的,因为其是前台进程,当我们带上&将其在后台运行,这时允许你在启动一个程序的同时继续使用终端会话进行其他操作
那我们又想知道了,为什么ctrl+c能够杀掉我们的前台进程呢:本质其实是ctrl+c被进程解释为了收到了信号。(2号信号
我们可以用kill -l指令查看信号:
在这里插入图片描述
共有62个信号,其中1到31称为普通信号,34到64称为实时信号
接下来我们来学习一个信号捕捉的函数:
在这里插入图片描述

  • signum代表的用户想要捕捉几号信号
  • handler是个函数指针,代表捕捉到的信号要执行的方法(用户自定义的)

下面我们写一个演示代码捕捉二号信号并且执行我们自定义的方法:

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <cstdio>
#include <cstdlib>
using namespace std;

void myhandler(int signo)
{
    cout << "process get a signal: " << signo <<" pid:"<<getpid()<<endl;
}


int main()
{
    
    signal(2,myhandler); 
   
    while(true)
    {
       cout << "I am a  process : " << getpid() << endl;
        sleep(1);
    }

    return 0 ; 
}

需要注意的是signal函数只需要设置一次即可,代表进程再收到2号信号时执行myhandler方法
运行起来后:
在这里插入图片描述
我们可以看到ctrl+c并不能再让进程退出了,而是执行了我们自定义的方法:打印收到的信号和当前的pid。
那又想到了,我们是否可以把所有普通信号都捕捉了呢?届时又会发生什么呢:

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <cstdio>
#include <cstdlib>
using namespace std;

void myhandler(int signo)
{
    cout << "process get a signal: " << signo <<" pid:"<<getpid()<<endl;
}


int main()
{
    
    //signal(2,myhandler); 

    for(int i = 1;i<=31;i++)
    {
        signal(i,myhandler);
    }
    while(true)
    {
       cout << "I am a  process : " << getpid() << endl;
        sleep(1);
    }

    return 0 ; 
}

在这里插入图片描述
可以看到给这个进程再发送信号时就会执行用户自定义的方法了:那么难道说真的所有的信号都能被signal捕捉并让用户设定自定义的方法吗?
在这里插入图片描述
在这里插入图片描述
其实不是的9号和19号信号无法被捕捉的,这是OS为了安全设置(不能什么信号都能让用户乱搞的)
继续学习几个函数:
在这里插入图片描述
参数表示对pid进程发送sig号信号

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <cstdio>
#include <cstdlib>
using namespace std;

int main()
{
     int cnt =0;
    while(true)
    {
       cout << "I am a  process : " << getpid() << endl;
       sleep(1);
       cnt++;

       if(cnt==5)
       {
           kill(getpid(),2);
          
       }
    } 
    return 0;
} 

在这里插入图片描述
如我们所料在打印五次后,kill函数向当前进程发送二号信号终止了进程。
在这里插入图片描述
raise函数即是向自己的进程发送sig信号:

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <cstdio>
#include <cstdlib>
using namespace std;

int main()
{
     int cnt =0;
    while(true)
    {
       cout << "I am a  process : " << getpid() << endl;
       sleep(1);
       cnt++;

       if(cnt==5)
       {
           //kill(getpid(),2);
           raise(2);
       }
    } 
    return 0;
} 

和kill(getpid(),2)的运行效果一样。
在这里插入图片描述
abort 函数是 C 标准库中的一个函数,用于异常终止程序。调用 abort 会导致程序立即终止,且不执行任何清理操作。这里就不演示了
根据以上我们需要知道:无论信号如何产生,一定是OS发送给进程的,因为OS是进程的管理者。

2.异常

(1).硬件异常

这个我们先写一个除零错误,程序运行起来:

#include <iostream>
using namespace std;

 int main()
{

    int a =1;
    a/=0;
    return 0 ; 
}

在这里插入图片描述
出现八号信号错误SIGFPE(浮点异常)

#include <iostream>

using namespace std;

 int main()
{

    int *p =nullptr;
    *p=20;
    return 0 ; 
} 

再编写一个野指针错误,运行程序:
在这里插入图片描述
出现11号信号段错误SIGSEGV,是我们最常见的错误。
OS之所以向我们进程发送错误信号(抛异常),是为了我们完成后续总结工作,并不是让我们解决的。
那硬件层面上的异常是如何产生的呢:
在这里插入图片描述
当OS根据cpu上的状态寄存器的溢出标志位,检测出代码出现异常时 溢出标志位0变1,就会向进程发送异常信号。
在这里插入图片描述
野指针问题即是MMU与cpu分析虚拟地址转换成物理地址发生错误
上面我们要知道被进程包裹的异常只会影响自己不会波及OS,任何异常都只会影响进程本身。

(2).core dump

在这里插入图片描述
在进程等待中我们讲过子进程退出码的8到15位代表着退出状态,0到7位是终止信号,第八位是core dump标志,他标识着收到的终止信号是否为core(核心转储)。

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>

using namespace std;

int main()
{
      pid_t id = fork();
      if(id == 0)
      {
          int cnt = 500;
          while(cnt)
          {
              cout << "i am a child process, pid: " << getpid() << " cnt: " << cnt << endl;
              sleep(1);
              cnt--;
          }

          exit(0);
      }

      int status = 0;
      pid_t rid = waitpid(id, &status, 0);
      if(rid == id)
      {
          cout << "child quit info, rid: " << rid << " exit code: " << ((status>>8)&0xFF) << " exit signal: " << (status&0x7F) <<" core dump: " << ((status>>7)&1) << endl;   
      }
}


以上是一个演示代码:

在这里插入图片描述
这样我们能观察到:
在这里插入图片描述
产生了core.pid形式的临时文件,当我们打开系统的core dump功能后,一旦进程出现异常,OS会将进程再内存中的运行信息,转储到当前目录下(磁盘),成为核心转储。通常,这个文件会包含程序的内存、寄存器状态和栈信息,帮助开发人员了解程序崩溃时的状态。
在这里插入图片描述
可以利用其进行事后调试

在Linux系统中,SIGTERM是请求程序正常终止的信号,而CORE是程序崩溃时生成的转储文件。这两者都是处理和调试程序的重要工具,但它们的作用和用途是不同的。
在这里插入图片描述

(3).软件条件产生信号

在这里插入图片描述在 Linux 下,alarm 函数是一个用于设定定时器的系统调用,通常用于在指定的时间后发送 SIGALRM 信号(14号信号)。这个函数可以帮助我们在程序中实现定时操作或超时机制,但在使用时需要注意以下几点:

  • seconds 是设定定时器的时间(秒)
  • 返回以前设置的定时器时间(如果有的话),或者 0(如果之前没有设置定时器)

一个简单的使用代码:

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>

using namespace std;

void settime(int signum)
{
    cout<<"signum is :"<<signum<<" pid is: "<<getpid()<<endl;
	alarm(2);
}

int main()
{
	signal(SIGALRM,settime);
	alarm(2);
	while(1)
	{
        sleep(1);
        cout<<"my pid is :"<<getpid()<<endl;
	}
	return 0;
}

在这里插入图片描述

二.信号的保存

1.信号的发送

对于普通信号而言,对于进程而言,收到哪一个信号自己有还是没有,是给进程的PCB发。

1.比特位的内容是0还是1表明是否收到
2.比特位的位置(第几个),表示信号的编号
3.所谓的“发信号”本质就是OS去修改task struct的信号位图对应的比特位。

  • 普通信号:有限、固定优先级、可能丢失、处理方式预定义。
  • 实时信号:数量更多、支持优先级和顺序、信号队列、可以携带附加数据。

2.block.pending.handler(保存)

首先先了解些信号的概念:

实际执行信号的处理动作称为信号递达 信号从产生到递达之间的状态,称为信号未决。 进程可以选择阻塞 (Block )某个信号。
被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作.
注意,阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作

信号在内核中的表示图
在这里插入图片描述

  • block表也是位图 比特位代表是否屏蔽(阻塞)
  • pending位图表收到哪个信号。即那些已经发送给进程但尚未处理的信号
  • handler表存放在接收到信号时执行的函数
(1).sigset_t类型

sigset_t 是一种信号集类型 ,方便对三个表进行操作。
在这里插入图片描述
上图为其常用的函数:

#include <signal.h>
int sigemptyset(sigset_t *set);

sigemptyset 是一个用于操作 sigset_t 类型的函数,它用于初始化一个信号集合为空集

#include <signal.h>
int sigaddset(sigset_t *set, int signum);

sigaddset 将指定的信号 signum 添加到信号集合 set 中。如果该信号已经在集合中,则集合保持不变。

#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

sigprocmask 是一个用于操作进程信号掩码的函数。信号掩码用于指定当前被阻塞的信号集。通过操纵信号掩码,可以控制哪些信号被阻塞,哪些信号可以被传递到进程。

  • how:用于指定如何修改信号掩码,取值可以是以下三种:
    SIG_BLOCK:将 set 中的信号添加到当前的信号掩码中,即阻塞这些信号。
    SIG_UNBLOCK:从当前的信号掩码中移除 set 中的信号,即解除对这些信号的阻塞。
    SIG_SETMASK:将当前的信号掩码设置为 set 中的信号集,即完全替换当前的信号掩码
  • set:指向一个 sigset_t 类型的信号集合,表示要修改的信号集。可以为 NULL,表示不修改当前的信号掩码
  • oldset:指向一个 sigset_t 类型的变量,用于存储调用函数前的旧信号掩码。如果不需要保存旧信号掩码,可以设置为 NULL。
#include <signal.h>
int sigpending(sigset_t *set);

sigpending 将当前被阻塞但尚未处理的信号存储到 set 中。这样可以检查哪些信号已经被阻塞,并且尚未处理。即pending表里面的内容
下面我们用一段代码演示下,信号的保存:

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

using namespace std;

void PrintPending(sigset_t &pending)
{
    for (int signo = 31; signo >= 1; signo--)
    {
        if (sigismember(&pending, signo))
        {
            cout << "1";
        }
        else
        {
            cout << "0";
        }
    }
    cout << "\n\n";
}

int main()
{


    先对2号信号进行屏蔽 --- 数据预备
    sigset_t bset, oset;   //在哪里开辟的空间???用户栈上的,属于用户区

    sigemptyset(&bset);
    sigemptyset(&oset);
    
    sigaddset(&bset, 2);       //1.2 调用系统调用,将数据设置进内核
    sigprocmask(SIG_SETMASK, &bset, &oset);   //我们已经把2好信号屏蔽了吗?ok

    
    sigset_t pending;
    int cnt = 0;
    while (true)
    {
       
        int n = sigpending(&pending);
        if (n < 0)
            continue;
        // 打印pending表
        PrintPending(pending);

        sleep(1);
        cnt++;
        //2解除阻塞
        if(cnt == 10)
        {
            cout << "unblock 2 signo" <<" pid: "<<getpid()<< endl;
            sigprocmask(SIG_SETMASK, &oset, nullptr); 
        }
    }

    return 0;
}

在这里插入图片描述
结果与我们预想的一想,刚开是的pending表没有被阻塞但尚未处理的信号,当我们ctrl+c发送二号信号后,因为事先二号信号已经被添加到block表(即被阻塞了),所以这是pending表上第二位比特位置1,即收到2号信号但是被阻塞尚未处理的信号。五秒后再次通过sigprocmask函数接触对2号信号的阻塞。这时将处理2号信号即终止进程。
还有一点需要明确的在之前我们了解到signal捕捉不了9号和19号信号因为OS考虑到安全问题,所以这里一样也不能阻塞9号和19号信号
还有一点需要注意的:如果一个信号没有被阻塞,我们多次给进程发送这个信号时,进程在处理此信号时会暂时将此信号的block置1即阻塞

三.信号的捕捉处理

1.什么时候捕捉

当我们进程从内核态允许访问OS的代码和数据返回到用户态只允许访问用户自己的代码和数据)的时候,进行信号的检测和处理,如果有信号,就进行处理
在这里插入图片描述

2.三顾进程地址空间

在这里插入图片描述

用户页表有几个进程,就有几份用户级页表,而内核页表只有1份每一个进程看到的3~4GB的东西都是一样的
进程视角:我们调用系统中的方法,就是在我自己的地址空间中进行执行的。
操作系统的本质:基于时钟中断的一个死循环!计算机硬件中,有一个时钟芯片,每个很短的时间,向计算机发送时钟中断。

3.如何处理信号

#include <signal.h>

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

sigaction 是一个用于设置信号处理程序的函数,相较于早期的 signal 函数,它提供了更细粒度的控制,并避免了一些 signal 函数中的缺陷。在 Linux 和其他 Unix 系统中,sigaction 被广泛使用来替代 signal

  • signum:要捕捉或处理的信号编号。
  • act:一个指向 struct sigaction 结构的指针,用于指定新的信号处理行为。如果这个指针是 NULL,则不会修改当前的信号处理行为。
  • oldact:一个指向 struct sigaction 结构的指针,用于存储之前的信号处理行为。如果这个指针是 NULL,则不保存之前的信号处理行为

struct sigaction结构

struct sigaction {
    void (*sa_handler)(int);     // 信号处理函数或以下三个宏之一: SIG_DFL, SIG_IGN, SIG_ERR
    void (*sa_sigaction)(int, siginfo_t *, void *); // 使用 SA_SIGINFO 标志时的信号处理函数
    sigset_t sa_mask;            // 在处理该信号时需要阻塞的信号集
    int sa_flags;                // 修改信号行为的标志
    void (*sa_restorer)(void);   // 已废弃,不应使用
};

目前我们只需要了解sa_handlersa_flags即可。
sa_handler指向处理方法的函数,sa_flags信号处理程序执行期间需要被阻塞的信号集,可添加多个信号。
下面我们来用段代码演示功能:

#include <iostream>
#include <cstring>
#include <ctime>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>

using namespace std;

 void PrintPending()
 {
     sigset_t set;
     sigpending(&set);

     for (int signo = 31; signo>= 1; signo--)
     {
         if (sigismember(&set, signo))
             cout << "1";
         else
             cout << "0";
     }
     cout << "\n";
 }

 void handler(int signo)
 {
     cout << "catch a signal, signal number : " << signo << endl;
     while (true)
     {
         PrintPending();
         sleep(1);
     }
 }

 int main()
 {
      struct sigaction act, oact;
      memset(&act, 0, sizeof(act));
      memset(&oact, 0, sizeof(oact))
      act.sa_handler = handler; 
      sigaction(2, &act, &oact);

      while (true)
      {
          cout << "I am a process: " << getpid() << endl;
          sleep(1);
      }

     return 0;
 }

在这里插入图片描述
在这里插入图片描述
由上图我们可以总结:

  1. pending位图,什么时候从1->0. 是在执行信号捕捉方法之前,先清0,在调用
  2. 信号被处理的时候,对应的信号也会被添加到block表中,防止信号捕捉被嵌套调用(处理一个信号时会将block置1 禁止重复调用

4.附加知识

子进程在退出时会向父进程发送17号SIGCHLD信号

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

pid_t waitpid(pid_t pid, int *status, int options);

options:控制 waitpid 行为的选项。常见选项包括: WNOHANG:如果没有子进程结束,则立即返回,不阻塞。
WUNTRACED:也返回已停止的子进程的状态。 WCONTINUED:也返回如果子进程继续执行的状态(适用于已停止的子进程)。

#include <iostream>
#include <cstring>
#include <ctime>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>

using namespace std;

 void handler(int signo)
 {
     sleep(5);
     pid_t rid;
     while ((rid = waitpid(-1, nullptr, WNOHANG)) > 0)
     {
         cout << "I am proccess: " << getpid() << " catch a signo: " << signo << " child process quit: " << rid << endl;
     }
 }

int main()
{
     srand(time(nullptr));
     //signal(17, SIG_IGN); // SIG_DFL -> action -> IGN
     signal(17, handler);
     
    for (int i = 0; i < 10; i++)
    {
        pid_t id = fork();
        if (id == 0)
        {
            while (true)
            {
                cout << "I am child process: " << getpid() << ", ppid: " << getppid() << endl;
                sleep(5);
                break;
            }
            cout << "child quit!!!" << endl;
            exit(0);
        }
         sleep(rand()%5+3);
        sleep(1);
    }
     //father
    while (true)
    {
        cout << "I am father process: " << getpid() << endl;
        sleep(1);
    }

    return 0;
}

在这里插入图片描述

所以在等待子进程退出时,我们可以基于信号的方式进行等待:17号信号的默认处理动作时SIG_DFL缺省—>什么都不做

5.volatile

防止编译器过度优化,保持内存可见性:

#include <iostream>
#include <cstring>
#include <ctime>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>

using namespace std;



 int flag = 0;

 void handler(int signo)
 {
     cout << "catch a signal: " << signo << endl;
     flag = 1;
 }

 int main()
 {
     signal(2, handler);
      //在优化条件下, flag变量可能被直接优化到CPU内的寄存器中
     while(!flag);  //flag 0, !falg 真

     cout << "process quit normal" << endl;
     return 0;
 }

在这里插入图片描述
在默认的编译器优化程度下是正常运行的:

mysignal2:mysignal2.cc
	g++ -o $@ $^  -O3 -g  -std=c++11

但是我们将编译器的优化程度拉满到O3时候:
在这里插入图片描述
就会出现这样的情况,其原因是在编译器优化条件下, flag变量可能被直接优化到CPU内的寄存器中,代码数据中更改flag的值自然就影响不到寄存器中的flag了。
这时候就需要关键字volatile登场了:

#include <iostream>
#include <cstring>
#include <ctime>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>

using namespace std;



 volatile int flag = 0;

 void handler(int signo)
 {
     cout << "catch a signal: " << signo << endl;
     flag = 1;
 }

 int main()
 {
     signal(2, handler);
      //在优化条件下, flag变量可能被直接优化到CPU内的寄存器中
     while(!flag);  //flag 0, !falg 真

     cout << "process quit normal" << endl;
     return 0;
 }


这时候即使在最高程度的编译器优化下,也可以正常完成代码逻辑了。

6.可重入函数

如果一个函数,被重复进入的情况下,出错了,或者可能出错,不可入函数!否则,叫做可重入函数。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

深圳水务展|2025深圳国际水务科技博览会

2025深圳国际水务科技博览会 展会主题: 新质生产力赋能水务产业高质量发展 展会时间&#xff1a;2025年7月24-26日 展会地点&#xff1a;深圳会展中心&#xff08;福田&#xff09; 主办单位&#xff1a; 广东省水利学会 深圳市水务学会 协办单位&#xff1a; 中国水利…

发论文idea来了!小样本学习+目标检测,12个创新点汇总

在一些特定应用领域&#xff0c;获取大规模且高质量标注的数据十分困难&#xff0c;比如医学图像分析。为了解决这个问题&#xff0c;研究者们提出了小样本目标检测。 小样本目标检测是一种结合了小样本学习和目标检测两者优势的技术&#xff0c;能够在有限的训练数据下&#…

elementPuls_Treeg更改颜色

elementPuls_Treeg更改颜色 .el-tree {background: transparent;color: #fff;}:deep(.el-tree-node__content:hover) {background: rgba(2, 167, 240, 0.5);}//选中的背景色:deep(.el-tree--highlight-current.el-tree-node.is-current> .el-tree-node__content) {backgrou…

上班族必备!这款免费录屏工具让你工作效率翻倍

www.bandicam.com/downloads/现在还依稀记得疫情爆发的那一年在学校上网课的场景&#xff0c;在家里上着网课担心错过老师讲的重点&#xff0c;就特地找了录屏的工具来使用&#xff0c;帮我录制重点片段&#xff0c;今天就针对录屏的工具整理了四款免费的录屏软件&#xff0c;有…

Linux系统驱动(五)

文章目录 一、实现机制二、字符设备驱动分布实现流程三、添加自己的系统调用函数1. 找到系统调用文件2. 找到 一、实现机制 应用层 vfs层 驱动层 字符设备按照字节流顺序访问&#xff0c;但是实际它提供了无序访问的功能 vi -t sys_open 内核中通过inode号可以唯一的找到一…

C语言典型例题27

《C程序设计教程&#xff08;第四版&#xff09;——谭浩强》 习题2.4 用下面的scanf函数输入数据 使a3,b7,x8.5,y71.8,c1A,c2a。问在键盘上怎么输入 代码 //《C程序设计教程&#xff08;第四版&#xff09;——谭浩强》 //习题2.4 用下面的scanf函数输入数据&#xff0c;使…

CentOS安装sentry

Sentry介绍 Sentry 是一套开源的实时的异常收集、追踪、监控系统。这套解决方案由对应各种语言的 SDK 和一套庞大的数据后台服务组成&#xff0c;通过 Sentry SDK 的配置&#xff0c;还可以上报错误关联的版本信息、发布环境。同时 Sentry SDK 会自动捕捉异常发生前的相关操作&…

电线电缆测厚双测径仪联控测厚系统

关键字:线缆测厚系统,绝缘层测厚设备,电线皮套测厚,电缆绝缘层测厚, 产品简介&#xff1a; 双测径仪联控测厚系统的工作原理基于光电测量技术。一台测径仪测量电缆的成品直径&#xff0c;另一台测径仪测量线芯的直径。通过这些测量数据&#xff0c;系统计算出绝缘层或护套层的厚…

IT课程学习搭子

各种IT课程齐全可学&#xff0c;价格你说了算&#xff0c;相比于培训班有以下优势&#xff1a; 1、避免被割韭菜&#xff0c;避免踩坑&#xff0c;避免交智商税&#xff0c;最低的成本学最有价值的课&#xff0c;同时又能达到比培训班更好的效果 2、收徒&#xff0c;带你学习 本…

第十五节、三段攻击动画的实现

一、创建攻击动画 新建图层 新建状态 放入攻击动画 二、攻击实现 三段式攻击 1、按下触发三段式 2、按键触发第一下攻击 设立两个参数 一个计数器 计数器并未使用 三、代码实现 1、注册攻击事件 2、设置动画参数关联

中国制造2025,会抛弃精益生产吗?

时至今日&#xff0c;“精益生产”模式依旧大行其道&#xff0c;它始终支持着中国制造业以最低的成本做出优质产品。我们认为&#xff0c;纵然是中国制造2025成为现实&#xff0c;精益生产模式也仍然是整个制造业的精髓之一。 首先&#xff0c;精益生产模式最重要的一根脊梁就是…

【密码学】密码协议的分类:①密钥建立协议

密码协议的分类有很多种方式&#xff0c;这里我采取的是基于协议实现的目的来分类。可以将密码协议分成三类&#xff1a;认证协议、密钥建立协议、认证密钥建立协议。这些协议在密码学和网络安全中扮演着至关重要的角色&#xff0c;下面我来对密钥建立协议详细介绍 密钥建立协议…

Rsync未授权访问漏洞 *

Rsync是Linux/Unix下的一个远程数据同步工具&#xff0c;可通过LAN/WAN快速同步多台主机间的文件和目录&#xff0c;默认运行在873端口。由于配置不当&#xff0c;导致任何人可未授权访问rsync&#xff0c;上传本地文件&#xff0c;下载服务器文件。Rsync 默认允许匿名访问&…

mysql数据库数据类型和约束

mysql数据库:数据类型和约束 常见的数据类型和约束 数据类型 数值类型&#xff1a;INT、BIGINT、FLOAT、DOUBLE&#xff0c;DECIMAL等。字符串类型&#xff1a;CHAR、VARCHAR、TEXT等。日期和时间类型&#xff1a;DATE、DATETIME、TIMESTAMP等。二进制类型&#xff1a;BLOB、LO…

《机器学习by周志华》学习笔记-决策树-02

1、剪枝处理(Pruning) 1.1、背景概念 上文「决策树01」的学习中,我们了解了著名的3种决策树算法ID3、C4.5、CART。这3种决策树算法最根本的核心就是根据特征选择离散属性作为节点来搭建树结构,运用搭好的结构进行推理。 剪枝(pruning)则就是将搭好的决策树去掉一些「非叶节…

[RTOS 学习记录] 预备知识:C语言结构体

这篇文章是我阅读《嵌入式实时操作系统μCOS-II原理及应用》后的读书笔记&#xff0c;记录目的是为了个人后续回顾复习使用。 文章目录 结构体结构体基础声明和定义结构体类型声明和定义结构体变量初始化结构体变量初始化各个成员使用列表符号初始化 使用结构体变量综上 结构体…

C语言程序设计-[5] 输入输出语句

C语言提供了一些输入输出的库函数&#xff0c;使用库函数&#xff0c;必须将相应的头文件“stdio.h”包含进来。 输入输出库函数可分为三类&#xff1a;字符输入输出函数、字符串输入输出函数和格式化输入输出函数。前两类功能单一&#xff0c;使用起来相对简单&#xff0c;以…

消息队列:Kafka吞吐量为什么比RocketMQ大

根据资料显示RocketMQ每秒能处理10W量级数据&#xff0c;而Kafka能处理17W量级数据。 这两者差别主要再使用的零拷贝技术不一样。 再什么情况下零拷贝技术诞生了 为了防止消息队列中的消息因为各种意外情况丢失&#xff0c;要对消息进行持久化处理&#xff0c;将其存储在磁盘…

Dubbo未授权访问漏洞

Dubbo是阿里巴巴公司开源的一个高性能优秀的 服务框架&#xff0c;使得应用可通过高性能的 RPC 实现服务的输 出和输入功能&#xff0c;可以和 Spring框架无缝集成。dubbo 因配置不当导致未授权访问漏洞。 》》》漏洞复现《《《 步骤一&#xff1a;使用以下语句在Fofa上进行资…

STM32智能小车(循迹、跟随、避障、测速、蓝牙、wifi、4g、语音识别)总结

前言 有需要帮忙代做51和32小车或者其他单片机项目&#xff0c;课程设计&#xff0c;报告&#xff0c;PCB原理图的小伙伴&#xff0c;可以在文章最下方加我V交流咨询&#xff0c;本篇文章的小车所有功能实现的代码还有硬件清单放在资源包里&#xff0c;有需要的自行下载即可&a…